Avoid walking the whole operation tree in IsInExpressionContext - #1409
Merged
Merged
Conversation
IsInExpressionContext enumerated operation.Ancestors() up to the root of the operation tree, allocating an iterator state machine per call and calling InheritsFrom on every argument and conversion along the way. Ten analyzers call it, several of them on the densest operation kinds (Invocation, Binary), and the common answer is false. Walk operation.Parent directly instead of the iterator, and stop at the first enclosing IBlockOperation that is not the body of a lambda. An expression tree can only be entered by converting a lambda, so a method body, a local function body or a nested statement block cannot be inside one. The lambda body itself must still be traversed, as IAnonymousFunctionOperation.Body is an IBlockOperation even for the expression-bodied lambdas that expression trees require.
meziantou
enabled auto-merge (squash)
September 6, 2026 04:21
This was referenced Sep 6, 2026
Closed
This was referenced Sep 24, 2026
Open
Open
Open
Open
Open
Open
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.
What
OperationUtilities.IsInExpressionContextnow walksoperation.Parentdirectly instead of theAncestors()iterator, and stops at the first enclosingIBlockOperationthat is not the body of a lambda.Why
The previous implementation enumerated
operation.Ancestors()all the way to the root of the operation tree, allocating ayield returnstate machine on every call and callingInheritsFrom(System.Linq.Expressions.Expression)on everyIArgumentOperationandIConversionOperationit passed. Ten analyzers call it —UseStringComparer,UseStringComparison,UseStringEquals,UsePatternMatchingForEqualityComparisons,DoNotUseEqualityOperatorsForSpanOfChar,AwaitAwaitableMethodInSyncMethod,NamedParameter,UseAwaitInsteadOfReturningTask,UsePatternMatchingInsteadOfHasValue— several of them on the densest operation kinds (Invocation,Binary), and the common answer isfalse, which meant walking the full depth every time.Note for reviewers
The lambda-body exception is what keeps the early exit correct.
IAnonymousFunctionOperation.Bodyis always anIBlockOperation, even for the expression-bodied lambdas that expression trees require, so the walk must pass through that one block to reach theExpression<>-typed conversion or argument above it. Every other block — method body, local function body, nested statement block — cannot sit under an expression tree, so it terminates the walk. No separateIMethodBodyOperationcase is needed: the body's block is reached first and stops there.Tests
DisabledInExpression_NestedInBlockstoUsePatternMatchingForEqualityComparisonsAnalyzerTests, covering theIArgumentOperationpath (IQueryable.Where(item => item == 0)) nested inside a statement block. The existingDisabledInExpressiononly covered the cast/conversion path.case IBlockOperation:makes bothDisabledInExpressiontests fail, because MA0140 is then reported inside the expression tree.dotnet run --project src/DocumentationGeneratorexits 0 with no markdown changes.