Skip to content

Fix VSTHRD110 firing in Expression-valued scenarios #1467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 11, 2025
Merged

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Jun 11, 2025

Summary

This PR fixes VSTHRD110 analyzer incorrectly firing when Task-valued lambdas are passed to methods expecting Expression<> parameters. In these scenarios, the lambda is converted to an expression tree (data structure) rather than being executed, so no warning should be shown.

Problem

When using libraries like Moq, VSTHRD110 would incorrectly fire on legitimate code:

using Moq;

var mock = new Mock<ILogger>();
mock.Verify(
    x => x.InfoAsync(It.IsAny<string>()), // VSTHRD110 incorrectly fires here
    Times.Never,
    "No Log should have been written");

public interface ILogger
{
    Task InfoAsync(string message);
}

The lambda x => x.InfoAsync(It.IsAny<string>()) is converted to an Expression<Func<ILogger, Task>> for inspection by Moq, not actually executed, so VSTHRD110 should not apply.

Solution

Added detection logic to AbstractVSTHRD110ObserveResultOfAsyncCallsAnalyzer that:

  1. Checks if invocation is within a lambda: Walks up the operation tree to find containing IAnonymousFunctionOperation
  2. Detects Expression<> conversion: Identifies when the lambda is converted to System.Linq.Expressions.Expression<T> via:
    • IConversionOperation (direct assignment: Expression<Func<T, Task>> expr = x => x.Method())
    • IArgumentOperation (method parameter: SomeMethod(x => x.Method()) where parameter is Expression<>)
  3. Suppresses diagnostic: Skips VSTHRD110 when expression tree conversion is detected

Test Coverage

Added comprehensive test cases covering:

  • ✅ Moq.Verify-like scenarios (mock.Verify(x => x.InfoAsync("test"), Times.Never))
  • ✅ Direct assignment (Expression<Func<T, Task>> expr = x => x.InfoAsync("test"))
  • ✅ Method parameters (SomeMethod(x => x.InfoAsync("test")) where parameter is Expression<>)
  • ✅ Regression test ensuring normal Task calls still trigger VSTHRD110

Edge Cases Handled

  • Intermediate operations like parentheses between lambda and conversion
  • Generic Expression<> types with various Func signatures
  • Other expression tree types in System.Linq.Expressions namespace

Fixes #1167.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • 1elvsblobprodwus2177.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • 2wrvsblobprodwus2180.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
  • 31bvsblobprodwus2143.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • 5o1vsblobprodwus2115.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • 5zivsblobprodwus217.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • 78kvsblobprodwus2152.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • 8wdvsblobprodwus2137.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • 9yyvsblobprodwus2157.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • abcvsblobprodwus2161.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • an0vsblobprodwus2146.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • b15vsblobprodwus2164.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • b53vsblobprodwus2154.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
  • cffvsblobprodwus218.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • d94vsblobprodwus2119.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
  • dyrvsblobprodwus2173.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • fcwvsblobprodwus2113.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • h8evsblobprodwus2120.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • i01vsblobprodwus216.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • isovsblobprodwus211.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • jk4vsblobprodwus2186.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
  • jlovsblobprodwus2163.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
  • km4vsblobprodwus2183.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • mdcvsblobprodwus2159.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • nypvsblobprodwus2178.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • pkwvsblobprodwus2126.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • s0ivsblobprodwus2134.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • s8nvsblobprodwus2158.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
  • tn2vsblobprodwus2124.vsblob.vsassets.io
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • v41vsblobprodwus2175.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • vd2vsblobprodwus2150.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • vipvsblobprodwus2139.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
  • wztvsblobprodwus2155.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • x0dvsblobprodwus2111.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • x1mvsblobprodwus2181.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • xktvsblobprodwus2141.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • xofvsblobprodwus2114.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • y1mvsblobprodwus2138.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet build src/Microsoft.VisualStudio.Threading.Analyzers/Microsoft.VisualStudio.Threading.Analyzers.csproj --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • z2bvsblobprodwus2185.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • zaxvsblobprodwus215.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)
  • zugvsblobprodwus2169.vsblob.vsassets.io
    • Triggering command: dotnet build --configuration Release (dns block)
    • Triggering command: dotnet restore --configfile nuget.config (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@Copilot Copilot AI changed the title [WIP] VSTHRD110 fires in Expression-valued scenarios Fix VSTHRD110 firing in Expression-valued scenarios Jun 11, 2025
@Copilot Copilot AI requested a review from AArnott June 11, 2025 03:00
Copilot finished work on behalf of AArnott June 11, 2025 03:00
@AArnott
Copy link
Member

AArnott commented Jun 11, 2025

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@AArnott
Copy link
Member

AArnott commented Jun 11, 2025

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@AArnott AArnott marked this pull request as ready for review June 11, 2025 13:40
@AArnott AArnott requested review from davkean and lifengl June 11, 2025 13:40
@AArnott AArnott enabled auto-merge (squash) June 11, 2025 13:40
@AArnott AArnott merged commit b3c2d21 into main Jun 11, 2025
6 checks passed
@AArnott AArnott deleted the copilot/fix-1167 branch June 11, 2025 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

VSTHRD110 fires in Expression-valued scenarios
3 participants