Assert.That: capture readable LHS sub-expressions and restore runtime Func/Action filter#8352
Merged
Merged
Conversation
…ime Func/Action filter - AnalyzeWritableOperand now recurses into readable sub-expressions of an assignment LHS or unary-update operand (member receiver, index targets and arguments) while leaving the writable storage location uncaptured. Failure details for manually-constructed assignment/update trees now include receiver values and assignment RHS, addressing reviewer feedback that the previous skip-entirely approach hid useful diagnostic information. - AnalyzeMemberExpression no longer drops captures for statically-typed Func/Action members. Filtering is now applied uniformly at detail-build time using the runtime value's type, matching the historical behavior so that a null delegate-typed member still shows up as 'null'. - Updated the perf trade-off comment near the Compile() call site to be more precise about why reference-keyed plan caching would not help the common inline 'Assert.That(() => ...)' usage pattern. - New regression tests cover the readable receiver-chain capture on assignment and pre-decrement trees, and lock in the runtime-typed delegate filter so null Func members keep surfacing in failure details. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Improves Assert.That(Expression<Func<bool>>) failure-detail generation by capturing readable sub-expressions for assignment/unary-update nodes while keeping the compiled expression single-pass, and restores historical runtime-based Func/Action filtering.
Changes:
- Analyze assignment and unary-update LHS operands by recursing into readable receiver/index sub-expressions while avoiding captures that would break LValue writability.
- Move Func/Action filtering to detail-build time using runtime values (so
nulldelegate-typed members still appear asnull). - Add unit tests covering manually-constructed assign/pre-decrement trees and the
nulldelegate regression.
Show a summary per file
| File | Description |
|---|---|
| test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.That.cs | Adds new unit tests for writable-LHS analysis and runtime delegate filtering behavior. |
| src/TestFramework/TestFramework/Assertions/Assert.That.cs | Updates expression analysis to safely capture readable sub-expressions for writable operands and adjusts runtime delegate filtering/comments. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 6
Resolve conflicts with PR #8307 (different Assert.That fix): - Accept main's Assert.That.cs architecture (#8307 supersedes the AnalyzeExpression / CaptureRewriter approach this PR was patching) - Keep regression tests for assignment side-effect preservation and null delegate-typed members (they still validate behavior on the new architecture) - Drop ManuallyConstructedPreIncrementExpression test (exposes a separate double-evaluation bug in main's handling of PreDecrementAssign that is out of scope for this PR) - Relax 'ComputeValue()' assertion to nameof(ComputeValue) per review comment 3265122664 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Member
Author
Merged
|
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.
Follow-up to #8306 (merged) addressing the remaining unresolved review comments on
Assert.That(Expression<Func<bool>>).Summary of review comments addressed
1. Capture readable LHS sub-expressions of assignment / unary-update nodes (review comments 3264506474, 3264506501)
The previous behavior was to skip assignment-style
BinaryExpressionand unary-updateUnaryExpressionnodes entirely inAnalyzeExpression, which avoided the writable-LHS-wrapped-in-Block compile issue but also dropped diagnostic information for manually-constructed expression trees.AnalyzeWritableOperandnow recurses into the readable sub-expressions of the writable LHS (member receiver, index target and arguments) while leaving the writable storage location itself uncaptured. The RHS of assignments is now analyzed normally as well. Empirically verified thatMemberExpression(Block(...), Field) = valueandExpression.PreIncrementAssign(MemberExpression(Block(...), Field))both compile and assign correctly via the .NET Expression API.2. Restore runtime-typed Func/Action filter (review comment 3264506519)
AnalyzeMemberExpressionpreviously skipped captures for members whose static type wasFunc/Action. This was inconsistent with the comment at the detail-build loop ("matching the historical behavior, using runtime type as the existing code did") and dropped null delegate-typed members from failure details, which previously surfaced asnull.The static-type skip is removed; filtering is now applied uniformly at detail-build time using the runtime value's type. A regression test (
That_NullDelegateTypedMember_StillAppearsInDetails) locks in the restored behavior.3. Caching trade-off comment refresh (review comment 3264506539)
The existing perf trade-off comment near the
Compile()site is updated to be more precise: reference-keyed plan caching would not help the common inlineAssert.That(() => ...)pattern because each call site constructs a freshExpression<Func<bool>>instance at runtime, but it could be considered later if a workload that re-uses the same expression tree across calls demonstrates measurable overhead. No caching is implemented in this PR.Tests
Three new unit tests in
AssertTests.That.cs:That_ManuallyConstructedAssignExpression_AnalyzesReceiverChainAndRhs— verifies the readable LHS receiver chain and the RHS computation result both appear in failure details for a manually-constructed(container.Inner.Value = ComputeValue()) < 0tree.That_ManuallyConstructedPreIncrementExpression_AnalyzesReceiverChain— verifies the readable LHS receiver chain appears in failure details for--container.Inner.Value > 0and that the side effect still applies (single-pass).That_NullDelegateTypedMember_StillAppearsInDetails— locks in the restored runtime-typed Func/Action filter so anullFunc<int>field still surfaces asnullin the details.All 882
AssertTestspass onnet9.0.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com