Repository: dotnet/roslyn
File: src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs (C#)

Code before:
                        case MemberResolutionKind.ConstructedParameterFailedConstraintCheck:
                        case MemberResolutionKind.NoCorrespondingNamedParameter:
                        case MemberResolutionKind.UseSiteError:
                        case MemberResolutionKind.BadNonTrailingNamedArgument:
                        case MemberResolutionKind.DuplicateNamedArgument:
                            return true;
                    }
                    break;

                case MemberResolutionKind.BadArgumentConversion:
                    if (expandedResult.Result.Kind == MemberResolutionKind.BadArgumentConversion &&
                        expandedResult.Result.ParamsElementTypeOpt.HasType &&
                        expandedResult.Result.ParamsElementTypeOpt.Type != (object)ErrorTypeSymbol.EmptyParamsCollectionElementTypeSentinel)
                    {
                        if (haveBadArgumentForLastParameter(normalResult) && haveBadArgumentForLastParameter(expandedResult))
                        {
                            // Errors are better if we use the expanded form in this case.
                            return true;
                        }

                        // Also prefer expanded form if the normal form has a bad argument for the last parameter
                        // but the expanded form doesn't. This handles cases like:
                        // M(char c, params object[] args) called with M(string[], string)
                        // where string[] -> char is bad and string -> object[] is bad in normal form,
                        // but string -> object is good in expanded form.
                        if (haveBadArgumentForLastParameter(normalResult) && !haveBadArgumentForLastParameter(expandedResult))
                        {
                            return true;
                        }
                    }

                    break;
            }
            return false;

            static bool haveBadArgumentForLastParameter(MemberResolutionResult<TMember> result)
            {
                int parameterCount = result.Member.GetParameterCount();

                foreach (int arg in result.Result.BadArgumentsOpt.TrueBits())
                {
                    if (parameterCount == result.Result.ParameterFromArgument(arg) + 1)
                    {
                        return true;
                    }
                }

                return false;
            }
        }


Review comment (bug):
@copilot  the 'if' statement above the 'if-statement' you added looks suspect.    The one above this checks `haveBadArgumentForLastParameter(expandedResult)` and this one checks `!haveBadArgumentForLastParameter(expandedResult)`.  So effectively both optoins are checked.  can we just simplify the above, and fully explain the situation.

Or, if the above 'if-statement' is actually incorrect and we only want to keep the new one, see if we can remove the old one if it doesn't make things worse.

Code after:
                        case MemberResolutionKind.ConstructedParameterFailedConstraintCheck:
                        case MemberResolutionKind.NoCorrespondingNamedParameter:
                        case MemberResolutionKind.UseSiteError:
                        case MemberResolutionKind.BadNonTrailingNamedArgument:
                        case MemberResolutionKind.DuplicateNamedArgument:
                            return true;
                    }
                    break;

                case MemberResolutionKind.BadArgumentConversion:
                    if (expandedResult.Result.Kind == MemberResolutionKind.BadArgumentConversion &&
                        expandedResult.Result.ParamsElementTypeOpt.HasType &&
                        expandedResult.Result.ParamsElementTypeOpt.Type != (object)ErrorTypeSymbol.EmptyParamsCollectionElementTypeSentinel)
                    {
                        // Prefer expanded form if the normal form has a bad argument for the last parameter.
                        // This handles two cases:
                        // 1. Both normal and expanded forms have bad arguments for the last parameter
                        //    (e.g., both fail but expanded gives better error messages)
                        // 2. Normal form has a bad argument for the last parameter but expanded form doesn't
                        //    (e.g., M(char c, params object[] args) called with M(string[], string) where
                        //    string -> object[] is bad in normal form but string -> object is good in expanded form)
                        if (haveBadArgumentForLastParameter(normalResult))
                        {
                            return true;
                        }
                    }

                    break;
            }
            return false;

            static bool haveBadArgumentForLastParameter(MemberResolutionResult<TMember> result)
            {
                int parameterCount = result.Member.GetParameterCount();

                foreach (int arg in result.Result.BadArgumentsOpt.TrueBits())
                {
                    if (parameterCount == result.Result.ParameterFromArgument(arg) + 1)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        // We need to know if this is a valid formal parameter list with a parameter array
        // as the final formal parameter. We might be in an error recovery scenario
        // where the params array is not an array type.
        public static bool IsValidParams(Binder binder, Symbol member, bool disallowExpandedNonArrayParams, out TypeWithAnnotations definitionElementType)