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