feat(language): which dims a mask reads is the language's answer, so two consumers cannot restrict one model differently - #269
Merged
Conversation
…two consumers cannot restrict one model differently Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 31, 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.
Note
The following content was generated by AI.
What this changes
where_parser.dims_read(where, name_dims)— which dims a resolved predicatereads. A parameter through its own dims, a variable through the frame it is
declared over, a comparison on a dimension through that dimension, and a lookup
through the dimension it maps out of.
Why it is the language's
lpspec answers this today, in
relational/engines/polars/predicates.py::predicate_dims— 37 lines walkingthe same closed union and applying the same reading. Its own docstring says
what that costs:
That is
what-counts-as-language.md's test met exactly: two consumers decidingthis separately would mask the same model differently, with no error anywhere.
A Julia consumer would inherit none of it.
And this repository was already answering it —
_check_where_dimswalks thesame leaves with the same reading, to ask whether they fit the frame. So the
rule had two homes here and a third downstream. It now has one, and the
load-time check is a reader of it.
What lpspec removes
predicate_dimsand itsLanguageErrorfence, both call sites moved todims_read— 37 lines, and a rule it should never have owned. Reached bymodule path, which
where_parser's own docstring already grants and lpspec'stest_the_language_is_imported_as_one_packagealready allows, so the packagesurface does not grow.
It also drops a wrong branch lpspec had and this does not:
ParameterComparisonNode.valueisfloat | strand thestris a flag, nevera declaration. A model comparing a
strparameter to a flag whose textcollides with another declared name would union that name's dims in. Not a
wrong answer downstream — it widens the set, which only costs lpspec a
join-strategy fallback — but it is a reading the language never had.
The messages do not move
_check_where_dimsreports per leaf, and that is why it is a separatereader rather than a
dims_read(...) <= frameone-liner: a reader told onlythat the predicate leaves the frame would still have to work out which half of
it did. Every one of the four sentences is byte-identical, and the existing
tests that assert them are untouched and green.
Verified
pixi run ci—lint,typecheck(0 errors),test(818 passed, 809on
main— nine new),docs-build --strict,render-texandcompile-tex(26 documents).
Equivalence, measured rather than argued.
dims_readwas run againstlpspec's
predicate_dimsover every predicate in lpspec's corpus — all 46models in
examples/, 43 predicates, 0 mismatches — with this branch onPYTHONPATHand lpspec unchanged.Exhaustiveness, and where it moved
The old
_check_where_dimsended inassert_never, so a predicate node addedwithout a branch was a type error. That fence is kept and improved: it now sits
on
_atom_dims, the one function that has to grow a branch, instead of on achecker that also had to grow a message. lpspec's version raised
LanguageErrorat runtime for the same case, which is a fence that fires atthe first model to use the node rather than at the commit that adds it.
atoms()is the walk both readers share — every leaf, connectives removed —and is public for the same reason
dims_readis: #253'sHolds.dimsisexactly this question asked of a declared predicate, so it should be this
answer.
Not done:
dims_readis not added tomath_spec.__all__.where_parseris deliberately out of it and reachable by module path, and a predicate walk
belongs to the module that defines predicates — by #246's test, this is a
capability that cost the package surface nothing, which is the shape to prefer.