in packages/pyright-internal/src/analyzer/typeEvaluator.ts [8274:8683]
type: printType(expandedSubtype),
}),
errorNode
);
}
return UnknownType.create();
}
}
case TypeCategory.None: {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportOptionalCall,
DiagnosticRule.reportOptionalCall,
Localizer.Diagnostic.noneNotCallable(),
errorNode
);
return undefined;
}
// TypeVars should have been expanded in most cases,
// but we still need to handle the case of Type[T] where
// T is a constrained type that contains a union. We also
// need to handle recursive type aliases.
case TypeCategory.TypeVar: {
expandedSubtype = transformPossibleRecursiveTypeAlias(expandedSubtype);
const callResult = validateCallArguments(
errorNode,
argList,
expandedSubtype,
typeVarMap,
skipUnknownArgCheck,
expectedType,
recursionCount
);
if (callResult.argumentErrors) {
argumentErrors = true;
}
return callResult.returnType || UnknownType.create();
}
case TypeCategory.Module: {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.moduleNotCallable(),
errorNode
);
return undefined;
}
}
return undefined;
}
);
return {
argumentErrors,
returnType: isNever(returnType) ? undefined : returnType,
isTypeIncomplete,
specializedInitSelfType,
};
}
// Matches the arguments passed to a function to the corresponding parameters in that
// function. This matching is done based on positions and keywords. Type evaluation and
// validation is left to the caller.
// This logic is based on PEP 3102: https://www.python.org/dev/peps/pep-3102/
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({