Skip to content
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

Merged

Conversation

chsienki
Copy link
Contributor

  • 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
@chsienki chsienki requested a review from a team as a code owner December 21, 2018 05:57
@chsienki
Copy link
Contributor Author

@dotnet/roslyn-compiler for review please

@jcouv
Copy link
Member

jcouv commented Dec 21, 2018

    public void AwaitUsingWithExpression_Reversed()

Please add a test for using declaration where await and using are reversed too.


Refers to: src/Compilers/CSharp/Test/Syntax/Parsing/AsyncStreamsParsingTests.cs:254 in bd69f1a. [](commit_id = bd69f1a, deletion_comment = False)

@@ -2505,6 +2505,132 @@ public void TestUsingVarWithVarDeclarationTree()
}
}

[Fact]
public void TestAwaitUsingVarWithDeclaration()
Copy link
Member

@jcouv jcouv Dec 21, 2018

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 = @"
Copy link
Member

@jcouv jcouv Dec 21, 2018

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

Copy link
Contributor Author

@chsienki chsienki Dec 21, 2018

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;
}
Copy link
Member

@jcouv jcouv Dec 21, 2018

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", @"
Copy link
Member

@jcouv jcouv Dec 21, 2018

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();
Copy link
Member

@jcouv jcouv Dec 21, 2018

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()
Copy link
Member

@jcouv jcouv Dec 21, 2018

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");
}
Copy link
Member

@jcouv jcouv Dec 21, 2018

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

Copy link
Contributor Author

@chsienki chsienki Dec 21, 2018

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);
}
}
Copy link
Member

@jcouv jcouv Dec 21, 2018

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>
Copy link
Member

@cston cston Dec 21, 2018

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>
Copy link
Member

@cston cston Dec 21, 2018

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

@cston
Copy link
Member

cston commented Dec 21, 2018

        if (pending.Branch is null)

Consider extracting a local. #Resolved


Refers to: src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs:262 in bd69f1a. [](commit_id = bd69f1a, deletion_comment = False)

@@ -1861,6 +1861,10 @@ public override BoundNode VisitMultipleLocalDeclarations(BoundMultipleLocalDecla

public override BoundNode VisitUsingLocalDeclarations(BoundUsingLocalDeclarations node)
{
if (((LocalDeclarationStatementSyntax)node.Syntax).AwaitKeyword != default)
Copy link
Member

@cston cston Dec 21, 2018

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

Copy link
Member

@jcouv jcouv Dec 21, 2018

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)

@cston
Copy link
Member

cston commented Dec 21, 2018

using Microsoft.CodeAnalysis.CSharp.Test.Utilities;

Include copyright. #Resolved


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingDeclarationTests.cs:1 in bd69f1a. [](commit_id = bd69f1a, deletion_comment = False)

await using C1 c = new C1();
}
}";
var compilation = CreateCompilationWithTasksExtensions(source + _asyncDisposable, options: TestOptions.DebugExe).VerifyDiagnostics();
Copy link
Member

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)
Copy link
Member

@jcouv jcouv Dec 21, 2018

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)
Copy link
Member

@jcouv jcouv Dec 21, 2018

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());
Copy link
Member

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

Copy link
Contributor Author

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)

Copy link
Member

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
Copy link
Member

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");
}
Copy link
Member

@jcouv jcouv Dec 21, 2018

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

Copy link
Member

@jcouv jcouv left a 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.

@jcouv jcouv self-assigned this Dec 21, 2018
@chsienki chsienki requested a review from a team as a code owner December 22, 2018 00:34
awaitOpt = BindAsyncDisposeAwaiter(node, node.AwaitKeyword, disposeMethod, diagnostics, ref hasErrors);
}

MessageID.IDS_FeatureUsingDeclarations.CheckFeatureAvailability(availableVersion: Compilation.LanguageVersion, diagnostics, node.Declaration.Location);
Copy link
Member

@jcouv jcouv Dec 22, 2018

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

Copy link
Contributor Author

@chsienki chsienki Dec 22, 2018

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()", @"
Copy link
Member

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);
}
Copy link
Member

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

Copy link
Member

@jcouv jcouv left a 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)

Copy link
Member

@jcouv jcouv left a 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
Copy link
Member

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));
Copy link
Member

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.)

Copy link
Contributor Author

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()
Copy link
Member

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?

Copy link
Member

@jaredpar jaredpar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@chsienki chsienki merged commit 2652bea into dotnet:features/enhanced-using Dec 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants