Skip to content

test: a program node nothing produces is caught, rather than waiting for a consumer to meet it - #201

Merged
FBumann merged 1 commit into
mainfrom
test/every-program-node-is-reachable
Aug 28, 2026
Merged

FBumann merged 1 commit into
mainfrom
test/every-program-node-is-reachable

Conversation

@FBumann

@FBumann FBumann commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Prompt: "Add a PR onto main that fixes that!"

Note

The following content was generated by AI.

cases: reached a release candidate in #168 with no lowering at all — CasesNode joined the AST, nothing produced a program node for it, and the suite was green on 780 tests. assert_never catches that only where some test happens to lower a file using the construct, and none did.

This is the fence that would have caught it on the first commit:

AssertionError: every_program_node.yaml lowers to none of ['Unlowered']. A node no file reaches is
one whose lowering nobody has run — add a declaration using the construct it stands for.

It is the sibling of test_the_golden_model_carries_every_node_kind_the_walk_renders, one state along: that one holds the renderer to the AST, this holds the lowering to the program.

The two fixtures cannot be one

The obvious move is to point this at tests/typesetting/golden/model.yaml, which already exists to carry every construct. It does not lower:

constraint 'composed': shift() over a variable-free expression leaves vacated positions with no
value, and inventing one is what silently pinned a bound to zero.

That is correct, not a bug — rendering accepts more than lowering does, so a fixture proving every render arm is reached cannot also prove every lowering arm is. tests/fixtures/every_program_node.yaml is the second one, and it is a file that lowers to all thirteen nodes and nothing else.

examples/ cannot serve either: the gallery reaches twelve of thirteen, missing Power. Adding a ** to a teaching model to satisfy a test would be the harness showing through the modelling.

Both directions

The reverse assertion — the fixture lowers to nothing that is not a program node — is what stops the fixture drifting into asserting nothing, the way five fences went vacuous at the language cut by scanning a directory that no longer existed.

Mutation table

Taken by hand — tools/mutate.py cannot express an added class — with a clean tree, git checkout -- to restore and __pycache__ dropped on both sides.

Mutation Caught
a node joins ExpressionNode with nothing producing it ✅ names it, and says what to do

That is the whole point of the fence, so it is the whole table: the mutation reproduces the exact shape cases: shipped in.

Verified separately, before writing it, that the fence is not vacuous: run over examples/*.yaml it goes red on main today, missing Power.

Verified: pixi run ci — lint, tests, docs-build --strict, 26 TeX documents. The fixture lives under tests/ and is not a gallery model, so nothing renders it.

Not done: the same fence for Predicate. The program's masks are the language's own WhereNodes since #177, so they are already held by the where-parser's tests — there is no second set to drift. Worth revisiting only if the program ever grows a mask type of its own.

…for a consumer to meet it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@FBumann
FBumann requested a review from brynpickering as a code owner August 28, 2026 10:44
@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 math-spec | 🛠️ Build #34273860 | 📁 Comparing 1b638d6 against latest (58e1d35)

  🔍 Preview build  

1 file changed
± CHANGELOG/index.html

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