Skip to content
Permalink
Browse files

Support async methods returning Task-like types

[Based on features/async-return with tests ported from that branch.]
  • Loading branch information...
cston committed Jul 12, 2016
1 parent b6ed189 commit 6c9e18649f576bd9df1e0db8ad21bfbce0454704
Showing with 2,336 additions and 435 deletions.
  1. +2 −3 src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
  2. +37 −40 src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
  3. +28 −50 src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs
  4. +95 −51 src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs
  5. +1 −1 src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
  6. +1 −2 src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs
  7. +1 −2 src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs
  8. +177 −48 src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodBuilderMemberCollection.cs
  9. +46 −59 src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.cs
  10. +1 −13 src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs
  11. +31 −6 src/Compilers/CSharp/Portable/Symbols/ArrayTypeSymbol.cs
  12. +3 −3 src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs
  13. +1 −1 src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs
  14. +2 −4 src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs
  15. +1 −17 src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs
  16. +76 −63 src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs
  17. +31 −37 src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs
  18. +2 −4 src/Compilers/CSharp/Portable/Symbols/TypeMap.cs
  19. +178 −0 src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs
  20. +294 −2 src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs
  21. +2 −0 src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj
  22. +477 −0 src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTasklikeMoreTests.cs
  23. +555 −0 src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTasklikeTests.cs
  24. +256 −19 src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs
  25. +1 −7 src/Compilers/CSharp/Test/Semantic/Semantics/RefLocalsAndReturns.cs
  26. +1 −0 src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
  27. +6 −0 src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs
  28. +3 −0 src/Compilers/Core/Portable/WellKnownMember.cs
  29. +24 −0 src/Compilers/Core/Portable/WellKnownMembers.cs
  30. +3 −3 src/EditorFeatures/Test2/FindReferences/FindReferencesTests.AsyncSymbols.vb
@@ -493,10 +493,9 @@ private BoundStatement BindLocalFunctionStatementParts(LocalFunctionStatementSyn
return new BoundLocalFunctionStatement(node, localSymbol, block, hasErrors);
}

private static bool ImplicitReturnIsOkay(MethodSymbol method)
private bool ImplicitReturnIsOkay(MethodSymbol method)
{
return method.ReturnsVoid || method.IsIterator ||
(method.IsAsync && method.DeclaringCompilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task) == method.ReturnType);
return method.ReturnsVoid || method.IsIterator || method.IsTaskReturningAsync(this.Compilation);
}

public BoundStatement BindExpressionStatement(ExpressionStatementSyntax node, DiagnosticBag diagnostics)
@@ -1208,7 +1208,7 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
// implicit conversion from EX to PX, and for at least one argument, the conversion from
// EX to PX is better than the conversion from EX to QX.

bool allSame = true; // Are all parameter types equivalent by identify conversions?
bool allSame = true; // Are all parameter types equivalent by identify conversions, ignoring Task-like differences?
int i;
for (i = 0; i < arguments.Count; ++i)
{
@@ -1251,9 +1251,12 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
out okToDowngradeToNeither);
}

var type1Normalized = type1.NormalizeTaskTypes(Compilation);
var type2Normalized = type2.NormalizeTaskTypes(Compilation);

if (r == BetterResult.Neither)
{
if (allSame && Conversions.ClassifyImplicitConversionFromType(type1, type2, ref useSiteDiagnostics).Kind != ConversionKind.Identity)
if (allSame && Conversions.ClassifyImplicitConversionFromType(type1Normalized, type2Normalized, ref useSiteDiagnostics).Kind != ConversionKind.Identity)
{
allSame = false;
}
@@ -1262,16 +1265,12 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
continue;
}

if (!considerRefKinds || Conversions.ClassifyImplicitConversionFromType(type1, type2, ref useSiteDiagnostics).Kind != ConversionKind.Identity)
if (Conversions.ClassifyImplicitConversionFromType(type1Normalized, type2Normalized, ref useSiteDiagnostics).Kind != ConversionKind.Identity)
{
// If considerRefKinds is false, conversion between parameter types isn't classified by the if condition.
// This assert is here to verify the assumption that the conversion is never an identity in that case and
// we can skip classification as an optimization.
Debug.Assert(considerRefKinds || Conversions.ClassifyImplicitConversionFromType(type1, type2, ref useSiteDiagnostics).Kind != ConversionKind.Identity);
allSame = false;
}

