feat(program): a where mask answers its own questions — conjuncts, names, atoms and the dims it is read at - #319
Closed
FBumann wants to merge 4 commits into
Closed
feat(program): a where mask answers its own questions — conjuncts, names, atoms and the dims it is read at#319FBumann wants to merge 4 commits into
FBumann wants to merge 4 commits into
Conversation
…to give, not each consumer's to re-derive `names_read(where)` returns the parameters, lookups and variables a resolved predicate's leaves test — the name rule beside `dims_read`'s dim rule, and its complement: a leaf is read at a dimension and of a declaration, and where `dims_read` gives the first this gives the second. A comparison on a dimension names no declaration, so it drops out here and stays `dims_read`'s; a lookup pair names both maps it compares. Built on `atoms`, closed by `assert_never` the same way `_atom_dims` is, so a predicate node added without a reading is a type error at the one branch rather than a name silently missed. A consumer asking which parameters a mask gates on — whether the corpus ever varies one — asks it here rather than walking the leaves itself. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jk5LAMCoiD39q4Xz4AMVw
…er than a bare node each consumer re-walks A declaration's `where` is now a `Mask`, not a bare `WhereNode`. The mask holds the resolved predicate as `.root` — the node an engine still dispatches on with `isinstance` — and carries the questions asked of it beside it: `.conjuncts`, `.names_read`, `.atoms`, `.dims_read`. This is the shape every other declaration already has (`DimensionDeclaration.maps`, `Program.footprint`): the question lives on the thing that holds the data, so two consumers cannot answer it differently, rather than each reaching for a free function with the raw node. The free functions stay as the one-home implementation the Mask delegates to and that internal frame checks call on raw nodes. Lowering wraps the resolved where; an always-true mask folds to `None` as before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jk5LAMCoiD39q4Xz4AMVw
…mask arrives in the same carrier Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D59sEp5d7CecJAwrzACbRh
FBumann
marked this pull request as draft
September 1, 2026 13:48
…ng once per walk rather than once per cases node Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D59sEp5d7CecJAwrzACbRh
Contributor
Author
|
Note The following content was generated by AI. Superseded by #327, which combines the reviewed stack into a single diff on the maintainer's call. The review history — two consumer reviews and one consolidated agent review, with mutation tables — lives in this stack's bodies; the tree at #327 is byte-identical to the stack's top. |
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.
Note
The following content was generated by AI.
A
wheremask reaches a consumer as aMaskthat answers every question the language owns about it:.conjuncts,.names_read,.atomsas properties of.root, and.dims— the dimensions the mask is read at — stored, resolved at lowering, the one moment that holds both the predicate and every name's dims.Region.whenarrives in the same carrier, so one helper covers every mask in a program. The freeconjuncts()is retired to_conjunctsbehind the property — one public door.Built from
mainper instruction (the stack was abandoned): the two commits of #317 and #318 are cherry-picked here, so this PR supersedes both.Why
dimsstops being a question the consumer answers (corrected after the consumer's review, superseded in shape by #324):dims_read(name_dims)required a name→dims mapping that the first consumer builds by hand today from publicProgramfields, and calls at three sites — a parameters+variables-only copy of the read-through rule that would silently narrow the answer the day an atom kind consulted the mapping for anything more. This PR stores the answer onMaskat lowering; #324, three PRs up the stack and on the same reviewer's proposal, moves it further — onto the leaves at resolution, makingMask.dimsderived and this PR's stored field an intermediate shape that never survives to the end of the stack. Read this PR forMaskas the carrier andRegion.whenjoining it; read #324 for wheredimsfinally lives.The trade, named:
Maskis the program's first denormalized node — a hand-builtMask(root, wrong_dims)disagrees with itself and nothing validates it. In production only lowering builds one, so it cannot bite there; a test fixture that hand-builds declarations now suppliesdimsby hand, and a wrong set is a silent behavioural switch in whatever reads it. Freedims_readstays available for synthetic predicates (and entersprogram.__all__in the stacked #321).Verified
pixi run ciexit 0 in this worktree: lint clean, 930 passed,mkdocs build --strictbuilds, 27 TeX documents compile.schema/andtests/typesetting/golden/untouched — the typesetter runs off the Spec side and never readsprogram.Region.when.Failing-first: with
src/stashed back to the unreworked tree, the new tests cannot even collect —TypeError: Mask.__init__() takes 2 positional arguments but 3 were given. The pins that pass on both trees (unmaskedwhere is None, constraintwhereis aMask, atoms-vs-conjuncts ona and (b or c)) close gaps mutation testing found in #318, where dropping_mask'sNonefold or collapsingatomsintoconjunctsleft the suite green.Post-review:
_Loweringcarriesname_dimsas acached_property, so a cases-heavy walk resolves the mapping once rather than once percases:node — machinery #324 then deletes with the mapping itself. Do not land this PR without the rest of the stack: alone, it ships an intermediate shape.Deliberately not done: relocating the resolved where-node vocabulary from
where_parserintoprogram— that is the stacked follow-up (#321), kept out so this diff stays the API reshape alone.conjuncts/names_read/atomsstay properties rather than stored fields: they are pure functions of.rootand cannot disagree with it; onlydimsneeds outside knowledge. One departure from the housedims: tuple[str, ...]style:Mask.dimsis afrozensetbecause a mask restricts an unordered set of axes — declaration tuples carry axis order, a mask has none.