Skip to content

Commit

Permalink
Fixed bug that resulted in false positive error when calling the same…
Browse files Browse the repository at this point in the history
… generic function with a ParamSpec multiple times as separate arguments within a call expression. This addresses #5453.
  • Loading branch information
msfterictraut committed Jul 11, 2023
1 parent c41bc9c commit 3a34332
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 21 deletions.
53 changes: 32 additions & 21 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9367,16 +9367,29 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
// 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.
const paramSpecScopeId = varArgListParam.type.scopeId;
if (
varArgListParam.type.scopeId === typeResult.type.details.typeVarScopeId ||
varArgListParam.type.scopeId === typeResult.type.details.constructorTypeVarScopeId
paramSpecScopeId === typeResult.type.details.typeVarScopeId ||
paramSpecScopeId === typeResult.type.details.constructorTypeVarScopeId
) {
paramSpecArgList = [];
paramSpecTarget = TypeVarType.cloneForParamSpecAccess(varArgListParam.type, /* access */ undefined);
} else {
positionalOnlyLimitIndex = varArgListParamIndex;
}
}
} else if (typeResult.type.details.paramSpec) {
const paramSpecScopeId = typeResult.type.details.paramSpec.scopeId;
if (
paramSpecScopeId === typeResult.type.details.typeVarScopeId ||
paramSpecScopeId === typeResult.type.details.constructorTypeVarScopeId
) {
paramSpecArgList = [];
paramSpecTarget = TypeVarType.cloneForParamSpecAccess(
typeResult.type.details.paramSpec,
/* access */ undefined
);
}
}

// If there are keyword arguments present after a *args argument,
Expand Down Expand Up @@ -9994,27 +10007,25 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
});
trySetActive(argList[argIndex], paramDetails.params[paramInfoIndex].param);
}
} else if (paramSpecArgList) {
paramSpecArgList.push(argList[argIndex]);
} else if (paramDetails.kwargsIndex !== undefined) {
if (paramSpecArgList) {
paramSpecArgList.push(argList[argIndex]);
} else {
const paramType = paramDetails.params[paramDetails.kwargsIndex].type;
validateArgTypeParams.push({
paramCategory: ParameterCategory.KwargsDict,
paramType,
requiresTypeVarMatching: requiresSpecialization(paramType),
argument: argList[argIndex],
errorNode: argList[argIndex].valueExpression ?? errorNode,
paramName: paramNameValue,
});
const paramType = paramDetails.params[paramDetails.kwargsIndex].type;
validateArgTypeParams.push({
paramCategory: ParameterCategory.KwargsDict,
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,
});
}
// Remember that this parameter has already received a value.
paramMap.set(paramNameValue, {
argsNeeded: 1,
argsReceived: 1,
isPositionalOnly: false,
});
assert(
paramDetails.params[paramDetails.kwargsIndex],
'paramDetails.kwargsIndex params entry is undefined'
Expand Down
21 changes: 21 additions & 0 deletions packages/pyright-internal/src/tests/samples/paramSpec45.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# This sample tests the case where the same function that uses a ParamSpec
# is called multiple times as arguments to the same call.

from typing import Callable, ParamSpec

P = ParamSpec("P")


def func1(func: Callable[P, object], *args: P.args, **kwargs: P.kwargs) -> object:
...


def func2(x: str) -> int:
...


def func3(y: str) -> int:
...


print(func1(func2, x="..."), func1(func3, y="..."))
5 changes: 5 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,11 @@ test('ParamSpec44', () => {
TestUtils.validateResults(results, 0);
});

test('ParamSpec45', () => {
const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec45.py']);
TestUtils.validateResults(results, 0);
});

test('ClassVar1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['classVar1.py']);

Expand Down

0 comments on commit 3a34332

Please sign in to comment.