Skip to content

Commit

Permalink
Improve method type argument inference around tuples and address back…
Browse files Browse the repository at this point in the history
…wards compatibility break. (#22295)

Fixes #20583.
  • Loading branch information
AlekseyTs authored Sep 26, 2017
1 parent e2f2c3c commit 133364a
Show file tree
Hide file tree
Showing 6 changed files with 578 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -587,63 +587,34 @@ private void MakeExplicitParameterTypeInferences(Binder binder, BoundExpression
{
ExplicitParameterTypeInference(argument, target, ref useSiteDiagnostics);
}
else if (argument.Kind == BoundKind.TupleLiteral)
else if (argument.Kind != BoundKind.TupleLiteral ||
!MakeExplicitParameterTypeInferences(binder, (BoundTupleLiteral)argument, target, isExactInference, ref useSiteDiagnostics))
{
MakeExplicitParameterTypeInferences(binder, (BoundTupleLiteral)argument, target, isExactInference, ref useSiteDiagnostics);
}
else if (IsReallyAType(source))
{
if (isExactInference)
{
ExactInference(source, target, ref useSiteDiagnostics);
}
else
{
LowerBoundInference(source, target, ref useSiteDiagnostics);
}
}
}

private void MakeExplicitParameterTypeInferences(Binder binder, BoundTupleLiteral argument, TypeSymbol target, bool isExactInference, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
// if tuple has a type we will try to match the type as a single input
// Example:
// if "(a: 1, b: 2)" is passed as T arg
// then T becomes (int a, int b)
var source = argument.Type;
if (IsReallyAType(source))
{
if (isExactInference)
// Either the argument is not a tuple literal, or we were unable to do the inference from its elements, let' try to infer from arguments's type
if (IsReallyAType(source))
{
// SPEC: * If V is one of the unfixed Xi then U is added to the set of
// SPEC: exact bounds for Xi.
if (ExactTypeParameterInference(source, target))
if (isExactInference)
{
return;
ExactInference(source, target, ref useSiteDiagnostics);
}
}
else
{
// SPEC: A lower-bound inference from a type U to a type V is made as follows:

// SPEC: * If V is one of the unfixed Xi then U is added to the set of
// SPEC: lower bounds for Xi.

if (LowerBoundTypeParameterInference(source, target))
else
{
return;
LowerBoundInference(source, target, ref useSiteDiagnostics);
}
}
}
}

private bool MakeExplicitParameterTypeInferences(Binder binder, BoundTupleLiteral argument, TypeSymbol target, bool isExactInference, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
// try match up element-wise to the destination tuple (or underlying type)
// Example:
// if "(a: 1, b: "qq")" is passed as (T, U) arg
// then T becomes int and U becomes string
if (target.Kind != SymbolKind.NamedType)
{
// tuples can only match to tuples or tuple underlying types.
return;
return false;
}

var destination = (NamedTypeSymbol)target;
Expand All @@ -653,7 +624,7 @@ private void MakeExplicitParameterTypeInferences(Binder binder, BoundTupleLitera
if (!destination.IsTupleOrCompatibleWithTupleOfCardinality(sourceArguments.Length))
{
// target is not a tuple of appropriate shape
return;
return false;
}

var destTypes = destination.GetElementTypesOfTupleOrCompatible();
Expand All @@ -669,6 +640,8 @@ private void MakeExplicitParameterTypeInferences(Binder binder, BoundTupleLitera
var destType = destTypes[i];
MakeExplicitParameterTypeInferences(binder, sourceArgument, destType, isExactInference, ref useSiteDiagnostics);
}

return true;
}

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -1604,13 +1577,13 @@ private bool ExactConstructedInference(TypeSymbol source, TypeSymbol target, ref
// SPEC: type C<U1...Uk> then an exact inference
// SPEC: is made from each Ui to the corresponding Vi.

var namedSource = source as NamedTypeSymbol;
var namedSource = source.TupleUnderlyingTypeOrSelf() as NamedTypeSymbol;
if ((object)namedSource == null)
{
return false;
}

var namedTarget = target as NamedTypeSymbol;
var namedTarget = target.TupleUnderlyingTypeOrSelf() as NamedTypeSymbol;
if ((object)namedTarget == null)
{
return false;
Expand Down Expand Up @@ -1868,6 +1841,9 @@ private bool LowerBoundConstructedInference(TypeSymbol source, TypeSymbol target
Debug.Assert((object)source != null);
Debug.Assert((object)target != null);

source = source.TupleUnderlyingTypeOrSelf();
target = target.TupleUnderlyingTypeOrSelf();

var constructedTarget = target as NamedTypeSymbol;
if ((object)constructedTarget == null)
{
Expand Down Expand Up @@ -2185,6 +2161,8 @@ private bool UpperBoundConstructedInference(TypeSymbol source, TypeSymbol target
{
Debug.Assert((object)source != null);
Debug.Assert((object)target != null);
source = source.TupleUnderlyingTypeOrSelf();
target = target.TupleUnderlyingTypeOrSelf();

var constructedSource = source as NamedTypeSymbol;
if ((object)constructedSource == null)
Expand Down
161 changes: 157 additions & 4 deletions src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9376,6 +9376,7 @@ class C
static void Main()
{
Test1(Nullable((a: 1, b: (a: 1, b: 2))), (a: 1, b: (object)1));
Test1((a: 1, b: (a: 1, b: 2)), Nullable((a: 1, b: (object)1)));
}

static T? Nullable<T>(T x) where T : struct
Expand All @@ -9395,7 +9396,10 @@ static void Test1<T, U>((T, U) x, (T, U) y)
comp.VerifyDiagnostics(
// (6,15): error CS1503: Argument 1: cannot convert from '(int a, (int a, int b) b)?' to '(int, object)'
// Test1(Nullable((a: 1, b: (a: 1, b: 2))), (a: 1, b: (object)1));
Diagnostic(ErrorCode.ERR_BadArgType, "Nullable((a: 1, b: (a: 1, b: 2)))").WithArguments("1", "(int a, (int a, int b) b)?", "(int, object)").WithLocation(6, 15)
Diagnostic(ErrorCode.ERR_BadArgType, "Nullable((a: 1, b: (a: 1, b: 2)))").WithArguments("1", "(int a, (int a, int b) b)?", "(int, object)").WithLocation(6, 15),
// (7,40): error CS1503: Argument 2: cannot convert from '(int a, object b)?' to '(int, (int a, int b))'
// Test1((a: 1, b: (a: 1, b: 2)), Nullable((a: 1, b: (object)1)));
Diagnostic(ErrorCode.ERR_BadArgType, "Nullable((a: 1, b: (object)1))").WithArguments("2", "(int a, object b)?", "(int, (int a, int b))").WithLocation(7, 40)
);
}

Expand Down Expand Up @@ -23886,10 +23890,10 @@ public static void Main()
var comp = CompileAndVerify(source, additionalRefs: s_valueTupleRefs, expectedOutput: "4");
}

[Fact(Skip = "https://github.com/dotnet/roslyn/issues/20583")]
[Fact]
[WorkItem(20494, "https://github.com/dotnet/roslyn/issues/20494")]
[WorkItem(20583, "https://github.com/dotnet/roslyn/issues/20583")]
public void MoreGenericTieBreaker_02()
public void MoreGenericTieBreaker_02a1()
{
var source =
@"using System;
Expand All @@ -23912,7 +23916,156 @@ public static void Main()
expectedOutput: @"12");
}

[Fact(Skip = "https://github.com/dotnet/roslyn/issues/20583")]
[Fact]
[WorkItem(20494, "https://github.com/dotnet/roslyn/issues/20494")]
[WorkItem(20583, "https://github.com/dotnet/roslyn/issues/20583")]
public void MoreGenericTieBreaker_02a2()
{
var source =
@"using System;
public class C
{
public static void Main()
{
// var b = (1, 2, 3, 4, 5, 6, 7, 8);
var b = new ValueTuple<int, int, int, int, int, int, int, ValueTuple<int>>(1, 2, 3, 4, 5, 6, 7, new ValueTuple<int>(8));
M1(ref b);
M2(ref b); // ok, should select M2<T1, T2, T3, T4, T5, T6, T7, T8>(ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>> a)
}
public static void M1<T1, T2, T3, T4, T5, T6, T7, TRest>(ref ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> a) where TRest : struct { Console.Write(1); }
public static void M2<T1, T2, T3, T4, T5, T6, T7, T8>(ref ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>> a) { Console.Write(2); }
public static void M2<T1, T2, T3, T4, T5, T6, T7, TRest>(ref ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> a) where TRest : struct { Console.Write(3); }
}
";
var comp = CompileAndVerify(source,
additionalRefs: s_valueTupleRefs,
expectedOutput: @"12");
}

[Fact]
[WorkItem(20494, "https://github.com/dotnet/roslyn/issues/20494")]
[WorkItem(20583, "https://github.com/dotnet/roslyn/issues/20583")]
public void MoreGenericTieBreaker_02a3()
{
var source =
@"using System;
public class C
{
public static void Main()
{
I<ValueTuple<int, int, int, int, int, int, int, ValueTuple<int>>> b = null;
M1(b);
M2(b);
}
public static void M1<T1, T2, T3, T4, T5, T6, T7, TRest>(I<ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>> a) where TRest : struct { Console.Write(1); }
public static void M2<T1, T2, T3, T4, T5, T6, T7, T8>(I<ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>>> a) { Console.Write(2); }
public static void M2<T1, T2, T3, T4, T5, T6, T7, TRest>(I<ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>> a) where TRest : struct { Console.Write(3); }
}

public interface I<in T>{}
";
var comp = CompileAndVerify(source,
additionalRefs: s_valueTupleRefs,
expectedOutput: @"12");
}

[Fact]
[WorkItem(20494, "https://github.com/dotnet/roslyn/issues/20494")]
[WorkItem(20583, "https://github.com/dotnet/roslyn/issues/20583")]
public void MoreGenericTieBreaker_02a4()
{
var source =
@"using System;
public class C
{
public static void Main()
{
I<ValueTuple<int, int, int, int, int, int, int, ValueTuple<int>>> b = null;
M1(b);
M2(b);
}
public static void M1<T1, T2, T3, T4, T5, T6, T7, TRest>(I<ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>> a) where TRest : struct { Console.Write(1); }
public static void M2<T1, T2, T3, T4, T5, T6, T7, T8>(I<ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>>> a) { Console.Write(2); }
public static void M2<T1, T2, T3, T4, T5, T6, T7, TRest>(I<ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>> a) where TRest : struct { Console.Write(3); }
}

public interface I<out T>{}
";
var comp = CompileAndVerify(source,
additionalRefs: s_valueTupleRefs,
expectedOutput: @"12");
}

[Fact]
[WorkItem(20494, "https://github.com/dotnet/roslyn/issues/20494")]
[WorkItem(20583, "https://github.com/dotnet/roslyn/issues/20583")]
public void MoreGenericTieBreaker_02a5()
{
var source =
@"using System;
public class C
{
public static void Main()
{
M1((1, 2, 3, 4, 5, 6, 7, 8));
M2((1, 2, 3, 4, 5, 6, 7, 8));
}
public static void M1<T1, T2, T3, T4, T5, T6, T7, TRest>(ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> a) where TRest : struct { Console.Write(1); }
public static void M2<T1, T2, T3, T4, T5, T6, T7, T8>(ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>> a) { Console.Write(2); }
public static void M2<T1, T2, T3, T4, T5, T6, T7, TRest>(ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest> a) where TRest : struct { Console.Write(3); }
}
";
var comp = CompileAndVerify(source,
additionalRefs: s_valueTupleRefs,
expectedOutput: @"12");
}

[Fact]
[WorkItem(20494, "https://github.com/dotnet/roslyn/issues/20494")]
[WorkItem(20583, "https://github.com/dotnet/roslyn/issues/20583")]
public void MoreGenericTieBreaker_02a6()
{
var source =
@"using System;
public class C
{
public static void Main()
{
M2((()=>1, ()=>2, ()=>3, ()=>4, ()=>5, ()=>6, ()=>7, ()=>8));
}
public static void M2<T1, T2, T3, T4, T5, T6, T7, T8>(ValueTuple<Func<T1>, Func<T2>, Func<T3>, Func<T4>, Func<T5>, Func<T6>, Func<T7>, ValueTuple<Func<T8>>> a) { Console.Write(2); }
public static void M2<T1, T2, T3, T4, T5, T6, T7, TRest>(ValueTuple<Func<T1>, Func<T2>, Func<T3>, Func<T4>, Func<T5>, Func<T6>, Func<T7>, TRest> a) where TRest : struct { Console.Write(3); }
}
";
var comp = CompileAndVerify(source,
additionalRefs: s_valueTupleRefs,
expectedOutput: @"2");
}

[Fact]
[WorkItem(20494, "https://github.com/dotnet/roslyn/issues/20494")]
[WorkItem(20583, "https://github.com/dotnet/roslyn/issues/20583")]
public void MoreGenericTieBreaker_02a7()
{
var source =
@"using System;
public class C
{
public static void Main()
{
M1((()=>1, ()=>2, ()=>3, ()=>4, ()=>5, ()=>6, ()=>7, ()=>8));
}
public static void M1<T1, T2, T3, T4, T5, T6, T7, TRest>(ValueTuple<Func<T1>, Func<T2>, Func<T3>, Func<T4>, Func<T5>, Func<T6>, Func<T7>, TRest> a) where TRest : struct { Console.Write(1); }
}
";
CreateStandardCompilation(source, references: s_valueTupleRefs).VerifyDiagnostics(
// (6,9): error CS0411: The type arguments for method 'C.M1<T1, T2, T3, T4, T5, T6, T7, TRest>(ValueTuple<Func<T1>, Func<T2>, Func<T3>, Func<T4>, Func<T5>, Func<T6>, Func<T7>, TRest>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
// M1((()=>1, ()=>2, ()=>3, ()=>4, ()=>5, ()=>6, ()=>7, ()=>8));
Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M1").WithArguments("C.M1<T1, T2, T3, T4, T5, T6, T7, TRest>(System.ValueTuple<System.Func<T1>, System.Func<T2>, System.Func<T3>, System.Func<T4>, System.Func<T5>, System.Func<T6>, System.Func<T7>, TRest>)").WithLocation(6, 9)
);
}

[Fact]
[WorkItem(20494, "https://github.com/dotnet/roslyn/issues/20494")]
[WorkItem(20583, "https://github.com/dotnet/roslyn/issues/20583")]
public void MoreGenericTieBreaker_02b()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1381,10 +1381,6 @@ HandleAsAGeneralExpression:
Return False
End If

If Not RefersToGenericParameterToInferArgumentFor(parameterType) Then
Return True
End If

' If a generic method is parameterized by T, an argument of type A matching a parameter of type
' P can be used to infer a type for T by these patterns:
'
Expand Down Expand Up @@ -1443,11 +1439,11 @@ HandleAsAGeneralExpression:
ElseIf parameterType.Kind = SymbolKind.NamedType Then
' e.g. handle goo(of T)(x as Bar(Of T)) We need to dig into Bar(Of T)

Dim parameterTypeAsNamedType = DirectCast(parameterType, NamedTypeSymbol)
Dim parameterTypeAsNamedType = DirectCast(parameterType.GetTupleUnderlyingTypeOrSelf(), NamedTypeSymbol)

If parameterTypeAsNamedType.IsGenericType Then

Dim argumentTypeAsNamedType = If(argumentType.Kind = SymbolKind.NamedType, DirectCast(argumentType, NamedTypeSymbol), Nothing)
Dim argumentTypeAsNamedType = If(argumentType.Kind = SymbolKind.NamedType, DirectCast(argumentType.GetTupleUnderlyingTypeOrSelf(), NamedTypeSymbol), Nothing)

If argumentTypeAsNamedType IsNot Nothing AndAlso argumentTypeAsNamedType.IsGenericType Then
If argumentTypeAsNamedType.OriginalDefinition.IsSameTypeIgnoringAll(parameterTypeAsNamedType.OriginalDefinition) Then
Expand Down Expand Up @@ -1633,6 +1629,10 @@ HandleAsAGeneralExpression:
inferenceRestrictions As RequiredConversion
) As Boolean

If Not RefersToGenericParameterToInferArgumentFor(parameterType) Then
Return True
End If

' First try to the things directly. Only if this fails will we bother searching for things like List->IEnumerable.
Dim Inferred As Boolean = InferTypeArgumentsFromArgumentDirectly(
argumentLocation,
Expand Down Expand Up @@ -1779,7 +1779,6 @@ HandleAsAGeneralExpression:
param,
digThroughToBasesAndImplements,
inferenceRestrictions)

End Function

Private Function FindMatchingBase(
Expand Down
Loading

0 comments on commit 133364a

Please sign in to comment.