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

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Jun 11, 2021

Spec: https://github.com/dotnet/csharplang/blob/main/proposals/async-method-builders.md
Test plan: #51999

Current design choices:

  • we don't need the return type of async methods with AsyncMethodBuilder to be proper task-like types
  • we do not care about the accessibility of the builder override
  • but all members of the builder overrides (factory type and final builder) must have public accessibility
  • there is no builder factory vs. builder indirection after all
  • the AsyncMethodBuilder is only allowed on lambdas when their return type is explicit

@jcouv jcouv added this to the C# 10 milestone Jun 11, 2021
@jcouv jcouv self-assigned this Jun 11, 2021
@jcouv jcouv marked this pull request as ready for review June 15, 2021 19:48
@jcouv jcouv requested a review from a team as a code owner June 15, 2021 19:48
/// Returns true if the method has a [AsyncMethodBuilder(typeof(B))] attribute. If so it returns the "B".
/// Validation of builder type B is left for elsewhere. This method returns B without validation of any kind.
/// </summary>
internal static bool HasMethodLevelBuilder(this MethodSymbol method, [NotNullWhen(true)] out object? builderArgument)
Copy link
Member

@cston cston Jun 16, 2021

Choose a reason for hiding this comment

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

HasMethodLevelBuilder(this MethodSymbol method

Consider changing this to an extension method on Symbol and call this method from TypeSymbolExtensions.IsCustomTaskType() as well. #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.

I'm looking at one usage of IsCustomTaskType in BoundLambda.CalculateReturnType which I need to think more about.

Copy link
Member

Choose a reason for hiding this comment

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

I didn't understand the comment about CalculateReturnType. Could we share this implementation with TypeSymbolExtensions.IsCustomTaskType()?

Copy link
Member Author

Choose a reason for hiding this comment

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

The comment about CalculateReturnType is no longer relevant now that we're checking the builder from the return type.
Factored logic with IsCustomTaskType.

@@ -190,6 +191,48 @@ public static bool IsAsyncReturningIAsyncEnumerator(this MethodSymbol method, CS
&& method.ReturnType.IsIAsyncEnumeratorType(compilation);
}

#nullable enable
public static bool IsAsyncReturningTaskViaOverride(this MethodSymbol method, [NotNullWhen(true)] out object? builderArgument)
Copy link
Member

@cston cston Jun 16, 2021

Choose a reason for hiding this comment

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

IsAsyncReturningTaskViaOverride

Are IsAsyncReturningGenericTask() and IsAsyncReturningTaskViaOverride() ever called separately? If not, consider combining. Same question for generic task pair. #Closed

var symbol = this.ContainingMemberOrLambda;
return symbol?.Kind == SymbolKind.Method && ((MethodSymbol)symbol).IsAsyncReturningTaskViaOverride(out _);
}

Copy link
Member

@cston cston Jun 16, 2021

Choose a reason for hiding this comment

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

Are these two methods ever called separately? If not, consider combining the methods. Same question for pair of methods below. #Closed

@cston
Copy link
Member

cston commented Jun 16, 2021

    public void BuilderFactoryOnMethod_CreateHasParamaeter()

Typo.


In reply to: 862119350


In reply to: 862119350


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs:991 in e1f1d41. [](commit_id = e1f1d41, deletion_comment = False)

@@ -692,7 +677,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

ignoredBuilderType, customBuilder: true, out MethodSymbol _) &&
TryGetBuilderMember(F,
isGeneric ? WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder_T__SetStateMachine : WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder__SetStateMachine,
ignoredBuilderType, customBuilder: true, out MethodSymbol _);
Copy link
Member

@cston cston Jun 18, 2021

Choose a reason for hiding this comment

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

Could we use the existing TryCreate() method instead? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

It doesn't make much difference. I did initially ;-) I'll change it back.

@@ -1056,6 +1056,11 @@ protected void AsyncMethodChecks(BindingDiagnosticBag diagnostics)
hasErrors = true;
}

