diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs index f71f931f4e643..d575fe8e612d9 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs @@ -492,8 +492,6 @@ internal static bool HasAnyBody(this BaseMethodDeclarationSyntax declaration) return (declaration.Body ?? (SyntaxNode?)declaration.ExpressionBody) != null; } -#nullable enable - internal static bool IsTopLevelStatement([NotNullWhen(true)] GlobalStatementSyntax? syntax) { return syntax?.Parent?.IsKind(SyntaxKind.CompilationUnit) == true; @@ -507,8 +505,19 @@ internal static bool IsSimpleProgramTopLevelStatement(GlobalStatementSyntax? syn internal static bool HasAwaitOperations(SyntaxNode node) { // Do not descend into functions - return node.DescendantNodesAndSelf(child => !IsNestedFunction(child)). - OfType().Any(); // PROTOTYPE(SimplePrograms): Recognize other async operations. + return node.DescendantNodesAndSelf(child => !IsNestedFunction(child)).Any(node => + { + switch (node) + { + case AwaitExpressionSyntax _: + case LocalDeclarationStatementSyntax local when local.AwaitKeyword.IsKind(SyntaxKind.AwaitKeyword): + case CommonForEachStatementSyntax @foreach when @foreach.AwaitKeyword.IsKind(SyntaxKind.AwaitKeyword): + case UsingStatementSyntax @using when @using.AwaitKeyword.IsKind(SyntaxKind.AwaitKeyword): + return true; + default: + return false; + } + }); } internal static bool IsNestedFunction(SyntaxNode child) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SimpleProgramsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SimpleProgramsTests.cs index 1e65128bccb06..493773af5795b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SimpleProgramsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SimpleProgramsTests.cs @@ -747,6 +747,128 @@ public void Dispose() CompileAndVerify(comp, expectedOutput: "12", verify: Verification.Skipped); } + [Fact] + public void LocalDeclarationStatement_10() + { + string source = @" +await using var x = new C(); +System.Console.Write(""body ""); + +class C : System.IAsyncDisposable, System.IDisposable +{ + public System.Threading.Tasks.ValueTask DisposeAsync() + { + System.Console.Write(""DisposeAsync""); + return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); + } + public void Dispose() + { + System.Console.Write(""IGNORED""); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "body DisposeAsync"); + } + + [Fact] + public void UsingStatement_01() + { + string source = @" +await using (var x = new C()) +{ + System.Console.Write(""body ""); +} + +class C : System.IAsyncDisposable, System.IDisposable +{ + public System.Threading.Tasks.ValueTask DisposeAsync() + { + System.Console.Write(""DisposeAsync""); + return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); + } + public void Dispose() + { + System.Console.Write(""IGNORED""); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "body DisposeAsync"); + } + + [Fact] + public void UsingStatement_02() + { + string source = @" +await using (new C()) +{ + System.Console.Write(""body ""); +} + +class C : System.IAsyncDisposable, System.IDisposable +{ + public System.Threading.Tasks.ValueTask DisposeAsync() + { + System.Console.Write(""DisposeAsync""); + return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask); + } + public void Dispose() + { + System.Console.Write(""IGNORED""); + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "body DisposeAsync"); + } + + [Fact] + public void ForeachStatement_01() + { + string source = @" +using System.Threading.Tasks; + +await foreach (var i in new C()) +{ +} + +System.Console.Write(""Done""); + +class C +{ + public Enumerator GetAsyncEnumerator() + { + return new Enumerator(); + } + public sealed class Enumerator + { + public async Task MoveNextAsync() + { + System.Console.Write(""MoveNextAsync ""); + await Task.Yield(); + return false; + } + public int Current + { + get => throw null; + } + public async Task DisposeAsync() + { + System.Console.Write(""DisposeAsync ""); + await Task.Yield(); + } + } +} +"; + var comp = CreateCompilationWithTasksExtensions(new[] { source, s_IAsyncEnumerable }, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "MoveNextAsync DisposeAsync Done"); + } + [Fact] public void LocalUsedBeforeDeclaration_01() {