diff --git a/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs b/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs index 26576c1..4b57903 100644 --- a/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs +++ b/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs @@ -284,9 +284,24 @@ _ when GetReplacement(symbol) is { } name => @base.WithIdentifier(Identifier(Glo /// public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node) - => renamedLocalFunctions.TryGetValue(node.Identifier.ValueText, out var newName) - ? node.WithIdentifier(Identifier(newName)) - : base.VisitIdentifierName(node); + { + if (renamedLocalFunctions.TryGetValue(node.Identifier.ValueText, out var newName)) + { + return node.WithIdentifier(Identifier(newName)); + } + + // A statement or an argument which mentions a removed CancellationToken is dropped whole, + // but an expression such as a catch filter has to keep its shape. CancellationToken.None + // stands in for the parameter: it is never cancelled, which is what the synchronized + // method does. + if (GetSymbol(node) is IParameterSymbol { Type: INamedTypeSymbol { IsCancellationToken: true } } parameter + && removedParameters.Contains(parameter)) + { + return IdentifierName(Global("System.Threading.CancellationToken.None")).WithTriviaFrom(node); + } + + return base.VisitIdentifierName(node); + } public override SyntaxNode? VisitLocalFunctionStatement(LocalFunctionStatementSyntax node) { diff --git a/tests/Generator.Tests/IsCancellationRequestedTests.cs b/tests/Generator.Tests/IsCancellationRequestedTests.cs index d36d90c..d367817 100644 --- a/tests/Generator.Tests/IsCancellationRequestedTests.cs +++ b/tests/Generator.Tests/IsCancellationRequestedTests.cs @@ -129,4 +129,47 @@ public Task WhileNotCancelledReplacedBySyncOnly() => """ } #endif """.Verify(sourceType: SourceType.MethodBody); + + [Fact] + public Task CatchFilterChecksCancellation() => """ +try +{ + await Task.Delay(1, ct); +} +catch (OperationCanceledException) when (ct.IsCancellationRequested) +{ + throw; +} +catch (Exception) +{ +} +""".Verify(sourceType: SourceType.MethodBody); + + [Fact] + public Task CatchFilterChecksCancellationAndMore() => """ +try +{ + await Task.Delay(1, ct); +} +catch (OperationCanceledException) when (ct.IsCancellationRequested || Environment.TickCount > 0) +{ + throw; +} +""".Verify(sourceType: SourceType.MethodBody); + + [Fact] + public Task CatchFilterKeepsPreservedToken() => """ +[CreateSyncVersion(PreserveCancellationToken = true)] +public async Task MethodAsync(CancellationToken ct) +{ + try + { + await Task.Delay(1, ct); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + throw; + } +} +""".Verify(); } diff --git a/tests/Generator.Tests/Snapshots/IsCancellationRequestedTests.CatchFilterChecksCancellation#g.verified.cs b/tests/Generator.Tests/Snapshots/IsCancellationRequestedTests.CatchFilterChecksCancellation#g.verified.cs new file mode 100644 index 0000000..a9ffebf --- /dev/null +++ b/tests/Generator.Tests/Snapshots/IsCancellationRequestedTests.CatchFilterChecksCancellation#g.verified.cs @@ -0,0 +1,12 @@ +//HintName: Test.Class.MethodAsync.g.cs +try +{ + global::System.Threading.Thread.Sleep(1); +} +catch (global::System.OperationCanceledException) when (global::System.Threading.CancellationToken.None.IsCancellationRequested) +{ + throw; +} +catch (global::System.Exception) +{ +} diff --git a/tests/Generator.Tests/Snapshots/IsCancellationRequestedTests.CatchFilterChecksCancellationAndMore#g.verified.cs b/tests/Generator.Tests/Snapshots/IsCancellationRequestedTests.CatchFilterChecksCancellationAndMore#g.verified.cs new file mode 100644 index 0000000..57b467a --- /dev/null +++ b/tests/Generator.Tests/Snapshots/IsCancellationRequestedTests.CatchFilterChecksCancellationAndMore#g.verified.cs @@ -0,0 +1,9 @@ +//HintName: Test.Class.MethodAsync.g.cs +try +{ + global::System.Threading.Thread.Sleep(1); +} +catch (global::System.OperationCanceledException) when (global::System.Threading.CancellationToken.None.IsCancellationRequested || global::System.Environment.TickCount > 0) +{ + throw; +} diff --git a/tests/Generator.Tests/Snapshots/IsCancellationRequestedTests.CatchFilterKeepsPreservedToken#g.verified.cs b/tests/Generator.Tests/Snapshots/IsCancellationRequestedTests.CatchFilterKeepsPreservedToken#g.verified.cs new file mode 100644 index 0000000..e9cb3b0 --- /dev/null +++ b/tests/Generator.Tests/Snapshots/IsCancellationRequestedTests.CatchFilterKeepsPreservedToken#g.verified.cs @@ -0,0 +1,12 @@ +//HintName: Test.Class.MethodAsync.g.cs +public void Method(global::System.Threading.CancellationToken ct) +{ + try + { + global::System.Threading.Thread.Sleep(1); + } + catch (global::System.OperationCanceledException) when (ct.IsCancellationRequested) + { + throw; + } +}