// One of them was better. Does that contradict a previous result or add a new fact?
// One of them was better, even if identical up to Task-likeness. Does that contradict a previous result or add a new fact?
if (result == BetterResult.Neither)
{
if (!(ignoreDowngradableToNeither && okToDowngradeToNeither))
@@ -1335,7 +1334,7 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
}

// In case the parameter type sequences {P1, P2, …, PN} and {Q1, Q2, …, QN} are
// equivalent (i.e. each Pi has an identity conversion to the corresponding Qi), the
// equivalent ignoring Task-like differences (i.e. each Pi has an identity conversion to the corresponding Qi), the
// following tie-breaking rules are applied, in order, to determine the better function
// member.

@@ -1369,7 +1368,10 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
var type1 = GetParameterType(i, m1.Result, m1.LeastOverriddenMember.GetParameters(), out refKind1);
var type2 = GetParameterType(i, m2.Result, m2.LeastOverriddenMember.GetParameters(), out refKind2);

if (Conversions.ClassifyImplicitConversionFromType(type1, type2, ref useSiteDiagnostics).Kind != ConversionKind.Identity)
var type1Normalized = type1.NormalizeTaskTypes(Compilation);
var type2Normalized = type2.NormalizeTaskTypes(Compilation);

if (Conversions.ClassifyImplicitConversionFromType(type1Normalized, type2Normalized, ref useSiteDiagnostics).Kind != ConversionKind.Identity)
{
allSame = false;
break;
@@ -1701,9 +1703,9 @@ private static BetterResult MoreSpecificType(TypeSymbol t1, TypeSymbol t2, ref H
}

// We should not have gotten here unless there were identity conversions between the
// two types.

Debug.Assert(n1.OriginalDefinition == n2.OriginalDefinition);
// two types, or they are different Task-likes. Ideally we'd assert that the two types (or
// Task equivalents) have the same OriginalDefinition but we don't have a Compilation
// here for NormalizeTaskTypes.

var allTypeArgs1 = ArrayBuilder<TypeSymbol>.GetInstance();
var allTypeArgs2 = ArrayBuilder<TypeSymbol>.GetInstance();
@@ -1809,19 +1811,16 @@ private BetterResult BetterConversionFromExpression(BoundExpression node, TypeSy

if (t1MatchesExactly)
{
if (t2MatchesExactly)
if (!t2MatchesExactly)
{
// both exactly match expression
return BetterResult.Neither;
// - E exactly matches T1
okToDowngradeToNeither = lambdaOpt != null && CanDowngradeConversionFromLambdaToNeither(BetterResult.Left, lambdaOpt, t1, t2, ref useSiteDiagnostics, false);
return BetterResult.Left;
}

// - E exactly matches T1
okToDowngradeToNeither = lambdaOpt != null && CanDowngradeConversionFromLambdaToNeither(BetterResult.Left, lambdaOpt, t1, t2, ref useSiteDiagnostics, false);
return BetterResult.Left;
}
else if (t2MatchesExactly)
{
// - E exactly matches T1
// - E exactly matches T2
okToDowngradeToNeither = lambdaOpt != null && CanDowngradeConversionFromLambdaToNeither(BetterResult.Right, lambdaOpt, t1, t2, ref useSiteDiagnostics, false);
return BetterResult.Right;
}
@@ -1873,7 +1872,7 @@ private bool ExpressionMatchExactly(BoundExpression node, TypeSymbol t, ref Hash
if (lambda.Symbol.IsAsync)
{
// Dig through Task<...> for an async lambda.
if (y.OriginalDefinition == Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task_T))
if (y.OriginalDefinition.IsGenericTaskType(Compilation))
{
y = ((NamedTypeSymbol)y).TypeArgumentsNoUseSiteDiagnostics[0];
}
@@ -2095,28 +2094,26 @@ public override BoundNode VisitReturnStatement(BoundReturnStatement node)
return BetterResult.Right;
}

var task_T = Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task_T);
bool type1IsGenericTask = type1.OriginalDefinition.IsGenericTaskType(Compilation);
bool type2IsGenericTask = type2.OriginalDefinition.IsGenericTaskType(Compilation);

if ((object)task_T != null)
if (type1IsGenericTask)
{
if (type1.OriginalDefinition == task_T)
if (type2IsGenericTask)
{
if (type2.OriginalDefinition == task_T)
{
// - T1 is Task<S1>, T2 is Task<S2>, and S1 is a better conversion target than S2
return BetterConversionTargetCore(((NamedTypeSymbol)type1).TypeArgumentsNoUseSiteDiagnostics[0],
((NamedTypeSymbol)type2).TypeArgumentsNoUseSiteDiagnostics[0],
ref useSiteDiagnostics, betterConversionTargetRecursionLimit);
}

// A shortcut, Task<T> type cannot satisfy other rules.
return BetterResult.Neither;
}
else if (type2.OriginalDefinition == task_T)
{
// A shortcut, Task<T> type cannot satisfy other rules.
return BetterResult.Neither;
// - T1 is Task<S1>, T2 is Task<S2>, and S1 is a better conversion target than S2
return BetterConversionTargetCore(((NamedTypeSymbol)type1).TypeArgumentsNoUseSiteDiagnostics[0],
((NamedTypeSymbol)type2).TypeArgumentsNoUseSiteDiagnostics[0],
ref useSiteDiagnostics, betterConversionTargetRecursionLimit);
}

// A shortcut, Task<T> type cannot satisfy other rules.
return BetterResult.Neither;
}
else if (type2IsGenericTask)
{
// A shortcut, Task<T> type cannot satisfy other rules.
return BetterResult.Neither;
}

NamedTypeSymbol d1;
@@ -1072,31 +1072,12 @@ private bool HadAmbiguousWorseMethods(DiagnosticBag diagnostics, ImmutableArray<
else
{
// error CS0121: The call is ambiguous between the following methods or properties: 'P.W(A)' and 'P.W(B)'
var first = worseResult1.LeastOverriddenMember.OriginalDefinition;
var second = worseResult2.LeastOverriddenMember.OriginalDefinition;

if (first.ContainingNamespace != second.ContainingNamespace)
{
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_AmbigCall,
new object[]
{
new FormattedSymbol(first, SymbolDisplayFormat.CSharpErrorMessageFormat),
new FormattedSymbol(second, SymbolDisplayFormat.CSharpErrorMessageFormat)
},
symbols), location);
}
else
{
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_AmbigCall,
new object[]
{
first,
second
},
symbols), location);
}
diagnostics.Add(
CreateAmbiguousCallDiagnosticInfo(
worseResult1.LeastOverriddenMember.OriginalDefinition,
worseResult2.LeastOverriddenMember.OriginalDefinition,
symbols),
location);
}

