Skip to content
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

Allow overriding the AsyncMethodBuilder on methods #54033

Merged
merged 28 commits into from
Jun 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
78a89b7
Allow AsyncMethodBuilder on method
jcouv Jun 7, 2021
f084cb2
Allow returning a non-task-like type
jcouv Jun 15, 2021
e1f1d41
Relax accessibility requirements for overrides
jcouv Jun 15, 2021
a39ee15
Factor extension methods
jcouv Jun 16, 2021
e4ab3c3
Fix nullability scenario
jcouv Jun 16, 2021
72cbc01
Fix lambda scenario with non-task-like return type
jcouv Jun 17, 2021
3e9518b
Fix test
jcouv Jun 17, 2021
2a3fdf1
Disallow non-task-like return type after all
jcouv Jun 17, 2021
869671b
Validate ignored builder type
jcouv Jun 18, 2021
22b5373
use TryCreate
jcouv Jun 18, 2021
8ac378a
Merge remote-tracking branch 'dotnet/main' into async-builder
jcouv Jun 18, 2021
2e7ad79
hasErros
jcouv Jun 21, 2021
4375aea
Factor extension method
jcouv Jun 21, 2021
43416a2
using
jcouv Jun 21, 2021
d5c0a27
Revert "Disallow non-task-like return type after all"
jcouv Jun 21, 2021
f7900d8
Revert "Validate ignored builder type"
jcouv Jun 21, 2021
e7ed4e3
Restore tests
jcouv Jun 21, 2021
af160e2
Remove validation on return type
jcouv Jun 21, 2021
4218507
Remove builder/factory indirection
jcouv Jun 21, 2021
ee207cb
tweaks
jcouv Jun 21, 2021
ec32fb2
Address feedback
jcouv Jun 22, 2021
9637282
Merge commit '9bcaddccfefc7d8e1804440419c17806e9ac9f27' into async-bu…
jcouv Jun 22, 2021
7da7da7
Integrate explicit return types and builder overrides
jcouv Jun 22, 2021
457aac7
Merge remote-tracking branch 'dotnet/main' into async-builder
jcouv Jun 22, 2021
3268386
Merge remote-tracking branch 'dotnet/main' into async-builder
jcouv Jun 22, 2021
9440d0f
Adjust error code in tests
jcouv Jun 23, 2021
a576b8c
Address feedback
jcouv Jun 23, 2021
36b5a5d
typo
jcouv Jun 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8640,15 +8640,15 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess
break;

case SyntaxKind.SimpleLambdaExpression:
resultIsUsed = (((SimpleLambdaExpressionSyntax)parent).Body != node) || ContainingMethodOrLambdaRequiresValue();
resultIsUsed = (((SimpleLambdaExpressionSyntax)parent).Body != node) || MethodOrLambdaRequiresValue(ContainingMemberOrLambda, Compilation);
break;

case SyntaxKind.ParenthesizedLambdaExpression:
resultIsUsed = (((ParenthesizedLambdaExpressionSyntax)parent).Body != node) || ContainingMethodOrLambdaRequiresValue();
resultIsUsed = (((ParenthesizedLambdaExpressionSyntax)parent).Body != node) || MethodOrLambdaRequiresValue(ContainingMemberOrLambda, Compilation);
break;

case SyntaxKind.ArrowExpressionClause:
resultIsUsed = (((ArrowExpressionClauseSyntax)parent).Expression != node) || ContainingMethodOrLambdaRequiresValue();
resultIsUsed = (((ArrowExpressionClauseSyntax)parent).Expression != node) || MethodOrLambdaRequiresValue(ContainingMemberOrLambda, Compilation);
break;

case SyntaxKind.ForStatement:
Expand Down Expand Up @@ -8678,13 +8678,11 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess
return new BoundConditionalAccess(node, receiver, access, accessType);
}

