Skip to content

Commit

Permalink
Support async methods returning Task-like types
Browse files Browse the repository at this point in the history
[Based on features/async-return with tests ported from that branch.]
  • Loading branch information
cston committed Jul 19, 2016
1 parent b6ed189 commit 6c9e186
Show file tree
Hide file tree
Showing 30 changed files with 2,336 additions and 435 deletions.
5 changes: 2 additions & 3 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1208,7 +1208,7 @@ private BetterResult BetterFunctionMember<TMember>(
// 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)
{
Expand Down Expand Up @@ -1251,9 +1251,12 @@ private BetterResult BetterFunctionMember<TMember>(
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;
}
Expand All @@ -1262,16 +1265,12 @@ private BetterResult BetterFunctionMember<TMember>(
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))
Expand Down Expand Up @@ -1335,7 +1334,7 @@ private BetterResult BetterFunctionMember<TMember>(
}

// 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.

Expand Down Expand Up @@ -1369,7 +1368,10 @@ private BetterResult BetterFunctionMember<TMember>(
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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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];
}
Expand Down Expand Up @@ -2095,28 +2094,26 @@ private BetterResult BetterConversionTargetCore(
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
{
Expand Down
Loading

0 comments on commit 6c9e186

Please sign in to comment.