function matchFunctionArgumentsToParameters()

in packages/pyright-internal/src/analyzer/typeEvaluator.ts [8344:9150]


    function matchFunctionArgumentsToParameters(
        errorNode: ExpressionNode,
        argList: FunctionArgument[],
        type: FunctionType,
        overloadIndex: number
    ): MatchArgsToParamsResult {
        const paramDetails = getParameterListDetails(type);
        let argIndex = 0;
        let matchedUnpackedListOfUnknownLength = false;
        let reportedArgError = false;
        let isTypeIncomplete = false;
        let isVariadicTypeVarFullyMatched = false;

        // Build a map of parameters by name.
        const paramMap = new Map<string, ParamAssignmentInfo>();
        paramDetails.params.forEach((paramInfo) => {
            const param = paramInfo.param;
            if (param.name && param.category === ParameterCategory.Simple) {
                paramMap.set(param.name, {
                    argsNeeded: param.category === ParameterCategory.Simple && !param.hasDefault ? 1 : 0,
                    argsReceived: 0,
                    isPositionalOnly: paramInfo.source === ParameterSource.PositionOnly,
                });
            }
        });

        let positionalOnlyLimitIndex = paramDetails.positionOnlyParamCount;
        let positionParamLimitIndex = paramDetails.firstKeywordOnlyIndex ?? paramDetails.params.length;

        const varArgListParamIndex = paramDetails.argsIndex;
        const varArgDictParamIndex = paramDetails.kwargsIndex;

        // Is this an function that uses the *args and **kwargs
        // from a param spec? If so, we need to treat all positional parameters
        // prior to the *args as positional-only according to PEP 612.
        let paramSpecArgList: FunctionArgument[] | undefined;
        let paramSpecTarget: TypeVarType | undefined;
        let hasParamSpecArgsKwargs = false;

        if (varArgListParamIndex !== undefined && varArgDictParamIndex !== undefined) {
            const varArgListParam = paramDetails.params[varArgListParamIndex].param;
            const varArgDictParam = paramDetails.params[varArgDictParamIndex].param;

            if (
                isParamSpec(varArgListParam.type) &&
                varArgListParam.type.paramSpecAccess === 'args' &&
                isParamSpec(varArgDictParam.type) &&
                varArgDictParam.type.paramSpecAccess === 'kwargs' &&
                varArgListParam.type.details.name === varArgDictParam.type.details.name
            ) {
                hasParamSpecArgsKwargs = true;

                // Does this function define the param spec, or is it an inner
                // function nested within another function that defines the param
                // spec? We need to handle these two cases differently.
                if (varArgListParam.type.scopeId === type.details.typeVarScopeId) {
                    paramSpecArgList = [];
                    paramSpecTarget = TypeVarType.cloneForParamSpecAccess(varArgListParam.type, undefined);
                } else {
                    positionalOnlyLimitIndex = varArgListParamIndex;
                }
            }
        }

        // If there are keyword arguments present, they may target one or
        // more parameters that are positional. In this case, we will limit
        // the number of positional parameters.
        argList.forEach((arg) => {
            if (arg.name) {
                const keywordParamIndex = paramDetails.params.findIndex(
                    (paramInfo) =>
                        paramInfo.param.name === arg.name!.value &&
                        paramInfo.param.category === ParameterCategory.Simple
                );

                // Is this a parameter that can be interpreted as either a keyword or a positional?
                // If so, we'll treat it as a keyword parameter in this case because it's being
                // targeted by a keyword argument.
                if (keywordParamIndex >= 0 && keywordParamIndex >= positionalOnlyLimitIndex) {
                    if (positionParamLimitIndex < 0 || keywordParamIndex < positionParamLimitIndex) {
                        positionParamLimitIndex = keywordParamIndex;
                    }
                }
            }
        });

        // If we didn't see any special cases, then all parameters are positional.
        if (positionParamLimitIndex < 0) {
            positionParamLimitIndex = paramDetails.params.length;
        }

        // Determine how many positional args are being passed before
        // we see a keyword arg.
        let positionalArgCount = argList.findIndex(
            (arg) => arg.argumentCategory === ArgumentCategory.UnpackedDictionary || arg.name !== undefined
        );
        if (positionalArgCount < 0) {
            positionalArgCount = argList.length;
        }

        let validateArgTypeParams: ValidateArgTypeParams[] = [];

        let activeParam: FunctionParameter | undefined;
        function trySetActive(arg: FunctionArgument, param: FunctionParameter) {
            if (arg.active) {
                activeParam = param;
            }
        }

        let foundUnpackedListArg =
            argList.find((arg) => arg.argumentCategory === ArgumentCategory.UnpackedList) !== undefined;

        // Map the positional args to parameters.
        let paramIndex = 0;
        let unpackedArgIndex = 0;

        while (argIndex < positionalArgCount) {
            if (argIndex < positionalOnlyLimitIndex && argList[argIndex].name) {
                const fileInfo = AnalyzerNodeInfo.getFileInfo(argList[argIndex].name!);
                addDiagnostic(
                    fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
                    DiagnosticRule.reportGeneralTypeIssues,
                    Localizer.Diagnostic.argPositional(),
                    argList[argIndex].name!
                );
                reportedArgError = true;
            }

            if (paramIndex >= positionParamLimitIndex) {
                if (!foundUnpackedListArg || argList[argIndex].argumentCategory !== ArgumentCategory.UnpackedList) {
                    addDiagnostic(
                        AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportGeneralTypeIssues,
                        DiagnosticRule.reportGeneralTypeIssues,
                        positionParamLimitIndex === 1
                            ? Localizer.Diagnostic.argPositionalExpectedOne()
                            : Localizer.Diagnostic.argPositionalExpectedCount().format({
                                  expected: positionParamLimitIndex,
                              }),
                        argList[argIndex].valueExpression || errorNode
                    );
                    reportedArgError = true;
                }
                break;
            }

            if (paramIndex >= paramDetails.params.length) {
                break;
            }

            const paramType = paramDetails.params[paramIndex].type;
            if (argList[argIndex].argumentCategory === ArgumentCategory.UnpackedList) {
                if (!argList[argIndex].valueExpression) {
                    break;
                }

                const isParamVariadic =
                    paramDetails.params[paramIndex].param.category === ParameterCategory.VarArgList &&
                    isVariadicTypeVar(paramType);
                let isArgCompatibleWithVariadic = false;
                const argTypeResult = getTypeForArgument(argList[argIndex]);
                const argType = argTypeResult.type;
                let listElementType: Type | undefined;
                let advanceToNextArg = false;

                // Handle the case where *args is being passed to a function defined
                // with a ParamSpec and a Concatenate operator. PEP 612 indicates that
                // all positional parameters specified in the Concatenate must be
                // filled explicitly.
                if (type.details.paramSpec && paramIndex < positionParamLimitIndex) {
                    addDiagnostic(
                        AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportGeneralTypeIssues,
                        DiagnosticRule.reportGeneralTypeIssues,
                        positionParamLimitIndex === 1
                            ? Localizer.Diagnostic.argPositionalExpectedOne()
                            : Localizer.Diagnostic.argPositionalExpectedCount().format({
                                  expected: positionParamLimitIndex,
                              }),
                        argList[argIndex].valueExpression || errorNode
                    );
                    reportedArgError = true;
                }

                // If this is a tuple with specified element types, use those
                // specified types rather than using the more generic iterator
                // type which will be a union of all element types.
                const combinedTupleType = combineSameSizedTuples(makeTopLevelTypeVarsConcrete(argType), tupleClassType);

                if (
                    !isParamVariadic &&
                    combinedTupleType &&
                    isClassInstance(combinedTupleType) &&
                    combinedTupleType.tupleTypeArguments &&
                    combinedTupleType.tupleTypeArguments.length > 0 &&
                    unpackedArgIndex < combinedTupleType.tupleTypeArguments.length
                ) {
                    listElementType = combinedTupleType.tupleTypeArguments[unpackedArgIndex].type;

                    // Determine if there are any more unpacked list arguments after
                    // this one. If not, we'll clear this flag because this unpacked
                    // list arg is bounded in length.
                    foundUnpackedListArg =
                        argList.find(
                            (arg, index) => index > argIndex && arg.argumentCategory === ArgumentCategory.UnpackedList
                        ) !== undefined;

                    unpackedArgIndex++;
                    if (unpackedArgIndex >= combinedTupleType.tupleTypeArguments.length) {
                        unpackedArgIndex = 0;
                        advanceToNextArg = true;
                    }
                } else if (isParamVariadic && isVariadicTypeVar(argType)) {
                    // Allow an unpacked variadic type variable to satisfy an
                    // unpacked variadic type variable.
                    listElementType = argType;
                    isArgCompatibleWithVariadic = true;
                    advanceToNextArg = true;
                    isVariadicTypeVarFullyMatched = true;
                } else if (
                    isClassInstance(argType) &&
                    isTupleClass(argType) &&
                    argType.tupleTypeArguments &&
                    argType.tupleTypeArguments.length === 1 &&
                    isVariadicTypeVar(argType.tupleTypeArguments[0].type)
                ) {
                    // Handle the case where an unpacked variadic type var has
                    // been packaged into a tuple.
                    listElementType = argType.tupleTypeArguments[0].type;
                    isArgCompatibleWithVariadic = true;
                    advanceToNextArg = true;
                    isVariadicTypeVarFullyMatched = true;
                } else if (isParamSpec(argType) && argType.paramSpecAccess === 'args') {
                    listElementType = undefined;
                } else {
                    listElementType =
                        getTypeFromIterator(argType, /* isAsync */ false, argList[argIndex].valueExpression!) ||
                        UnknownType.create();

                    if (paramDetails.params[paramIndex].param.category !== ParameterCategory.VarArgList) {
                        matchedUnpackedListOfUnknownLength = true;
                    }
                }

                const funcArg: FunctionArgument | undefined = listElementType
                    ? {
                          argumentCategory: ArgumentCategory.Simple,
                          type: listElementType,
                      }
                    : undefined;
                if (funcArg && argTypeResult.isIncomplete) {
                    isTypeIncomplete = true;
                }

                const paramName = paramDetails.params[paramIndex].param.name;

                // It's not allowed to use unpacked arguments with a variadic *args
                // parameter unless the argument is a variadic arg as well.
                if (isParamVariadic && !isArgCompatibleWithVariadic) {
                    addDiagnostic(
                        AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportGeneralTypeIssues,
                        DiagnosticRule.reportGeneralTypeIssues,
                        Localizer.Diagnostic.unpackedArgWithVariadicParam(),
                        argList[argIndex].valueExpression || errorNode
                    );
                    reportedArgError = true;
                } else {
                    if (paramSpecArgList) {
                        paramSpecArgList.push(argList[argIndex]);
                    }

                    if (funcArg) {
                        validateArgTypeParams.push({
                            paramCategory: paramDetails.params[paramIndex].param.category,
                            paramType,
                            requiresTypeVarMatching: requiresSpecialization(paramType),
                            argument: funcArg,
                            errorNode: argList[argIndex].valueExpression || errorNode,
                            paramName: paramDetails.params[paramIndex].param.isNameSynthesized ? undefined : paramName,
                        });
                    }
                }

                trySetActive(argList[argIndex], paramDetails.params[paramIndex].param);

                // Note that the parameter has received an argument.
                if (
                    paramName &&
                    paramDetails.params[paramIndex].param.category === ParameterCategory.Simple &&
                    paramMap.has(paramName)
                ) {
                    paramMap.get(paramName)!.argsReceived++;
                }

                if (
                    advanceToNextArg ||
                    paramDetails.params[paramIndex].param.category === ParameterCategory.VarArgList
                ) {
                    argIndex++;
                }

                if (
                    isVariadicTypeVarFullyMatched ||
                    paramDetails.params[paramIndex].param.category !== ParameterCategory.VarArgList
                ) {
                    paramIndex++;
                }
            } else if (paramDetails.params[paramIndex].param.category === ParameterCategory.VarArgList) {
                trySetActive(argList[argIndex], paramDetails.params[paramIndex].param);

                if (paramSpecArgList) {
                    paramSpecArgList.push(argList[argIndex]);
                    argIndex++;
                } else {
                    let paramCategory = paramDetails.params[paramIndex].param.category;
                    let effectiveParamType = paramType;
                    const paramName = paramDetails.params[paramIndex].param.name;

                    if (
                        isUnpackedTuple(paramType) &&
                        paramType.tupleTypeArguments &&
                        paramType.tupleTypeArguments.length > 0
                    ) {
                        effectiveParamType = paramType.tupleTypeArguments[0].type;
                    }

                    paramCategory = isVariadicTypeVar(effectiveParamType)
                        ? ParameterCategory.VarArgList
                        : ParameterCategory.Simple;

                    const remainingArgCount = positionalArgCount - argIndex;
                    const remainingParamCount = positionParamLimitIndex - paramIndex - 1;

                    if (remainingArgCount <= remainingParamCount) {
                        if (remainingArgCount < remainingParamCount) {
                            // Have we run out of arguments and still have parameters left to fill?
                            addDiagnostic(
                                AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportGeneralTypeIssues,
                                DiagnosticRule.reportGeneralTypeIssues,
                                remainingArgCount === 1
                                    ? Localizer.Diagnostic.argMorePositionalExpectedOne()
                                    : Localizer.Diagnostic.argMorePositionalExpectedCount().format({
                                          expected: remainingArgCount,
                                      }),
                                argList[argIndex].valueExpression || errorNode
                            );
                            reportedArgError = true;
                        }

                        paramIndex++;
                    } else {
                        validateArgTypeParams.push({
                            paramCategory,
                            paramType: effectiveParamType,
                            requiresTypeVarMatching: requiresSpecialization(paramType),
                            argument: argList[argIndex],
                            errorNode: argList[argIndex].valueExpression || errorNode,
                            paramName,
                            mapsToVarArgList: true,
                        });

                        argIndex++;
                    }
                }
            } else {
                const paramName = paramDetails.params[paramIndex].param.name;
                validateArgTypeParams.push({
                    paramCategory: paramDetails.params[paramIndex].param.category,
                    paramType,
                    requiresTypeVarMatching: requiresSpecialization(paramType),
                    argument: argList[argIndex],
                    errorNode: argList[argIndex].valueExpression || errorNode,
                    paramName: paramDetails.params[paramIndex].param.isNameSynthesized ? undefined : paramName,
                });
                trySetActive(argList[argIndex], paramDetails.params[paramIndex].param);

                // Note that the parameter has received an argument.
                if (paramName && paramMap.has(paramName)) {
                    paramMap.get(paramName)!.argsReceived++;
                }

                argIndex++;
                paramIndex++;
            }
        }

        // Check if there weren't enough positional arguments to populate all of
        // the positional-only parameters.
        if (
            positionalOnlyLimitIndex >= 0 &&
            paramIndex < positionalOnlyLimitIndex &&
            (!foundUnpackedListArg || hasParamSpecArgsKwargs)
        ) {
            const firstParamWithDefault = paramDetails.params.findIndex((paramInfo) => paramInfo.param.hasDefault);
            const positionOnlyWithoutDefaultsCount =
                firstParamWithDefault >= 0 && firstParamWithDefault < positionalOnlyLimitIndex
                    ? firstParamWithDefault
                    : positionalOnlyLimitIndex;
            const argsRemainingCount = positionOnlyWithoutDefaultsCount - positionalArgCount;
            if (argsRemainingCount > 0) {
                addDiagnostic(
                    AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportGeneralTypeIssues,
                    DiagnosticRule.reportGeneralTypeIssues,
                    argsRemainingCount === 1
                        ? Localizer.Diagnostic.argMorePositionalExpectedOne()
                        : Localizer.Diagnostic.argMorePositionalExpectedCount().format({
                              expected: argsRemainingCount,
                          }),
                    argList.length > positionalArgCount
                        ? argList[positionalArgCount].valueExpression || errorNode
                        : errorNode
                );
                reportedArgError = true;
            }
        }

        if (!reportedArgError) {
            let unpackedDictionaryArgType: Type | undefined;

            // Now consume any keyword arguments.
            while (argIndex < argList.length) {
                if (argList[argIndex].argumentCategory === ArgumentCategory.UnpackedDictionary) {
                    // Verify that the type used in this expression is a Mapping[str, T].
                    const argType = getTypeForArgument(argList[argIndex]).type;
                    if (isAnyOrUnknown(argType)) {
                        unpackedDictionaryArgType = argType;
                    } else if (isClassInstance(argType) && ClassType.isTypedDictClass(argType)) {
                        // Handle the special case where it is a TypedDict and we know which
                        // keys are present.
                        const typedDictEntries = getTypedDictMembersForClass(evaluatorInterface, argType);
                        const diag = new DiagnosticAddendum();

                        typedDictEntries.forEach((entry, name) => {
                            const paramEntry = paramMap.get(name);
                            if (paramEntry && !paramEntry.isPositionalOnly) {
                                if (paramEntry.argsReceived > 0) {
                                    diag.addMessage(Localizer.Diagnostic.paramAlreadyAssigned().format({ name }));
                                } else {
                                    paramEntry.argsReceived++;

                                    const paramInfoIndex = paramDetails.params.findIndex(
                                        (paramInfo) => paramInfo.param.name === name
                                    );
                                    assert(paramInfoIndex >= 0);
                                    const paramType = paramDetails.params[paramInfoIndex].type;

                                    validateArgTypeParams.push({
                                        paramCategory: ParameterCategory.Simple,
                                        paramType,
                                        requiresTypeVarMatching: requiresSpecialization(paramType),
                                        argument: {
                                            argumentCategory: ArgumentCategory.Simple,
                                            type: entry.valueType,
                                        },
                                        errorNode: argList[argIndex].valueExpression || errorNode,
                                        paramName: name,
                                    });
                                }
                            } else if (paramDetails.kwargsIndex !== undefined) {
                                const paramType = paramDetails.params[paramDetails.kwargsIndex].type;
                                validateArgTypeParams.push({
                                    paramCategory: ParameterCategory.VarArgDictionary,
                                    paramType,
                                    requiresTypeVarMatching: requiresSpecialization(paramType),
                                    argument: {
                                        argumentCategory: ArgumentCategory.Simple,
                                        type: entry.valueType,
                                    },
                                    errorNode: argList[argIndex].valueExpression || errorNode,
                                    paramName: name,
                                });

                                // Remember that this parameter has already received a value.
                                paramMap.set(name, {
                                    argsNeeded: 1,
                                    argsReceived: 1,
                                    isPositionalOnly: false,
                                });
                            } else {
                                diag.addMessage(Localizer.Diagnostic.paramNameMissing().format({ name }));
                            }
                        });

                        if (!diag.isEmpty()) {
                            addDiagnostic(
                                AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportGeneralTypeIssues,
                                DiagnosticRule.reportGeneralTypeIssues,
                                Localizer.Diagnostic.unpackedTypedDictArgument() + diag.getString(),
                                argList[argIndex].valueExpression || errorNode
                            );
                            reportedArgError = true;
                        }
                    } else if (isParamSpec(argType) && argType.paramSpecAccess === 'kwargs') {
                        unpackedDictionaryArgType = AnyType.create();
                    } else {
                        const mappingType = getTypingType(errorNode, 'Mapping');
                        const strObjType = getBuiltInObject(errorNode, 'str');

                        if (
                            mappingType &&
                            isInstantiableClass(mappingType) &&
                            strObjType &&
                            isClassInstance(strObjType)
                        ) {
                            const mappingTypeVarMap = new TypeVarMap(getTypeVarScopeId(mappingType));
                            let isValidMappingType = false;

                            // If this was a TypeVar (e.g. for pseudo-generic classes),
                            // don't emit this error.
                            if (isTypeVar(argType)) {
                                isValidMappingType = true;
                            } else if (
                                canAssignType(
                                    ClassType.cloneAsInstance(mappingType),
                                    argType,
                                    /* diag */ undefined,
                                    mappingTypeVarMap
                                )
                            ) {
                                const specializedMapping = applySolvedTypeVars(
                                    mappingType,
                                    mappingTypeVarMap
                                ) as ClassType;
                                const typeArgs = specializedMapping.typeArguments;
                                if (typeArgs && typeArgs.length >= 2) {
                                    if (canAssignType(strObjType, typeArgs[0])) {
                                        isValidMappingType = true;
                                    }
                                    unpackedDictionaryArgType = typeArgs[1];
                                } else {
                                    isValidMappingType = true;
                                    unpackedDictionaryArgType = UnknownType.create();
                                }
                            }

                            if (!isValidMappingType) {
                                addDiagnostic(
                                    AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportGeneralTypeIssues,
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    Localizer.Diagnostic.unpackedDictArgumentNotMapping(),
                                    argList[argIndex].valueExpression || errorNode
                                );
                                reportedArgError = true;
                            }
                        }
                    }

                    if (paramSpecArgList) {
                        paramSpecArgList.push(argList[argIndex]);
                    }
                } else {
                    // Protect against the case where a non-keyword argument appears after
                    // a keyword argument. This will have already been reported as a parse
                    // error, but we need to protect against it here.
                    const paramName = argList[argIndex].name;
                    if (paramName) {
                        const paramNameValue = paramName.value;
                        const paramEntry = paramMap.get(paramNameValue);
                        if (paramEntry && !paramEntry.isPositionalOnly) {
                            if (paramEntry.argsReceived > 0) {
                                addDiagnostic(
                                    AnalyzerNodeInfo.getFileInfo(paramName).diagnosticRuleSet.reportGeneralTypeIssues,
                                    DiagnosticRule.reportGeneralTypeIssues,
                                    Localizer.Diagnostic.paramAlreadyAssigned().format({ name: paramNameValue }),
                                    paramName
                                );
                                reportedArgError = true;
                            } else {
                                paramEntry.argsReceived++;

                                const paramInfoIndex = paramDetails.params.findIndex(
                                    (paramInfo) => paramInfo.param.name === paramNameValue
                                );
                                assert(paramInfoIndex >= 0);
                                const paramType = paramDetails.params[paramInfoIndex].type;

                                validateArgTypeParams.push({
                                    paramCategory: ParameterCategory.Simple,
                                    paramType,
                                    requiresTypeVarMatching: requiresSpecialization(paramType),
                                    argument: argList[argIndex],
                                    errorNode: argList[argIndex].valueExpression || errorNode,
                                    paramName: paramNameValue,
                                });
                                trySetActive(argList[argIndex], paramDetails.params[paramInfoIndex].param);
                            }
                        } else if (paramDetails.kwargsIndex !== undefined) {
                            if (paramSpecArgList) {
                                paramSpecArgList.push(argList[argIndex]);
                            } else {
                                const paramType = paramDetails.params[paramDetails.kwargsIndex].type;
                                validateArgTypeParams.push({
                                    paramCategory: ParameterCategory.VarArgDictionary,
                                    paramType,
                                    requiresTypeVarMatching: requiresSpecialization(paramType),
                                    argument: argList[argIndex],
                                    errorNode: argList[argIndex].valueExpression || errorNode,
                                    paramName: paramNameValue,
                                });

                                // Remember that this parameter has already received a value.
                                paramMap.set(paramNameValue, {
                                    argsNeeded: 1,
                                    argsReceived: 1,
                                    isPositionalOnly: false,
                                });
                            }
                            trySetActive(argList[argIndex], paramDetails.params[paramDetails.kwargsIndex].param);
                        } else {
                            addDiagnostic(
                                AnalyzerNodeInfo.getFileInfo(paramName).diagnosticRuleSet.reportGeneralTypeIssues,
                                DiagnosticRule.reportGeneralTypeIssues,
                                Localizer.Diagnostic.paramNameMissing().format({ name: paramName.value }),
                                paramName
                            );
                            reportedArgError = true;
                        }
                    } else if (argList[argIndex].argumentCategory === ArgumentCategory.Simple) {
                        const fileInfo = AnalyzerNodeInfo.getFileInfo(errorNode);
                        addDiagnostic(
                            fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
                            DiagnosticRule.reportGeneralTypeIssues,
                            positionParamLimitIndex === 1
                                ? Localizer.Diagnostic.argPositionalExpectedOne()
                                : Localizer.Diagnostic.argPositionalExpectedCount().format({
                                      expected: positionParamLimitIndex,
                                  }),
                            argList[argIndex].valueExpression || errorNode
                        );
                        reportedArgError = true;
                    }
                }

                argIndex++;
            }

            // If there are keyword-only parameters that haven't been matched but we
            // have an unpacked dictionary arg, assume that it applies to them.
            if (unpackedDictionaryArgType && (!foundUnpackedListArg || paramDetails.argsIndex !== undefined)) {
                // Don't consider any position-only parameters, since they cannot be matched to
                // **kwargs arguments. Consider parameters that are either positional or keyword
                // if there is no *args argument.
                paramDetails.params.forEach((paramInfo, paramIndex) => {
                    const param = paramInfo.param;
                    if (
                        paramIndex >= paramDetails.firstPositionOrKeywordIndex &&
                        param.category === ParameterCategory.Simple &&
                        param.name &&
                        !param.hasDefault &&
                        paramMap.has(param.name) &&
                        paramMap.get(param.name)!.argsReceived === 0
                    ) {
                        const paramType = paramDetails.params[paramIndex].type;
                        validateArgTypeParams.push({
                            paramCategory: ParameterCategory.Simple,
                            paramType,
                            requiresTypeVarMatching: requiresSpecialization(paramType),
                            argument: {
                                argumentCategory: ArgumentCategory.Simple,
                                type: unpackedDictionaryArgType!,
                            },
                            errorNode:
                                argList.find((arg) => arg.argumentCategory === ArgumentCategory.UnpackedDictionary)
                                    ?.valueExpression ?? errorNode,
                            paramName: param.isNameSynthesized ? undefined : param.name,
                        });

                        paramMap.get(param.name)!.argsReceived = 1;
                    }
                });
            }

            // Determine whether there are any parameters that require arguments
            // but have not yet received them. If we received a dictionary argument
            // (i.e. an arg starting with a "**"), we will assume that all parameters
            // are matched.
            if (!unpackedDictionaryArgType && !FunctionType.isDefaultParameterCheckDisabled(type)) {
                const unassignedParams = [...paramMap.keys()].filter((name) => {
                    const entry = paramMap.get(name)!;
                    return !entry || entry.argsReceived < entry.argsNeeded;
                });

                if (unassignedParams.length > 0) {
                    const missingParamNames = unassignedParams.map((p) => `"${p}"`).join(', ');
                    addDiagnostic(
                        AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportGeneralTypeIssues,
                        DiagnosticRule.reportGeneralTypeIssues,
                        unassignedParams.length === 1
                            ? Localizer.Diagnostic.argMissingForParam().format({ name: missingParamNames })
                            : Localizer.Diagnostic.argMissingForParams().format({ names: missingParamNames }),
                        errorNode
                    );
                    reportedArgError = true;
                }

                // Add any implicit (default) arguments that are needed for resolving
                // generic types. For example, if the function is defined as
                // def foo(v1: _T = 'default')
                // and _T is a TypeVar, we need to match the TypeVar to the default
                // value's type if it's not provided by the caller.
                paramDetails.params.forEach((paramInfo) => {
                    const param = paramInfo.param;
                    if (param.category === ParameterCategory.Simple && param.name) {
                        const entry = paramMap.get(param.name)!;
                        if (entry.argsNeeded === 0 && entry.argsReceived === 0) {
                            if (
                                param.defaultType &&
                                !isEllipsisType(param.defaultType) &&
                                requiresSpecialization(param.type)
                            ) {
                                validateArgTypeParams.push({
                                    paramCategory: param.category,
                                    paramType: param.type,
                                    requiresTypeVarMatching: true,
                                    argument: {
                                        argumentCategory: ArgumentCategory.Simple,
                                        type: param.defaultType,
                                    },
                                    errorNode: errorNode,
                                    paramName: param.isNameSynthesized ? undefined : param.name,
                                });
                            }
                        }
                    }
                });
            }
        }

        // If we're in speculative mode and an arg/param mismatch has already been reported, don't
        // bother doing the extra work here. This occurs frequently when attempting to find the
        // correct overload.
        if (!reportedArgError || !speculativeTypeTracker.isSpeculative(undefined)) {
            // If there are arguments that map to a variadic *args parameter that hasn't
            // already been matched, see if the type of that *args parameter is a variadic
            // type variable. If so, we'll preprocess those arguments and combine them
            // into a tuple.
            if (
                paramDetails.argsIndex !== undefined &&
                paramDetails.params[paramDetails.argsIndex].param.hasDeclaredType &&
                !isVariadicTypeVarFullyMatched
            ) {
                const paramType = paramDetails.params[paramDetails.argsIndex].type;
                const variadicArgs = validateArgTypeParams.filter((argParam) => argParam.mapsToVarArgList);

                if (isTypeVar(paramType) && paramType.details.isVariadic) {
                    if (tupleClassType && isInstantiableClass(tupleClassType)) {
                        const tupleTypeArgs: TupleTypeArgument[] = variadicArgs.map((argParam) => {
                            return {
                                type: stripLiteralValue(getTypeForArgument(argParam.argument).type),
                                isUnbounded: argParam.argument.argumentCategory === ArgumentCategory.UnpackedList,
                            };
                        });
                        const specializedTuple = ClassType.cloneAsInstance(
                            specializeTupleClass(
                                tupleClassType,
                                tupleTypeArgs,
                                /* isTypeArgumentExplicit */ true,
                                /* stripLiterals */ true,
                                /* isUnpackedTuple */ true
                            )
                        );

                        const combinedArg: ValidateArgTypeParams = {
                            paramCategory: ParameterCategory.VarArgList,
                            paramType,
                            requiresTypeVarMatching: true,
                            argument: { argumentCategory: ArgumentCategory.Simple, type: specializedTuple },
                            errorNode,
                            paramName: paramDetails.params[paramDetails.argsIndex].param.name,
                            mapsToVarArgList: true,
                        };

                        validateArgTypeParams = [
                            ...validateArgTypeParams.filter((argParam) => !argParam.mapsToVarArgList),
                            combinedArg,
                        ];
                    }
                }
            }
        }

        let relevance = 0;
        if (matchedUnpackedListOfUnknownLength) {
            // Lower the relevance if we made assumptions about the length
            // of an unpacked argument. This will favor overloads that
            // associate this case with a *args parameter.
            relevance--;
        }

        // Special-case the builtin isinstance and issubclass functions.
        if (
            ['isinstance', 'issubclass'].some((name) => name === type.details.builtInName) &&
            validateArgTypeParams.length === 2
        ) {
            validateArgTypeParams[1].expectingType = true;
        }

        return {
            overload: type,
            overloadIndex,
            argumentErrors: reportedArgError,
            isTypeIncomplete,
            argParams: validateArgTypeParams,
            paramSpecTarget,
            paramSpecArgList,
            activeParam,
            relevance,
        };
    }