Skip to content

Commit

Permalink
Use GetResult(Exception) method for awaits
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed Mar 27, 2023
1 parent 87b5be1 commit ffccf68
Show file tree
Hide file tree
Showing 9 changed files with 617 additions and 20 deletions.
97 changes: 89 additions & 8 deletions src/Compilers/CSharp/Portable/Binder/Binder_Await.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ internal bool GetAwaitableExpressionInfo(
SyntaxNode node,
BindingDiagnosticBag diagnostics)
{
// PROTOTYPE test all scenarios that rely on this method
return GetAwaitableExpressionInfo(expression, expression, out _, out _, out _, out _, out getAwaiterGetResultCall, node, diagnostics);
}

Expand Down Expand Up @@ -272,6 +273,7 @@ private bool GetAwaitableExpressionInfo(
return false;
}

// PROTOTYPE deal with dynamic scenario
if (expression.HasDynamicType())
{
isDynamic = true;
Expand All @@ -284,9 +286,11 @@ private bool GetAwaitableExpressionInfo(
}

TypeSymbol awaiterType = getAwaiter.Type!;
bool isExplicitAwait = node is AwaitExpressionSyntax; // PROTOTYPE deal with implicit awaits
return GetIsCompletedProperty(awaiterType, node, expression.Type!, diagnostics, out isCompleted)
&& AwaiterImplementsINotifyCompletion(awaiterType, node, diagnostics)
&& GetGetResultMethod(getAwaiter, node, expression.Type!, diagnostics, out getResult, out getAwaiterGetResultCall);
&& ((isExplicitAwait && GetGetResultExceptionMethod(getAwaiter, node, expression.Type!, out getResult, out getAwaiterGetResultCall))
|| GetGetResultMethod(getAwaiter, node, expression.Type!, diagnostics, out getResult, out getAwaiterGetResultCall));
}

