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]); paramSpecArgList.push(argList[argIndex]);
} else if (paramDetails.kwargsIndex !== undefined) { } else if (paramDetails.kwargsIndex !== undefined) {
const paramType = paramDetails.params[paramDetails.kwargsIndex].type; const paramType = paramDetails.params[paramDetails.kwargsIndex].type;
validateArgTypeParams.push({ if (isParamSpec(paramType)) {
paramCategory: ParameterCategory.KwargsDict, if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {
paramType, addDiagnostic(
requiresTypeVarMatching: requiresSpecialization(paramType), DiagnosticRule.reportCallIssue,
argument: argList[argIndex], LocMessage.paramNameMissing().format({ name: paramName.value }),
errorNode: argList[argIndex].valueExpression ?? errorNode, paramName
paramName: paramNameValue, );
}); }
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. // Remember that this parameter has already received a value.
paramMap.set(paramNameValue, { paramMap.set(paramNameValue, {
argsNeeded: 1, argsNeeded: 1,
argsReceived: 1, argsReceived: 1,
isPositionalOnly: false, isPositionalOnly: false,
}); });
assert( assert(
paramDetails.params[paramDetails.kwargsIndex], paramDetails.params[paramDetails.kwargsIndex],
'paramDetails.kwargsIndex params entry is undefined' 'paramDetails.kwargsIndex params entry is undefined'
); );
}
trySetActive(argList[argIndex], paramDetails.params[paramDetails.kwargsIndex].param); trySetActive(argList[argIndex], paramDetails.params[paramDetails.kwargsIndex].param);
} else { } else {
if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) { if (!canSkipDiagnosticForNode(errorNode) && !isTypeIncomplete) {

View File

@ -35,11 +35,13 @@ async def func2():
@overload @overload
def func3(x: int) -> None: ... def func3(x: int) -> None:
...
@overload @overload
def func3(x: str) -> str: ... def func3(x: str) -> str:
...
def func3(x: int | str) -> str | None: 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 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) return decorator2(inner)
@ -87,18 +90,22 @@ def func6(x: Iterable[Callable[P, None]]) -> Callable[P, None]:
class Callback1: class Callback1:
def __call__(self, x: int | str, y: int = 3) -> None: ... def __call__(self, x: int | str, y: int = 3) -> None:
...
class Callback2: class Callback2:
def __call__(self, x: int, /) -> None: ... def __call__(self, x: int, /) -> None:
...
class Callback3: 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): def func8(cb1: Callback1, cb2: Callback2, cb3: Callback3):
@ -107,3 +114,8 @@ def func8(cb1: Callback1, cb2: Callback2, cb3: Callback3):
v2 = func7(cb1, cb3) v2 = func7(cb1, cb3)
reveal_type(v2, expected_text="(x: int | str, y: int = 3) -> None") 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', () => { test('ParamSpec3', () => {
const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec3.py']); const results = TestUtils.typeAnalyzeSampleFiles(['paramSpec3.py']);
TestUtils.validateResults(results, 1); TestUtils.validateResults(results, 2);
}); });
test('ParamSpec4', () => { test('ParamSpec4', () => {