private bool ContainingMethodOrLambdaRequiresValue()
internal static bool MethodOrLambdaRequiresValue(Symbol symbol, CSharpCompilation compilation)
{
var containingMethod = ContainingMemberOrLambda as MethodSymbol;
return
(object)containingMethod == null ||
!containingMethod.ReturnsVoid &&
!containingMethod.IsAsyncReturningTask(this.Compilation);
return symbol is MethodSymbol method &&
!method.ReturnsVoid &&
!method.IsAsyncEffectivelyReturningTask(compilation);
}

private BoundConditionalAccess GenerateBadConditionalAccessNodeError(ConditionalAccessExpressionSyntax node, BoundExpression receiver, BoundExpression access, BindingDiagnosticBag diagnostics)
Expand Down
21 changes: 11 additions & 10 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ BoundBlock runAnalysis(BoundBlock block, BindingDiagnosticBag blockDiagnostics)

private bool ImplicitReturnIsOkay(MethodSymbol method)
{
return method.ReturnsVoid || method.IsIterator || method.IsAsyncReturningTask(this.Compilation);
return method.ReturnsVoid || method.IsIterator || method.IsAsyncEffectivelyReturningTask(this.Compilation);
}

public BoundStatement BindExpressionStatement(ExpressionStatementSyntax node, BindingDiagnosticBag diagnostics)
Expand Down Expand Up @@ -2656,16 +2656,16 @@ protected bool IsInAsyncMethod()
return IsInAsyncMethod(this.ContainingMemberOrLambda as MethodSymbol);
}

protected bool IsTaskReturningAsyncMethod()
protected bool IsEffectivelyTaskReturningAsyncMethod()
{
var symbol = this.ContainingMemberOrLambda;
return symbol?.Kind == SymbolKind.Method && ((MethodSymbol)symbol).IsAsyncReturningTask(this.Compilation);
return symbol?.Kind == SymbolKind.Method && ((MethodSymbol)symbol).IsAsyncEffectivelyReturningTask(this.Compilation);
}

protected bool IsGenericTaskReturningAsyncMethod()
protected bool IsEffectivelyGenericTaskReturningAsyncMethod()
{
var symbol = this.ContainingMemberOrLambda;
return symbol?.Kind == SymbolKind.Method && ((MethodSymbol)symbol).IsAsyncReturningGenericTask(this.Compilation);
return symbol?.Kind == SymbolKind.Method && ((MethodSymbol)symbol).IsAsyncEffectivelyReturningGenericTask(this.Compilation);
}

