Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ private static ImmutableArray<ISymbol> FindAwaitableAlternatives(WellKnownExtens
: containingSymbol.ContainingType; // If not dotted, than the scope is the current type. Local function support is missing here.
var members = GetMethodSymbolsInScope($"{methodSymbol.Name}Async", wellKnownExtensionMethodContainer, invokedType, methodSymbol.ContainingType);
var awaitableCandidates = members.Where(x => x.IsAwaitableNonDynamic());
var awaitableAlternatives = SpeculativeBindCandidates(semanticModel, codeBlock, awaitableRoot, invocationExpression, awaitableCandidates).ToImmutableArray();
return awaitableAlternatives;
var awaitableAlternatives = SpeculativeBindCandidates(semanticModel, codeBlock, awaitableRoot, invocationExpression, awaitableCandidates);
var withoutContainer = awaitableAlternatives.Where(x => !containingSymbol.Equals(x)).ToImmutableArray(); // Exclude candidates that would resolve to the containing method (endless loop)
Comment thread
mary-georgiou marked this conversation as resolved.
Outdated
return withoutContainer;
}
return ImmutableArray<ISymbol>.Empty;
}
Expand Down Expand Up @@ -158,7 +159,8 @@ private static IMethodSymbol SpeculativeBindCandidate(SemanticModel semanticMode
}
if (original == awaitableRoot && result is ExpressionSyntax resultExpression)
Comment thread
mary-georgiou marked this conversation as resolved.
{
result = SyntaxFactory.AwaitExpression(resultExpression);
result = SyntaxFactory.ParenthesizedExpression(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is is possible to add a test that fails if this line is not changed to add parentheses around the replacement?

@martin-strecker-sonarsource martin-strecker-sonarsource May 2, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No. This is not possible. This is the funny part with SyntaxTree manipulations: You can end up with a syntax tree that is not expressible in code (If you take the transformed syntax tree, convert it to code, and convert it back to a tree, the new tree looks different). The parentheses are not needed in the tree because the tree looks like parentheses are present. The parentheses will be needed for the code fix though.
With the parentheses, debugging is a bit better because the text representation of the syntax tree is better.
I can remove all the trivia and parentheses handling because, for the speculative binding, they are not needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe add a quick comment there to capture this?
On why there are parentheses.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'll merge this to make it to the next release. Let's add a comment in one of the next PRs.
I'll keep a task on my calendar to not forget.

SyntaxFactory.AwaitExpression(resultExpression.WithoutTrivia().WithLeadingTrivia(SyntaxFactory.ElasticSpace))).WithTriviaFrom(resultExpression);
}
return result;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,13 @@ public void UseAwaitableMethod_EF() =>
.AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCoreSqlServer(EntityFrameworkVersion))
.AddPaths("UseAwaitableMethod_EF.cs")
.Verify();

[TestMethod]
public void UseAwaitableMethod_MongoDb() =>
builder
.WithOptions(ParseOptionsHelper.FromCSharp11)
.AddReferences(NuGetMetadataReference.MongoDBDriver())
.AddPaths("UseAwaitableMethod_MongoDBDriver.cs")
.Verify();
#endif
}
17 changes: 17 additions & 0 deletions analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,20 @@ async Task Test()
VoidMethod(1); // FN. CancellationToken.None could be provided by the code fix
}
}

class ResolvesToSelf
{
public void Synchronous() { }

public async Task SynchronousAsync()
{
Synchronous(); // Compliant. The fix would cause an endless loop
}

public void Generic<T>() { }

public async Task GenericAsync<T>()
{
Generic<T>(); // Compliant. The fix would cause an endless loop
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;

public record Person(int Id, string Name);

public class MongoDBDriver
{
public async Task Query(IMongoCollection<Person> personCollection)
{
var sort = Builders<Person>.Sort.Descending(nameof(Person.Name));
// FP for
// * Find https://mongodb.github.io/mongo-csharp-driver/2.8/apidocs/html/M_MongoDB_Driver_IMongoCollectionExtensions_Find__1_3.htm
// * FindAsync https://mongodb.github.io/mongo-csharp-driver/2.8/apidocs/html/M_MongoDB_Driver_IMongoCollectionExtensions_FindAsync__1_3.htm
// Speculative binding finds "FindAsync" but the return type IAsyncCursor<> of FindAsync is not compatible with return type IFindFluent<,> of "Find"
// Speculative binding does overload resolution according to the C# rules, which ignore return types.
// It seems to ignore the compiler binding error for the following "Sort" which is only defined on IFindFluent, but not in IAsyncCursor.
var snapshot = await personCollection.Find(s => s.Id > 10) // Noncompliant FP
.Sort(sort) // Not defined on IAsyncCursor (return type of FindAsync)
.FirstOrDefaultAsync().ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ public static References MicrosoftNetSdkFunctions(string packageVersion = Consta
public static References MicrosoftNetWebApiCore(string packageVersion) => Create("Microsoft.AspNet.WebApi.Core", packageVersion);
public static References MicrosoftSqlServerCompact(string packageVersion = "4.0.8876.1") => Create("Microsoft.SqlServer.Compact", packageVersion);
public static References MicrosoftWebXdt(string packageVersion = "3.0.0") => Create("Microsoft.Web.Xdt", packageVersion);
public static References MongoDBDriver(string packageVersion = Constants.NuGetLatestVersion) =>
Create("MongoDB.Driver", packageVersion)
.Concat(MongoDBDriverCore(packageVersion));
public static References MongoDBDriverCore(string packageVersion = Constants.NuGetLatestVersion) => Create("MongoDB.Driver.Core", packageVersion);
public static References MonoPosixNetStandard(string packageVersion = "1.0.0") => Create("Mono.Posix.NETStandard", packageVersion, "linux-x64");
public static References MonoDataSqlite(string packageVersion = Constants.NuGetLatestVersion) => Create("Mono.Data.Sqlite", packageVersion);
public static References Moq(string packageVersion) => Create("Moq", packageVersion);
Expand Down