Fix VSTHRD103 missing diagnostic for sync extension methods with async alternatives in the same static class#1569
Open
Fix VSTHRD103 missing diagnostic for sync extension methods with async alternatives in the same static class#1569
Conversation
… exists in same static class Agent-Logs-Url: https://github.com/microsoft/vs-threading/sessions/d0908deb-1f03-4a2b-8c97-4c4530598e07 Co-authored-by: drewnoakes <350947+drewnoakes@users.noreply.github.com>
…ng only) Agent-Logs-Url: https://github.com/microsoft/vs-threading/sessions/d0908deb-1f03-4a2b-8c97-4c4530598e07 Co-authored-by: drewnoakes <350947+drewnoakes@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix VSTHRD103 diagnostic for sync extension method calls
Fix VSTHRD103 missing diagnostic for sync extension methods with async alternatives in the same static class
Apr 12, 2026
There was a problem hiding this comment.
Pull request overview
Fixes a gap in VSTHRD103 where synchronous reduced extension method invocations (instance-syntax) were not producing diagnostics when an *Async alternative existed as an extension method in the same (or any in-scope) static class.
Changes:
- Updates VSTHRD103’s async-alternative lookup to use the reduced extension method’s receiver type as the
LookupSymbolscontainer (instead of the declaring static class). - Adds a regression test covering the scenario where both sync/async extension methods live in the same static class and the sync one is invoked via instance syntax.
- Verifies the code fix produces
await receiver.MethodAsync()as expected.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/Microsoft.VisualStudio.Threading.Analyzers.CSharp/VSTHRD103UseAsyncOptionAnalyzer.cs | Adjusts symbol lookup to correctly find async alternatives for reduced extension method invocations. |
| test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD103UseAsyncOptionAnalyzerTests.cs | Adds a regression test + code-fix verification for the same-static-class extension method repro. |
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.
VSTHRD103 failed to flag synchronous extension method calls from async methods when the
*Asynccounterpart was defined in the same static class (e.g.,exec.GetOutput()withExecutableExtensions.GetOutputAsync(this IExecutable)in scope).Root cause
AnalyzeInvocationcalledLookupSymbols(container: methodSymbol.ContainingType, ...)to find async alternatives. For reduced extension methods (instance-syntax calls likeexec.GetOutput()),ContainingTypeis the declaring static class (e.g.,ExecutableExtensions).LookupSymbolswith a static class as container returns nothing useful — it looks for instance-accessible members, but static classes have no instances and their extension methods are scoped to other receiver types.Fix
VSTHRD103UseAsyncOptionAnalyzer.cs— WhenmethodSymbol.ReducedFromis non-null (reduced extension method), usereducedFrom.Parameters[0].Type(the receiver type, e.g.,IExecutable) as theLookupSymbolscontainer instead ofContainingType. WithincludeReducedExtensionMethods: true, this correctly finds extension methods applicable to the receiver type across all in-scope static classes.VSTHRD103UseAsyncOptionAnalyzerTests.cs— AddedSyncExtensionMethodWhereAsyncAlternativeExistsInSameStaticClassGeneratesWarningcovering the repro scenario from the issue, including code fix verification.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
77wvsblobprodwus2160.vsblob.vsassets.io/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/40092A0EBCDDE0FDE0834F13B5E29B51/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)7t8vsblobprodwus2168.vsblob.vsassets.io/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/D5B55DFC9E70C92859A4309871089FB3/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)84hvsblobprodwus2148.vsblob.vsassets.io/opt/hostedtoolcache/dotnet/dotnet dotnet build src/Microsoft.VisualStudio.Threading.Analyzers.CSharp/Microsoft.VisualStudio.Threading.Analyzers.CSharp.csproj -c Release g.Analyzers.Tests/VSTHRD200UseAsyncNamingConventionAnalyzerTests.cs g.Analyzers.Tests/VSTHRD114AvoidReturningNullTaskAnalyzerTests.cs g.Analyzers.Tests/Usings.cs g.Analyzers.Tests/VSTHRD112ImplementSystemIAsyncDisposableAnalyzerTests.cs g.Analyzers.Tests/VSTHRD004AwaitSwitchToMainThreadAsyncAnalyzerTests.cs g.Analyzers.Tests/VSTHRD106UseInvokeAsyncForAsyncEventsAnalyzerTests.cs g.Analyzers.Tests/Properties/AssemblyInfo.cs g.An�� g.Analyzers.Tests/VSTHRD100AsyncVoidMethodAnalyzerTests.cs g.Analyzers.Tests/VSTHRD003UseJtfRunAsyncAnalyzerTests.cs(dns block)ba0vsblobprodwus2130.vsblob.vsassets.io/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/40092A0EBCDDE0FDE0834F13B5E29B51/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/D5B55DFC9E70C92859A4309871089FB3/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)d94vsblobprodwus2119.vsblob.vsassets.io/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/40092A0EBCDDE0FDE0834F13B5E29B51/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)i01vsblobprodwus216.vsblob.vsassets.io/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/40092A0EBCDDE0FDE0834F13B5E29B51/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/F7D2419353110B6F32053972B32A361D/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)mt2vsblobprodwus2110.vsblob.vsassets.io/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/D5B55DFC9E70C92859A4309871089FB3/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/40092A0EBCDDE0FDE0834F13B5E29B51/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)ox9vsblobprodwus2149.vsblob.vsassets.io/home/REDACTED/work/vs-threading/vs-threading/bin/Microsoft.VisualStudio.Threading.Analyzers.Tests/Release/net8.0/Microsoft.VisualStudio.Threading.Analyzers.Tests /home/REDACTED/work/vs-threading/vs-threading/bin/Microsoft.VisualStudio.Threading.Analyzers.Tests/Release/net8.0/Microsoft.VisualStudio.Threading.Analyzers.Tests --filter-class VSTHRD103UseAsyncOptionAnalyzerTests --server dotnettestcli --dotnet-test-pipe /tmp/d45fc87461b74b3fbc5a66cab98b99b1 -main/ebpf/in-path/mkcert(dns block)/home/REDACTED/work/vs-threading/vs-threading/bin/Microsoft.VisualStudio.Threading.Analyzers.Tests/Release/net8.0/Microsoft.VisualStudio.Threading.Analyzers.Tests /home/REDACTED/work/vs-threading/vs-threading/bin/Microsoft.VisualStudio.Threading.Analyzers.Tests/Release/net8.0/Microsoft.VisualStudio.Threading.Analyzers.Tests --filter-class VSTHRD103UseAsyncOptionAnalyzerTests --server dotnettestcli --dotnet-test-pipe /tmp/2685de22ba764cc6b0df2a7cb8f7bb40(dns block)/home/REDACTED/work/vs-threading/vs-threading/bin/Microsoft.VisualStudio.Threading.Analyzers.Tests/Release/net8.0/Microsoft.VisualStudio.Threading.Analyzers.Tests /home/REDACTED/work/vs-threading/vs-threading/bin/Microsoft.VisualStudio.Threading.Analyzers.Tests/Release/net8.0/Microsoft.VisualStudio.Threading.Analyzers.Tests --filter-method VSTHRD103UseAsyncOptionAnalyzerTests.SyncInvocationWhereAsyncOptionExistsInExtensionMethodGeneratesWarning --server dotnettestcli --dotnet-test-pipe /tmp/85071a81f502400780298f33ce475a2c gsign/gh-gpgsign--quiet(dns block)s0ivsblobprodwus2134.vsblob.vsassets.io/home/REDACTED/work/vs-threading/vs-threading/bin/Microsoft.VisualStudio.Threading.Analyzers.Tests/Release/net8.0/Microsoft.VisualStudio.Threading.Analyzers.Tests /home/REDACTED/work/vs-threading/vs-threading/bin/Microsoft.VisualStudio.Threading.Analyzers.Tests/Release/net8.0/Microsoft.VisualStudio.Threading.Analyzers.Tests --filter-method VSTHRD103UseAsyncOptionAnalyzerTests.SyncInvocationWhereAsyncOptionExistsInExtensionMethodGeneratesWarning --server dotnettestcli --dotnet-test-pipe /tmp/3c5c720932494dc88b1b18a4b70827a9(dns block)/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/40092A0EBCDDE0FDE0834F13B5E29B51/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/B42A990C1810BF03B6D8FCD090EC1B53/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)x0dvsblobprodwus2111.vsblob.vsassets.io/opt/hostedtoolcache/dotnet/dotnet dotnet restore --no-dependencies /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/D5B55DFC9E70C92859A4309871089FB3/missingpackages_workingdir --packages /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/missingpackages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal --configfile /tmp/codeql-scratch-eb66641681425106/dbs/csharp/working/nugetconfig/nuget.config --force(dns block)If you need me to access, download, or install something from one of these locations, you can either: