Changed interpretation of function definitions with (*args: Any, **kwargs: Any) to be ... in compliance with latest typing spec. This addresses #7848. (#7859)

This commit is contained in:
Eric Traut 2024-05-06 23:45:41 -07:00 committed by GitHub
parent 0ead803de9
commit c49af8f349
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 88 additions and 3 deletions

View File

@ -17926,6 +17926,16 @@ export function createTypeEvaluator(
}
}
// If the function contains an *args and a **kwargs parameter and both
// are annotated as Any or are unannotated, make it exempt from
// args/kwargs compatibility checks.
const variadicsWithAnyType = functionType.details.parameters.filter(
(param) => param.category !== ParameterCategory.Simple && param.name && isAnyOrUnknown(param.type)
);
if (variadicsWithAnyType.length >= 2) {
functionType.details.flags |= FunctionTypeFlags.SkipArgsKwargsCompatibilityCheck;
}
// If there was a defined return type, analyze that first so when we
// walk the contents of the function, return statements can be
// validated against this type.

View File

@ -3068,6 +3068,14 @@ export function isTypeSame(type1: Type, type2: Type, options: TypeSameOptions =
return false;
}
// If one function is ... and the other is not, they are not the same.
if (
FunctionType.shouldSkipArgsKwargsCompatibilityCheck(type1) !==
FunctionType.shouldSkipArgsKwargsCompatibilityCheck(functionType2)
) {
return false;
}
const positionOnlyIndex1 = params1.findIndex((param) => isPositionOnlySeparator(param));
const positionOnlyIndex2 = params2.findIndex((param) => isPositionOnlySeparator(param));

View File

@ -0,0 +1,61 @@
# This sample tests the case where a callback protocol uses
# a (*args: Any, **kwargs: Any) signature.
from typing import Any, Callable, Concatenate, ParamSpec, Protocol, TypeVar
P = ParamSpec("P")
T_contra = TypeVar("T_contra", contravariant=True)
class Proto1(Protocol):
def __call__(self, *args, **kwargs) -> None: ...
class Proto2(Protocol):
def __call__(self, a: int, /, *args, **kwargs) -> None: ...
class Proto3(Protocol):
def __call__(self, a: int, *args: Any, **kwargs: Any) -> None: ...
class Proto4(Protocol[P]):
def __call__(self, a: int, *args: P.args, **kwargs: P.kwargs) -> None: ...
class Proto5(Protocol[T_contra]):
def __call__(self, *args: T_contra, **kwargs: T_contra) -> None: ...
class Proto6(Protocol):
def __call__(self, a: int, /, *args: Any, k: str, **kwargs: Any) -> None:
pass
class Proto7(Protocol):
def __call__(self, a: float, /, b: int, *, k: str, m: str) -> None:
pass
def func(
p1: Proto1,
p2: Proto2,
p3: Proto3,
p4: Proto4[...],
p5: Proto5[Any],
p7: Proto7,
c1: Callable[..., None],
c2: Callable[Concatenate[int, ...], None],
):
x1: Callable[..., None] = p1
x2: Proto1 = c1
x3: Callable[..., None] = p5
x4: Proto5[Any] = c1
x5: Callable[Concatenate[int, ...], None] = p2
x6: Proto2 = c2
x7: Callable[..., None] = p3
x8: Proto3 = c1
x9: Proto4[...] = p3
x10: Proto3 = p4
x11: Proto6 = p7

View File

@ -72,7 +72,7 @@ class Class5(metaclass=Meta1):
r5 = accepts_callable(Class5)
reveal_type(r5, expected_text="(*args: Any, **kwargs: Any) -> NoReturn")
reveal_type(r5, expected_text="(...) -> NoReturn")
class Class6Proxy: ...

View File

@ -136,7 +136,7 @@ class ParentClass:
my_method43: Callable[..., None]
def my_method44(self, *args: Any, **kwargs: Any) -> None: ...
def my_method44(self, *args: object, **kwargs: object) -> None: ...
def my_method45(self, __i: int) -> None: ...
@ -300,7 +300,7 @@ class ChildClass(ParentClass):
def my_method43(self, a: int, b: str, c: str) -> None: ...
# This should generate an error because kwargs is missing.
def my_method44(self, *args) -> None: ...
def my_method44(self, *object) -> None: ...
def my_method45(self, i: int, /) -> None: ...

View File

@ -67,6 +67,12 @@ test('CallbackProtocol9', () => {
TestUtils.validateResults(analysisResults, 2);
});
test('CallbackProtocol10', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['callbackProtocol10.py']);
TestUtils.validateResults(analysisResults, 0);
});
test('Assignment1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['assignment1.py']);