Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
20 changes: 18 additions & 2 deletions src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//HintName: Test.Class.ReadAsync.g.cs
static void Read(dynamic d)
{
d.ReadPayload();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//HintName: Test.Class.ReadAsync.g.cs
static dynamic Read(dynamic d)
{
dynamic payload = d.ReadPayload();
return payload;
}
19 changes: 19 additions & 0 deletions tests/Generator.Tests/TaskTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dynamic> ReadAsync(dynamic d)
{
dynamic payload = await d.ReadPayloadAsync().ConfigureAwait(false);
return payload;
}
""".Verify();
}
Loading