Keep a node's codomain when a rewrite rebuilds it (#955) - #956
Merged
Conversation
Entity.Replace rebuilds every node on the path to a change, and each rebuild went
through a New(...) helper whose `new(...)` starts from the type's default codomain.
So any rewrite dropped a domain(...) annotation, Substitute included, since that is
built on Replace:
"domain(sqrt(x), ZZ)".ToEntity().Substitute("x", "4/9") evaluated to 2/3
2/3 is not an integer and the constraint existed to refuse it. The failure was silent
and one-sided -- constraints were only ever weakened, so a rewrite could make an
undefined expression look defined and never the reverse.
47 rebuild sites, all of the same shape: 41 target-typed `? this : new(...)` and 6
written with an explicit type in Piecewise, Interval, ConditionalSet and Matrix. Each
now carries the original's codomain.
The blast radius was the open question on the issue and it is zero: the suite passes
7263 with the fix in, unchanged, so nothing depended on the annotation being lost.
Three tests pin it -- through Replace, through Substitute, and one level deeper --
and all three fail without the change.
Found while implementing #873, whose parser-side fix is blocked behind this: any
post-parse rewrite has the same hole.
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.
Closes #955.
Entity.Replacerebuilds every node on the path to a change, and each rebuild goes through aNew(...)helper whosenew(...)starts from the type's default codomain rather than the one the original carried. So any rewrite dropped adomain(...)annotation —Substituteincluded, since that is built onReplace:2/3 is not an integer, and the constraint existed to refuse it.
The old behaviour was silent and one-sided: constraints were only ever weakened, never strengthened, so a rewrite could make an undefined expression look defined but never the reverse. That is the direction that gets acted on rather than noticed.
The fix
47 rebuild sites, all the same shape:
ReferenceEquals(...) ? this : new(...)across the continuous and discrete node classesProvidedf,Piecewise, twoIntervaloverloads,ConditionalSet,MatrixEach now carries the original's codomain. I audited for leftovers rather than trusting the sweep: there is no remaining
? this : new(without it.The blast radius, which was the open question on the issue
Zero. The full suite passes 7263, 0 failed with the fix in — unchanged from master. Nothing in the library depended on the annotation being lost, which is what made me willing to take the broad option rather than the narrower "only preserve a non-default codomain" one.
Three tests pin the behaviour and all three fail without the source change (verified by stashing only
Sources/AngouriMath/):ReplacedirectlySubstitute, which is what a caller actually reaches forRecorded in
BREAKING-CHANGES.mdas Silent: an expression that answered a value where it should have refused now refuses.How it was found, and what it unblocks
Implementing #873 — parsing a quotient of two integer literals as a
Rational— by rewriting the parsed tree.domain(1/2, ZZ)stopped answeringNaN, and it turned out not to be a parser bug at all. #873 is blocked behind this rather than able to work around it, since any post-parse rewrite has the same hole; it becomes straightforward once this lands.What it does not do
WithCodomain,Codomain's declaration, or domain checking itself.🤖 Generated with Claude Code