-
Notifications
You must be signed in to change notification settings - Fork 4k
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
"Arbitrary async returns" should use attribute #13405
Changes from 13 commits
b82a3f9
1c806ae
abc0efd
6ecd515
424da4a
416cf98
1d6580a
3d82d1a
8b8d09d
d7076e8
62a472f
01eafc2
0ddec05
bcbd12f
33e65fb
c9633b0
eef0c70
0e30e90
f2cdba8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -133,16 +133,40 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method, | |
collection: out collection); | ||
} | ||
|
||
// TODO: in both nongeneric and generic forks below, we should unify both "custom tasklike" and "Task/Task<T>" | ||
// down the same custom-tasklike path. (Should mscorlib ever device to put [AsyncBuilder] on Task then that | ||
// will happen anyway). | ||
|
||
if (method.IsTaskReturningAsync(F.Compilation)) | ||
{ | ||
var returnType = (NamedTypeSymbol)method.ReturnType; | ||
NamedTypeSymbol builderType; | ||
MethodSymbol createBuilderMethod; | ||
PropertySymbol taskProperty; | ||
bool customBuilder = returnType.IsCustomTaskType(out builderType, out createBuilderMethod); | ||
MethodSymbol createBuilderMethod = null; | ||
PropertySymbol taskProperty = null; | ||
|
||
object builderArgument; | ||
bool customBuilder = returnType.IsCustomTaskType(out builderArgument); | ||
if (customBuilder) | ||
{ | ||
taskProperty = GetCustomTaskProperty(F, builderType); | ||
builderType = builderArgument as NamedTypeSymbol; | ||
if (builderType?.IsErrorType() == true) | ||
{ | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than an empty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way I'd done cascading conditions was "(1) if an error has been reported, (2) otherwise if it's an error but one hasn't been reported, (3) otherwise success". How about I add a comment to the first branch to indicate this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Has the error necessarily been reported? What if the task-like type and builder type are from metadata and the builder type is missing? Do we need to report a use-site error here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely agree, the empty In reply to: 76644154 [](ancestors = 76644154) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
else if ((object)builderType == null || | ||
builderType.SpecialType == SpecialType.System_Void || | ||
builderType.DeclaredAccessibility != returnType.DeclaredAccessibility || | ||
builderType.IsGenericType) | ||
{ | ||
// might be null if type isn't a named type, e.g. [AsyncBuilder(typeof(object[]))] | ||
var diagnostic = new CSDiagnostic( | ||
new CSDiagnosticInfo(ErrorCode.ERR_BadAsyncReturn), F.Syntax.Location); | ||
F.Diagnostics.Add(diagnostic); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
else | ||
{ | ||
taskProperty = GetCustomTaskProperty(F, builderType); | ||
createBuilderMethod = GetCustomCreateMethod(F, builderType); | ||
} | ||
} | ||
else | ||
{ | ||
|
@@ -196,10 +220,12 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method, | |
resultType = typeMap.SubstituteType(resultType).Type; | ||
} | ||
returnType = returnType.ConstructedFrom.Construct(resultType); | ||
NamedTypeSymbol builderType; | ||
MethodSymbol createBuilderMethod; | ||
PropertySymbol taskProperty; | ||
bool customBuilder = returnType.IsCustomTaskType(out builderType, out createBuilderMethod); | ||
NamedTypeSymbol builderType = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor point: initializer is not needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
MethodSymbol createBuilderMethod = null; | ||
PropertySymbol taskProperty = null; | ||
|
||
object builderArgument; | ||
bool customBuilder = returnType.IsCustomTaskType(out builderArgument); | ||
if (!customBuilder) | ||
{ | ||
builderType = F.WellKnownType(WellKnownType.System_Runtime_CompilerServices_AsyncTaskMethodBuilder_T); | ||
|
@@ -208,7 +234,38 @@ internal static bool TryCreate(SyntheticBoundNodeFactory F, MethodSymbol method, | |
} | ||
if (customBuilder) | ||
{ | ||
taskProperty = GetCustomTaskProperty(F, builderType); | ||
builderType = builderArgument as NamedTypeSymbol; | ||
|
||
// builderType must be an open generic of arity 1, and its containing types must be nongeneric | ||
bool isBuilderGenericityOk = ((object)builderType != null && | ||
!builderType.IsErrorType() && | ||
builderType.Arity == 1 && | ||
builderType.TypeArguments[0] is UnboundArgumentErrorTypeSymbol); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
for (var containingType = builderType.ContainingType; | ||
containingType != null && isBuilderGenericityOk; | ||
containingType = containingType.ContainingType) | ||
{ | ||
isBuilderGenericityOk &= (containingType.Arity == 0); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Loop can be replaced by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
if (builderType?.IsErrorType() == true) | ||
{ | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to revist this. The empty block stands out and looks like a badge merge. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
else if ((object)builderType == null || | ||
builderType.SpecialType == SpecialType.System_Void || | ||
builderType.DeclaredAccessibility != returnType.DeclaredAccessibility || | ||
!isBuilderGenericityOk) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please extract a helper method that does most of these checks, for re-use in generic and non-generic cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
F.Diagnostics.Add(new CSDiagnostic( | ||
new CSDiagnosticInfo(ErrorCode.ERR_BadAsyncReturn), F.Syntax.Location)); | ||
} | ||
else | ||
{ | ||
builderType = builderType.ConstructedFrom.Construct(resultType); | ||
taskProperty = GetCustomTaskProperty(F, builderType); | ||
createBuilderMethod = GetCustomCreateMethod(F, builderType); | ||
} | ||
} | ||
else | ||
{ | ||
|
@@ -341,6 +398,40 @@ private static bool TryGetBuilderMember<TSymbol>( | |
return true; | ||
} | ||
|
||
private static MethodSymbol GetCustomCreateMethod( | ||
SyntheticBoundNodeFactory F, | ||
NamedTypeSymbol builderType) | ||
{ | ||
// The Create method's return type is expected to be buildderType. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// The WellKnownMembers routines aren't able to enforce that, which is this method exists. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// | ||
// TODO: consider removing WellKnownMember.System_Runtime_CompilerServices_AsyncTaskMethodBuilder*__Create | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In the master branch TODOs should all have associated bug numbers or be removed from source tree. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// and using GetCustomCreateMethod for the Task types as well as for custom tasklikes. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove TODO or replace with reference to issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
const string methodName = "Create"; | ||
var members = builderType.GetMembers(methodName); | ||
foreach (var member in members) | ||
{ | ||
if (member.Kind != SymbolKind.Method) | ||
{ | ||
continue; | ||
} | ||
var method = (MethodSymbol)member; | ||
if ((method.DeclaredAccessibility == Accessibility.Public) && | ||
method.IsStatic && | ||
method.ParameterCount == 0 && | ||
!method.IsGenericMethod && | ||
method.ReturnType == builderType) | ||
{ | ||
return method; | ||
} | ||
} | ||
var diagnostic = new CSDiagnostic( | ||
new CSDiagnosticInfo(ErrorCode.ERR_MissingPredefinedMember, builderType, methodName), | ||
F.Syntax.Location); | ||
F.Diagnostics.Add(diagnostic); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. |
||
return null; | ||
} | ||
|
||
private static PropertySymbol GetCustomTaskProperty( | ||
SyntheticBoundNodeFactory F, | ||
NamedTypeSymbol builderType) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1273,9 +1273,8 @@ internal static bool IsNonGenericTaskType(this TypeSymbol type, CSharpCompilatio | |
{ | ||
return false; | ||
} | ||
NamedTypeSymbol builderType; | ||
MethodSymbol createBuilderMethod; | ||
return namedType.IsCustomTaskType(out builderType, out createBuilderMethod); | ||
object builderArgument; | ||
return namedType.IsCustomTaskType(out builderArgument); | ||
} | ||
|
||
internal static bool IsGenericTaskType(this TypeSymbol type, CSharpCompilation compilation) | ||
|
@@ -1289,51 +1288,42 @@ internal static bool IsGenericTaskType(this TypeSymbol type, CSharpCompilation c | |
{ | ||
return true; | ||
} | ||
NamedTypeSymbol builderType; | ||
MethodSymbol createBuilderMethod; | ||
return namedType.IsCustomTaskType(out builderType, out createBuilderMethod); | ||
object builderArgument; | ||
return namedType.IsCustomTaskType(out builderArgument); | ||
} | ||
|
||
/// <summary> | ||
/// Returns true if the type is generic or non-generic task-like type. If so, the async | ||
/// method builder type is returned along with the method to construct that type. | ||
/// Returns true if the type is generic or non-generic custom task-like type due to the | ||
/// [AsyncBuilder(typeof(B))] attribute. It returns the "B". | ||
/// </summary> | ||
internal static bool IsCustomTaskType(this NamedTypeSymbol type, out NamedTypeSymbol builderType, out MethodSymbol createBuilderMethod) | ||
/// <remarks> | ||
/// For the Task types themselves, this method might return true or false depending on mscorlib. | ||
/// The definition of "custom task-like type" is one that has an [AsyncBuilder(typeof(B))] attribute, | ||
/// no more, no less. Validation of builder type B is left for elsewhere. This method returns B | ||
/// without validation of any kind. | ||
/// </remarks> | ||
internal static bool IsCustomTaskType(this NamedTypeSymbol type, out object builderArgument) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went back and forth on this. If you write Now I said that I'm looking specifically for the So if anyone wants to report errors on what the argument was, then returning it just as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not? :) I figure it could have gone either way. There is a spectrum of possibilities...
I picked the first option because (A) the extremes seem more defensible than the middle grounds, (B) it felt a touch more efficient because places like overload resolution and type inference which might potentially be used a lot only do the minimal checking, (C) I think it's a better user experience while you're developing your custom tasklike if it remains tasklike even while you're in the process of editing+fixing the code of your builder. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should allow And is returning Returning an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like using extension methods when the target type is defined in the same assembly and namespace. It's just harder to find all the places where applicable extension methods are defined. Especially since this method is not even defined in Not sure what you mean by public API -- If the method was implemented using the true public API, e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure where to go with this...
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I vote for an instance method on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have very few methods that operate on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, "leave as is" :) |
||
{ | ||
Debug.Assert((object)type != null); | ||
Debug.Assert(type.SpecialType != SpecialType.System_Void); | ||
|
||
var arity = type.Arity; | ||
if (arity < 2) | ||
{ | ||
// Find the public static CreateAsyncMethodBuilder method. | ||
var members = type.GetMembers(WellKnownMemberNames.CreateAsyncMethodBuilder); | ||
foreach (var member in members) | ||
// Find the AsyncBuilder attribute. | ||
foreach (var attr in type.GetAttributes()) | ||
{ | ||
if (member.Kind != SymbolKind.Method) | ||
{ | ||
continue; | ||
} | ||
var method = (MethodSymbol)member; | ||
if ((method.DeclaredAccessibility == Accessibility.Public) && | ||
method.IsStatic && | ||
(method.ParameterCount == 0) && | ||
!method.IsGenericMethod) | ||
if (attr.IsTargetAttribute(type, AttributeDescription.AsyncBuilderAttribute) | ||
&& attr.CommonConstructorArguments.Length == 1 | ||
&& attr.CommonConstructorArguments[0].Kind == TypedConstantKind.Type) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we testing constructors with multiple arguments or single argument but not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added unit tests in 33e65fb |
||
{ | ||
var returnType = method.ReturnType as NamedTypeSymbol; | ||
if ((object)returnType == null || returnType.Arity != arity) | ||
{ | ||
break; | ||
} | ||
builderType = returnType; | ||
createBuilderMethod = method; | ||
builderArgument = attr.CommonConstructorArguments[0].Value; | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
builderType = null; | ||
createBuilderMethod = null; | ||
builderArgument = null; | ||
return false; | ||
} | ||
|
||
|
@@ -1412,9 +1402,8 @@ private static bool NormalizeTaskTypesInNamedType(CSharpCompilation compilation, | |
typeArgumentsBuilder.Free(); | ||
} | ||
|
||
NamedTypeSymbol builderType; | ||
MethodSymbol createBuilderMethod; | ||
if (type.OriginalDefinition.IsCustomTaskType(out builderType, out createBuilderMethod)) | ||
object builderArgument; | ||
if (type.OriginalDefinition.IsCustomTaskType(out builderArgument)) | ||
{ | ||
int arity = type.Arity; | ||
Debug.Assert(arity < 2); | ||
|
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.
TODOs should be removed or replaced by links to bugs.
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.
Removed in eef0c70#diff-995d1bf33206eef8c980dbd08b8601b0L136