if (this.HasMethodLevelBuilder(out _))
{
hasErrors = MessageID.IDS_AsyncMethodBuilderOverride.CheckFeatureAvailability(diagnostics, this.DeclaringCompilation, errorLocation);
Copy link
Member

@cston cston Jun 19, 2021

Choose a reason for hiding this comment

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

hasErrors

It looks like this may reset hasErrors that was set above. #Closed

@jcouv
Copy link
Member Author

jcouv commented Jun 21, 2021

@dotnet/roslyn-compiler for a second review. Let me know if you need a walkthrough. Thanks

@jcouv jcouv marked this pull request as draft June 21, 2021 18:52
@jcouv jcouv requested a review from a team as a code owner June 22, 2021 03:25
@jcouv
Copy link
Member Author

jcouv commented Jun 22, 2021

Merged with explicit return types. We'll have to merge #54282 before continuing the review.


In reply to: 865499273

@cston
Copy link
Member

cston commented Jun 22, 2021

[AsyncMethodBuilder(typeof(MyTaskMethodBuilder))]

My mistake - the attribute does not affect callers.


In reply to: 865452212


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs:95 in ee207cb. [](commit_id = ee207cb, deletion_comment = False)

@jcouv
Copy link
Member Author

jcouv commented Jun 22, 2021

@dotnet/roslyn-compiler for review. Thanks

@cston
Copy link
Member

cston commented Jun 22, 2021

            // (7,71): error CS8934: The AsyncMethodBuilder attribute is disallowed on anonymous methods without an explicit return type.

CS8935 here and in other locations.


In reply to: 866274208


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs:889 in 3268386. [](commit_id = 3268386, deletion_comment = False)

@@ -448,6 +475,7 @@ private static NamedTypeSymbol ValidateBuilderType(SyntheticBoundNodeFactory F,
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).

@@ -815,5 +815,29 @@ internal static ImmutableArray<INamespaceSymbol> GetPublicSymbols(this Immutable
{
return symbol.GetSymbol<FunctionPointerTypeSymbol>();
}

/// <summary>
/// Returns true if the method has a [AsyncMethodBuilder(typeof(B))] attribute. If so it returns the "B".
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.

the

Nit: remove the #Closed

Copy link
Member

Choose a reason for hiding this comment

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

or add type

&& attr.CommonConstructorArguments.Length == 1
&& attr.CommonConstructorArguments[0].Kind == TypedConstantKind.Type)
{
builderArgument = attr.CommonConstructorArguments[0].ValueInternal!;
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.

ValueInternal

If we know this is a type, why not have the builderArgument be a TypeSymbol? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Kept in line with existing IsCustomTaskType method.

@333fred
Copy link
Member

333fred commented Jun 23, 2021

    internal static bool IsCustomTaskType(this NamedTypeSymbol type, [NotNullWhen(true)] out object? builderArgument)

Same question here, I guess.


In reply to: 867086783


In reply to: 867086783


In reply to: 867086783


Refers to: src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs:1643 in 3268386. [](commit_id = 3268386, deletion_comment = False)

/// Returns true if the method has a [AsyncMethodBuilder(typeof(B))] attribute. If so it returns the "B".
/// Validation of builder type B is left for elsewhere. This method returns B without validation of any kind.
/// </summary>
internal static bool HasAsyncMethodBuilder(this Symbol symbol, [NotNullWhen(true)] out object? builderArgument)
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.

HasAsyncMethodBuilder

Nit: feels like a better name for this is HasAsyncMethodBuilderAttribute, since it specifically won't return the builder type if the method has a Task-like type with no attribute. #Closed

@333fred
Copy link
Member

333fred commented Jun 23, 2021

            // (75,19): error CS1983: The return type of an async method must be void, Task, Task<T>, a task-like type, IAsyncEnumerable<T>, or IAsyncEnumerator<T>

These errors have nothing to do with accessibility? Also, several of these have no issues?


In reply to: 867133974


In reply to: 867133974


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs:662 in 3268386. [](commit_id = 3268386, deletion_comment = False)

@333fred
Copy link
Member

333fred commented Jun 23, 2021

[AsyncMethodBuilder(typeof(MyTaskMethodBuilder))] static async MyTask () => {{ System.Console.Write(""Lambda1 ""); await Task.Delay(0); }} // 1

I would have expected this to error?


In reply to: 867139447


In reply to: 867139447


In reply to: 867139447


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs:918 in 3268386. [](commit_id = 3268386, deletion_comment = False)

@333fred
Copy link
Member

333fred commented Jun 23, 2021

Done review pass (commit 25)

@jcouv
Copy link
Member Author

jcouv commented Jun 23, 2021

[AsyncMethodBuilder(typeof(MyTaskMethodBuilder))] static async MyTask () => {{ System.Console.Write(""Lambda1 ""); await Task.Delay(0); }} // 1

Resolved offline


In reply to: 867139447


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs:918 in 3268386. [](commit_id = 3268386, deletion_comment = False)

@jcouv
Copy link
Member Author

jcouv commented Jun 23, 2021

    public void BuilderOnMethod_WrongAccessibility()

Will fix this test to have builder on method and adjust name.


In reply to: 867181628


In reply to: 867181628


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMethodBuilderOverrideTests.cs:632 in 9440d0f. [](commit_id = 9440d0f, deletion_comment = False)

@jcouv jcouv requested a review from 333fred June 23, 2021 22:23
@jcouv jcouv merged commit b796152 into dotnet:main Jun 24, 2021
@ghost ghost modified the milestones: C# 10, Next Jun 24, 2021
@jcouv jcouv deleted the async-builder branch June 24, 2021 06:57
@RikkiGibson RikkiGibson modified the milestones: Next, 17.0.P2 Jun 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants