|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CSharp.RuntimeBinder.Syntax;
namespace Microsoft.CSharp.RuntimeBinder.Semantics
{
internal readonly partial struct ExpressionBinder
{
////////////////////////////////////////////////////////////////////////////////
// This table is used to implement the last set of 'better' conversion rules
// when there are no implicit conversions between T1(down) and T2 (across)
// Use all the simple types plus 1 more for Object
// See CLR section 7.4.1.3
private static readonly byte[][] s_betterConversionTable =
{
// BYTE SHORT INT LONG FLOAT DOUBLE DECIMAL CHAR BOOL SBYTE USHORT UINT ULONG IPTR UIPTR OBJECT
new byte[] /* BYTE*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3},
new byte[] /* SHORT*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 3, 3, 3},
new byte[] /* INT*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 3, 3, 3},
new byte[] /* LONG*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3},
new byte[] /* FLOAT*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
new byte[] /* DOUBLE*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
new byte[] /* DECIMAL*/{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
new byte[] /* CHAR*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
new byte[] /* BOOL*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
new byte[] /* SBYTE*/ {1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 3, 3, 3},
new byte[] /* USHORT*/ {3, 2, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3},
new byte[] /* UINT*/ {3, 2, 2, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3},
new byte[] /* ULONG*/ {3, 2, 2, 2, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3},
new byte[] /* IPTR*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
new byte[] /* UIPTR*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
new byte[] /* OBJECT*/ {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}
};
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
private static BetterType WhichMethodIsBetterTieBreaker(
CandidateFunctionMember node1,
CandidateFunctionMember node2,
CType pTypeThrough,
ArgInfos args)
{
MethPropWithInst mpwi1 = node1.mpwi;
MethPropWithInst mpwi2 = node2.mpwi;
// Same signatures. If they have different lifting numbers, the smaller number wins.
// Otherwise, if one is generic and the other isn't then the non-generic wins.
// Otherwise, if one is expanded and the other isn't then the non-expanded wins.
// Otherwise, if one has fewer modopts than the other then it wins.
if (node1.ctypeLift != node2.ctypeLift)
{
return node1.ctypeLift < node2.ctypeLift ? BetterType.Left : BetterType.Right;
}
// Non-generic wins.
if (mpwi1.TypeArgs.Count != 0)
{
if (mpwi2.TypeArgs.Count == 0)
{
return BetterType.Right;
}
}
else if (mpwi2.TypeArgs.Count != 0)
{
return BetterType.Left;
}
// Non-expanded wins
if (node1.fExpanded)
{
if (!node2.fExpanded)
{
return BetterType.Right;
}
}
else if (node2.fExpanded)
{
return BetterType.Left;
}
// See if one's parameter types (un-instantiated) are more specific.
BetterType nT = CompareTypes(
RearrangeNamedArguments(mpwi1.MethProp().Params, mpwi1, pTypeThrough, args),
RearrangeNamedArguments(mpwi2.MethProp().Params, mpwi2, pTypeThrough, args));
if (nT == BetterType.Left || nT == BetterType.Right)
{
return nT;
}
// Fewer modopts wins.
if (mpwi1.MethProp().modOptCount != mpwi2.MethProp().modOptCount)
{
return mpwi1.MethProp().modOptCount < mpwi2.MethProp().modOptCount ? BetterType.Left : BetterType.Right;
}
// Bona-fide tie.
return BetterType.Neither;
}
private static BetterType CompareTypes(TypeArray ta1, TypeArray ta2)
{
if (ta1 == ta2)
{
return BetterType.Same;
}
if (ta1.Count != ta2.Count)
{
// The one with more parameters is more specific.
return ta1.Count > ta2.Count ? BetterType.Left : BetterType.Right;
}
BetterType nTot = BetterType.Neither;
for (int i = 0; i < ta1.Count; i++)
{
CType type1 = ta1[i];
CType type2 = ta2[i];
BetterType nParam = BetterType.Neither;
LAgain:
if (type1.TypeKind != type2.TypeKind)
{
if (type1 is TypeParameterType)
{
nParam = BetterType.Right;
}
else if (type2 is TypeParameterType)
{
nParam = BetterType.Left;
}
}
else
{
switch (type1.TypeKind)
{
default:
Debug.Fail("Bad kind in CompareTypes");
break;
case TypeKind.TK_TypeParameterType:
break;
case TypeKind.TK_PointerType:
case TypeKind.TK_ParameterModifierType:
case TypeKind.TK_ArrayType:
case TypeKind.TK_NullableType:
type1 = type1.BaseOrParameterOrElementType;
type2 = type2.BaseOrParameterOrElementType;
goto LAgain;
case TypeKind.TK_AggregateType:
nParam = CompareTypes(((AggregateType)type1).TypeArgsAll, ((AggregateType)type2).TypeArgsAll);
break;
}
}
if (nParam == BetterType.Right || nParam == BetterType.Left)
{
if (nTot == BetterType.Same || nTot == BetterType.Neither)
{
nTot = nParam;
}
else if (nParam != nTot)
{
return BetterType.Neither;
}
}
}
return nTot;
}
////////////////////////////////////////////////////////////////////////////////
// Find the index of a name on a list.
// There is no failure case; we require that the name actually
// be on the list
private static int FindName(List<Name> names, Name name)
{
int index = names.IndexOf(name);
Debug.Assert(index != -1);
return index;
}
////////////////////////////////////////////////////////////////////////////////
// We need to rearrange the method parameters so that the type of any specified named argument
// appears in the same place as the named argument. Consider the example below:
// Foo(int x = 4, string y = "", long l = 4)
// Foo(string y = "", string x="", long l = 5)
// and the call site:
// Foo(y:"a")
// After rearranging the parameter types we will have:
// (string, int, long) and (string, string, long)
// By rearranging the arguments as such we make sure that any specified named arguments appear in the same position for both
// methods and we also maintain the relative order of the other parameters (the type long appears after int in the above example)
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
private static TypeArray RearrangeNamedArguments(TypeArray pta, MethPropWithInst mpwi, CType pTypeThrough, ArgInfos args)
{
#if DEBUG
// We never have a named argument that is in a position in the argument
// list past the end of what would be the formal parameter list.
for (int i = pta.Count; i < args.carg; i++)
{
Debug.Assert(!(args.prgexpr[i] is ExprNamedArgumentSpecification));
}
#endif
// If we've no args we can skip. If the last argument isn't named then either we
// have no named arguments, and we can skip, or we have non-trailing named arguments
// and we MUST skip!
if (args.carg == 0 || !(args.prgexpr[args.carg - 1] is ExprNamedArgumentSpecification))
{
return pta;
}
CType type = pTypeThrough ?? mpwi.GetType();
CType[] typeList = new CType[pta.Count];
MethodOrPropertySymbol methProp = GroupToArgsBinder.FindMostDerivedMethod(mpwi.MethProp(), type);
// We initialize the new type array with the parameters for the method.
for (int iParam = 0; iParam < pta.Count; iParam++)
{
typeList[iParam] = pta[iParam];
}
var prgexpr = args.prgexpr;
// We then go over the specified arguments and put the type for any named argument in the right position in the array.
for (int iParam = 0; iParam < args.carg; iParam++)
{
if (prgexpr[iParam] is ExprNamedArgumentSpecification named)
{
// We find the index of the type of the argument in the method parameter list and store that in a temp
int index = FindName(methProp.ParameterNames, named.Name);
CType tempType = pta[index];
// Starting from the current position in the type list up until the location of the type of the optional argument
// We shift types by one:
// before: (int, string, long)
// after: (string, int, long)
// We only touch the types between the current position and the position of the type we need to move
for (int iShift = iParam; iShift < index; iShift++)
{
typeList[iShift + 1] = typeList[iShift];
}
typeList[iParam] = tempType;
}
}
return TypeArray.Allocate(typeList);
}
////////////////////////////////////////////////////////////////////////////////
// Determine which method is better for the purposes of overload resolution.
// Better means: as least as good in all params, and better in at least one param.
// Better w/r to a param means is an ordering, from best down:
// 1) same type as argument
// 2) implicit conversion from argument to formal type
// Because of user defined conversion opers this relation is not transitive.
//
// If there is a tie because of identical signatures, the tie may be broken by the
// following rules:
// 1) If one is generic and the other isn't, the non-generic wins.
// 2) Otherwise if one is expanded (params) and the other isn't, the non-expanded wins.
// 3) Otherwise if one has more specific parameter types (at the declaration) it wins:
// This occurs if at least on parameter type is more specific and no parameter type is
// less specific.
//* A type parameter is less specific than a non-type parameter.
//* A constructed type is more specific than another constructed type if at least
// one type argument is more specific and no type argument is less specific than
// the corresponding type args in the other.
// 4) Otherwise if one has more modopts than the other does, the smaller number of modopts wins.
//
// Returns Left if m1 is better, Right if m2 is better, or Neither/Same
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
private BetterType WhichMethodIsBetter(
CandidateFunctionMember node1,
CandidateFunctionMember node2,
CType pTypeThrough,
ArgInfos args)
{
MethPropWithInst mpwi1 = node1.mpwi;
MethPropWithInst mpwi2 = node2.mpwi;
// Substitutions should have already been done on these!
TypeArray pta1 = RearrangeNamedArguments(node1.@params, mpwi1, pTypeThrough, args);
TypeArray pta2 = RearrangeNamedArguments(node2.@params, mpwi2, pTypeThrough, args);
// If the parameter types for both candidate methods are identical,
// use the tie breaking rules.
if (pta1 == pta2)
{
return WhichMethodIsBetterTieBreaker(node1, node2, pTypeThrough, args);
}
// Otherwise, do a parameter-by-parameter comparison:
//
// Given an argument list A with a set of argument expressions {E1, ... En} and
// two applicable function members Mp and Mq with parameter types {P1,... Pn} and
// {Q1, ... Qn}, Mp is defined to be a better function member than Mq if:
//* for each argument the implicit conversion from Ex to Qx is not better than
// the implicit conversion from Ex to Px.
//* for at least one argument, the conversion from Ex to Px is better than the
// conversion from Ex to Qx.
BetterType betterMethod = BetterType.Neither;
int carg = args.carg;
for (int i = 0; i < carg; i++)
{
Expr arg = args.prgexpr[i];
CType p1 = pta1[i];
CType p2 = pta2[i];
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// RUNTIME BINDER ONLY CHANGE
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// We need to consider conversions from the actual runtime type
// since we could have private interfaces that we are converting
CType argType = arg?.RuntimeObjectActualType ?? args.types[i];
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// END RUNTIME BINDER ONLY CHANGE
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
BetterType betterConversion = WhichConversionIsBetter(argType, p1, p2);
if (betterMethod == BetterType.Right)
{
if (betterConversion == BetterType.Left)
{
betterMethod = BetterType.Neither;
break;
}
}
else if (betterMethod == BetterType.Left)
{
if (betterConversion == BetterType.Right)
{
betterMethod = BetterType.Neither;
break;
}
}
else
{
Debug.Assert(betterMethod == BetterType.Neither);
if (betterConversion == BetterType.Right || betterConversion == BetterType.Left)
{
betterMethod = betterConversion;
}
}
}
// We may have different sizes if we had optional parameters. If thats the case,
// the one with fewer parameters wins (ie less optional parameters) unless it is
// expanded. If so, the one with more parameters wins (ie option beats expanded).
if (pta1.Count != pta2.Count && betterMethod == BetterType.Neither)
{
if (node1.fExpanded)
{
if (!node2.fExpanded)
{
return BetterType.Right;
}
}
else if (node2.fExpanded)
{
return BetterType.Left;
}
// Here, if both methods needed to use optionals to fill in the signatures,
// then we are ambiguous. Otherwise, take the one that didn't need any
// optionals.
if (pta1.Count == carg)
{
return BetterType.Left;
}
if (pta2.Count == carg)
{
return BetterType.Right;
}
return BetterType.Neither;
}
return betterMethod;
}
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
private BetterType WhichConversionIsBetter(CType argType, CType p1, CType p2)
{
Debug.Assert(argType != null);
Debug.Assert(p1 != null);
Debug.Assert(p2 != null);
// 7.4.2.3 Better Conversion From Expression
//
// Given an implicit conversion C1 that converts from an expression E to a type T1
// and an implicit conversion C2 that converts from an expression E to a type T2, the
// better conversion of the two conversions is determined as follows:
//* if T1 and T2 are the same type, neither conversion is better.
//* If E has a type S and the conversion from S to T1 is better than the conversion from
// S to T2 then C1 is the better conversion.
//* If E has a type S and the conversion from S to T2 is better than the conversion from
// S to T1 then C2 is the better conversion.
//* If E is a lambda expression or anonymous method for which an inferred return type X
// exists and T1 is a delegate type and T2 is a delegate type and T1 and T2 have identical
// parameter lists:
// * If T1 is a delegate of return type Y1 and T2 is a delegate of return type Y2 and the
// conversion from X to Y1 is better than the conversion from X to Y2, then C1 is the
// better return type.
// * If T1 is a delegate of return type Y1 and T2 is a delegate of return type Y2 and the
// conversion from X to Y2 is better than the conversion from X to Y1, then C2 is the
// better return type.
if (p1 == p2)
{
return BetterType.Same;
}
// 7.4.2.4 Better conversion from type
//
// Given a conversion C1 that converts from a type S to a type T1 and a conversion C2
// that converts from a type S to a type T2, the better conversion of the two conversions
// is determined as follows:
//* If T1 and T2 are the same type, neither conversion is better
//* If S is T1, C1 is the better conversion.
//* If S is T2, C2 is the better conversion.
//* If an implicit conversion from T1 to T2 exists and no implicit conversion from T2 to
// T1 exists, C1 is the better conversion.
//* If an implicit conversion from T2 to T1 exists and no implicit conversion from T1 to
// T2 exists, C2 is the better conversion.
//
// [Otherwise, see table above for better integral type conversions.]
if (argType == p1)
{
return BetterType.Left;
}
if (argType == p2)
{
return BetterType.Right;
}
bool a2b = canConvert(p1, p2);
bool b2a = canConvert(p2, p1);
if (a2b != b2a)
{
return a2b ? BetterType.Left : BetterType.Right;
}
if (p1.IsPredefined && p2.IsPredefined)
{
PredefinedType pt1 = p1.PredefinedType;
if (pt1 <= PredefinedType.PT_OBJECT)
{
PredefinedType pt2 = p2.PredefinedType;
if (pt2 <= PredefinedType.PT_OBJECT)
{
return (BetterType)s_betterConversionTable[(int)pt1][(int)pt2];
}
}
}
return BetterType.Neither;
}
////////////////////////////////////////////////////////////////////////////////
// Determine best method for overload resolution. Returns null if no best
// method, in which case two tying methods are returned for error reporting.
[RequiresUnreferencedCode(Binder.TrimmerWarning)]
[RequiresDynamicCode(Binder.DynamicCodeWarning)]
private CandidateFunctionMember FindBestMethod(
List<CandidateFunctionMember> list,
CType pTypeThrough,
ArgInfos args,
out CandidateFunctionMember methAmbig1,
out CandidateFunctionMember methAmbig2)
{
Debug.Assert(list.Count != 0);
Debug.Assert(list[0].mpwi != null);
// select the best method:
/*
Effectively, we pick the best item from a set using a non-transitive ranking function
So, pick the first item (candidate) and compare against next (contender), if there is
no next, goto phase 2
If first is better, move to next contender, if none proceed to phase 2
If second is better, make the contender the candidate and make the item following
contender into the new contender, if there is none, goto phase 2
If neither, make contender+1 into candidate and contender+2 into contender, if possible,
otherwise, if contender was last, return null, otherwise if new candidate is last,
goto phase 2
Phase 2: compare all items before candidate to candidate
If candidate always better, return it, otherwise return null
*/
// Record two method that are ambiguous for error reporting.
CandidateFunctionMember ambig1 = null;
CandidateFunctionMember ambig2 = null;
bool ambiguous = false;
CandidateFunctionMember candidate = list[0];
for (int i = 1; i < list.Count; i++)
{
CandidateFunctionMember contender = list[i];
Debug.Assert(candidate != contender);
switch (WhichMethodIsBetter(candidate, contender, pTypeThrough, args))
{
case BetterType.Left:
ambiguous = false; // (meaning m1 is better...)
break;
case BetterType.Right:
ambiguous = false;
candidate = contender;
break;
default:
// in case of tie we don't want to bother with the contender who tied...
ambig1 = candidate;
ambig2 = contender;
i++;
if (i < list.Count)
{
contender = list[i];
candidate = contender;
}
else
{
ambiguous = true;
}
break;
}
}
if (!ambiguous)
{
// Now, compare the candidate with items previous to it...
foreach (CandidateFunctionMember contender in list)
{
if (contender == candidate)
{
// We hit our winner, so its good enough...
methAmbig1 = null;
methAmbig2 = null;
return candidate;
}
switch (WhichMethodIsBetter(contender, candidate, pTypeThrough, args))
{
case BetterType.Right:
// meaning m2 is better
continue;
case BetterType.Same:
case BetterType.Neither:
ambig1 = candidate;
ambig2 = contender;
break;
}
break;
}
}
// an ambiguous call. Return two of the ambiguous set.
if (ambig1 != null & ambig2 != null)
{
methAmbig1 = ambig1;
methAmbig2 = ambig2;
}
else
{
// For some reason, we have an ambiguity but never had a tie.
// This can easily happen in a circular graph of candidate methods.
methAmbig1 = list[0];
methAmbig2 = list[1];
}
return null;
}
}
}
|