Skip to content

fix(typesetting): a cased expression whose otherwise holds a variable is set as one the solver decides - #252

Merged
FBumann merged 5 commits into
feat/cases-proved-apartfrom
fix/cases-otherwise-sweep
Aug 30, 2026
Merged

FBumann merged 5 commits into
feat/cases-proved-apartfrom
fix/cases-otherwise-sweep

Conversation

@FBumann

@FBumann FBumann commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Prompt: "Lets do a PR stacked that fixes all of those!" — the six findings from a review of #168.

Stacked on feat/cases-proved-apart, so it targets #168 rather than main.

Note

The following content was generated by AI.

Five fixes from a review pass over #168, four of them fallout from #251's default: → otherwise: rename. Each lands with a test that is red without it.

A cased expression whose only variable is in its otherwise: printed as data. chosen_expressions read block.cases alone, so once the fallback moved out of cases: it stopped counting toward what the block is. previous_status in the commitment example is exactly that shape — two cases holding a constant and a parameter, the variable in the fallback — so its generated page said \mathrm{previous\_status} while the hand-written language reference said \mathit. The tree contradicted itself; docs/examples/commitment.md is regenerated here.

An error inside a case named the constraint that used it. A cased expression is expanded where its name stood, so a fault in an arm was reported as Constraint 'ramp_up', case 'boundary' — a case on a constraint that has none — once per constraint naming the expression. The arm now names its declaration, the arm with no when is named otherwise rather than a case (what validation, dimensions and expansion already call it), and an exact repeat prints once.

n < 1 against n > 0 on an int was refused at n is 0.5. The gap between two literals got a midpoint representative regardless of dtype, so an integer partition was refused with a witness the data cannot produce and a rewrite the file had already made. Dates were already discrete for this reason; integers now are too.

The soundness fuzz could not fail. Every pair it built was a complement — m against not m — so its grid assertion was X and not X. Collapsing every subject to a single cell, the largest cell-coverage bug there is, left both seeds green while nine other tests in the file went red.

An unreachable guard and five stale default spellings, the retired name in program.py, format.py and two test docstrings.

Evidence per fix
fix test on the unfixed tree
the fallback is chosen test_the_fallback_reaching_a_variable_is_chosen fails in all three formats
the arm names its declaration test_a_fault_in_an_arm_names_the_declaration_and_is_reported_once, test_the_fallback_is_not_named_as_a_case both fail
integers are discrete test_an_integer_admits_no_value_between_its_bands fails; test_a_magnitude_still_admits_one guards the over-correction and fails if discrete is forced true
the fuzz is real test_a_pair_proved_apart_stays_apart_on_a_finer_grid passed both seeds under the one-cell mutation before, fails both after
the dead guard — deleting it leaves the suite green, which is the finding

The fuzz, same atoms and grid, 2000 draws:

pairs proved apart with a grid point both claim
real implementation 221 0
_cells_for collapsed to one cell 1770 1295
Checks

pixi run ci clean on the committed tree: lint (all eleven hooks, pyrefly included), 884 passed, mkdocs build --strict, 27 TeX documents compiled.

884 against the base branch's 877: seven new tests — one across three formats, two in test_exclusivity.py, two in test_validation.py.

Departed from a default: one PR rather than five stacked. These are five findings from one review pass on an unmerged branch, and five PRs stacked on an open PR would cost more to land than to read. Split into five commits instead.

Not done here. Two minor findings from the same review, left out to keep this to the six:

  • expression: .nan reaches the parser as the name nan and fails with 'nan' not found. _number_is_an_expression argues booleans are left to fail because the type reads better than the spelling; the same argument covers non-finite floats.
  • cases: {} beside an otherwise: now fails with raw pydantic (Dictionary should have at least 1 item after validation, not 0). The retired message named the rewrite — "which is what a plain expression: already says". Restoring it means giving up minProperties: 1 in the published schema, which is a trade worth deciding rather than assuming.

#168's own body still describes the default design throughout — the YAML block, the "the name is reserved and the position is fixed" paragraph, and the near-miss list. #251 flagged that as its own not-done; it wants rewriting rather than appending to, and it is yours rather than mine.

FBumann and others added 5 commits August 30, 2026 19:54
… is set as one the solver decides

Reading only `cases:` for what a block is left the fallback out, so a
quantity whose only variable sits there printed upright — the notation
for something the model is handed rather than something it solves for.
`previous_status` in the commitment example is exactly that shape, and
its generated page disagreed with the hand-written one in the language
reference.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX
…res it, and is printed once

A cased expression is expanded where its name stood, so a fault in one
of its arms was reported against the constraint that pulled it in — as
`Constraint 'ramp_up', case 'boundary'`, a case on a constraint that has
none, once per constraint naming the expression. The arm now names its
declaration, and the arm with no `when` is named `otherwise` rather than
a case, which is what every other check already calls it. Two errors
that differ at all carry different contexts, so an exact repeat is one
fault seen twice and is printed once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX
…ved apart

The cells for an ordered subject put a representative in every gap
between the literals a pair of masks names, and the midpoint it chose
was one no integer can be. So `n < 1` against `n > 0` on an `int` was
refused with a witness of `n is 0.5` — a coordinate the data cannot
produce, naming a rewrite the file had already made. A date was already
handled this way; an integer is discrete for the same reason and now
says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX
…an one and its negation

Every pair it generated was a complement by construction, so the grid
assertion was `X and not X` — false at every point, under every
implementation, for every set of cells. Collapsing each subject to a
single cell, which is the most a cell-coverage bug could ever be, left
both seeds green while nine other tests in the file went red. Drawn
independently and filtered to the pairs the check proves apart, the same
mutation now fails it 1295 times over.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX
…otherwise rename left behind

`_lower_expression` looked its name up in the very mapping its only
caller iterates, so the `KeyError` and its `did_you_mean` were a branch
nothing reaches — and the one place in the file raising something other
than `LanguageError`. Deleting it leaves the suite green, which is what
the guard was worth.

Beside it, five docstrings and a test name still called the fallback
`default`, which the language no longer has.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJMiQWNo9b5VD3Aac5gTXX
@FBumann
FBumann requested a review from brynpickering as a code owner August 30, 2026 17:56
@FBumann
FBumann requested a review from FabianHofmann August 30, 2026 18:06
@FBumann
FBumann merged commit d487679 into feat/cases-proved-apart Aug 30, 2026
5 checks passed
@FBumann
FBumann deleted the fix/cases-otherwise-sweep branch September 9, 2026 06:45
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