Skip to content

Commit c7d7f9b

Browse files
authored
Support await using/foreach statements in a Simple Program. (#42485)
Related to #41704.
1 parent 64cd5ac commit c7d7f9b

File tree

2 files changed

+135
-4
lines changed

2 files changed

+135
-4
lines changed

src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,6 @@ internal static bool HasAnyBody(this BaseMethodDeclarationSyntax declaration)
492492
return (declaration.Body ?? (SyntaxNode?)declaration.ExpressionBody) != null;
493493
}
494494

495-
#nullable enable
496-
497495
internal static bool IsTopLevelStatement([NotNullWhen(true)] GlobalStatementSyntax? syntax)
498496
{
499497
return syntax?.Parent?.IsKind(SyntaxKind.CompilationUnit) == true;
@@ -507,8 +505,19 @@ internal static bool IsSimpleProgramTopLevelStatement(GlobalStatementSyntax? syn
507505
internal static bool HasAwaitOperations(SyntaxNode node)
508506
{
509507
// Do not descend into functions
510-
return node.DescendantNodesAndSelf(child => !IsNestedFunction(child)).
511-
OfType<AwaitExpressionSyntax>().Any(); // PROTOTYPE(SimplePrograms): Recognize other async operations.
508+
return node.DescendantNodesAndSelf(child => !IsNestedFunction(child)).Any(node =>
509+
{
510+
switch (node)
511+
{
512+
case AwaitExpressionSyntax _:
513+
case LocalDeclarationStatementSyntax local when local.AwaitKeyword.IsKind(SyntaxKind.AwaitKeyword):
514+
case CommonForEachStatementSyntax @foreach when @foreach.AwaitKeyword.IsKind(SyntaxKind.AwaitKeyword):
515+
case UsingStatementSyntax @using when @using.AwaitKeyword.IsKind(SyntaxKind.AwaitKeyword):
516+
return true;
517+
default:
518+
return false;
519+
}
520+
});
512521
}
513522

514523
internal static bool IsNestedFunction(SyntaxNode child)

src/Compilers/CSharp/Test/Semantic/Semantics/SimpleProgramsTests.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,128 @@ public void Dispose()
747747
CompileAndVerify(comp, expectedOutput: "12", verify: Verification.Skipped);
748748
}
749749

750+
[Fact]
751+
public void LocalDeclarationStatement_10()
752+
{
753+
string source = @"
754+
await using var x = new C();
755+
System.Console.Write(""body "");
756+
757+
class C : System.IAsyncDisposable, System.IDisposable
758+
{
759+
public System.Threading.Tasks.ValueTask DisposeAsync()
760+
{
761+
System.Console.Write(""DisposeAsync"");
762+
return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask);
763+
}
764+
public void Dispose()
765+
{
766+
System.Console.Write(""IGNORED"");
767+
}
768+
}
769+
";
770+
var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions);
771+
comp.VerifyDiagnostics();
772+
CompileAndVerify(comp, expectedOutput: "body DisposeAsync");
773+
}
774+
775+
[Fact]
776+
public void UsingStatement_01()
777+
{
778+
string source = @"
779+
await using (var x = new C())
780+
{
781+
System.Console.Write(""body "");
782+
}
783+
784+
class C : System.IAsyncDisposable, System.IDisposable
785+
{
786+
public System.Threading.Tasks.ValueTask DisposeAsync()
787+
{
788+
System.Console.Write(""DisposeAsync"");
789+
return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask);
790+
}
791+
public void Dispose()
792+
{
793+
System.Console.Write(""IGNORED"");
794+
}
795+
}
796+
";
797+
var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions);
798+
comp.VerifyDiagnostics();
799+
CompileAndVerify(comp, expectedOutput: "body DisposeAsync");
800+
}
801+
802+
[Fact]
803+
public void UsingStatement_02()
804+
{
805+
string source = @"
806+
await using (new C())
807+
{
808+
System.Console.Write(""body "");
809+
}
810+
811+
class C : System.IAsyncDisposable, System.IDisposable
812+
{
813+
public System.Threading.Tasks.ValueTask DisposeAsync()
814+
{
815+
System.Console.Write(""DisposeAsync"");
816+
return new System.Threading.Tasks.ValueTask(System.Threading.Tasks.Task.CompletedTask);
817+
}
818+
public void Dispose()
819+
{
820+
System.Console.Write(""IGNORED"");
821+
}
822+
}
823+
";
824+
var comp = CreateCompilationWithTasksExtensions(new[] { source, IAsyncDisposableDefinition }, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions);
825+
comp.VerifyDiagnostics();
826+
CompileAndVerify(comp, expectedOutput: "body DisposeAsync");
827+
}
828+
829+
[Fact]
830+
public void ForeachStatement_01()
831+
{
832+
string source = @"
833+
using System.Threading.Tasks;
834+
835+
await foreach (var i in new C())
836+
{
837+
}
838+
839+
System.Console.Write(""Done"");
840+
841+
class C
842+
{
843+
public Enumerator GetAsyncEnumerator()
844+
{
845+
return new Enumerator();
846+
}
847+
public sealed class Enumerator
848+
{
849+
public async Task<bool> MoveNextAsync()
850+
{
851+
System.Console.Write(""MoveNextAsync "");
852+
await Task.Yield();
853+
return false;
854+
}
855+
public int Current
856+
{
857+
get => throw null;
858+
}
859+
public async Task DisposeAsync()
860+
{
861+
System.Console.Write(""DisposeAsync "");
862+
await Task.Yield();
863+
}
864+
}
865+
}
866+
";
867+
var comp = CreateCompilationWithTasksExtensions(new[] { source, s_IAsyncEnumerable }, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions);
868+
comp.VerifyDiagnostics();
869+
CompileAndVerify(comp, expectedOutput: "MoveNextAsync DisposeAsync Done");
870+
}
871+
750872
[Fact]
751873
public void LocalUsedBeforeDeclaration_01()
752874
{

0 commit comments

Comments
 (0)