fix: remove ignored nullability markers from MIRROR declarations - #1148
fix: remove ignored nullability markers from MIRROR declarations#1148nielspardon wants to merge 1 commit into
Conversation
Under MIRROR nullability the output nullability is derived from the arguments, so the spec requires that neither argument nor return types carry a `?` marker, since it is ignored. Seven impls in the standard catalog violated this: - `functions_boolean.yaml`: `or`, `and`, `and_not`, `xor` and `not` declared both their arguments and their return type as `boolean?`. - `functions_rounding_decimal.yaml`: `ceil` and `floor` declared their derived return type as `decimal?<precision, 0>`. Derived behavior is unchanged: with the markers ignored per spec, `or(boolean, boolean) -> boolean` and `or(boolean?, boolean?) -> boolean?`, which is what Kleene logic wants and what the existing test cases already assert. This aligns the declarations with the rule. `bool_and`, `bool_or` and `round` are left as-is: they declare `DECLARED_OUTPUT`, where the return marker is authoritative. Also adds `validate_nullability_markers` and a test over the shipped catalog so the markers cannot be reintroduced. The check skips DISCRETE (markers are meaningful there), only looks at outermost nullability so nested markers like `func<any1 -> boolean?>` still pass, and reads the final line of a derivation expression so a `?` ternary in an intermediate line is not mistaken for a marker. Fixes substrait-io#1041 Fixes substrait-io#853
Breaking-change policy: no code impact in any active libraryPer the breaking change policy, a breaking change needs a migration strategy implemented in all active libraries before it lands. I audited all four. None of them reads the declared marker under
Supporting checks:
All four were inspected at commits level with their respective upstream One observable difference, for the record
🤖 Generated with AI |
Under
MIRRORnullability the output nullability is derived from the arguments, so the spec requires that neither argument nor return types carry a?marker, since it is ignored.text/simple_extensions_schema.yaml:Seven impls in the standard catalog violated this. I enumerated them by loading every
extensions/functions_*.yamlthrough a parser rather than grepping, so this is the complete set:functions_boolean.yamlorfunctions_boolean.yamlandfunctions_boolean.yamland_notfunctions_boolean.yamlxorfunctions_boolean.yamlnotfunctions_rounding_decimal.yamlceildecimal?<precision, 0>)functions_rounding_decimal.yamlfloordecimal?<precision, 0>)bool_and,bool_orandroundare left as-is: they declareDECLARED_OUTPUT, where the return marker is authoritative.This is not a breaking change
Derived behavior is unchanged. With the markers ignored per spec,
or(boolean, boolean) -> booleanandor(boolean?, boolean?) -> boolean?, which is what Kleene logic wants and what the existing test cases intests/cases/boolean/already assert. No plan that was legal before becomes illegal.It does matter for implementations that validate a plan's declared output type against the declaration, though: a producer that reads
return: boolean?literally will declareboolean?foror(boolean, boolean)and be rejected by a conforming validator. That inconsistency is what this fixes.Regression guard
Also adds
validate_nullability_markersplus a test over the shipped catalog, so the markers cannot be reintroduced. The check:DISCRETE, where markers are meaningful on both sides;func<any1 -> boolean?>andlist<i32?>still pass;?ternary in an intermediate line (scale = init_prec > 38 ? scale_after_borrow : init_scale) is not mistaken for a marker.Verified it fails with a clear message when either marker is reintroduced.
Fixes #1041
Fixes #853
🤖 Generated with AI
This change is