return true;
@@ -1144,31 +1125,12 @@ private bool HadAmbiguousBestMethods(DiagnosticBag diagnostics, ImmutableArray<S

// error CS0121: The call is ambiguous between the following methods or properties:
// 'P.Ambiguous(object, string)' and 'P.Ambiguous(string, object)'
var first = validResult1.LeastOverriddenMember.OriginalDefinition;
var second = validResult2.LeastOverriddenMember.OriginalDefinition;

if (first.ContainingNamespace != second.ContainingNamespace)
{
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_AmbigCall,
new object[]
{
new FormattedSymbol(first, SymbolDisplayFormat.CSharpErrorMessageFormat),
new FormattedSymbol(second, SymbolDisplayFormat.CSharpErrorMessageFormat)
},
symbols), location);
}
else
{
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_AmbigCall,
new object[]
{
first,
second
},
symbols), location);
}
diagnostics.Add(
CreateAmbiguousCallDiagnosticInfo(
validResult1.LeastOverriddenMember.OriginalDefinition,
validResult2.LeastOverriddenMember.OriginalDefinition,
symbols),
location);

return true;
}
@@ -1202,6 +1164,22 @@ private int TryGetFirstTwoValidResults(out MemberResolutionResult<TMember> first
return count;
}

private static DiagnosticInfoWithSymbols CreateAmbiguousCallDiagnosticInfo(Symbol first, Symbol second, ImmutableArray<Symbol> symbols)
{
var arguments = (first.ContainingNamespace != second.ContainingNamespace) ?
new object[]
{
new FormattedSymbol(first, SymbolDisplayFormat.CSharpErrorMessageFormat),
new FormattedSymbol(second, SymbolDisplayFormat.CSharpErrorMessageFormat)
} :
new object[]
{
first,
second
};
return new DiagnosticInfoWithSymbols(ErrorCode.ERR_AmbigCall, arguments, symbols);
}

[Conditional("DEBUG")]
private void AssertNone(MemberResolutionKind kind)
{

0 comments on commit 6c9e186

Please sign in to comment.
You can’t perform that action at this time.