protected bool IsIAsyncEnumerableOrIAsyncEnumeratorReturningAsyncMethod()
Expand Down Expand Up @@ -2768,7 +2768,7 @@ private BoundStatement BindReturn(ReturnStatementSyntax syntax, BindingDiagnosti
// on a lambda expression of unknown return type.
if ((object)retType != null)
{
if (retType.IsVoidType() || IsTaskReturningAsyncMethod())
if (retType.IsVoidType() || IsEffectivelyTaskReturningAsyncMethod())
{
if (arg != null)
{
Expand Down Expand Up @@ -2808,7 +2808,7 @@ private BoundStatement BindReturn(ReturnStatementSyntax syntax, BindingDiagnosti
if (arg == null)
{
// Error case: non-void-returning or Task<T>-returning method or lambda but just have "return;"
var requiredType = IsGenericTaskReturningAsyncMethod()
var requiredType = IsEffectivelyGenericTaskReturningAsyncMethod()
? retType.GetMemberTypeArgumentsNoUseSiteDiagnostics().Single()
: retType;

Expand Down Expand Up @@ -2851,7 +2851,7 @@ internal BoundExpression CreateReturnConversion(
{
Debug.Assert(returnRefKind == RefKind.None);

if (!IsGenericTaskReturningAsyncMethod())
if (!IsEffectivelyGenericTaskReturningAsyncMethod())
{
conversion = Conversion.NoConversion;
badAsyncReturnAlreadyReported = true;
Expand Down Expand Up @@ -2888,7 +2888,8 @@ internal BoundExpression CreateReturnConversion(
if (!badAsyncReturnAlreadyReported)
{
RefKind unusedRefKind;
if (IsGenericTaskReturningAsyncMethod() && TypeSymbol.Equals(argument.Type, this.GetCurrentReturnType(out unusedRefKind), TypeCompareKind.ConsiderEverything2))
if (IsEffectivelyGenericTaskReturningAsyncMethod()
&& TypeSymbol.Equals(argument.Type, this.GetCurrentReturnType(out unusedRefKind), TypeCompareKind.ConsiderEverything2))
{
// Since this is an async method, the return expression must be of type '{0}' rather than 'Task<{0}>'
Error(diagnostics, ErrorCode.ERR_BadAsyncReturnExpression, argument.Syntax, returnType);
Expand Down Expand Up @@ -3177,7 +3178,7 @@ internal BoundBlock CreateBlockFromExpression(CSharpSyntaxNode node, ImmutableAr
expression = BindToTypeForErrorRecovery(expression);
statement = new BoundReturnStatement(syntax, RefKind.None, expression) { WasCompilerGenerated = true };
}
else if (returnType.IsVoidType() || IsTaskReturningAsyncMethod())
else if (returnType.IsVoidType() || IsEffectivelyTaskReturningAsyncMethod())
{
// If the return type is void then the expression is required to be a legal
// statement expression.
Expand Down
21 changes: 3 additions & 18 deletions src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs
Original file line number Diff line number Diff line change
Expand Up @@ -570,21 +570,6 @@ private static TypeWithAnnotations DelegateReturnTypeWithAnnotations(MethodSymbo
return invokeMethod.ReturnTypeWithAnnotations;
}

private bool DelegateNeedsReturn(MethodSymbol? invokeMethod)
{
if (invokeMethod is null || invokeMethod.ReturnsVoid)
{
return false;
}

if (IsAsync && invokeMethod.ReturnType.IsNonGenericTaskType(this.Binder.Compilation))
{
return false;
}

return true;
}

internal NamedTypeSymbol? InferDelegateType(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(Binder.ContainingMemberOrLambda is { });
Expand Down Expand Up @@ -716,7 +701,7 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType)
bool reachableEndpoint = ControlFlowPass.Analyze(compilation, lambdaSymbol, block, diagnostics.DiagnosticBag);
if (reachableEndpoint)
{
if (DelegateNeedsReturn(invokeMethod))
if (Binder.MethodOrLambdaRequiresValue(lambdaSymbol, this.Binder.Compilation))
Copy link
Member Author

@jcouv jcouv Jun 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Changes to this method are covered by BuilderFactoryOnMethod_OnLambda_NotTaskLikeTypes. With this change the test doesn't report ERR_AnonymousReturnExpected nor ERR_CantConvAsyncAnonFuncReturns below. #Resolved

{
// Not all code paths return a value in {0} of type '{1}'
diagnostics.Add(ErrorCode.ERR_AnonymousReturnExpected, lambdaSymbol.DiagnosticLocation, this.MessageID.Localize(), delegateType);
Expand All @@ -731,8 +716,8 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType)
{
if (returnType.HasType && // Can be null if "delegateType" is not actually a delegate type.
!returnType.IsVoidType() &&
!returnType.Type.IsNonGenericTaskType(compilation) &&
!returnType.Type.IsGenericTaskType(compilation))
!lambdaSymbol.IsAsyncEffectivelyReturningTask(compilation) &&
!lambdaSymbol.IsAsyncEffectivelyReturningGenericTask(compilation))
{
// Cannot convert async {0} to delegate type '{1}'. An async {0} may return void, Task or Task&lt;T&gt;, none of which are convertible to '{1}'.
diagnostics.Add(ErrorCode.ERR_CantConvAsyncAnonFuncReturns, lambdaSymbol.DiagnosticLocation, lambdaSymbol.MessageID.Localize(), delegateType);
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -6588,6 +6588,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="IDS_FeatureWithOnAnonymousTypes" xml:space="preserve">
<value>with on anonymous types</value>
</data>
<data name="IDS_AsyncMethodBuilderOverride" xml:space="preserve">
<value>async method builder override</value>
</data>
<data name="IDS_FeaturePositionalFieldsInRecords" xml:space="preserve">
<value>positional fields in records</value>
</data>
Expand Down Expand Up @@ -6713,4 +6716,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="HDN_DuplicateWithGlobalUsing_Title" xml:space="preserve">
<value>The using directive appeared previously as global using</value>
</data>
<data name="ERR_BuilderAttributeDisallowed" xml:space="preserve">
<value>The AsyncMethodBuilder attribute is disallowed on anonymous methods without an explicit return type.</value>
</data>
</root>
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1957,10 +1957,10 @@ internal enum ErrorCode
ERR_InterfaceImplementedByUnmanagedCallersOnlyMethod = 8932,
HDN_DuplicateWithGlobalUsing = 8933,
ERR_CantConvAnonMethReturnType = 8934,
ERR_BuilderAttributeDisallowed = 8935,

#endregion


// Note: you will need to re-generate compiler code after adding warnings (eng\generate-compiler-code.cmd)
}
}
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/MessageID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ internal enum MessageID
IDS_FeatureExtendedPropertyPatterns = MessageBase + 12802,
IDS_FeatureStaticAbstractMembersInInterfaces = MessageBase + 12803,
IDS_FeatureLambdaReturnType = MessageBase + 12804,
IDS_AsyncMethodBuilderOverride = MessageBase + 12805,
}

// Message IDs may refer to strings that need to be localized.
Expand Down Expand Up @@ -346,6 +347,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureExtendedPropertyPatterns:
case MessageID.IDS_FeatureStaticAbstractMembersInInterfaces: // semantic check
case MessageID.IDS_FeatureLambdaReturnType: // semantic check
case MessageID.IDS_AsyncMethodBuilderOverride: // semantic check
return LanguageVersion.Preview;

// C# 9.0 features.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static BoundBlock Rewrite(
#endif
var compilation = method.DeclaringCompilation;

if (method.ReturnsVoid || method.IsIterator || method.IsAsyncReturningTask(compilation))
if (method.ReturnsVoid || method.IsIterator || method.IsAsyncEffectivelyReturningTask(compilation))
{
// we don't analyze synthesized void methods.
if ((method.IsImplicitlyDeclared && !method.IsScriptInitializer) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2558,7 +2558,7 @@ private bool TryGetReturnType(out TypeWithAnnotations type, out FlowAnalysisAnno
return true;
}

if (returnType.Type.IsGenericTaskType(compilation))
if (method.IsAsyncEffectivelyReturningGenericTask(compilation))
{
type = ((NamedTypeSymbol)returnType.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single();
annotations = FlowAnalysisAnnotations.None;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
out createBuilderMethod);
if ((object)createBuilderMethod == null)
{
collection = default(AsyncMethodBuilderMemberCollection);
collection = default;
return false;
}
return TryCreate(
Expand All @@ -178,18 +178,30 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
collection: out collection);
}

if (method.IsAsyncReturningTask(F.Compilation))
object methodLevelBuilder = null;
if (method.IsAsyncEffectivelyReturningTask(F.Compilation))
{
var returnType = (NamedTypeSymbol)method.ReturnType;
NamedTypeSymbol builderType;
MethodSymbol createBuilderMethod = null;
PropertySymbol taskProperty = null;

bool useMethodLevelBuilder = method.HasAsyncMethodBuilderAttribute(out methodLevelBuilder);
bool customBuilder;
object builderArgument;
bool customBuilder = returnType.IsCustomTaskType(out builderArgument);

if (useMethodLevelBuilder)
{
customBuilder = true;
builderArgument = methodLevelBuilder;
}
else
{
customBuilder = returnType.IsCustomTaskType(out builderArgument);
}

if (customBuilder)
{
builderType = ValidateBuilderType(F, builderArgument, returnType.DeclaredAccessibility, isGeneric: false);
builderType = ValidateBuilderType(F, builderArgument, returnType.DeclaredAccessibility, isGeneric: false, useMethodLevelBuilder);
if ((object)builderType != null)
{
taskProperty = GetCustomTaskProperty(F, builderType, returnType);
Expand All @@ -213,13 +225,15 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
customBuilder,
out taskProperty);
}

if ((object)builderType == null ||
(object)createBuilderMethod == null ||
(object)taskProperty == null)
{
collection = default(AsyncMethodBuilderMemberCollection);
collection = default;
return false;
}

return TryCreate(
F,
customBuilder: customBuilder,
Expand All @@ -236,7 +250,7 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
collection: out collection);
}

if (method.IsAsyncReturningGenericTask(F.Compilation))
if (method.IsAsyncEffectivelyReturningGenericTask(F.Compilation))
{
var returnType = (NamedTypeSymbol)method.ReturnType;
var resultType = returnType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().Type;
Expand All @@ -252,12 +266,23 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
NamedTypeSymbol builderType;
MethodSymbol createBuilderMethod = null;
PropertySymbol taskProperty = null;

bool useMethodLevelBuilder = method.HasAsyncMethodBuilderAttribute(out methodLevelBuilder);
bool customBuilder;
object builderArgument;
bool customBuilder = returnType.IsCustomTaskType(out builderArgument);

if (useMethodLevelBuilder)
{
customBuilder = true;
builderArgument = methodLevelBuilder;
}
else
{
customBuilder = returnType.IsCustomTaskType(out builderArgument);
}

if (customBuilder)
{
builderType = ValidateBuilderType(F, builderArgument, returnType.DeclaredAccessibility, isGeneric: true);
builderType = ValidateBuilderType(F, builderArgument, returnType.DeclaredAccessibility, isGeneric: true, useMethodLevelBuilder);
if ((object)builderType != null)
{
builderType = builderType.ConstructedFrom.Construct(resultType);
Expand All @@ -283,13 +308,15 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
customBuilder,
out taskProperty);
}

if ((object)builderType == null ||
(object)taskProperty == null ||
(object)createBuilderMethod == null)
{
collection = default(AsyncMethodBuilderMemberCollection);
collection = default;
return false;
}

return TryCreate(
F,
customBuilder: customBuilder,
Expand All @@ -309,14 +336,14 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
throw ExceptionUtilities.UnexpectedValue(method);
}

private static NamedTypeSymbol ValidateBuilderType(SyntheticBoundNodeFactory F, object builderAttributeArgument, Accessibility desiredAccessibility, bool isGeneric)
private static NamedTypeSymbol ValidateBuilderType(SyntheticBoundNodeFactory F, object builderAttributeArgument, Accessibility desiredAccessibility, bool isGeneric, bool forMethodLevelBuilder = false)
{
var builderType = builderAttributeArgument as NamedTypeSymbol;

if ((object)builderType != null &&
!builderType.IsErrorType() &&
!builderType.IsVoidType() &&
builderType.DeclaredAccessibility == desiredAccessibility)
(forMethodLevelBuilder || builderType.DeclaredAccessibility == desiredAccessibility))
{
bool isArityOk = isGeneric
? builderType.IsUnboundGenericType && builderType.ContainingType?.IsGenericType != true && builderType.Arity == 1
Expand Down Expand Up @@ -376,7 +403,7 @@ private static bool TryCreate(
return true;
}

collection = default(AsyncMethodBuilderMemberCollection);
collection = default;
return false;
}

Expand Down Expand Up @@ -448,6 +475,7 @@ private static MethodSymbol GetCustomCreateMethod(
method.IsStatic &&
method.ParameterCount == 0 &&
!method.IsGenericMethod &&
method.RefKind == RefKind.None &&
Copy link
Member

@333fred 333fred Jun 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually just a bug fix? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this change for proper behavior of method-level builders (see test BuilderOnMethod_CreateHasRefReturn).
But yes it was a misbehavior of task-like types (see test AsyncMethod_CreateHasRefReturn).

method.ReturnType.Equals(builderType, TypeCompareKind.AllIgnoreOptions))
{
return method;
Expand Down
Loading