Skip to content

Fix VSTHRD103 missing diagnostic for sync extension methods with async alternatives in the same static class#1569

Open
Copilot wants to merge 3 commits intomainfrom
copilot/fix-vsthrd103-diagnostic-issue
Open

Fix VSTHRD103 missing diagnostic for sync extension methods with async alternatives in the same static class#1569
Copilot wants to merge 3 commits intomainfrom
copilot/fix-vsthrd103-diagnostic-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 12, 2026

VSTHRD103 failed to flag synchronous extension method calls from async methods when the *Async counterpart was defined in the same static class (e.g., exec.GetOutput() with ExecutableExtensions.GetOutputAsync(this IExecutable) in scope).

Root cause

AnalyzeInvocation called LookupSymbols(container: methodSymbol.ContainingType, ...) to find async alternatives. For reduced extension methods (instance-syntax calls like exec.GetOutput()), ContainingType is the declaring static class (e.g., ExecutableExtensions). LookupSymbols with 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 — When methodSymbol.ReducedFrom is non-null (reduced extension method), use reducedFrom.Parameters[0].Type (the receiver type, e.g., IExecutable) as the LookupSymbols container instead of ContainingType. With includeReducedExtensionMethods: true, this correctly finds extension methods applicable to the receiver type across all in-scope static classes.

  • VSTHRD103UseAsyncOptionAnalyzerTests.cs — Added SyncExtensionMethodWhereAsyncAlternativeExistsInSameStaticClassGeneratesWarning covering the repro scenario from the issue, including code fix verification.

// Before fix: no diagnostic on exec.GetOutput()
// After fix: VSTHRD103 flags GetOutput, code fix produces:
string result = await exec.GetOutputAsync();

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
    • Triggering command: /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
    • Triggering command: /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
    • Triggering command: /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
    • Triggering command: /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)
    • Triggering command: /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
    • Triggering command: /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
    • Triggering command: /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)
    • Triggering command: /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
    • Triggering command: /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)
    • Triggering command: /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
    • Triggering command: /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)
    • Triggering command: /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)
    • Triggering command: /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
    • Triggering command: /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)
    • Triggering command: /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)
    • Triggering command: /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
    • Triggering command: /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:

Copilot AI and others added 2 commits April 12, 2026 13:29
… 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>
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
Copilot AI requested a review from drewnoakes April 12, 2026 13:33
@drewnoakes drewnoakes marked this pull request as ready for review April 13, 2026 09:10
Copilot AI review requested due to automatic review settings April 13, 2026 09:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 LookupSymbols container (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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

VSTHRD103 does not flag sync extension method when async alternative exists in the same static class

3 participants