-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Move runtime async method validation into initial binding #78310
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
base: features/runtime-async
Are you sure you want to change the base?
Move runtime async method validation into initial binding #78310
Conversation
…ually perform constraint validation on generically-constructed runtime helper methods.
@@ -58,11 +59,12 @@ internal BoundAwaitableInfo BindAwaitInfo(BoundAwaitableValuePlaceholder placeho | |||
out PropertySymbol? isCompleted, | |||
out MethodSymbol? getResult, | |||
getAwaiterGetResultCall: out _, | |||
out MethodSymbol? runtimeAsyncAwaitCall, |
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.
&& GetGetResultMethod(getAwaiter, node, expression.Type!, diagnostics, out getResult, out getAwaiterGetResultCall) | ||
&& (!isRuntimeAsyncEnabled || getRuntimeAwaitAwaiter(awaiterType, out runtimeAsyncAwaitCall)); | ||
|
||
bool tryGetRuntimeAwaitHelper(out MethodSymbol? runtimeAwaitHelper) |
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.
- There are captures. This reduces readability and causes allocations. Consider reducing to only capture
this
. Same forgetRuntimeAwaitAwaiter
below - I'd suggest keeping the
isRuntimeAsyncEnabled
check at the call site (if (isRuntimeAsyncEnabled && tryGetRuntimeAwaitHelper(...)
) for clarity
|
||
var exprOriginalType = expression.Type!.OriginalDefinition; | ||
SpecialMember awaitCall; | ||
TypeWithAnnotations? maybeNestedType = null; |
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.
nit: "nestedType" seems strange. Consider "resultType"
runtimeAwaitHelper = runtimeAwaitHelper.Construct([nestedType]); | ||
checkMethodGenericConstraints(runtimeAwaitHelper, diagnostics, expression.Syntax.Location); | ||
} | ||
#if DEBUG |
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.
nit: do we need this directive?
return true; | ||
} | ||
|
||
void checkMethodGenericConstraints(MethodSymbol method, BindingDiagnosticBag diagnostics, Location location) |
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.
Would this helper from ConstraintHelper
do the job? public static bool CheckConstraints(this MethodSymbol method, in CheckConstraintsArgs args)
@@ -693,6 +693,10 @@ | |||
<Field Name="GetAwaiter" Type="BoundExpression?" Null="allow"/> | |||
<Field Name="IsCompleted" Type="PropertySymbol?" Null="allow"/> | |||
<Field Name="GetResult" Type="MethodSymbol?" Null="allow"/> | |||
<!-- Refers to the runtime async helper we call for awaiting. Either this is an instance of an AsyncHelpers.Await call, and |
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.
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.
- Consider adding a
Validate
method withHasValidate="true"
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.
- Let's take a note to revisit the
GetAwaitExpressionInfo
API which exposes various bits ofBoundAwaitableInfo
. We may also want to updateNullableWalker.VisitAwaitExpression
. [update:] On second thought, maybe we don't need to, as these methods are from corlib (we can afford some assumptions) - It would also be good to review the various consumers of
BoundAwaitableInfo
and check if some can assert thatRuntimeAsyncAwaitMethod
is null (for example,AsyncMethodToStateMachineRewriter.VisitAwaitExpression
)
// Keep in sync with VB's AssemblySymbol.RuntimeSupportsAsyncMethods | ||
internal bool RuntimeSupportsAsyncMethods | ||
=> RuntimeSupportsFeature(SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__Async) | ||
|| _overrideRuntimeSupportsAsyncMethods; | ||
=> GetSpecialType(InternalSpecialType.System_Runtime_CompilerServices_AsyncHelpers) is { TypeKind: TypeKind.Class, IsStatic: true }; |
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.
Should we remove the runtime feature entry from the design doc?
_factory.Compilation.GetSpecialType(InternalSpecialType.System_Runtime_CompilerServices_AsyncHelpers))); | ||
Debug.Assert(runtimeAsyncAwaitMethod.Name is "Await" or "UnsafeAwaitAwaiter" or "AwaitAwaiter"); | ||
|
||
if (runtimeAsyncAwaitMethod.Name == "Await") |
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.
nit: this feels a bit weird. Would it be better to split this into two fields in BoundAwaitableInfo
(one for Await
case and one for AwaitAwait
case)?
// await default(System.Threading.Tasks.ValueTask<int>); | ||
Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "default(System.Threading.Tasks.ValueTask<int>)").WithArguments("System.Runtime.CompilerServices.AsyncHelpers.Await<T>(System.Threading.Tasks.ValueTask<T>)", "T", "int").WithLocation(1, 7) | ||
); | ||
} |
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.
- Consider adding a test for
void
-returning method showing we'll still generate a state machine. - could be in a later PR: consider adding a test for
await using
andawait foreach
in a runtime-async method - nit: should we include a test where the types are present but not from corlib?
&& !ReferenceEquals(methodReturn, GetSpecialType(InternalSpecialType.System_Threading_Tasks_ValueTask)) | ||
&& !ReferenceEquals(methodReturn, GetSpecialType(InternalSpecialType.System_Threading_Tasks_ValueTask_T))) | ||
{ | ||
return false; |
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 there a corresponding update to the design doc? Or should we have a follow-up comment to make the void
-returning method scenario work at some point?
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.
Done with review pass (iteration 10)
We now do method construction and validation for runtime async helpers up front in initial binding, rather than doing it in
RuntimeAsyncRewriter
. I've also renamed the APIs as per dotnet/runtime#114310 (comment) (though I haven't added ConfigureAwait support yet, that will be the next PR). We now validate:System.Runtime.CompilerServices.AsyncHelpers
, defined in corelib. This means that I now need a fairly extensive corelib mock to be able to compile. When we have a testing runtime that defines these helpers, we can remove the giant mock and use the real one.Task
,Task<T>
,ValueTask
, orValueTask<T>
.Relates to test plan #75960