diff --git a/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs index c4ccc7a9..1104cf48 100755 --- a/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs @@ -290,8 +290,17 @@ private bool HasAsyncEquivalent(IInvocationOperation operation, [NotNullWhen(tru IncludeExtensionsMethods: true, SyntaxNode: operation.Syntax); + // When the method name is the same as the original method, and the original is non-generic + // while a candidate is generic, the compiler will always prefer the non-generic original + // over the generic candidate. Awaiting the call would therefore still resolve to the + // non-generic (non-awaitable) method, making the suggested fix invalid. + var sameNameSearch = string.Equals(methodName, targetMethod.Name, StringComparison.Ordinal); + foreach (var candidateMethod in _overloadFinder.FindSimilarMethods(targetMethod, options, methodName, default)) { + if (sameNameSearch && !targetMethod.IsGenericMethod && candidateMethod.IsGenericMethod) + continue; + if (IsPotentialAsyncEquivalent(operation, candidateMethod)) return candidateMethod; } @@ -300,6 +309,9 @@ private bool HasAsyncEquivalent(IInvocationOperation operation, [NotNullWhen(tru { foreach (var candidateMethod in _overloadFinder.FindSimilarMethods(targetMethod, options, methodName, [new OverloadParameterType(CancellationTokenSymbol)])) { + if (sameNameSearch && !targetMethod.IsGenericMethod && candidateMethod.IsGenericMethod) + continue; + if (IsPotentialAsyncEquivalent(operation, candidateMethod)) return candidateMethod; } diff --git a/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_AsyncContextTests.cs b/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_AsyncContextTests.cs index d407bd65..3f822668 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_AsyncContextTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_AsyncContextTests.cs @@ -502,6 +502,41 @@ public async Task A() .ValidateAsync(); } + [Fact] + public async Task Method_NonGenericOverloadWithGenericAwaitableOverload_NoDiagnostic() + { + // The non-generic method has a same-named generic overload with an awaitable return type. + // The compiler always prefers the non-generic method, so adding await would still resolve + // to the non-generic (non-awaitable) method, making the suggestion invalid (false positive). + await CreateProjectBuilder() + .WithSourceCode(""" + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + class Test + { + public async Task A() + { + var x = "hello"; + Assert.That(x); + } + } + + static class Assert + { + public static ValueAssertion That(string? value) => throw null; + public static ValueAssertion That(T value) => throw null; + } + + class ValueAssertion { } + + class ValueAssertion + { + public TaskAwaiter GetAwaiter() => throw null; + } + """) + .ValidateAsync(); + } + [Fact] public async Task Console_NoDiagnostic() {