in src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs [1291:1678]
private BetterResult BetterFunctionMember<TMember>(
MemberResolutionResult<TMember> m1,
MemberResolutionResult<TMember> m2,
ArrayBuilder<BoundExpression> arguments,
bool considerRefKinds,
ref HashSet<DiagnosticInfo> useSiteDiagnostics)
where TMember : Symbol
{
Debug.Assert(m1.Result.IsValid);
Debug.Assert(m2.Result.IsValid);
Debug.Assert(arguments != null);
// SPEC:
// Parameter lists for each of the candidate function members are constructed in the following way:
// The expanded form is used if the function member was applicable only in the expanded form.
// Optional parameters with no corresponding arguments are removed from the parameter list
// The parameters are reordered so that they occur at the same position as the corresponding argument in the argument list.
// We don't actually create these lists, for efficiency reason. But we iterate over the arguments
// and get the correspond parameter types.
BetterResult result = BetterResult.Neither;
bool okToDowngradeResultToNeither = false;
bool ignoreDowngradableToNeither = false;
// Given an argument list A with a set of argument expressions { E1, E2, ..., EN } and two
// applicable function members MP and MQ with parameter types { P1, P2, ..., PN } and { Q1, Q2, ..., 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, and for at least one argument, the conversion from
// EX to PX is better than the conversion from EX to QX.
var m1LeastOverridenParameters = m1.LeastOverriddenMember.GetParameters();
var m2LeastOverridenParameters = m2.LeastOverriddenMember.GetParameters();
bool allSame = true; // Are all parameter types equivalent by identify conversions, ignoring Task-like differences?
int i;
for (i = 0; i < arguments.Count; ++i)
{
var argumentKind = arguments[i].Kind;
// If these are both applicable varargs methods and we're looking at the __arglist argument
// then clearly neither of them is going to be better in this argument.
if (argumentKind == BoundKind.ArgListOperator)
{
Debug.Assert(i == arguments.Count - 1);
Debug.Assert(m1.Member.GetIsVararg() && m2.Member.GetIsVararg());
continue;
}
var parameter1 = GetParameter(i, m1.Result, m1LeastOverridenParameters);
var type1 = GetParameterType(parameter1, m1.Result);
var parameter2 = GetParameter(i, m2.Result, m2LeastOverridenParameters);
var type2 = GetParameterType(parameter2, m2.Result);
bool okToDowngradeToNeither;
BetterResult r;
r = BetterConversionFromExpression(arguments[i],
type1,
m1.Result.ConversionForArg(i),
parameter1.RefKind,
type2,
m2.Result.ConversionForArg(i),
parameter2.RefKind,
considerRefKinds,
ref useSiteDiagnostics,
out okToDowngradeToNeither);
var type1Normalized = type1.NormalizeTaskTypes(Compilation);
var type2Normalized = type2.NormalizeTaskTypes(Compilation);
if (r == BetterResult.Neither)
{
if (allSame && Conversions.ClassifyImplicitConversionFromType(type1Normalized, type2Normalized, ref useSiteDiagnostics).Kind != ConversionKind.Identity)
{
allSame = false;
}
// We learned nothing from this one. Keep going.
continue;
}
if (Conversions.ClassifyImplicitConversionFromType(type1Normalized, type2Normalized, ref useSiteDiagnostics).Kind != ConversionKind.Identity)
{
allSame = false;
}
// 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))
{
// Add a new fact; we know that one of them is better when we didn't know that before.
result = r;
okToDowngradeResultToNeither = okToDowngradeToNeither;
}
}
else if (result != r)
{
// We previously got, say, Left is better in one place. Now we have that Right
// is better in one place. We know we can bail out at this point; neither is
// going to be better than the other.
// But first, let's see if we can ignore the ambiguity due to an undocumented legacy behavior of the compiler.
// This is not part of the language spec.
if (okToDowngradeResultToNeither)
{
if (okToDowngradeToNeither)
{
// Ignore the new information and the current result. Going forward,
// continue ignoring any downgradable information.
result = BetterResult.Neither;
okToDowngradeResultToNeither = false;
ignoreDowngradableToNeither = true;
continue;
}
else
{
// Current result can be ignored, but the new information cannot be ignored.
// Let's ignore the current result.
result = r;
okToDowngradeResultToNeither = false;
continue;
}
}
else if (okToDowngradeToNeither)
{
// Current result cannot be ignored, but the new information can be ignored.
// Let's ignore it and continue with the current result.
continue;
}
result = BetterResult.Neither;
break;
}
else
{
Debug.Assert(result == r);
Debug.Assert(result == BetterResult.Left || result == BetterResult.Right);
okToDowngradeResultToNeither = (okToDowngradeResultToNeither && okToDowngradeToNeither);
}
}
// Was one unambiguously better? Return it.
if (result != BetterResult.Neither)
{
return result;
}
// In case the parameter type sequences {P1, P2, …, PN} and {Q1, Q2, …, QN} are
// 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.
int m1ParameterCount;
int m2ParameterCount;
int m1ParametersUsedIncludingExpansionAndOptional;
int m2ParametersUsedIncludingExpansionAndOptional;
GetParameterCounts(m1, arguments, out m1ParameterCount, out m1ParametersUsedIncludingExpansionAndOptional);
GetParameterCounts(m2, arguments, out m2ParameterCount, out m2ParametersUsedIncludingExpansionAndOptional);
// We might have got out of the loop above early and allSame isn't completely calculated.
// We need to ensure that we are not going to skip over the next 'if' because of that.
if (allSame && m1ParametersUsedIncludingExpansionAndOptional == m2ParametersUsedIncludingExpansionAndOptional)
{
// Complete comparison for the remaining parameter types
for (i = i + 1; i < arguments.Count; ++i)
{
var argumentKind = arguments[i].Kind;
// If these are both applicable varargs methods and we're looking at the __arglist argument
// then clearly neither of them is going to be better in this argument.
if (argumentKind == BoundKind.ArgListOperator)
{
Debug.Assert(i == arguments.Count - 1);
Debug.Assert(m1.Member.GetIsVararg() && m2.Member.GetIsVararg());
continue;
}
var parameter1 = GetParameter(i, m1.Result, m1LeastOverridenParameters);
var type1 = GetParameterType(parameter1, m1.Result);
var type1Normalized = type1.NormalizeTaskTypes(Compilation);
var parameter2 = GetParameter(i, m2.Result, m2LeastOverridenParameters);
var type2 = GetParameterType(parameter2, m2.Result);
var type2Normalized = type2.NormalizeTaskTypes(Compilation);
if (Conversions.ClassifyImplicitConversionFromType(type1Normalized, type2Normalized, ref useSiteDiagnostics).Kind != ConversionKind.Identity)
{
allSame = false;
break;
}
}
}
// SPEC VIOLATION: When checking for matching parameter type sequences {P1, P2, …, PN} and {Q1, Q2, …, QN},
// native compiler includes types of optional parameters. We partially duplicate this behavior
// here by comparing the number of parameters used taking params expansion and
// optional parameters into account.
if (!allSame || m1ParametersUsedIncludingExpansionAndOptional != m2ParametersUsedIncludingExpansionAndOptional)
{
// SPEC VIOLATION: Even when parameter type sequences {P1, P2, …, PN} and {Q1, Q2, …, QN} are
// not equivalent, we have tie-breaking rules.
//
// Relevant code in the native compiler is at the end of
// BetterTypeEnum ExpressionBinder::WhichMethodIsBetter(
// const CandidateFunctionMember &node1,
// const CandidateFunctionMember &node2,
// Type* pTypeThrough,
// ArgInfos*args)
//
if (m1ParametersUsedIncludingExpansionAndOptional != m2ParametersUsedIncludingExpansionAndOptional)
{
if (m1.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
{
if (m2.Result.Kind != MemberResolutionKind.ApplicableInExpandedForm)
{
return BetterResult.Right;
}
}
else if (m2.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
{
Debug.Assert(m1.Result.Kind != MemberResolutionKind.ApplicableInExpandedForm);
return BetterResult.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 (m1ParametersUsedIncludingExpansionAndOptional == arguments.Count)
{
return BetterResult.Left;
}
else if (m2ParametersUsedIncludingExpansionAndOptional == arguments.Count)
{
return BetterResult.Right;
}
}
return PreferValOverInParameters(arguments, m1, m1LeastOverridenParameters, m2, m2LeastOverridenParameters);
}
// If MP is a non-generic method and MQ is a generic method, then MP is better than MQ.
if (m1.Member.GetMemberArity() == 0)
{
if (m2.Member.GetMemberArity() > 0)
{
return BetterResult.Left;
}
}
else if (m2.Member.GetMemberArity() == 0)
{
return BetterResult.Right;
}
// Otherwise, if MP is applicable in its normal form and MQ has a params array and is
// applicable only in its expanded form, then MP is better than MQ.
if (m1.Result.Kind == MemberResolutionKind.ApplicableInNormalForm && m2.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
{
return BetterResult.Left;
}
if (m1.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm && m2.Result.Kind == MemberResolutionKind.ApplicableInNormalForm)
{
return BetterResult.Right;
}
// SPEC ERROR: The spec has a minor error in working here. It says:
//
// Otherwise, if MP has more declared parameters than MQ, then MP is better than MQ.
// This can occur if both methods have params arrays and are applicable only in their
// expanded forms.
//
// The explanatory text actually should be normative. It should say:
//
// Otherwise, if both methods have params arrays and are applicable only in their
// expanded forms, and if MP has more declared parameters than MQ, then MP is better than MQ.
if (m1.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm && m2.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm)
{
if (m1ParameterCount > m2ParameterCount)
{
return BetterResult.Left;
}
if (m1ParameterCount < m2ParameterCount)
{
return BetterResult.Right;
}
}
// Otherwise if all parameters of MP have a corresponding argument whereas default
// arguments need to be substituted for at least one optional parameter in MQ then MP is
// better than MQ.
bool hasAll1 = m1.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm || m1ParameterCount == arguments.Count;
bool hasAll2 = m2.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm || m2ParameterCount == arguments.Count;
if (hasAll1 && !hasAll2)
{
return BetterResult.Left;
}
if (!hasAll1 && hasAll2)
{
return BetterResult.Right;
}
// Otherwise, if MP has more specific parameter types than MQ, then MP is better than
// MQ. Let {R1, R2, …, RN} and {S1, S2, …, SN} represent the uninstantiated and
// unexpanded parameter types of MP and MQ. MP's parameter types are more specific than
// MQ's if, for each parameter, RX is not less specific than SX, and, for at least one
// parameter, RX is more specific than SX
// NB: OriginalDefinition, not ConstructedFrom. Substitutions into containing symbols
// must also be ignored for this tie-breaker.
var uninst1 = ArrayBuilder<TypeSymbol>.GetInstance();
var uninst2 = ArrayBuilder<TypeSymbol>.GetInstance();
var m1Original = m1.LeastOverriddenMember.OriginalDefinition.GetParameters();
var m2Original = m2.LeastOverriddenMember.OriginalDefinition.GetParameters();
for (i = 0; i < arguments.Count; ++i)
{
// If these are both applicable varargs methods and we're looking at the __arglist argument
// then clearly neither of them is going to be better in this argument.
if (arguments[i].Kind == BoundKind.ArgListOperator)
{
Debug.Assert(i == arguments.Count - 1);
Debug.Assert(m1.Member.GetIsVararg() && m2.Member.GetIsVararg());
continue;
}
var parameter1 = GetParameter(i, m1.Result, m1Original);
uninst1.Add(GetParameterType(parameter1, m1.Result));
var parameter2 = GetParameter(i, m2.Result, m2Original);
uninst2.Add(GetParameterType(parameter2, m2.Result));
}
result = MoreSpecificType(uninst1, uninst2, ref useSiteDiagnostics);
uninst1.Free();
uninst2.Free();
if (result != BetterResult.Neither)
{
return result;
}
// UNDONE: Otherwise if one member is a non-lifted operator and the other is a lifted
// operator, the non-lifted one is better.
// Otherwise: Position in interactive submission chain. The last definition wins.
if (m1.Member.ContainingType.TypeKind == TypeKind.Submission && m2.Member.ContainingType.TypeKind == TypeKind.Submission)
{
// script class is always defined in source:
var compilation1 = m1.Member.DeclaringCompilation;
var compilation2 = m2.Member.DeclaringCompilation;
int submissionId1 = compilation1.GetSubmissionSlotIndex();
int submissionId2 = compilation2.GetSubmissionSlotIndex();
if (submissionId1 > submissionId2)
{
return BetterResult.Left;
}
if (submissionId1 < submissionId2)
{
return BetterResult.Right;
}
}
// Otherwise, if one has fewer custom modifiers, that is better
int m1ModifierCount = m1.LeastOverriddenMember.CustomModifierCount();
int m2ModifierCount = m2.LeastOverriddenMember.CustomModifierCount();
if (m1ModifierCount != m2ModifierCount)
{
return (m1ModifierCount < m2ModifierCount) ? BetterResult.Left : BetterResult.Right;
}
// Otherwise, prefer methods with 'val' parameters over 'in' parameters.
return PreferValOverInParameters(arguments, m1, m1LeastOverridenParameters, m2, m2LeastOverridenParameters);
}