-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Async-streams: allow pattern-based disposal in await using and foreach #32731
Changes from 2 commits
d772212
e3622a4
5945e59
1e16d7e
898aa41
e2d996c
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 |
---|---|---|
|
@@ -677,7 +677,7 @@ internal MethodSymbol TryFindDisposePatternMethod(BoundExpression expr, SyntaxNo | |
{ | ||
Debug.Assert(!(expr is null)); | ||
Debug.Assert(!(expr.Type is null)); | ||
Debug.Assert(expr.Type.IsValueType && expr.Type.IsRefLikeType); // pattern dispose lookup is only valid on ref structs | ||
Debug.Assert((expr.Type.IsValueType && expr.Type.IsRefLikeType) || hasAwait); // pattern dispose lookup is only valid on ref structs or asynchronous usings | ||
|
||
// Don't try and lookup if we're not enabled | ||
if (MessageID.IDS_FeatureUsingDeclarations.RequiredVersion() > Compilation.LanguageVersion) | ||
|
@@ -691,8 +691,15 @@ internal MethodSymbol TryFindDisposePatternMethod(BoundExpression expr, SyntaxNo | |
diagnostics, | ||
out var disposeMethod); | ||
|
||
if ((!hasAwait && disposeMethod?.ReturnsVoid == false) | ||
|| (hasAwait && disposeMethod?.ReturnType.TypeSymbol.IsNonGenericTaskType(Compilation) == false) | ||
if (disposeMethod?.IsExtensionMethod == true) | ||
{ | ||
// Extension methods should just be ignored, rather than rejected after-the-fact | ||
// Tracked by https://github.com/dotnet/roslyn/issues/32767 | ||
|
||
// extension methods do not contribute to pattern-based disposal | ||
disposeMethod = 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 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. In the In the |
||
} | ||
else if ((!hasAwait && disposeMethod?.ReturnsVoid == false) | ||
|| result == PatternLookupResult.NotAMethod) | ||
{ | ||
HashSet<DiagnosticInfo> useSiteDiagnostics = null; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -503,8 +503,11 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, | |
|
||
private bool GetAwaitDisposeAsyncInfo(ref ForEachEnumeratorInfo.Builder builder, DiagnosticBag diagnostics) | ||
{ | ||
BoundExpression placeholder = new BoundAwaitableValuePlaceholder(_syntax.Expression, | ||
this.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask, diagnostics, this._syntax)); | ||
var awaitableType = builder.DisposeMethod is null | ||
? this.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask, diagnostics, this._syntax) | ||
: builder.DisposeMethod.ReturnType.TypeSymbol; | ||
|
||
BoundExpression placeholder = new BoundAwaitableValuePlaceholder(_syntax.Expression, awaitableType); | ||
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. Nit: Why does the local change the type here of the |
||
|
||
bool hasErrors = false; | ||
builder.DisposeAwaitableInfo = BindAwaitInfo(placeholder, _syntax.Expression, _syntax.AwaitKeyword.GetLocation(), diagnostics, ref hasErrors); | ||
|
@@ -836,16 +839,17 @@ private void GetDisposalInfoForEnumerator(ref ForEachEnumeratorInfo.Builder buil | |
TypeSymbol enumeratorType = builder.GetEnumeratorMethod.ReturnType.TypeSymbol; | ||
HashSet<DiagnosticInfo> useSiteDiagnostics = null; | ||
|
||
if (!enumeratorType.IsSealed || | ||
// 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 useSiteDiagnostics).IsImplicit) | ||
{ | ||
builder.NeedsDisposal = true; | ||
} | ||
else if (enumeratorType.IsRefLikeType) | ||
else if (enumeratorType.IsRefLikeType || isAsync) | ||
{ | ||
// if it wasn't directly convertable to IDisposable, see if it is pattern disposable | ||
// 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 | ||
var patternDisposeDiags = new DiagnosticBag(); | ||
var receiver = new BoundDisposableValuePlaceholder(_syntax, enumeratorType); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,6 +90,7 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo | |
AwaitableInfo awaitOpt = null; | ||
TypeSymbol declarationTypeOpt = null; | ||
MethodSymbol disposeMethodOpt = null; | ||
TypeSymbol awaitableTypeOpt = null; | ||
|
||
if (isExpression) | ||
{ | ||
|
@@ -117,11 +118,16 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo | |
|
||
if (hasAwait) | ||
{ | ||
TypeSymbol taskType = originalBinder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask); | ||
hasErrors |= ReportUseSiteDiagnostics(taskType, diagnostics, awaitKeyword); | ||
|
||
BoundExpression placeholder = new BoundAwaitableValuePlaceholder(syntax, taskType).MakeCompilerGenerated(); | ||
awaitOpt = originalBinder.BindAwaitInfo(placeholder, syntax, awaitKeyword.GetLocation(), diagnostics, ref hasErrors); | ||
if (awaitableTypeOpt is null) | ||
{ | ||
awaitOpt = AwaitableInfo.Empty; | ||
} | ||
else | ||
{ | ||
hasErrors |= ReportUseSiteDiagnostics(awaitableTypeOpt, diagnostics, awaitKeyword); | ||
BoundExpression placeholder = new BoundAwaitableValuePlaceholder(syntax, awaitableTypeOpt).MakeCompilerGenerated(); | ||
awaitOpt = originalBinder.BindAwaitInfo(placeholder, syntax, awaitKeyword.GetLocation(), diagnostics, ref hasErrors); | ||
} | ||
} | ||
|
||
// This is not awesome, but its factored. | ||
|
@@ -146,6 +152,7 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo | |
hasErrors); | ||
} | ||
|
||
// initializes iDisposableConversion, awaitableTypeOpt and disposeMethodOpt | ||
bool populateDisposableConversionOrDisposeMethod(bool fromExpression) | ||
{ | ||
HashSet<DiagnosticInfo> useSiteDiagnostics = null; | ||
|
@@ -155,14 +162,19 @@ bool populateDisposableConversionOrDisposeMethod(bool fromExpression) | |
|
||
if (iDisposableConversion.IsImplicit) | ||
{ | ||
if (hasAwait) | ||
{ | ||
awaitableTypeOpt = originalBinder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask); | ||
} | ||
return true; | ||
} | ||
|
||
TypeSymbol type = fromExpression ? expressionOpt.Type : declarationTypeOpt; | ||
|
||
// If this is a ref struct, try binding via pattern. | ||
// If this is a ref struct, or we're in a valid asynchronous using, try binding via pattern. | ||
// We won't need to try and bind a second time if it fails, as async dispose can't be pattern based (ref structs are not allowed in async methods) | ||
if (!(type is null) && type.IsValueType && type.IsRefLikeType) | ||
bool isRefStruct = !(type is null) && type.IsValueType && type.IsRefLikeType; | ||
if (!(type is null) && (isRefStruct || hasAwait)) | ||
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.
For readability, consider avoiding the duplicate
|
||
{ | ||
BoundExpression receiver = fromExpression | ||
? expressionOpt | ||
|
@@ -171,6 +183,10 @@ bool populateDisposableConversionOrDisposeMethod(bool fromExpression) | |
disposeMethodOpt = originalBinder.TryFindDisposePatternMethod(receiver, syntax, hasAwait, diagnostics); | ||
if (!(disposeMethodOpt is null)) | ||
{ | ||
if (hasAwait) | ||
{ | ||
awaitableTypeOpt = disposeMethodOpt.ReturnType.TypeSymbol; | ||
} | ||
return true; | ||
} | ||
} | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
Why do we say
IsValueType && IsRefLikeType
? When canIsRefLikeType
betrue
butIsValueType
isfalse
?