Marshal anonymous environments like every other shape - #91
Draft
HowardvanRooijen wants to merge 1 commit into
Draft
HowardvanRooijen wants to merge 1 commit into
HowardvanRooijen wants to merge 1 commit into
Conversation
An anonymous environment holding a nested object threw a bare NullReferenceException from inside the native interop. A nested environment has no handle of its own, and the anonymous-type branch of GetSolution evaluated the handle before looking at the type, so the null went straight to Model.Eval. The three sites in ConvertZ3Expression guard against exactly that; this fourth one did not. The fourth site is gone. The branch carried a marshaller of its own that handled bool and int and refused everything else; it now calls ConvertZ3Expression like every other environment shape, so an anonymous environment supports every type a named one does, nested objects included - the nested object is materialised by the recursion in there rather than evaluated here. Measured across eleven shapes, all of which threw before. A collection is the one thing the shared path cannot do for an anonymous type, because the element count is read from the instance and the instance is created uninitialised. It is refused by name, with the reason, rather than left to fail on the null count. EvaluateWithCompletion takes a non-nullable Expr again. It was made nullable in #51 for this one caller, and the paragraph explaining that goes with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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 #75.
The defect
An anonymous environment holding a nested object threw a bare
NullReferenceExceptionfrominside the native interop:
A nested environment has no Z3 handle of its own, only children. The three evaluation sites in
ConvertZ3Expressionguard against that null and throw anArgumentExceptionnaming the member;the fourth site - the anonymous-type branch of
GetSolution- evaluated the handle before lookingat the type, so the null went straight to
Model.Eval. TheNotSupportedExceptiontwo lineslater, which would at least have named the property, was never reached.
Why the fix is bigger than the issue suggested
#75 proposed moving the type check above the evaluation, so the existing
NotSupportedExceptionfires first. That would have improved the message and kept the branch as it was: a third copy of
the marshaller, handling
boolandintonly, so that adouble,long,string,decimal,short,floatorDateTimethat is perfectly usable in a named environment wasrefused in an anonymous one - pinned in
EnvironmentTypeTestsas "a real restriction ratherthan a defect".
Measured, the alternative costs less and delivers more. Replacing that branch's marshaller with a
call to
ConvertZ3Expression- the one every other environment shape uses - makes all of thefollowing work, with nothing else changed:
new { inner = new Leaf(), n = 0 }- the issue's reproNullReferenceExceptioninnerconstructed,n = 1t.inner.A == 5NullReferenceExceptioninner.A = 5new { inner = new { a = 0 }, n = 0 }- nested anonymousNullReferenceExceptioninner.a = 4new { p = new Pair(), n = 0 }wherePairhas adoubleNullReferenceExceptionp.Y = 2.5double,long,string,decimal,short,float,DateTimepropertiesNotSupportedExceptionnew { v = new int[2], n = 0 }NotSupportedException: Unsupported parameter type for v.NotSupportedException: ... a collection in an anonymous environment cannot be pre-sized.So #75 stops being a bad diagnostic on an unsupported shape and becomes a supported shape. The
scope was put to the maintainer with these numbers and chosen over the diagnostic-only fix.
The change
replaces the inline
bool/intchain. A nested object is materialised by the recursion insideConvertZ3Expression- the same recursion that handles it for a named environment - rather thanevaluated here, so the null never reaches Z3 because nothing evaluates it.
Collections are the one exception, and are refused by name. The shared path reads the element
count from the collection already on the instance, and an anonymous instance is created
uninitialised -
NewTheorem<T>(T dummy)discards the argument, as its parameter name says - sothere is nothing to read. Without a guard that is #78's
NullReferenceExceptionon the count;with it, the message says which member and why. The guard uses a new
IsCollectionhelper, whichalso replaces the two places in
GetEnvironmentthat spelled the same test out inline.EvaluateWithCompletiontakes a non-nullableExpragain. #51 made itExpr?for exactlyone reason - this branch passed a nullable - and documented that in a paragraph of remarks. Both
the annotation and the paragraph are gone; the clean build under
TreatWarningsAsErrorsis thecompiler confirming no caller can hand it a null any more.
Net: 26 lines out, 28 in, in
Theorem.cs.Tests
225 → 230, in
EnvironmentTypeTests.cs.Solve_AnonymousTypeWithAnUnconstrainedNestedObject_ConstructsItSolve_AnonymousTypeWithANestedObject_PopulatesTheNestedPropertiesSolve_AnonymousTypeWithANestedAnonymousType_PopulatesItSolve_AnonymousTypeWithDoubleProperty_PopulatesItThrowsNotSupportedExceptionpin, now a round-tripSolve_AnonymousTypeWithEveryScalarType_PopulatesEachOneSolve_AnonymousTypeWithACollectionProperty_ThrowsNotSupportedExceptionThree existing remarks that described the anonymous branch's separate evaluation call are
rewritten to say what is now true.
Mutation results
NullReferenceExceptionbool/intmarshallerSolve_AnonymousTypeEnvironment_PopulatesEveryPropertyand the unconstrained-property test stay green, because they areboolandint- which is precisely why the restriction survived as long as it didVerification
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.