Read the decimal element the loop selected - #81
Open
HowardvanRooijen wants to merge 2 commits into
Open
HowardvanRooijen wants to merge 2 commits into
HowardvanRooijen wants to merge 2 commits into
Conversation
The decimal arm of the array element loop evaluated subEnv.Expr - the whole array constant - instead of numValExpr, the element the loop had just selected with MkSelect. Every other arm of that loop uses numValExpr. Casting an ArrayExpr to RatNum can never succeed, so the arm always threw InvalidCastException rather than giving every element the same value, which is what the issue predicted as the alternative. #64 recorded this code as unreachable. That holds only for a constraint naming a decimal constant, which fails during translation. With the elements left free the theorem solves and the loop runs, which is how the three new tests observe the fix. Fixes #55. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a defect in ConvertZ3Expression’s collection element loop where the decimal arm evaluated/cast the array constant instead of the selected element expression, and adds characterization tests to ensure the correct expression is being cast (per #55) while keeping existing behavior pinned for #64.
Changes:
- Update
TypeCode.Decimalarray-element marshalling to readnumValExpr(the selected element) rather than re-evaluatingsubEnv.Expr(the array constant). - Add three tests that validate the
decimalelement loop now fails on the element expression (not the array) for free-element and constrained-element shapes, including the generic-collection branch. - Refresh/realign test documentation line references and remarks to reflect current reachability and failure mode distinctions between translation vs marshalling.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| solutions/Z3.Linq/Theorem.cs | Fixes decimal element loop to use the selected element expression (numValExpr) consistently with other element types. |
| solutions/Z3.Linq.Tests/EnvironmentTypeTests.cs | Updates comment line references to match shifted line numbers in Theorem.cs. |
| solutions/Z3.Linq.Tests/CollectionSymbolTests.cs | Adjusts remarks around reachability/failure modes and adds new tests characterizing the #55 fix for decimal collections. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The rewritten remarks on the decimal array pin ended with </remarks>, and the original closing tag below it was left in place, so the comment closed twice. Raised by the Copilot review on #81. The build did not catch it because no project sets GenerateDocumentationFile, so the compiler never parses the XML - see #82. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 1, 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.
Fixes #55.
The defect
Inside the array element loop, every arm reads
numValExpr- the element the loop selected withMkSelect(arrVal, MkInt(i)). TheDecimalarm did not. It calledEvala second time, onsubEnv.Expr, which is the array constant itself:What actually happens - the issue overstates one half and #64 overstates the other
The issue says every element gets the same wrong value or the cast fails outright. Measured, it
is always the cast:
subEnv.Expris anArrayExpr, and noArrayExpris aRatNum. There is noinput that produces the same-value symptom - including with #64's sort mapping corrected locally, so
this is not #64 masking it.
#64 says this code is unreachable because a
decimal[]never survives translation. That is trueonly of a constraint naming a decimal constant, which is the shape #64 tested. Leave the elements
free and the theorem solves normally, and the element loop runs:
decimal[], elements freeInvalidCastException: ... 'ArrayExpr' to type 'RatNum'InvalidCastException: ... 'FPNum' to type 'RatNum'List<decimal>, elements free... 'ArrayExpr' ...... 'FPNum' ...decimal[],Values[0] == Values[1]... 'ArrayExpr' ...... 'FPNum' ...decimal[],Values[0] == 1.5mZ3Exception(#64)Z3Exception(#64)So #55 is observable, and the tests below observe it. What the fix changes is which expression the
cast rejects: after it, the decimal arm fails exactly where the
floatanddoublearms beside itfail, on #64's floating-point range sort, rather than on a mistake of its own.
This reachability applies to the other element types too -
float[],double[]andlong[]withfree elements all reach marshalling and fail at their cast rather than in translation. That is #64's
territory, not this PR's, but it does make the remark added for #54's array arm in #80 imprecise, so
that remark is corrected here in the file this PR already edits.
The change
Three lines below
Theorem.cs:477shift up by one; the four affected citations were checked againsttheir targets rather than shifted blindly, and one (
Theorem.cs:654-699) was already off by a linebefore this change.
Evidence that the fix is right, not just different
The tests can only assert on the failure, because #64 still declares a decimal array's range as a
floating-point sort. To check the fix actually produces correct values, #64's
Decimalmapping wascorrected locally -
IntSortdomain,RealSortrange, two sites - and the probe re-run. Thischange is not in this PR; it is how the fix was verified.
Values[0] == 1.5m,Values[1] == 2.5m1.5, 2.5InvalidCastExceptionList<decimal>1.5, 2.5InvalidCastExceptiondecimal[3], first two constrained1.5, 2.5, 2.5InvalidCastException0.1manddecimal.MaxValue0.1, 79228162514264337593543950335InvalidCastException0, 0InvalidCastExceptionEach element takes its own value, at full decimal precision. That is the behaviour #55 asks for, and
it arrives the moment #64 stops standing in the way.
Tests
177 -> 180, all in
CollectionSymbolTests.Solve_DecimalArrayWithFreeElements_CastsTheElementNotTheArraySolve_DecimalListWithFreeElements_CastsTheElementNotTheArrayToArrayand shares only the element readSolve_DecimalArrayWithElementsConstrainedToEachOther_CastsTheElementNotTheArrayEach asserts two things, deliberately split: the message must not name an array - that is #55,
and it is what the fix changes - and it does name
FPNum, which pins #64's sort mapping andmust be updated when #64 is fixed. These are the first message-based assertions in the suite; they
are here because the value cannot be asserted while #64 stands.
Three remarks were corrected rather than left to rot: the class remarks (failures split between
translation and marshalling depending on the constraint), the existing
decimal[]pin (it is oneshape of #64, not proof of unreachability), and the
float[]pin added by #80.Mutation results
EvaluateWithCompletion(model, subEnv.Expr)The value-level mutations are the probe table above: with the sort mapping corrected, reverting the
fix turns every correct answer into an exception, so the tests are not merely pinning an exception
type that happens to have changed.
Verification
dotnet build solutions/Z3.Linq.slnx -c Release- clean,TreatWarningsAsErrorson./build.ps1 -Configuration Release- 46 tasks, 0 errors, 0 warningsRelease note
Releases remain on hold under #60 until Microsoft.Z3 5.x reaches nuget.org, so this reaches
mainbut not consumers. Nothing about the hold changes.