Skip to content

Fix binding order for Dispose in async foreach#60022

Merged
jcouv merged 3 commits intodotnet:mainfrom
jcouv:dispose-order
Mar 15, 2022
Merged

Fix binding order for Dispose in async foreach#60022
jcouv merged 3 commits intodotnet:mainfrom
jcouv:dispose-order

Conversation

@jcouv
Copy link
Copy Markdown
Member

@jcouv jcouv commented Mar 8, 2022

Fixes #59955

The affected scenario is when we enumerate using instance methods (as opposed to through interfaces), but we have a choice for disposal. In that scenario, we'll start preferring the instance AsyncDispose() method.

For scenarios where we enumerate through interfaces, we'll find the AsyncDispose() method on the enumerator type (ie. IAsyncEnumerator), so a conversion is removed (see affected CFG tests).

Clarifying spec in PR dotnet/csharplang#5898.

FYI @bernd5

@jcouv jcouv self-assigned this Mar 8, 2022
@jcouv jcouv force-pushed the dispose-order branch 3 times, most recently from 6fb6a16 to 0938a6b Compare March 8, 2022 03:59
this.PeekToken(1).Kind == SyntaxKind.ForEachKeyword)
{
return this.ParseForEachStatement(attributes, ParseAwaitKeyword(MessageID.IDS_FeatureAsyncStreams));
return this.ParseForEachStatement(attributes, ParseAwaitKeyword(MessageID.None));
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

📝 I had to move those errors to binding layer, as they were affecting binding logic (which uses BoundNode.HasAnyErrors), but we want to bind the same way regardless of LangVer.

// await using (var x = this)
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "await").WithArguments("asynchronous using", "8.0").WithLocation(6, 9)
);
tree.GetDiagnostics().Verify(); // LangVer error reported in binding
Copy link
Copy Markdown
Member Author

@jcouv jcouv Mar 8, 2022

Choose a reason for hiding this comment

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

📝 See CodeGenAwaitUsingTests.TestWithCSharp7_3

// await foreach (var i in collection)
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "await").WithArguments("async streams", "8.0").WithLocation(6, 9)
);
tree.GetDiagnostics().Verify(); // LangVer error reported in binding
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

📝 See CodeGenAwaitForeachTests.TestWithCSharp7_3

// await foreach (var (i, j) in collection)
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "await").WithArguments("async streams", "8.0").WithLocation(6, 9)
);
tree.GetDiagnostics().Verify(); // LangVer error reported in binding
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

📝 See CodeGenAwaitForeachTests.TestDeconstructionWithCSharp7_3

var boundNode = GetBoundForEachStatement(text, TestOptions.Regular7_3);
var boundNode = GetBoundForEachStatement(text, TestOptions.Regular7_3,
// error CS8370: Feature 'using declarations' is not available in C# 7.3. Please use language version 8.0 or greater.
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3).WithArguments("using declarations", "8.0").WithLocation(1, 1)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

📝 Added documentation for breaking change.

this.Conversions.ClassifyImplicitConversionFromType(enumeratorType,
isAsync ? this.Compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable) : this.Compilation.GetSpecialType(SpecialType.System_IDisposable),
ref useSiteInfo).IsImplicit)
MethodSymbol patternDisposeMethod = null;
Copy link
Copy Markdown
Member Author

@jcouv jcouv Mar 8, 2022

Choose a reason for hiding this comment

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

📝 The two conditional branches were swapped (pattern-based comes first, then convert to interface) and the LangVer check no longer affects binding result.

@jcouv jcouv added the Feature - Async Streams Async Streams label Mar 8, 2022
@jcouv jcouv marked this pull request as ready for review March 8, 2022 17:56
@jcouv jcouv requested a review from a team as a code owner March 8, 2022 17:56

