fix(theorem): partial-eval constraint body before visiting (cross-submission capture) - #43
Conversation
…mission capture) (#3) Theorem.AssertConstraints now calls PartialEvaluator.PartialEval on each constraint body before ExpressionVisitor.Visit. This folds host-captured constants (locals declared outside the theorem parameter scope, e.g. in a different .NET Interactive submission or closure) into literal ConstantExpressions, sidestepping the reflective MemberExpression resolution in ExpressionVisitor.VisitMember that crashed across dynamic-assembly boundaries. Bug: in .NET Interactive / Polyglot Notebooks, each cell compiles as a separate dynamic assembly (Submission#N). When a constraint lambda captures a local declared in another submission, VisitMember resolved the FieldInfo on the lambda submission type while the closure target carried the declaring submission type: System.ArgumentException: Field 'b0' defined on type 'Submission#N' is not a field on the target object of type 'Submission#M' This was invisible in xUnit (single-assembly) because the closure and the field live in the same assembly there. The fix is a no-op for same-assembly captures (PartialEval folds them to the same literal VisitMember would have produced via reflection), so all existing tests pass unchanged. Subtrees referencing the theorem parameter (e.g. t.Values[i]) are preserved (not evaluable without a parameter binding). Validation: - dotnet test: 6/6 passing (Z3.Linq.Tests, both CollectionHandling modes) - polyglot-repro/CrossSubmissionCaptureRepro.ipynb: cross-submission capture now solves (7,8,9); before the fix it raised ArgumentException. See #2. Developed with AI assistance (GLM-5.2) as part of the CoursIA pedagogical Z3.Linq series (https://github.com/jsboige/CoursIA).
|
Very interesting - revisiting your suggestion in #29 has been on my backlog for a while - especially as we could leverage some agentic AI to help with any of the integration. |
|
Thanks Howard — read your note on #29 and the stack (congrats on the sweep, #110 looks like the right capstone). Three quick things on this PR: Scope reframed beyond .NET Interactive. Now that Polyglot Notebooks are dropped upstream, I want to be explicit that this fix is not Interactive-specific: any host that compiles expression trees with closures across assembly boundaries — dynamic assemblies ( Test, as offered. A real test for this rides with the test-project PR I promised on #29 rather than ballooning this one: the honest xUnit reproduction needs the closure field to live on a foreign type, which single-assembly tests cannot produce by construction — so the design is a small compiled-on-the-fly assembly (Roslyn scripting) that captures a local and hands the constraint across, making the pre-fix Targeting. The two scoping questions from #29 still stand (main vs staging branch, anything to leave alone) — happy to aim this and the upcoming PRs at the tip of your stack if that saves you rebase work; on our side the drift adaptation is agent-driven, matching the workflow you described. |
|
The reproduction I offered earlier is in — and it landed one branch over rather than inside this PR, to keep the fix diff surgical (this branch still touches exactly the two files). Where it lives: Red on 1 failed / 0 passed — the exact exception from the wild, down to the submission numbering. Green on this PR's branch: 1 passed / 0 failed, same test, same machine, only the partial-eval change in between. The single added dependency is |
Summary
Fixes a crash in
Theorem.AssertConstraintswhen the constraint lambda captures a local declared in a different .NET Interactive / Polyglot Notebooks submission.Theorem.AssertConstraintsnow runsPartialEvaluator.PartialEvalon each constraint body beforeExpressionVisitor.Visit. This folds host-captured constants (locals declared outside the theorem parameter scope, e.g. in a different.NET Interactivesubmission or closure) into literalConstantExpressions, sidestepping the reflectiveMemberExpressionresolution inExpressionVisitor.VisitMemberthat crashed across dynamic-assembly boundaries.Bug
In .NET Interactive / Polyglot Notebooks, each cell compiles as a separate dynamic assembly (
Submission#N). When a constraint lambda captures a local declared in another submission,VisitMemberresolved theFieldInfoon the lambda submission type while the closure target carried the declaring submission type:This was invisible in xUnit (single-assembly) because the closure and the field live in the same assembly there.
Repro (manual, .NET Interactive)
Two consecutive cells in a .NET Interactive notebook (Polyglot Notebooks):
Before the fix,
Solve()raisedArgumentException: Field 'b0' defined on type 'Submission#N' is not a field on the target object of type 'Submission#M'.After the fix,
Solve()returns the expected modelx = 0.Fix
Theorem.AssertConstraintsnow callsPartialEvaluator.PartialEvalon each constraint body before handing it toExpressionVisitor.Visit. The partial evaluator folds host-captured constants (locals declared outside the theorem parameter scope) into literalConstantExpressions, soVisitMemberis never asked to resolve them reflectively.The fix is a no-op for same-assembly captures (PartialEval folds them to the same literal
VisitMemberwould have produced via reflection), so all existing xUnit tests pass unchanged.Subtrees referencing the theorem parameter (e.g.
t.Values[i]) are preserved — they are not evaluable without a parameter binding and are left for the visitor to handle..gitignore
Adds
.deploy/to the ignore list — used bydotnet publish -o .deployfor the polyglot repro infrastructure.Tests
This repository has no xUnit test infrastructure covering the cross-submission capture scenario (the bug is invisible there by construction — single-assembly), so no new test is included. The repro above is the canonical failing case and is exercised in the companion pedagogical repository (
MyIA.AI.Notebooks/ML/DataScienceWithAgents/02-ML-Cours/2.8b-Theorie-PAC-Lean.ipynbfamily + the standalonepolyglot-repro/CrossSubmissionCaptureRepro.ipynbreproduction).Notes
Cherry-picked from a downstream fork (
MyIntelligenceAgency/Z3.Linq, branchfix/c805-fc44dfa-partial-eval-cross-submission). The companion reproduction notebook and downstream.csprojglue are intentionally not included in this PR — they belong in the downstream pedagogical repo, not here.