/// <summary>
Expand Down Expand Up @@ -433,43 +437,120 @@ private bool AwaiterImplementsINotifyCompletion(TypeSymbol awaiterType, SyntaxNo
/// </remarks>
private bool GetGetResultMethod(BoundExpression awaiterExpression, SyntaxNode node, TypeSymbol awaitedExpressionType, BindingDiagnosticBag diagnostics, out MethodSymbol? getResultMethod, [NotNullWhen(true)] out BoundExpression? getAwaiterGetResultCall)
{
var awaiterType = awaiterExpression.Type;
getAwaiterGetResultCall = MakeInvocationExpression(node, awaiterExpression, WellKnownMemberNames.GetResult, ImmutableArray<BoundExpression>.Empty, diagnostics);
if (getAwaiterGetResultCall.HasAnyErrors)
if (!ValidateGetResultMethod(awaiterExpression, node, awaitedExpressionType, getAwaiterGetResultCall, diagnostics, out getResultMethod))
{
getResultMethod = null;
getAwaiterGetResultCall = null;
return false;
}

return true;
}

private static bool ValidateGetResultMethod(BoundExpression awaiterExpression, SyntaxNode node, TypeSymbol awaitedExpressionType,
BoundExpression getAwaiterGetResultCall, BindingDiagnosticBag diagnostics, [NotNullWhen(true)] out MethodSymbol? getResultMethod)
{
var awaiterType = awaiterExpression.Type;
getResultMethod = null;
if (getAwaiterGetResultCall.HasAnyErrors)
{
return false;
}

RoslynDebug.Assert(awaiterType is object);
if (getAwaiterGetResultCall.Kind != BoundKind.Call)
{
Error(diagnostics, ErrorCode.ERR_NoSuchMember, node, awaiterType, WellKnownMemberNames.GetResult);
getResultMethod = null;
getAwaiterGetResultCall = null;
return false;
}

getResultMethod = ((BoundCall)getAwaiterGetResultCall).Method;
if (getResultMethod.IsExtensionMethod)
{
Error(diagnostics, ErrorCode.ERR_NoSuchMember, node, awaiterType, WellKnownMemberNames.GetResult);
getResultMethod = null;
getAwaiterGetResultCall = null;
return false;
}

if (HasOptionalOrVariableParameters(getResultMethod) || getResultMethod.IsConditional)
{
Error(diagnostics, ErrorCode.ERR_BadAwaiterPattern, node, awaiterType, awaitedExpressionType);
return false;
}

return true;
}

/// <summary>
/// Finds the GetResult(out Exception) method of an Awaiter type.
/// </summary>
private bool GetGetResultExceptionMethod(BoundExpression awaiterExpression, SyntaxNode node, TypeSymbol awaitedExpressionType,
out MethodSymbol? getResultMethod, [NotNullWhen(true)] out BoundExpression? getAwaiterGetResultCall)
{
// PROTOTYPE should this binding logic be conditional on LangVer?
Debug.Assert(awaiterExpression.Type is not null);

var discarded = new BindingDiagnosticBag();
getAwaiterGetResultCall = TryMakeGetResultExceptionInvocation(node, awaiterExpression, discarded);

if (getAwaiterGetResultCall is null ||
!ValidateGetResultMethod(awaiterExpression, node, awaitedExpressionType, getAwaiterGetResultCall, discarded, out getResultMethod))
{
getResultMethod = null;
getAwaiterGetResultCall = null;
return false;
}

// The lack of a GetResult method will be reported by ValidateGetResult().
return true;

// Find the GetResult(out Exception) method.
BoundExpression? TryMakeGetResultExceptionInvocation(SyntaxNode node, BoundExpression awaiterExpression, BindingDiagnosticBag diagnostics)
{
Debug.Assert(awaiterExpression.Type is not null);
Debug.Assert(!awaiterExpression.Type.IsDynamic());

const string methodName = WellKnownMemberNames.GetResult;
var memberAccess = BindInstanceMemberAccess(
node, node, awaiterExpression, methodName, rightArity: 0,
typeArgumentsSyntax: default, typeArgumentsWithAnnotations: default,
invoked: true, indexed: false, diagnostics: diagnostics);

// PROTOTYPE we should not drop these diagnostics
//memberAccess = CheckValue(memberAccess, BindValueKind.RValueOrMethodGroup, diagnostics);
memberAccess.WasCompilerGenerated = true;

if (memberAccess.Kind != BoundKind.MethodGroup)
{
return null;
}

var analyzedArguments = AnalyzedArguments.GetInstance();

try
{
// PROTOTYPE handle missing System.Exception type
var outPlaceholder = new BoundGetResultOutExceptionPlaceholder(node, GetWellKnownType(WellKnownType.System_Exception, diagnostics, node));
analyzedArguments.Arguments.Add(outPlaceholder);
analyzedArguments.RefKinds.Add(RefKind.Out);

BoundExpression getAwaiterGetResultCall = BindMethodGroupInvocation(
node, node, methodName, (BoundMethodGroup)memberAccess, analyzedArguments, diagnostics, queryClause: null,
allowUnexpandedForm: true, anyApplicableCandidates: out bool anyApplicableCandidates);

getAwaiterGetResultCall.WasCompilerGenerated = true;

if (!anyApplicableCandidates) // PROTOTYPE is this necessary/useful?
{
return null;
}

return getAwaiterGetResultCall;
}
finally
{
analyzedArguments.Free();
}
}
}

private static bool HasOptionalOrVariableParameters(MethodSymbol method)
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ internal partial class BoundAwaitableValuePlaceholder
public sealed override bool IsEquivalentToThisReference => false; // Preserving old behavior
}

internal partial class BoundGetResultOutExceptionPlaceholder
{
public sealed override bool IsEquivalentToThisReference => throw ExceptionUtilities.Unreachable();
}

internal partial class BoundDisposableValuePlaceholder
{
public sealed override bool IsEquivalentToThisReference => false;
Expand Down
9 changes: 9 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@
<Field Name="IsDiscardExpression" Type="bool" Null="NotApplicable"/>
</Node>

<!--
This node is used to represent the out argument to the `GetResult(out System.Exception)`
invocation in an async method.
It does not survive the initial binding.
-->
<Node Name="BoundGetResultOutExceptionPlaceholder" Base="BoundValuePlaceholderBase">
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
</Node>

<!--
In a tuple binary operator, this node is used to represent tuple elements in a tuple binary
operator, and to represent an element-wise comparison result to convert back to bool.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ffccf68

Please sign in to comment.