Propagate an undefined value for possibly-zero ///% divisors#274
Open
AaronWebster wants to merge 1 commit into
Open
Propagate an undefined value for possibly-zero ///% divisors#274AaronWebster wants to merge 1 commit into
undefined value for possibly-zero ///% divisors#274AaronWebster wants to merge 1 commit into
Conversation
Relaxes the strict division-by-zero rejection from the initial `//`/`%`
rollout: a divisor that is merely *possibly* zero is now allowed and
propagates an `undefined` value through the expression type system,
instead of being a blanket compile error. Only a *provably always* zero
divisor (range exactly {0}, e.g. `x // 0`) remains a hard error.
At runtime a `// 0` (or `% 0`) yields an Unknown `Maybe`, which poisons
the enclosing expression -- a field's size, existence condition, or
value -- so `Ok()` becomes false with no undefined behavior, rather than
the whole schema failing to compile.
- ir_data.py: add a separate `can_be_undefined` boolean flag to
IntegerType and BooleanType. It is never an in-band "undefined"
sentinel in minimum_value/maximum_value/modulus/modular_value, so the
many sites that parse those as integers are unaffected.
- expression_bounds.py: `//`/`%` introduce the flag when the divisor's
range includes zero; additive, multiplicative, comparison (incl.
`&&`/`||`), choice, and `$max` combinators propagate it.
- ir_util.py / header_generator.py: a value that can be undefined is not
a compile-time constant even when its defined range collapses to a
single value (e.g. `0 // d` folds to {0}), so `is_constant_type`,
`constant_value`, and the codegen literal-folding shortcut all decline
to fold it -- otherwise codegen would emit a bogus literal.
- constraints.py: narrow the divisor check to provably-always-zero.
- emboss_arithmetic.h: rewrite FlooringQuotient / FlooringRemainder as
hand-written "special ops" (like And/Or/Choice) that return an Unknown
Maybe when the divisor is zero, even with both operands Known.
- Soundness guard for the Ok()/switch discriminant optimization: an
existence condition that can be undefined is never treated as provably
known. A differential test (FieldGatedByDivision) asserts the runtime
`Ok()` behavior for a zero divisor.
- Tests + regenerated golden.
Note: `IsComplete()` still returns false for a `/0` size; making it
return true (the design doc's preferred behavior) is a later change.
This was referenced Jul 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 2 of 3 of the integer division/modulus series.
Stacked on #273 (base branch:
emboss/divmod-operators). Review/merge #273first; the base retargets to
masterautomatically once #273 merges. The diffbelow is only this phase's changes.
Relaxes the strict division-by-zero rejection from part 1: a divisor that is
merely possibly zero is now allowed and propagates an
undefinedvaluethrough the expression type system, instead of being a blanket compile error.
Only a provably always zero divisor (range exactly
{0}, e.g.x // 0)remains a hard error.
At runtime a
// 0(or% 0) yields an UnknownMaybe, which poisons theenclosing expression -- a field's size, existence condition, or value -- so
Ok()becomes false with no undefined behavior, rather than the whole schemafailing to compile.
What this does
ir_data.py: a separatecan_be_undefinedboolean flag onIntegerTypeand
BooleanType(never an in-band sentinel inminimum_value/maximum_value/modulus/modular_value, so the many sitesthat parse those as integers are unaffected).
expression_bounds.py:///%introduce the flag when the divisor'srange includes zero; additive, multiplicative, comparison (incl.
&&/||),choice, and
$maxcombinators propagate it.ir_util.py/header_generator.py: a value that can be undefined is nota compile-time constant even when its defined range collapses to a single
value (e.g.
0 // dfolds to{0}), sois_constant_type,constant_value,and the codegen literal-folding shortcut all decline to fold it.
constraints.py: narrow the divisor check to provably-always-zero.emboss_arithmetic.h: rewriteFlooringQuotient/FlooringRemainderashand-written "special ops" (like And/Or/Choice) that return an Unknown
Maybewhen the divisor is zero, even with both operands Known.
Ok()/switch discriminant optimization: anexistence condition that can be undefined is never treated as provably known.
A differential test (
FieldGatedByDivision) asserts the runtimeOk()behavior for a zero divisor.
IsComplete()still returns false for a/0size here; part 3 makes it returntrue (the design doc's preferred behavior).
bazel test //...is green.PR chain (merge in order): #273 → #274 (this) → #275