Fix VSTHRD103 missing diagnostic for sync extension methods with async alternatives in the same static class - #1569
Merged
Andrew Arnott (AArnott) merged 3 commits intoApr 25, 2026
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
Drew Noakes (drewnoakes)
marked this pull request as ready for review
April 13, 2026 09:10
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. |
Andrew Arnott (AArnott)
approved these changes
Apr 25, 2026
Andrew Arnott (AArnott)
deleted the
copilot/fix-vsthrd103-diagnostic-issue
branch
April 25, 2026 17:51
This was referenced Jun 22, 2026
Merged
Merged
Merged
Merged
This was referenced Jul 27, 2026
This was referenced Aug 4, 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> [](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>
This was referenced Aug 7, 2026
This was referenced Aug 15, 2026
Closed
This was referenced Aug 22, 2026
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: