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 9 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
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -6585,6 +6585,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
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 @@ -225,6 +225,7 @@ internal enum MessageID
IDS_FeatureLambdaAttributes = MessageBase + 12800,
IDS_FeatureWithOnAnonymousTypes = MessageBase + 12801,
IDS_FeatureExtendedPropertyPatterns = MessageBase + 12802,
IDS_AsyncMethodBuilderOverride = MessageBase + 12803,
}

// Message IDs may refer to strings that need to be localized.
Expand Down Expand Up @@ -342,6 +343,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureInferredDelegateType: // semantic check
case MessageID.IDS_FeatureLambdaAttributes: // semantic check
case MessageID.IDS_FeatureExtendedPropertyPatterns:
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 @@ -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 Down Expand Up @@ -189,11 +189,33 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
bool customBuilder = returnType.IsCustomTaskType(out builderArgument);
if (customBuilder)
{
builderType = ValidateBuilderType(F, builderArgument, returnType.DeclaredAccessibility, isGeneric: false);
if ((object)builderType != null)
if (method.HasMethodLevelBuilder(out var methodLevelBuilder))
{
var initialBuilderType = ValidateBuilderType(F, methodLevelBuilder, returnType.DeclaredAccessibility, isGeneric: false, forOverride: true);
if ((object)initialBuilderType != null)
{
// We only get the Create method from the initial builder type, then we'll use its return type as builder type
createBuilderMethod = GetCustomCreateMethod(F, initialBuilderType, forOverride: true);
builderType = createBuilderMethod?.ReturnType as NamedTypeSymbol;
if ((object)builderType != null)
{
taskProperty = GetCustomTaskProperty(F, builderType, returnType);
validateIgnoredBuilderType(F, returnType, builderArgument, isGeneric: false);
}
}
else
{
builderType = null;
}
}
else
{
taskProperty = GetCustomTaskProperty(F, builderType, returnType);
createBuilderMethod = GetCustomCreateMethod(F, builderType);
builderType = ValidateBuilderType(F, builderArgument, returnType.DeclaredAccessibility, isGeneric: false);
if ((object)builderType != null)
{
taskProperty = GetCustomTaskProperty(F, builderType, returnType);
createBuilderMethod = GetCustomCreateMethod(F, builderType);
}
}
}
else
Expand All @@ -217,7 +239,7 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
(object)createBuilderMethod == null ||
(object)taskProperty == null)
{
collection = default(AsyncMethodBuilderMemberCollection);
collection = default;
return false;
}
return TryCreate(
Expand Down Expand Up @@ -257,12 +279,35 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
bool customBuilder = returnType.IsCustomTaskType(out builderArgument);
if (customBuilder)
{
builderType = ValidateBuilderType(F, builderArgument, returnType.DeclaredAccessibility, isGeneric: true);
if ((object)builderType != null)
if (method.HasMethodLevelBuilder(out var methodLevelBuilder))
{
builderType = builderType.ConstructedFrom.Construct(resultType);
taskProperty = GetCustomTaskProperty(F, builderType, returnType);
createBuilderMethod = GetCustomCreateMethod(F, builderType);
var initialBuilderType = ValidateBuilderType(F, methodLevelBuilder, returnType.DeclaredAccessibility, isGeneric: true, forOverride: true);
if ((object)initialBuilderType != null)
{
// We only get the Create method from the initial builder type, then we'll use its return type as builder type
initialBuilderType = initialBuilderType.ConstructedFrom.Construct(resultType);
createBuilderMethod = GetCustomCreateMethod(F, initialBuilderType, forOverride: true);
builderType = createBuilderMethod?.ReturnType as NamedTypeSymbol;
if ((object)builderType != null)
{
taskProperty = GetCustomTaskProperty(F, builderType, returnType);
validateIgnoredBuilderType(F, returnType, builderArgument, isGeneric: true);
}
}
else
{
builderType = null;
}
}
else
{
builderType = ValidateBuilderType(F, builderArgument, returnType.DeclaredAccessibility, isGeneric: true);
if ((object)builderType != null)
{
builderType = builderType.ConstructedFrom.Construct(resultType);
taskProperty = GetCustomTaskProperty(F, builderType, returnType);
createBuilderMethod = GetCustomCreateMethod(F, builderType);
}
}
}
else
Expand All @@ -287,7 +332,7 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
(object)taskProperty == null ||
(object)createBuilderMethod == null)
{
collection = default(AsyncMethodBuilderMemberCollection);
collection = default;
return false;
}
return TryCreate(
Expand All @@ -307,16 +352,52 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method,
}

throw ExceptionUtilities.UnexpectedValue(method);

void validateIgnoredBuilderType(SyntheticBoundNodeFactory F, NamedTypeSymbol returnType, object builderArgument, bool isGeneric)
{
var ignoredBuilderType = ValidateBuilderType(F, builderArgument, returnType.DeclaredAccessibility, isGeneric);

if ((object)ignoredBuilderType != null)
{
if (isGeneric)
{
var resultType = returnType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().Type;
ignoredBuilderType = ignoredBuilderType.ConstructedFrom.Construct(resultType);
}

GetCustomCreateMethod(F, ignoredBuilderType, forOverride: false);
GetCustomTaskProperty(F, ignoredBuilderType, returnType);

_ = TryGetBuilderMember(F,
isGeneric ? WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder_T__SetException : WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder__SetException,
ignoredBuilderType, customBuilder: true, out MethodSymbol _) &&
TryGetBuilderMember(F,
isGeneric ? WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder_T__SetResult : WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder__SetResult,
ignoredBuilderType, customBuilder: true, out MethodSymbol _) &&
TryGetBuilderMember(F,
isGeneric ? WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder_T__AwaitOnCompleted : WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder__AwaitOnCompleted,
ignoredBuilderType, customBuilder: true, out MethodSymbol _) &&
TryGetBuilderMember(F,
isGeneric ? WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder_T__AwaitUnsafeOnCompleted : WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder__AwaitUnsafeOnCompleted,
ignoredBuilderType, customBuilder: true, out MethodSymbol _) &&
TryGetBuilderMember(F,
isGeneric ? WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder_T__Start_T : WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder__Start_T,
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.

}
}
}

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 forOverride = false)
{
var builderType = builderAttributeArgument as NamedTypeSymbol;

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

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

Expand Down Expand Up @@ -429,9 +510,12 @@ private static bool TryGetBuilderMember<TSymbol>(
return true;
}

// For method-level builders, we allow the `Create` method to return a different type.
// We'll just use that type as the final builder type.
private static MethodSymbol GetCustomCreateMethod(
SyntheticBoundNodeFactory F,
NamedTypeSymbol builderType)
NamedTypeSymbol builderType,
bool forOverride = false)
{
// The Create method's return type is expected to be builderType.
// The WellKnownMembers routines aren't able to enforce that, which is why this method exists.
Expand All @@ -448,7 +532,8 @@ private static MethodSymbol GetCustomCreateMethod(
method.IsStatic &&
method.ParameterCount == 0 &&
!method.IsGenericMethod &&
method.ReturnType.Equals(builderType, TypeCompareKind.AllIgnoreOptions))
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).

(forOverride || method.ReturnType.Equals(builderType, TypeCompareKind.AllIgnoreOptions)))
{
return method;
}
Expand Down
27 changes: 27 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
Expand Down Expand Up @@ -190,6 +191,32 @@ public static bool IsAsyncReturningIAsyncEnumerator(this MethodSymbol method, CS
&& method.ReturnType.IsIAsyncEnumeratorType(compilation);
}

#nullable enable
/// <summary>
/// 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.

{
Debug.Assert(method is not null);

// Find the AsyncMethodBuilder attribute.
foreach (var attr in method.GetAttributes())
{
if (attr.IsTargetAttribute(method, AttributeDescription.AsyncMethodBuilderAttribute)
&& attr.CommonConstructorArguments.Length == 1
&& attr.CommonConstructorArguments[0].Kind == TypedConstantKind.Type)
{
builderArgument = attr.CommonConstructorArguments[0].ValueInternal!;
return true;
}
}

builderArgument = null;
return false;
}
#nullable disable

internal static CSharpSyntaxNode ExtractReturnTypeSyntax(this MethodSymbol method)
{
if (method is SynthesizedSimpleProgramEntryPointSymbol synthesized)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

}

for (NamedTypeSymbol curr = this.ContainingType; (object)curr != null; curr = curr.ContainingType)
{
var sourceNamedTypeSymbol = curr as SourceNamedTypeSymbol;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1638,7 +1638,7 @@ internal static bool IsCustomTaskType(this NamedTypeSymbol type, [NotNullWhen(tr
var arity = type.Arity;
if (arity < 2)
{
// Find the AsyncBuilder attribute.
// Find the AsyncMethodBuilder attribute.
foreach (var attr in type.GetAttributes())
{
if (attr.IsTargetAttribute(type, AttributeDescription.AsyncMethodBuilderAttribute)
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf

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

5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf

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

Loading