diff --git a/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs b/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs index 26576c1..24e7a66 100644 --- a/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs +++ b/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs @@ -34,6 +34,7 @@ internal sealed class AsyncToSyncRewriter(SemanticModel semanticModel, bool disa // Members private const string CompletedTask = nameof(Task.CompletedTask); + private const string ConfigureAwait = nameof(Task.ConfigureAwait); private const string Delay = nameof(Task<>.Delay); private const string FromResult = nameof(Task<>.FromResult); private const string WaitAsync = "WaitAsync"; @@ -493,8 +494,23 @@ bool InitializedToMemory(SyntaxNode node) if (symbol is null) { - // A missing symbol usually means the code doesn't compile due to an error or a missing reference / usings. - return base.VisitInvocationExpression(node); + // A missing symbol usually means the code doesn't compile due to an error or a missing + // reference / usings, but a call whose argument another generator supplies cannot bind + // either, generators not seeing each other's output. An awaited ConfigureAwait has no + // place in the synchronized method whether or not the call it follows binds, so drop + // it by its shape, as the Async suffix is already dropped by its shape. + var unbound = base.VisitInvocationExpression(node); + + var awaitedConfigureAwait = node is + { + Parent: AwaitExpressionSyntax, + Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: ConfigureAwait }, + ArgumentList.Arguments.Count: 1, + }; + + return awaitedConfigureAwait && unbound is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax rewritten } + ? KeepExpressionBeforeDot(rewritten) + : unbound; } if (symbol is not IMethodSymbol methodSymbol) diff --git a/tests/Generator.Tests/Snapshots/TaskTests.ConfigureAwaitOnAnUnboundCall#ReadAsync.g.verified.cs b/tests/Generator.Tests/Snapshots/TaskTests.ConfigureAwaitOnAnUnboundCall#ReadAsync.g.verified.cs new file mode 100644 index 0000000..7981ca7 --- /dev/null +++ b/tests/Generator.Tests/Snapshots/TaskTests.ConfigureAwaitOnAnUnboundCall#ReadAsync.g.verified.cs @@ -0,0 +1,5 @@ +//HintName: Test.Class.ReadAsync.g.cs +static void Read(dynamic d) +{ + d.ReadPayload(); +} diff --git a/tests/Generator.Tests/Snapshots/TaskTests.ConfigureAwaitOnAnUnboundCallInADeclaration#ReadAsync.g.verified.cs b/tests/Generator.Tests/Snapshots/TaskTests.ConfigureAwaitOnAnUnboundCallInADeclaration#ReadAsync.g.verified.cs new file mode 100644 index 0000000..af0b6d8 --- /dev/null +++ b/tests/Generator.Tests/Snapshots/TaskTests.ConfigureAwaitOnAnUnboundCallInADeclaration#ReadAsync.g.verified.cs @@ -0,0 +1,6 @@ +//HintName: Test.Class.ReadAsync.g.cs +static dynamic Read(dynamic d) +{ + dynamic payload = d.ReadPayload(); + return payload; +} diff --git a/tests/Generator.Tests/TaskTests.cs b/tests/Generator.Tests/TaskTests.cs index cfb7a02..a2c2b6d 100644 --- a/tests/Generator.Tests/TaskTests.cs +++ b/tests/Generator.Tests/TaskTests.cs @@ -64,5 +64,24 @@ public async Task MethodAsync() private async Task FooAsync() => await Task.CompletedTask; private void Foo() { } +""".Verify(); + + [Fact] + public Task ConfigureAwaitOnAnUnboundCall() => """ +[CreateSyncVersion] +static async Task ReadAsync(dynamic d) +{ + await d.ReadPayloadAsync().ConfigureAwait(false); +} +""".Verify(); + + [Fact] + public Task ConfigureAwaitOnAnUnboundCallInADeclaration() => """ +[CreateSyncVersion] +static async Task ReadAsync(dynamic d) +{ + dynamic payload = await d.ReadPayloadAsync().ConfigureAwait(false); + return payload; +} """.Verify(); }