- 
                Notifications
    
You must be signed in to change notification settings  - Fork 4.2k
 
Fix binding order for Dispose in async foreach #60022
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -106,3 +106,34 @@ https://github.com/dotnet/roslyn/issues/57750 | |
| void M(IEnumerable<char> s = "hello") | ||
| ``` | ||
| 
     | 
||
| 8. <a name="8"></a>Starting with Visual Studio 17.2, an async `foreach` prefers to bind using a pattern-based `DisposeAsync()` method rather than `IAsyncDisposable.DisposeAsync()`. | ||
| For instance, the `DisposeAsync()` will be picked, rather than the `IAsyncEnumerator<int>.DisposeAsync()` method on `AsyncEnumerator`: | ||
| ```csharp | ||
| await foreach (var i in new AsyncEnumerable()) | ||
| { | ||
| } | ||
| 
     | 
||
| struct AsyncEnumerable | ||
| { | ||
| public AsyncEnumerator GetAsyncEnumerator(CancellationToken token) => new AsyncEnumerator(); | ||
| } | ||
| 
     | 
||
| struct AsyncEnumerator : IAsyncDisposable | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Answered above  | 
||
| { | ||
| public int Current => 0; | ||
| public async ValueTask<bool> MoveNextAsync() | ||
| { | ||
| await Task.Yield(); | ||
| return false; | ||
| } | ||
| public async ValueTask DisposeAsync() | ||
| { | ||
| Console.WriteLine("PICKED"); | ||
| await Task.Yield(); | ||
| } | ||
| ValueTask IAsyncDisposable.DisposeAsync() => throw null; // no longer picked | ||
| } | ||
| ``` | ||
| 
     | 
||
| 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. | ||
| 
     | 
||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -200,6 +200,11 @@ internal override BoundStatement BindForEachDeconstruction(BindingDiagnosticBag | |
| 
     | 
||
| private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagnostics, Binder originalBinder) | ||
| { | ||
| if (IsAsync) | ||
| { | ||
| CheckFeatureAvailability(_syntax, MessageID.IDS_FeatureAsyncStreams, diagnostics, _syntax.AwaitKeyword.GetLocation()); | ||
| } | ||
| 
     | 
||
| // Use the right binder to avoid seeing iteration variable | ||
| BoundExpression collectionExpr = originalBinder.GetBinder(_syntax.Expression).BindRValueWithoutTargetType(_syntax.Expression, diagnostics); | ||
| 
     | 
||
| 
          
            
          
           | 
    @@ -995,33 +1000,24 @@ private void GetDisposalInfoForEnumerator(ref ForEachEnumeratorInfo.Builder buil | |
| TypeSymbol enumeratorType = builder.GetEnumeratorInfo.Method.ReturnType; | ||
| CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); | ||
| 
     | 
||
| // For async foreach, we don't do the runtime check | ||
| if ((!enumeratorType.IsSealed && !isAsync) || | ||
| 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. Choose a reason for hiding this commentThe 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.  | 
||
| if (enumeratorType.IsRefLikeType || isAsync) | ||
| { | ||
| builder.NeedsDisposal = true; | ||
| } | ||
| else if (Compilation.IsFeatureEnabled(MessageID.IDS_FeatureUsingDeclarations) && | ||
| (enumeratorType.IsRefLikeType || isAsync)) | ||
| { | ||
| // if it wasn't directly convertable to IDisposable, see if it is pattern-disposable | ||
| // again, we throw away any binding diagnostics, and assume it's not disposable if we encounter errors | ||
| // we throw away any binding diagnostics, and assume it's not disposable if we encounter errors | ||
| var receiver = new BoundDisposableValuePlaceholder(_syntax, enumeratorType); | ||
| MethodSymbol disposeMethod = TryFindDisposePatternMethod(receiver, _syntax, isAsync, BindingDiagnosticBag.Discarded); | ||
| if (disposeMethod is object) | ||
| patternDisposeMethod = TryFindDisposePatternMethod(receiver, _syntax, isAsync, BindingDiagnosticBag.Discarded); | ||
| if (patternDisposeMethod is object) | ||
| { | ||
| Debug.Assert(!disposeMethod.IsExtensionMethod); | ||
| Debug.Assert(disposeMethod.ParameterRefKinds.IsDefaultOrEmpty); | ||
| Debug.Assert(!patternDisposeMethod.IsExtensionMethod); | ||
| Debug.Assert(patternDisposeMethod.ParameterRefKinds.IsDefaultOrEmpty); | ||
| 
     | 
||
| var argsBuilder = ArrayBuilder<BoundExpression>.GetInstance(disposeMethod.ParameterCount); | ||
| var argsBuilder = ArrayBuilder<BoundExpression>.GetInstance(patternDisposeMethod.ParameterCount); | ||
| var argsToParams = default(ImmutableArray<int>); | ||
| bool expanded = disposeMethod.HasParamsParameter(); | ||
| bool expanded = patternDisposeMethod.HasParamsParameter(); | ||
| 
     | 
||
| BindDefaultArguments( | ||
| _syntax, | ||
| disposeMethod.Parameters, | ||
| patternDisposeMethod.Parameters, | ||
| argsBuilder, | ||
| argumentRefKindsBuilder: null, | ||
| ref argsToParams, | ||
| 
        
          
        
         | 
    @@ -1031,11 +1027,30 @@ private void GetDisposalInfoForEnumerator(ref ForEachEnumeratorInfo.Builder buil | |
| diagnostics); | ||
| 
     | 
||
| builder.NeedsDisposal = true; | ||
| builder.PatternDisposeInfo = new MethodArgumentInfo(disposeMethod, argsBuilder.ToImmutableAndFree(), argsToParams, defaultArguments, expanded); | ||
| builder.PatternDisposeInfo = new MethodArgumentInfo(patternDisposeMethod, argsBuilder.ToImmutableAndFree(), argsToParams, defaultArguments, expanded); | ||
| 
     | 
||
| if (!isAsync) | ||
| { | ||
| // We already checked feature availability for async scenarios | ||
| CheckFeatureAvailability(expr.Syntax, MessageID.IDS_FeatureDisposalPattern, diagnostics); | ||
| } | ||
| } | ||
| } | ||
| 
     | 
||
| diagnostics.Add(_syntax, useSiteInfo); | ||
| if (!enumeratorType.IsRefLikeType && patternDisposeMethod is null) | ||
| { | ||
| // If it wasn't pattern-disposable, see if it's directly convertable to IDisposable | ||
| // For async foreach, we don't do the runtime check in unsealed case | ||
| if ((!enumeratorType.IsSealed && !isAsync) || | ||
| this.Conversions.ClassifyImplicitConversionFromType(enumeratorType, | ||
| isAsync ? this.Compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable) : this.Compilation.GetSpecialType(SpecialType.System_IDisposable), | ||
| ref useSiteInfo).IsImplicit) | ||
| { | ||
| builder.NeedsDisposal = true; | ||
| } | ||
| 
     | 
||
| diagnostics.Add(_syntax, useSiteInfo); | ||
| } | ||
| } | ||
| 
     | 
||
| private ForEachEnumeratorInfo.Builder GetDefaultEnumeratorInfo(ForEachEnumeratorInfo.Builder builder, BindingDiagnosticBag diagnostics, TypeSymbol collectionExprType) | ||
| 
          
            
          
           | 
    ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
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.
Is
AsyncEnumerablerequired to implementIAsyncEnumerable<int>? #ResolvedThere 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.
No. This sample illustrate pattern-based enumeration and a choice for method of disposal (pattern-based or interface-based).