Skip to content

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

Merged
Andrew Arnott (AArnott) merged 3 commits into
mainfrom
copilot/fix-vsthrd103-diagnostic-issue
Apr 25, 2026
Merged

Fix VSTHRD103 missing diagnostic for sync extension methods with async alternatives in the same static class#1569
Andrew Arnott (AArnott) merged 3 commits into
mainfrom
copilot/fix-vsthrd103-diagnostic-issue

Conversation

Copilot AI commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

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
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
@drewnoakes
Drew Noakes (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

Copilot AI left a comment

Copy link
Copy Markdown

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.

@AArnott
Andrew Arnott (AArnott) merged commit ea0435c into main Apr 25, 2026
13 checks passed
@AArnott
Andrew Arnott (AArnott) deleted the copilot/fix-vsthrd103-diagnostic-issue branch April 25, 2026 17:51
This was referenced Jun 22, 2026
James Gunn (gunndabad) pushed a commit to DFE-Digital/teaching-record-system that referenced this pull request Aug 7, 2026
….23 (#3704)

Updated
[Microsoft.VisualStudio.Threading.Analyzers](https://github.com/microsoft/vs-threading)
from 17.14.15 to 18.7.23.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.VisualStudio.Threading.Analyzers's
releases](https://github.com/microsoft/vs-threading/releases)._

## 18.7.23

## What's Changed

### Fixes

* Fix `CancellationToken.Combine` with 3+ cancelable tokens by @​AArnott
in microsoft/vs-threading#1443
* Fix VSTHRD110 firing in Expression-valued scenarios by @​AArnott with
@​Copilot in microsoft/vs-threading#1467
* Fix super set for VSTHRD103 by @​AArnott in
microsoft/vs-threading#1545
* Fix VSTHRD114 not firing for null in ternary conditional expressions
by @​AArnott with @​Copilot in
microsoft/vs-threading#1548
* Disable VSTHRD010 in AppWithoutMainThread.editorconfig by
@​ArturDorochowicz in
microsoft/vs-threading#1562
* Fix VSTHRD103 missing diagnostic for sync extension methods with async
alternatives in the same static class by @​drewnoakes with @​Copilot in
microsoft/vs-threading#1569

### Enhancements

* Add `JoinableTaskFactory.DisableProcessing()` by @​AArnott in
microsoft/vs-threading#1576
* Add `NoMessagePumpSyncContext..ctor(SynchronizationContext)` for
Post/Send behaviors by @​AArnott in
microsoft/vs-threading#1578
* Add trim and NativeAOT safety attributes by @​AArnott in
microsoft/vs-threading#1471
* Add `IPendingExecutionRequestState` interface to expose completion
state of `SingleExecuteProtector` by @​lifengl in
microsoft/vs-threading#1447
* Add AdditionalFiles support to VSTHRD103 analyzer for excluding
specific APIs by @​AArnott with @​Copilot in
microsoft/vs-threading#1465
* Document InvalidOperationException for AsyncReaderWriterLock
acquisition methods by @​AArnott with @​Copilot in
microsoft/vs-threading#1466
* Allow library code to detect the JoinableTaskContext is not associated
with Main thread by @​lifengl in
microsoft/vs-threading#1477
* remove NotifyOfCrossThreadDependency call inside
get_NoMessagePumpSynchronizationContext by @​lifengl in
microsoft/vs-threading#1486
* reduce overhead when running in no main thread mode by @​lifengl in
microsoft/vs-threading#1502
* Join tasks waited by JoinableTaskCollection.JoinUntilEmpty in
dumpasync result by @​lifengl in
microsoft/vs-threading#1538
* Remove slow regex from threading analyzers by @​AArnott in
microsoft/vs-threading#1547


## New Contributors
* @​jgrosic made their first contribution in
microsoft/vs-threading#1485
* @​AbhitejJohn made their first contribution in
microsoft/vs-threading#1533
* @​ArturDorochowicz made their first contribution in
microsoft/vs-threading#1562
* @​microsoft-github-policy-service[bot] made their first contribution
in microsoft/vs-threading#1587

**Full Changelog**:
microsoft/vs-threading@v17.14.15...v18.7.23

Commits viewable in [compare
view](microsoft/vs-threading@v17.14.15...v18.7.23).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Microsoft.VisualStudio.Threading.Analyzers&package-manager=nuget&previous-version=17.14.15&new-version=18.7.23)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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

4 participants