Skip to content

Propagate an undefined value for possibly-zero ///% divisors#274

Open
AaronWebster wants to merge 1 commit into
emboss/divmod-operatorsfrom
emboss/divmod-undefined
Open

Propagate an undefined value for possibly-zero ///% divisors#274
AaronWebster wants to merge 1 commit into
emboss/divmod-operatorsfrom
emboss/divmod-undefined

Conversation

@AaronWebster

@AaronWebster AaronWebster commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Part 2 of 3 of the integer division/modulus series.

Stacked on #273 (base branch: emboss/divmod-operators). Review/merge #273
first; the base retargets to master automatically once #273 merges. The diff
below 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 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.

What this does

  • ir_data.py: a separate can_be_undefined boolean flag on IntegerType
    and BooleanType (never an in-band 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.
  • 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.

IsComplete() still returns false for a /0 size here; part 3 makes it return
true (the design doc's preferred behavior).

bazel test //... is green.


PR chain (merge in order): #273#274 (this) → #275

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant