-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Await using declarations #31980
Await using declarations #31980
Conversation
chsienki
commented
Dec 21, 2018
- Add syntax, binding and lowering for await using declarations
- Add tests
- Update syntax nodes - Add parsing tests
- Add awaitable info to bound node - Find and store the awaiter if async - Lower the declaration node correctly - Add tests
@dotnet/roslyn-compiler for review please |
@@ -2505,6 +2505,132 @@ public void TestUsingVarWithVarDeclarationTree() | |||
} | |||
} | |||
|
|||
[Fact] | |||
public void TestAwaitUsingVarWithDeclaration() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TestAwaitUsingVarWithDeclaration [](start = 20, length = 32)
nit: I don't know if this test adds much compared to the one below (TestAwaitUsingVarWithDeclarationTree
). #Closed
using Xunit; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen | ||
{ | ||
public class CodeGenUsingDeclarationTests : EmitMetadataTestBase | ||
{ | ||
private const string _asyncDisposable = @" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_asyncDisposable [](start = 29, length = 16)
You could use CSharpTestBase.AsyncStreamsTypes
instead. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, they haven't been merged in from master yet. I'll make a note to switch it over when they do. (#28892) #Pending
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { } | ||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { } | ||
public MyTask Task => _task; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: extra empty lines below #Closed
}"; | ||
var compilation = CreateCompilationWithTasksExtensions(source + _asyncDisposable, options: TestOptions.DebugExe).VerifyDiagnostics(); | ||
|
||
CompileAndVerify(compilation, expectedOutput: "Dispose async").VerifyIL("C2.Main", @" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VerifyIL("C2.Main" [](start = 75, length = 18)
I think this is not the method you intended to inspect.
C2.Main
is an auto-generated wrapper for the real method. I expect to see a construction of new C1()
and a try/finally
in the IL. #Closed
{ | ||
static async Task Main() | ||
{ | ||
await using C1 c = new C1(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new C1(); [](start = 27, length = 9)
Please add a Write in the constructor for C1, and add a Write below the using
. This way the order of evaluation is made completely visible. #Closed
using System.Threading.Tasks; | ||
class C1 : IAsyncDisposable | ||
{ | ||
public ValueTask DisposeAsync() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public ValueTask DisposeAsync() [](start = 4, length = 32)
Please add a variant of this test with an explicit implementation of IAsyncDisposable. #Closed
var compilation = CreateCompilationWithTasksExtensions(source + _asyncDisposable, options: TestOptions.DebugExe).VerifyDiagnostics(); | ||
|
||
CompileAndVerify(compilation, expectedOutput: "Dispose async"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} [](start = 7, length = 2)
Please add tests where the resource is null
(both with interface or with pattern-based instance or extension method).
Can you clarify: will pattern-based using-declaration (with and without await
) be restricted to ref structs?
Also, please add tests for await using
within a try
block, a catch
block and a finally
block. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it will be restricted, its just in the PR after this one (will teach me to queue stuff up :( )
I'll skip the pattern based tests though, as the next PR makes those invalid (can't have ref struct in an async method).
} | ||
N(SyntaxKind.SemicolonToken); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} [](start = 7, length = 2)
Please also add a parsing test for await var x = b;
#Closed
/// <summary> | ||
/// Binds an awaiter for asynchronous dispose | ||
/// </summary> | ||
/// <param name="node">The synatx node to bind for</param> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
synatx [](start = 35, length = 6)
Typo #Resolved
/// <param name="awaitKeyword">The await keyword of the syntax</param> | ||
/// <param name="disposeMethodOpt">The dispose method to call, or null to use IAsyncDisposable.DisposeAsync</param> | ||
/// <param name="diagnostics">Populated with any errors</param> | ||
/// <param name="hasErrors">True if errors occured during binding</param> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
occured [](start = 51, length = 7)
Typo #Resolved
@@ -1861,6 +1861,10 @@ public override BoundNode VisitMultipleLocalDeclarations(BoundMultipleLocalDecla | |||
|
|||
public override BoundNode VisitUsingLocalDeclarations(BoundUsingLocalDeclarations node) | |||
{ | |||
if (((LocalDeclarationStatementSyntax)node.Syntax).AwaitKeyword != default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
((LocalDeclarationStatementSyntax)node.Syntax).AwaitKeyword != default [](start = 16, length = 70)
Could this be node.AwaitOpt != null
? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I seem t o recall there was a reason for doing a syntax check, but I could be wrong.
Looking at VisitForEachStatement
, I see it does a syntax check. But VisitUsingStatement
does a check on .AwaitOpt
...
Note: when you merge recent bits from master into this branch, you'll need to add a check for AwaitUsingAddsPendingBranch
(which has since been renamed AwaitUsingAndForeachAddsPendingBranch
). I don't remember the scenario to hit this though.
Here's the relevant PR: #31527
In reply to: 243702724 [](ancestors = 243702724)
await using C1 c = new C1(); | ||
} | ||
}"; | ||
var compilation = CreateCompilationWithTasksExtensions(source + _asyncDisposable, options: TestOptions.DebugExe).VerifyDiagnostics(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
source + _asyncDisposable [](start = 67, length = 25)
Consider using new[] { source, _asyncDisposable }
rather than concatenating source files.
/// <param name="diagnostics">Populated with any errors</param> | ||
/// <param name="hasErrors">True if errors occured during binding</param> | ||
/// <returns>An <see cref="AwaitableInfo"/> with the bound information</returns> | ||
internal AwaitableInfo BindAsyncDisposeAwaiter(SyntaxNode node, SyntaxToken awaitKeyword, MethodSymbol disposeMethodOpt, DiagnosticBag diagnostics, ref bool hasErrors) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal [](start = 8, length = 8)
protected?
#Closed
internal AwaitableInfo BindAsyncDisposeAwaiter(SyntaxNode node, SyntaxToken awaitKeyword, MethodSymbol disposeMethodOpt, DiagnosticBag diagnostics, ref bool hasErrors) | ||
{ | ||
TypeSymbol taskType = disposeMethodOpt is null | ||
? this.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetWellKnownType [](start = 55, length = 16)
Please add a test where ValueTask is made missing (see MakeTypeMissing
test helper).
Also a test where an obsolete or use-site diagnostic is reported. #Closed
} | ||
else | ||
{ | ||
return this.ParseLocalDeclarationStatement(parseAwaitKeywordForAsyncStreams()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return this.ParseLocalDeclarationStatement(parseAwaitKeywordForAsyncStreams()); [](start = 28, length = 79)
maybe good to add a parsing test for this case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have TestAwaitUsingVarWithVarDeclarationTree, unless I'm misunderstanding you?
In reply to: 243707369 [](ancestors = 243707369)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, didn't realize. Thanks
@@ -13,7 +13,7 @@ public StackAllocArrayCreationExpressionSyntax Update(SyntaxToken stackAllocKeyw | |||
public partial class LocalDeclarationStatementSyntax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LocalDeclarationStatementSyntax [](start = 25, length = 31)
Not caused by this PR: I didn't understand why this type is in this file (for stackalloc). Seems like it should be split into a separate file (for local declaration).
var compilation = CreateCompilationWithTasksExtensions(source + _asyncDisposable, options: TestOptions.DebugExe).VerifyDiagnostics(); | ||
|
||
CompileAndVerify(compilation, expectedOutput: "Dispose async"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} [](start = 8, length = 1)
Please test
- an async using-declaration with multiple resources.
- two async using-declarations in a row.
- language version 7.3 (you're missing a diagnostic for this)
When you pull the latest bits from master, it will also be good to do a little bit of cross-validation with async-iterator methods (adding some yield return
statements between two async using-declarations, and such. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (iteration 3). Mostly test feedback.
awaitOpt = BindAsyncDisposeAwaiter(node, node.AwaitKeyword, disposeMethod, diagnostics, ref hasErrors); | ||
} | ||
|
||
MessageID.IDS_FeatureUsingDeclarations.CheckFeatureAvailability(availableVersion: Compilation.LanguageVersion, diagnostics, node.Declaration.Location); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CheckFeatureAvailability [](start = 51, length = 24)
Feels like this should be a parsing diagnostic rather than a binding diagnostic, unless there is some difficulty I'm missing. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, i'm just still learning where things go :) will move to parsing. #Closed
"; | ||
var compilation = CreateCompilationWithTasksExtensions(source + _asyncDisposable, options: TestOptions.DebugExe).VerifyDiagnostics(); | ||
|
||
CompileAndVerify(compilation, expectedOutput: expectedOutput).VerifyIL("C2.<Main>d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"C2.
d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()" [](start = 83, length = 77)
I've given up on IL verification for large async methods... I usually do a CompileAndVerify(...).Dump()
verification by hand.
|
||
var compilation = CreateCompilationWithTasksExtensions(source + _asyncDisposable, options: TestOptions.DebugExe).VerifyDiagnostics(); | ||
CompileAndVerify(compilation, expectedOutput: expectedOutput); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} [](start = 8, length = 1)
nit: extra empty line below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (iteration 5)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (iteration 8)
// c1 is obsolete | ||
using C1 c1 = new C1(); | ||
|
||
// no warning, we don't warn on dispose being obsolete becuase it comes through interface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
becuase [](start = 63, length = 7)
Typo
@@ -2354,7 +2354,7 @@ public void TestUsingWithDeclaration() | |||
public void TestUsingVarWithDeclaration() | |||
{ | |||
var text = "using T a = b;"; | |||
var statement = this.ParseStatement(text); | |||
var statement = this.ParseStatement(text, options: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp8) [](start = 63, length = 71)
Is there a difference between this and TestOptions.Regular8
? (Just curious if there's a reason some tests use one and some use the other.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe they are equivalent. I've switched to TestOptions.Regular8
to be consistent / more concise though.
} | ||
|
||
[Fact] | ||
public void TestAwaitUsingVarWithVarAnNoUsingDeclarationTree() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An [](start = 44, length = 2)
And
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.