struct AsyncEnumerable
{
AsyncEnumerator<int> GetAsyncEnumerator(CancellationToken token) => new AsyncEnumerator();
Copy link
Copy Markdown
Contributor

@cston cston Mar 9, 2022

Choose a reason for hiding this comment

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

Suggested change
AsyncEnumerator<int> GetAsyncEnumerator(CancellationToken token) => new AsyncEnumerator();
public AsyncEnumerator GetAsyncEnumerator(CancellationToken token) => new AsyncEnumerator();
``` #Closed

}
```

9. <a name="9"></a>Starting with Visual Studio 17.2, a `foreach` using a ref struct enumerator type reports an error if the language version is set to 7.3 or older.
Copy link
Copy Markdown
Contributor

@cston cston Mar 9, 2022

Choose a reason for hiding this comment

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

Perhaps "earlier" or "lower".

Suggested change
9. <a name="9"></a>Starting with Visual Studio 17.2, a `foreach` using a ref struct enumerator type reports an error if the language version is set to 7.3 or older.
9. <a name="9"></a>Starting with Visual Studio 17.2, a `foreach` using a ref struct enumerator type reports an error if the language version is set to 7.3 or earlier.
``` #Closed

{
}

struct AsyncEnumerable
Copy link
Copy Markdown
Contributor

@cston cston Mar 9, 2022

Choose a reason for hiding this comment

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

Is AsyncEnumerable required to implement IAsyncEnumerable<int>? #Resolved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No. This sample illustrate pattern-based enumeration and a choice for method of disposal (pattern-based or interface-based).

AsyncEnumerator<int> GetAsyncEnumerator(CancellationToken token) => new AsyncEnumerator();
}

struct AsyncEnumerator : IAsyncDisposable
Copy link
Copy Markdown
Contributor

@cston cston Mar 9, 2022

Choose a reason for hiding this comment

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

Is AsyncEnumerator required to implement IAsyncEnumerator<int>? #Resolved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Answered above

@cston
Copy link
Copy Markdown
Contributor

cston commented Mar 9, 2022

    private SyntaxToken ParseAwaitKeyword(MessageID feature)

Can the ParseAwaitKeyword(MessageID feature) parameter be removed now?


In reply to: 1063390690


Refers to: src/Compilers/CSharp/Portable/Parser/LanguageParser.cs:7888 in f45129b. [](commit_id = f45129b, deletion_comment = False)

var comp = CreateCompilation(source, parseOptions: TestOptions.Regular7_3);
comp.VerifyDiagnostics(
// error CS8370: Feature 'using declarations' is not available in C# 7.3. Please use language version 8.0 or greater.
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3).WithArguments("using declarations", "8.0").WithLocation(1, 1)
Copy link
Copy Markdown
Contributor

@cston cston Mar 9, 2022

Choose a reason for hiding this comment

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

"using declarations"

This seems incorrect since there are no using declarations. #Closed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The "using declarations" feature bundled two things: using declarations and pattern-based disposal.
I'm fine to split but it snowballs the PR a bit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think we need to separate "pattern-based disposal" from "using declarations", but it feels like this error message should reference "async streams" rather than "using declarations".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You can hit this with foreach on a ref struct. It's not specific to async streams...

Assert.Equal(2, op.Info.GetEnumeratorArguments.Length);
Assert.Equal(3, op.Info.MoveNextArguments.Length);
Assert.True(op.Info.DisposeArguments.IsDefault);
Assert.Null(op.Info.PatternDisposeMethod);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Assert.Null(op.Info.PatternDisposeMethod);

Are we testing the case where PatternDisposeMethod is null?

@cston
Copy link
Copy Markdown
Contributor

cston commented Mar 9, 2022

    public void AwaitUsingDeclaration_WithCSharp73()

The behavior should be identical to the C#8 case now. Consider merging with AwaitUsingDeclaration() (and adding aLanguageVersion parameter to the test) so that is clear.


In reply to: 1063404480


In reply to: 1063404480


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

@cston
Copy link
Copy Markdown
Contributor

cston commented Mar 9, 2022

    public void AwaitForeach_WithCSharp73()

The behavior should be identical to the C#8 case now. Consider merging with AwaitForeach() (and adding aLanguageVersion parameter to the test) so that is clear.


In reply to: 1063404768


In reply to: 1063404768


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

@cston
Copy link
Copy Markdown
Contributor

cston commented Mar 9, 2022

    public void DeconstructionAwaitForeach_WithCSharp73()

The behavior should be identical to the C#8 case now. Consider merging with DeconstructionAwaitForeach() (and adding aLanguageVersion parameter to the test) so that is clear.


In reply to: 1063405174


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

var comp = CreateCompilation(source, parseOptions: TestOptions.Regular7_3);
comp.VerifyDiagnostics(
// error CS8370: Feature 'using declarations' is not available in C# 7.3. Please use language version 8.0 or greater.
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3).WithArguments("using declarations", "8.0").WithLocation(1, 1)
Copy link
Copy Markdown
Contributor

@cston cston Mar 10, 2022

Choose a reason for hiding this comment

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

WithLocation(1, 1)

Should the location be new Enumerable1() to give an indication what caused the error? #Closed

@jcouv jcouv requested review from 333fred and cston March 10, 2022 01:54
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Mar 10, 2022

@cston @333fred for review. Thanks

// If IDisposable is available but its Dispose method is not, then diagnostics will be reported only if the enumerator
// is potentially disposable.

// For async foreach, we don't do the runtime check
Copy link
Copy Markdown
Contributor

@cston cston Mar 11, 2022

Choose a reason for hiding this comment

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

// For async foreach, we don't do the runtime check

Consider moving to line 1045, after the following, so it's clear what this refers to.

// if it wasn't pattern-disposable, see if it's directly convertable to IDisposable
``` #Resolved

@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Mar 11, 2022

Tagging @dotnet/docs @BillWagner for new breaking changes.

@jcouv jcouv requested a review from cston March 11, 2022 08:39
@jcouv
Copy link
Copy Markdown
Member Author

jcouv commented Mar 11, 2022

@dotnet/roslyn-compiler for second review. Thanks

Copy link
Copy Markdown
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

LGTM (commit 3)

@jcouv jcouv enabled auto-merge (squash) March 14, 2022 23:29
@jcouv jcouv merged commit 8192820 into dotnet:main Mar 15, 2022
@ghost ghost added this to the Next milestone Mar 15, 2022
@allisonchou allisonchou modified the milestones: Next, 17.2.P3 Mar 28, 2022
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.

async foreach binds to IAsyncDisposable.DisposeAsync() rather than pattern based DisposeAsync()

4 participants