Fixed bug that results in a false negative when passing an extra keyword argument to a callable that is parameterized with a ParamSpec. This addresses #8294. (#8299)

This commit is contained in:
Eric Traut 2024-07-02 16:35:08 -07:00 committed by GitHub
parent 280ed52e8c
commit 8a766d59a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 49 additions and 26 deletions

View File

@ -10867,25 +10867,36 @@ export function createTypeEvaluator(
paramSpecArgList.push(argList[argIndex]);
} else if (paramDetails.kwargsIndex !== undefined) {
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,
});
if (isParamSpec(paramType)) {
if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
addDiagnostic(
DiagnosticRule.reportCallIssue,
LocMessage.paramNameMissing().format({ name: paramName.value }),
paramName
);
}
reportedArgError = true;
} else {
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,
});
assert(
paramDetails.params[paramDetails.kwargsIndex],
'paramDetails.kwargsIndex params entry is undefined'
);
// 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'
);
}
trySetActive(argList[argIndex], paramDetails.params[paramDetails.kwargsIndex].param);
} else {
if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {

View File

@ -35,11 +35,13 @@ async def func2():
@overload
def func3(x: int) -> None: ...
def func3(x: int) -> None:
...
@overload
def func3(x: str) -> str: ...
def func3(x: str) -> str:
...
def func3(x: int | str) -> str | None:
@ -73,7 +75,8 @@ def decorator2(f: Callable[P, R]) -> Callable[P, R]:
def func5(f: Callable[[], list[T1]]) -> Callable[[list[T2]], list[T1 | T2]]:
def inner(res: list[T2], /) -> list[T1 | T2]: ...
def inner(res: list[T2], /) -> list[T1 | T2]:
...
return decorator2(inner)
@ -87,18 +90,22 @@ def func6(x: Iterable[Callable[P, None]]) -> Callable[P, None]:
class Callback1:
def __call__(self, x: int | str, y: int = 3) -> None: ...
def __call__(self, x: int | str, y: int = 3) -> None:
...
class Callback2:
def __call__(self, x: int, /) -> None: ...
def __call__(self, x: int, /) -> None:
...
class Callback3:
def __call__(self, *args, **kwargs) -> None: ...
def __call__(self, *args, **kwargs) -> None:
...
def func7(f1: Callable[P, R], f2: Callable[P, R]) -> Callable[P, R]: ...
def func7(f1: Callable[P, R], f2: Callable[P, R]) -> Callable[P, R]:
...
def func8(cb1: Callback1, cb2: Callback2, cb3: Callback3):
@ -107,3 +114,8 @@ def func8(cb1: Callback1, cb2: Callback2, cb3: Callback3):
v2 = func7(cb1, cb3)
reveal_type(v2, expected_text="(x: int | str, y: int = 3) -> None")
def func9(f: Callable[P, object], *args: P.args, **kwargs: P.kwargs) -> object:
# This should generate an error because "name" doesn't exist.
return f(*args, **kwargs, name="")

View File

@ -558,7 +558,7 @@ test('ParamSpec2', () => {
test('ParamSpec3', () => {
const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec3.py']);
TestUtils.validateResults(results, 1);
TestUtils.validateResults(results, 2);
});
test('ParamSpec4', () => {