Fix binding order for Dispose in async foreach#60022
Conversation
6fb6a16 to
0938a6b
Compare
| this.PeekToken(1).Kind == SyntaxKind.ForEachKeyword) | ||
| { | ||
| return this.ParseForEachStatement(attributes, ParseAwaitKeyword(MessageID.IDS_FeatureAsyncStreams)); | ||
| return this.ParseForEachStatement(attributes, ParseAwaitKeyword(MessageID.None)); |
There was a problem hiding this comment.
📝 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 |
There was a problem hiding this comment.
📝 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 |
There was a problem hiding this comment.
📝 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 |
There was a problem hiding this comment.
📝 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) |
There was a problem hiding this comment.
📝 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; |
There was a problem hiding this comment.
📝 The two conditional branches were swapped (pattern-based comes first, then convert to interface) and the LangVer check no longer affects binding result.
|
|
||
| struct AsyncEnumerable | ||
| { | ||
| AsyncEnumerator<int> GetAsyncEnumerator(CancellationToken token) => new AsyncEnumerator(); |
There was a problem hiding this comment.
| 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. |
There was a problem hiding this comment.
Perhaps "earlier" or "lower".
| 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 |
There was a problem hiding this comment.
Is AsyncEnumerable required to implement IAsyncEnumerable<int>? #Resolved
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Is AsyncEnumerator required to implement IAsyncEnumerator<int>? #Resolved
Can the 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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); |
The behavior should be identical to the C#8 case now. Consider merging with 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) |
The behavior should be identical to the C#8 case now. Consider merging with 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) |
The behavior should be identical to the C#8 case now. Consider merging with 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) |
| // 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 |
|
Tagging @dotnet/docs @BillWagner for new breaking changes. |
|
@dotnet/roslyn-compiler for second review. Thanks |
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