C#: Fix issue with partial method extraction.#21351
Draft
michaelnebel wants to merge 9 commits intogithub:mainfrom
Draft
C#: Fix issue with partial method extraction.#21351michaelnebel wants to merge 9 commits intogithub:mainfrom
michaelnebel wants to merge 9 commits intogithub:mainfrom
Conversation
51d3823 to
b87b59d
Compare
Contributor
Author
|
DCA looks good.
|
ca855c8 to
a930cbf
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a C# extractor issue where partial members (notably partial methods) could have their bodies extracted multiple times due to symbol/caching interactions between defining vs implementing declarations.
Changes:
- Prefer extracting the body-declaring symbol for methods/properties/events (via new
GetBodyDeclaringSymbolhelpers) to avoid duplicate extraction for partial members. - Refactor body detection (
Block/ExpressionBody) into cached helpers and useHasBodyfor accessors/event accessors. - Update/add library tests and expectations, including a new dataflow regression test involving a partial method body.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| csharp/extractor/Semmle.Extraction.CSharp/CodeAnalysisExtensions/SymbolExtensions.cs | Adds GetBodyDeclaringSymbol helpers (methods/properties/events). |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs | Creates ordinary method entities from the body-declaring symbol. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Method.cs | Uses Symbol for NumberOfLines after symbol selection changes. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs | Creates property entities from the body-declaring symbol; updates accessor extraction to use Symbol. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs | Creates indexer entities from the body-declaring symbol; updates accessor extraction to use Symbol. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs | Creates event entities from the body-declaring symbol; updates accessor extraction to use Symbol. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedSymbol.cs | Adds cached Block/ExpressionBody and HasBody helpers. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs | Uses HasBody when deciding whether to mark accessors as compiler-generated. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/EventAccessor.cs | Uses HasBody when deciding whether to mark event accessors as compiler-generated. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs | Renames/refactors synthetic-body logic (MakeSyntheticBody) and uses HasBody. |
| csharp/ql/lib/change-notes/2026-02-23-partial-extraction-fix.md | Adds changelog entry for the extraction fix. |
| csharp/ql/test/library-tests/partial/Partial.cs | Extends partial-member test input with a partial method with body and parameters. |
| csharp/ql/test/library-tests/partial/PartialMethodBody.ql | Extends test query to assert body-count is 0/1 (detect duplicates). |
| csharp/ql/test/library-tests/dataflow/methods/Methods.cs | Adds a partial-method dataflow scenario for regression coverage. |
| csharp/ql/test/library-tests/dataflow/methods/MethodFlow.ql | Adds a path-problem query to validate flow through a partial method body. |
| csharp/ql/test/library-tests/*.expected | Updates expected outputs to reflect the new extraction behavior and new tests. |
Comment on lines
108
to
112
| public BlockSyntax? Block => vBlock ??= GetBlock(Symbol); | ||
|
|
||
| private ExpressionSyntax? vExpressionBody; | ||
| public ExpressionSyntax? ExpressionBody => vExpressionBody ??= GetExpressionBody(Symbol); | ||
|
|
There was a problem hiding this comment.
Block/ExpressionBody use vBlock ??= / vExpressionBody ??= which won’t memoize a null result, so symbols without bodies will re-scan DeclaringSyntaxReferences on every access. If the intent is to cache the lookup, consider caching the computed state separately (or using Lazy<T?>) so null is memoized too.
Suggested change
| public BlockSyntax? Block => vBlock ??= GetBlock(Symbol); | |
| private ExpressionSyntax? vExpressionBody; | |
| public ExpressionSyntax? ExpressionBody => vExpressionBody ??= GetExpressionBody(Symbol); | |
| private bool vBlockComputed; | |
| public BlockSyntax? Block | |
| { | |
| get | |
| { | |
| if (!vBlockComputed) | |
| { | |
| vBlock = GetBlock(Symbol); | |
| vBlockComputed = true; | |
| } | |
| return vBlock; | |
| } | |
| } | |
| private ExpressionSyntax? vExpressionBody; | |
| private bool vExpressionBodyComputed; | |
| public ExpressionSyntax? ExpressionBody | |
| { | |
| get | |
| { | |
| if (!vExpressionBodyComputed) | |
| { | |
| vExpressionBody = GetExpressionBody(Symbol); | |
| vExpressionBodyComputed = true; | |
| } | |
| return vExpressionBody; | |
| } | |
| } |
…too look for both when checking whether a body is available.
a930cbf to
0423882
Compare
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.
It turns out that our current implementation for extracting partial methods in some cases might extract multiple method bodies.
An example:
In this case the following happens in the extractor:
obj(as the parameter is accessed).objas a side effect also extracts its parent, which is the symbol representing the partial declaration ofM.As a result of the above, the body might be extracted twice.
In this PR we skip the extraction of the partial definition and only extract the partial declaration (if one exists).
Review on a commit by commit basis is recommended.