private BetterResult BetterFunctionMember()

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);
        }