From e77f3a1125f15cf7c8a3b3834b107461276ec96b Mon Sep 17 00:00:00 2001 From: "evgeny.bovykin" Date: Wed, 23 Apr 2025 14:03:34 +0200 Subject: [PATCH] Update conformance test suite GitOrigin-RevId: da76f385342a1492bcd536750eea4f7ecc86a715 --- .../tests/_directives_deprecated_library.py | 51 +++ .../typing/conformance/tests/_ignored.txt | 9 +- .../tests/_qualifiers_final_annotation_1.py | 7 + .../tests/_qualifiers_final_annotation_2.py | 7 + .../tests/aliases_type_statement.py | 10 +- .../tests/annotations_coroutines.py | 5 +- .../tests/annotations_forward_refs.py | 5 +- .../tests/annotations_generators.py | 5 +- .../conformance/tests/callables_kwargs.py | 11 + .../conformance/tests/classes_classvar.py | 13 +- .../conformance/tests/classes_override.py | 18 +- .../tests/constructors_callable.py | 2 +- .../tests/dataclasses_transform_class.py | 3 + .../tests/dataclasses_transform_converter.py | 133 +++++++ .../tests/dataclasses_transform_meta.py | 3 + .../conformance/tests/dataclasses_usage.py | 3 +- .../tests/directives_deprecated.py | 120 ++++++ .../typing/conformance/tests/enums_members.py | 6 +- .../conformance/tests/generics_base_class.py | 25 ++ .../conformance/tests/generics_basic.py | 19 +- .../conformance/tests/generics_defaults.py | 8 +- .../tests/generics_defaults_referential.py | 12 +- .../conformance/tests/generics_scoping.py | 6 + .../conformance/tests/generics_self_basic.py | 3 + .../tests/generics_syntax_declarations.py | 6 +- .../tests/generics_syntax_infer_variance.py | 13 +- .../tests/generics_type_erasure.py | 4 +- .../tests/generics_typevartuple_basic.py | 4 +- .../conformance/tests/generics_variance.py | 16 +- .../tests/generics_variance_inference.py | 4 +- .../conformance/tests/overloads_basic.py | 40 +- .../tests/overloads_consistency.py | 118 ++++++ .../tests/overloads_definitions.py | 229 ++++++++++++ .../tests/overloads_definitions_stub.pyi | 150 ++++++++ .../conformance/tests/overloads_evaluation.py | 345 ++++++++++++++++++ .../tests/protocols_class_objects.py | 3 + .../conformance/tests/protocols_explicit.py | 10 +- .../conformance/tests/protocols_generic.py | 2 +- .../conformance/tests/protocols_merging.py | 4 +- .../tests/qualifiers_final_annotation.py | 23 +- .../conformance/tests/specialtypes_never.py | 4 + .../conformance/tests/tuples_type_compat.py | 43 ++- .../conformance/tests/tuples_unpacked.py | 4 +- .../tests/typeddicts_class_syntax.py | 10 +- .../conformance/tests/typeddicts_required.py | 3 + 45 files changed, 1403 insertions(+), 116 deletions(-) create mode 100644 python/testData/typing/conformance/tests/_directives_deprecated_library.py create mode 100644 python/testData/typing/conformance/tests/_qualifiers_final_annotation_1.py create mode 100644 python/testData/typing/conformance/tests/_qualifiers_final_annotation_2.py create mode 100644 python/testData/typing/conformance/tests/dataclasses_transform_converter.py create mode 100644 python/testData/typing/conformance/tests/directives_deprecated.py create mode 100644 python/testData/typing/conformance/tests/overloads_consistency.py create mode 100644 python/testData/typing/conformance/tests/overloads_definitions.py create mode 100644 python/testData/typing/conformance/tests/overloads_definitions_stub.pyi create mode 100644 python/testData/typing/conformance/tests/overloads_evaluation.py diff --git a/python/testData/typing/conformance/tests/_directives_deprecated_library.py b/python/testData/typing/conformance/tests/_directives_deprecated_library.py new file mode 100644 index 000000000000..454f5d69e655 --- /dev/null +++ b/python/testData/typing/conformance/tests/_directives_deprecated_library.py @@ -0,0 +1,51 @@ +""" +Support module for directive_deprecated. +""" + +from typing import Self, overload +from typing_extensions import deprecated + + +@deprecated("Use Spam instead") +class Ham: + ... + + +@deprecated("It is pining for the fjords") +def norwegian_blue(x: int) -> int: + ... + + +@overload +@deprecated("Only str will be allowed") +def foo(x: int) -> str: + ... + + +@overload +def foo(x: str) -> str: + ... + + +def foo(x: int | str) -> str: + ... + + +class Spam: + @deprecated("There is enough spam in the world") + def __add__(self, other: object) -> Self: + ... + + @property + @deprecated("All spam will be equally greasy") + def greasy(self) -> float: + ... + + @property + def shape(self) -> str: + ... + + @shape.setter + @deprecated("Shapes are becoming immutable") + def shape(self, value: str) -> None: + ... diff --git a/python/testData/typing/conformance/tests/_ignored.txt b/python/testData/typing/conformance/tests/_ignored.txt index 8250a8e3cb47..4229f6e97f12 100644 --- a/python/testData/typing/conformance/tests/_ignored.txt +++ b/python/testData/typing/conformance/tests/_ignored.txt @@ -3,7 +3,6 @@ aliases_recursive.py aliases_type_statement.py aliases_typealiastype.py aliases_variance.py -annotations_coroutines.py annotations_forward_refs.py annotations_generators.py annotations_typeexpr.py @@ -20,12 +19,16 @@ dataclasses_hash.py dataclasses_inheritance.py dataclasses_order.py dataclasses_slots.py +dataclasses_transform_converter.py dataclasses_transform_field.py dataclasses_transform_meta.py dataclasses_usage.py +directives_deprecated.py directives_type_checking.py directives_version_platform.py +enums_members.py generics_base_class.py +generics_basic.py generics_defaults.py generics_defaults_referential.py generics_paramspec_components.py @@ -49,6 +52,10 @@ namedtuples_type_compat.py namedtuples_usage.py narrowing_typeis.py overloads_basic.py +overloads_consistency.py +overloads_definitions.py +overloads_definitions_stub.pyi +overloads_evaluation.py protocols_class_objects.py protocols_definition.py protocols_explicit.py diff --git a/python/testData/typing/conformance/tests/_qualifiers_final_annotation_1.py b/python/testData/typing/conformance/tests/_qualifiers_final_annotation_1.py new file mode 100644 index 000000000000..b2521b19ce3f --- /dev/null +++ b/python/testData/typing/conformance/tests/_qualifiers_final_annotation_1.py @@ -0,0 +1,7 @@ +""" +Used as part of the test for the typing.Final special form. +""" + +from typing import Final + +TEN: Final[int] = 10 diff --git a/python/testData/typing/conformance/tests/_qualifiers_final_annotation_2.py b/python/testData/typing/conformance/tests/_qualifiers_final_annotation_2.py new file mode 100644 index 000000000000..603d8bcb67ae --- /dev/null +++ b/python/testData/typing/conformance/tests/_qualifiers_final_annotation_2.py @@ -0,0 +1,7 @@ +""" +Used as part of the test for the typing.Final special form. +""" + +from typing import Final + +PI: Final = 3.14 diff --git a/python/testData/typing/conformance/tests/aliases_type_statement.py b/python/testData/typing/conformance/tests/aliases_type_statement.py index 23fe137cb3a4..c373b53ef93b 100644 --- a/python/testData/typing/conformance/tests/aliases_type_statement.py +++ b/python/testData/typing/conformance/tests/aliases_type_statement.py @@ -48,10 +48,8 @@ type BadTypeAlias11 = 1 # E type BadTypeAlias12 = list or set # E type BadTypeAlias13 = f"{'int'}" # E -if 1 < 2: - type BadTypeAlias14 = int # E: redeclared -else: - type BadTypeAlias14 = int +type BadTypeAlias14 = int # E[TA14]: redeclared +type BadTypeAlias14 = int # E[TA14]: redeclared def func3(): @@ -87,5 +85,5 @@ type RecursiveTypeAlias4[T] = T | RecursiveTypeAlias4[str] # E: circular definit type RecursiveTypeAlias5[T] = T | list[RecursiveTypeAlias5[T]] -type RecursiveTypeAlias6 = RecursiveTypeAlias7 # E: circular definition -type RecursiveTypeAlias7 = RecursiveTypeAlias6 +type RecursiveTypeAlias6 = RecursiveTypeAlias7 # E[RTA6+]: circular definition +type RecursiveTypeAlias7 = RecursiveTypeAlias6 # E[RTA6+]: circular definition diff --git a/python/testData/typing/conformance/tests/annotations_coroutines.py b/python/testData/typing/conformance/tests/annotations_coroutines.py index b2200f6fe036..d5b145f45e9f 100644 --- a/python/testData/typing/conformance/tests/annotations_coroutines.py +++ b/python/testData/typing/conformance/tests/annotations_coroutines.py @@ -16,7 +16,10 @@ async def func1(ignored: int, /) -> str: return "spam" -assert_type(func1, Callable[[int], Coroutine[Any, Any, str]]) +# Don't use assert_type here because some type checkers infer +# the narrower type types.CoroutineType rather than typing.Coroutine +# in this case. +v1: Callable[[int], Coroutine[Any, Any, str]] = func1 async def func2() -> None: diff --git a/python/testData/typing/conformance/tests/annotations_forward_refs.py b/python/testData/typing/conformance/tests/annotations_forward_refs.py index b64a8e93613b..18f111e0cc1a 100644 --- a/python/testData/typing/conformance/tests/annotations_forward_refs.py +++ b/python/testData/typing/conformance/tests/annotations_forward_refs.py @@ -38,7 +38,7 @@ var1 = 1 # The following should all generate errors because they are not legal type # expressions, despite being enclosed in quotes. def invalid_annotations( - p1: "eval(" ".join(map(chr, [105, 110, 116])))", # E + p1: "eval(''.join(map(chr, [105, 110, 116])))", # E p2: "[int, str]", # E p3: "(int, str)", # E p4: "[int for i in range(1)]", # E @@ -88,6 +88,9 @@ class ClassD: y: int = 0 # E: Refers to local int, which isn't a legal type expression + def __init__(self) -> None: + self.ClassC = ClassC() + assert_type(ClassD.str, str) assert_type(ClassD.x, int) diff --git a/python/testData/typing/conformance/tests/annotations_generators.py b/python/testData/typing/conformance/tests/annotations_generators.py index 43d53c0e5264..2def79eaa850 100644 --- a/python/testData/typing/conformance/tests/annotations_generators.py +++ b/python/testData/typing/conformance/tests/annotations_generators.py @@ -179,7 +179,10 @@ async def generator29() -> AsyncIterator[int]: raise NotImplementedError -assert_type(generator29, Callable[[], Coroutine[Any, Any, AsyncIterator[int]]]) +# Don't use assert_type here because some type checkers infer +# the narrower type types.CoroutineType rather than typing.Coroutine +# in this case. +v1: Callable[[], Coroutine[Any, Any, AsyncIterator[int]]] = generator29 async def generator30() -> AsyncIterator[int]: diff --git a/python/testData/typing/conformance/tests/callables_kwargs.py b/python/testData/typing/conformance/tests/callables_kwargs.py index d8138efed66a..c52319fc1071 100644 --- a/python/testData/typing/conformance/tests/callables_kwargs.py +++ b/python/testData/typing/conformance/tests/callables_kwargs.py @@ -121,3 +121,14 @@ T = TypeVar("T", bound=TD2) def func6(**kwargs: Unpack[T]) -> None: # E: unpacked value must be a TypedDict, not a TypeVar bound to TypedDict. ... + +# > The situation where the destination callable contains **kwargs: Unpack[TypedDict] and +# > the source callable doesn’t contain **kwargs should be disallowed. This is because, +# > we cannot be sure that additional keyword arguments are not being passed in when an instance of a subclass +# > had been assigned to a variable with a base class type and then unpacked in the destination callable invocation + +def func7(*, v1: int, v3: str, v2: str = "") -> None: + ... + + +v7: TDProtocol6 = func7 # E: source does not have kwargs diff --git a/python/testData/typing/conformance/tests/classes_classvar.py b/python/testData/typing/conformance/tests/classes_classvar.py index 56bb2a7dd145..2f6ed04fb556 100644 --- a/python/testData/typing/conformance/tests/classes_classvar.py +++ b/python/testData/typing/conformance/tests/classes_classvar.py @@ -57,8 +57,14 @@ class ClassA(Generic[T, P]): good1: CV[int] = 1 good2: ClassVar[list[str]] = [] good3: ClassVar[Any] = 1 + # > If an assigned value is available, the type should be inferred as some type + # > to which this value is assignable. + # Here, type checkers could infer good4 as `float` or `Any`, for example. good4: ClassVar = 3.1 - good5: Annotated[ClassVar[list[int]], ""] = [] + # > If the `ClassVar` qualifier is used without any assigned value, the type + # > should be inferred as `Any`: + good5: ClassVar #E? Type checkers may error on uninitialized ClassVar + good6: Annotated[ClassVar[list[int]], ""] = [] def method1(self, a: ClassVar[int]): # E: ClassVar not allowed here x: ClassVar[str] = "" # E: ClassVar not allowed here @@ -75,7 +81,7 @@ bad12: TypeAlias = ClassVar[str] # E: ClassVar not allowed here assert_type(ClassA.good1, int) assert_type(ClassA.good2, list[str]) assert_type(ClassA.good3, Any) -assert_type(ClassA.good4, float) +assert_type(ClassA.good5, Any) class BasicStarship: @@ -83,6 +89,9 @@ class BasicStarship: damage: int # Instance variable without default stats: ClassVar[dict[str, int]] = {} # Class variable + def __init__(self, damage: int) -> None: + self.damage = damage + class Starship: captain: str = "Picard" diff --git a/python/testData/typing/conformance/tests/classes_override.py b/python/testData/typing/conformance/tests/classes_override.py index 8565ad7384ec..2f3147d2cd88 100644 --- a/python/testData/typing/conformance/tests/classes_override.py +++ b/python/testData/typing/conformance/tests/classes_override.py @@ -49,8 +49,8 @@ class ChildA(ParentA): def method2(self, x: int | str) -> int | str: # OK return 0 - @override - def method3(self) -> int: # E: no matching signature in ancestor + @override # E[method3] + def method3(self) -> int: # E[method3]: no matching signature in ancestor return 1 @overload # E[method4] @@ -61,7 +61,7 @@ class ChildA(ParentA): def method4(self, x: str) -> str: ... - @override + @override # E[method4] def method4(self, x: int | str) -> int | str: # E[method4]: no matching signature in ancestor return 0 @@ -75,18 +75,18 @@ class ChildA(ParentA): # > only normal methods but also @property, @staticmethod, and @classmethod. @staticmethod - @override - def static_method1() -> int: # E: no matching signature in ancestor + @override # E[static_method1] + def static_method1() -> int: # E[static_method1]: no matching signature in ancestor return 1 @classmethod - @override - def class_method1(cls) -> int: # E: no matching signature in ancestor + @override # E[class_method1] + def class_method1(cls) -> int: # E[class_method1]: no matching signature in ancestor return 1 @property - @override - def property1(self) -> int: # E: no matching signature in ancestor + @override # E[property1] + def property1(self) -> int: # E[property1]: no matching signature in ancestor return 1 diff --git a/python/testData/typing/conformance/tests/constructors_callable.py b/python/testData/typing/conformance/tests/constructors_callable.py index d50f93c3bab8..7ad04dcb74b0 100644 --- a/python/testData/typing/conformance/tests/constructors_callable.py +++ b/python/testData/typing/conformance/tests/constructors_callable.py @@ -114,7 +114,7 @@ class Class6: """__new__ that causes __init__ to be ignored""" def __new__(cls) -> Class6Proxy: - return Class6Proxy.__new__(cls) + return Class6Proxy() def __init__(self, x: int) -> None: """This __init__ is ignored for purposes of conversion""" diff --git a/python/testData/typing/conformance/tests/dataclasses_transform_class.py b/python/testData/typing/conformance/tests/dataclasses_transform_class.py index bb33fbd0c135..ad0aa835533d 100644 --- a/python/testData/typing/conformance/tests/dataclasses_transform_class.py +++ b/python/testData/typing/conformance/tests/dataclasses_transform_class.py @@ -36,6 +36,9 @@ class ModelBase: ) -> None: ... + def __init__(self, not_a_field: str) -> None: + self.not_a_field = not_a_field + class Customer1(ModelBase, frozen=True): id: int = model_field() diff --git a/python/testData/typing/conformance/tests/dataclasses_transform_converter.py b/python/testData/typing/conformance/tests/dataclasses_transform_converter.py new file mode 100644 index 000000000000..277998c7f525 --- /dev/null +++ b/python/testData/typing/conformance/tests/dataclasses_transform_converter.py @@ -0,0 +1,133 @@ +""" +Tests the dataclass_transform mechanism supports the "converter" parameter +in a field specifier class. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/dataclasses.html#converters + +from typing import ( + Any, + Callable, + TypeVar, + dataclass_transform, + overload, +) + +T = TypeVar("T") +S = TypeVar("S") + + +def model_field( + *, + converter: Callable[[S], T], + default: S | None = None, + default_factory: Callable[[], S] | None = None, +) -> T: + ... + + +@dataclass_transform(field_specifiers=(model_field,)) +class ModelBase: + ... + + +# > The converter must be a callable that must accept a single positional +# > argument (but may accept other optional arguments, which are ignored for +# > typing purposes). + + +def bad_converter1() -> int: + return 0 + + +def bad_converter2(*, x: int) -> int: + return 0 + + +class DC1(ModelBase): + field1: int = model_field(converter=bad_converter1) # E + field2: int = model_field(converter=bad_converter2) # E + + +# > The type of the first positional parameter provides the type of the +# > synthesized __init__ parameter for the field. + +# > The return type of the callable must be assignable to the field’s +# > declared type. + + +def converter_simple(s: str) -> int: + return int(s) + + +def converter_with_param_before_args(s: str, *args: int, **kwargs: int) -> int: + return int(s) + + +def converter_with_args(*args: str) -> int: + return int(args[0]) + + +@overload +def overloaded_converter(s: str) -> int: + ... + + +@overload +def overloaded_converter(s: list[str]) -> int: + ... + + +def overloaded_converter(s: str | list[str], *args: str) -> int | str: + return 0 + + +class ConverterClass: + @overload + def __init__(self, val: str) -> None: + ... + + @overload + def __init__(self, val: bytes) -> None: + ... + + def __init__(self, val: str | bytes) -> None: + pass + + +class DC2(ModelBase): + field0: int = model_field(converter=converter_simple) + field1: int = model_field(converter=converter_with_param_before_args) + field2: int = model_field(converter=converter_with_args) + field3: ConverterClass = model_field(converter=ConverterClass) + field4: int = model_field(converter=overloaded_converter) + field5: dict[str, str] = model_field(converter=dict, default=()) + + +DC2(1, "f1", "f2", b"f3", []) # E +DC2("f0", "f1", "f2", 1, []) # E +DC2("f0", "f1", "f2", "f3", 3j) # E + + +dc1 = DC2("f0", "f1", "f2", b"f6", []) + +dc1.field0 = "f1" +dc1.field3 = "f6" +dc1.field3 = b"f6" + +dc1.field0 = 1 # E +dc1.field3 = 1 # E + +dc2 = DC2("f0", "f1", "f2", "f6", "1", (("a", "1"), ("b", "2"))) + + +# > If default or default_factory are provided, the type of the default value +# > should be assignable to the first positional parameter of the converter. + + +class DC3(ModelBase): + field0: int = model_field(converter=converter_simple, default="") + field1: int = model_field(converter=converter_simple, default=1) # E + + field2: int = model_field(converter=converter_simple, default_factory=str) + field3: int = model_field(converter=converter_simple, default_factory=int) # E diff --git a/python/testData/typing/conformance/tests/dataclasses_transform_meta.py b/python/testData/typing/conformance/tests/dataclasses_transform_meta.py index 4414ce725952..dff70a2e4c14 100644 --- a/python/testData/typing/conformance/tests/dataclasses_transform_meta.py +++ b/python/testData/typing/conformance/tests/dataclasses_transform_meta.py @@ -25,6 +25,9 @@ def model_field( class ModelMeta(type): not_a_field: str + def __init__(self, not_a_field: str) -> None: + self.not_a_field = not_a_field + class ModelBase(metaclass=ModelMeta): def __init_subclass__( diff --git a/python/testData/typing/conformance/tests/dataclasses_usage.py b/python/testData/typing/conformance/tests/dataclasses_usage.py index 4338cfa026e1..7dbba033e8d7 100644 --- a/python/testData/typing/conformance/tests/dataclasses_usage.py +++ b/python/testData/typing/conformance/tests/dataclasses_usage.py @@ -117,6 +117,7 @@ class DC8(DC7): def __init__(self, a: DC7, y: int): self.__dict__ = a.__dict__ + self.y = y a = DC7(3) @@ -173,7 +174,7 @@ class DC13: x_squared: int -# This should generate an error because there is no +# This should generate an error because there is no matching # override __init__ method and no synthesized __init__. DC13(3) # E diff --git a/python/testData/typing/conformance/tests/directives_deprecated.py b/python/testData/typing/conformance/tests/directives_deprecated.py new file mode 100644 index 000000000000..e56b470c4d27 --- /dev/null +++ b/python/testData/typing/conformance/tests/directives_deprecated.py @@ -0,0 +1,120 @@ +""" +Tests the warnings.deprecated function. +""" + +# pyright: reportDeprecated=true + +# Specification: https://typing.readthedocs.io/en/latest/spec/directives.html#deprecated +# See also https://peps.python.org/pep-0702/ + +from typing import Protocol, override +from typing_extensions import deprecated + +# > Type checkers should produce a diagnostic whenever they encounter a usage of an object +# > marked as deprecated. [...] For deprecated classes and functions, this includes: + +# > * `from` imports + +from _directives_deprecated_library import Ham # E: Use of deprecated class Ham +import _directives_deprecated_library as library + + +# > * References through module, class, or instance attributes + +library.norwegian_blue(1) # E: Use of deprecated function norwegian_blue +map(library.norwegian_blue, [1, 2, 3]) # E: Use of deprecated function norwegian_blue + + +# > For deprecated overloads, this includes all calls that resolve to the deprecated overload. + +library.foo(1) # E: Use of deprecated overload for foo +library.foo("x") # OK + + +ham = Ham() # E?: OK (already reported above) + + +# > * Any syntax that indirectly triggers a call to the function. + +spam = library.Spam() + +_ = spam + 1 # E: Use of deprecated method Spam.__add__ +spam += 1 # E: Use of deprecated method Spam.__add__ + +spam.greasy # E: Use of deprecated property Spam.greasy +spam.shape # OK + +spam.shape = "cube" # E: Use of deprecated property setter Spam.shape +spam.shape += "cube" # E: Use of deprecated property setter Spam.shape + + +class Invocable: + @deprecated("Deprecated") + def __call__(self) -> None: + ... + + +invocable = Invocable() +invocable() # E: Use of deprecated method __call__ + + +# > * Any usage of deprecated objects in their defining module + + +@deprecated("Deprecated") +def lorem() -> None: + ... + + +lorem() # E: Use of deprecated function lorem + + +# > There are additional scenarios where deprecations could come into play. +# > For example, an object may implement a `typing.Protocol`, +# > but one of the methods required for protocol compliance is deprecated. +# > As scenarios such as this one appear complex and relatively unlikely to come up in practice, +# > this PEP does not mandate that type checkers detect them. + + +class SupportsFoo1(Protocol): + @deprecated("Deprecated") + def foo(self) -> None: + ... + + def bar(self) -> None: + ... + + +class FooConcrete1(SupportsFoo1): + @override + def foo(self) -> None: # E?: Implementation of deprecated method foo + ... + + def bar(self) -> None: + ... + + +def foo_it(f: SupportsFoo1) -> None: + f.foo() # E: Use of deprecated method foo + f.bar() + + +class SupportsFoo2(Protocol): + def foo(self) -> None: + ... + + +class FooConcrete2: + @deprecated("Deprecated") + def foo(self) -> None: + ... + + +def takes_foo(f: SupportsFoo2) -> None: + ... + + +def caller(c: FooConcrete2) -> None: + takes_foo( + c + ) # E?: FooConcrete2 is a SupportsFoo2, but only because of a deprecated method diff --git a/python/testData/typing/conformance/tests/enums_members.py b/python/testData/typing/conformance/tests/enums_members.py index 11bcde2ec8da..52007e4d4689 100644 --- a/python/testData/typing/conformance/tests/enums_members.py +++ b/python/testData/typing/conformance/tests/enums_members.py @@ -5,7 +5,7 @@ Tests that the type checker can distinguish enum members from non-members. # Specification: https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members from enum import Enum, member, nonmember -from typing import Literal, assert_type, reveal_type +from typing import Literal, assert_type # > If an attribute is defined in the class body with a type annotation but # > with no assigned value, a type checker should assume this is a non-member @@ -19,6 +19,10 @@ class Pet(Enum): # E?: Uninitialized attributes (pyre) CAT = 1 # Member attribute DOG = 2 # Member attribute + def __init__(self, genus: str, species: str) -> None: + self.genus = genus + self.species = species + assert_type(Pet.genus, str) assert_type(Pet.species, str) diff --git a/python/testData/typing/conformance/tests/generics_base_class.py b/python/testData/typing/conformance/tests/generics_base_class.py index bfcb634760d5..61de59455209 100644 --- a/python/testData/typing/conformance/tests/generics_base_class.py +++ b/python/testData/typing/conformance/tests/generics_base_class.py @@ -71,3 +71,28 @@ class BadClass1(Generic[T, T]): # E class GoodClass1(dict[T, T]): # OK pass + +# > Type variables are applied to the defined class in the order in which +# > they first appear in any generic base classes. + +T1 = TypeVar("T1") +T2 = TypeVar("T2") +T3 = TypeVar("T3") + +class Parent1(Generic[T1, T2]): ... +class Parent2(Generic[T1, T2]): ... +class Child(Parent1[T1, T3], Parent2[T2, T3]): ... + +def takes_parent1(x: Parent1[int, bytes]): ... +def takes_parent2(x: Parent2[str, bytes]): ... + +child: Child[int, bytes, str] = Child() +takes_parent1(child) # OK +takes_parent2(child) # OK + +# > A type checker should report an error when the type variable order is +# > inconsistent. + +class Grandparent(Generic[T1, T2]): ... +class Parent(Grandparent[T1, T2]): ... +class BadChild(Parent[T1, T2], Grandparent[T2, T1]): ... # E diff --git a/python/testData/typing/conformance/tests/generics_basic.py b/python/testData/typing/conformance/tests/generics_basic.py index 0a35336bddb5..179d84758d3e 100644 --- a/python/testData/typing/conformance/tests/generics_basic.py +++ b/python/testData/typing/conformance/tests/generics_basic.py @@ -7,7 +7,7 @@ Tests for basic usage of generics. from __future__ import annotations from collections.abc import Sequence -from typing import Any, Generic, TypeVar, assert_type +from typing import Any, Generic, Protocol, TypeVar, assert_type T = TypeVar("T") @@ -157,6 +157,23 @@ def test_my_map(m1: MyMap1[str, int], m2: MyMap2[int, str]): m1[0] # E m2[0] # E +# > All arguments to ``Generic`` or ``Protocol`` must be type variables. + +class Bad1(Generic[int]): ... # E +class Bad2(Protocol[int]): ... # E + +# > All type parameters for the class must appear within the ``Generic`` or +# > ``Protocol`` type argument list. + +T_co = TypeVar("T_co", covariant=True) +S_co = TypeVar("S_co", covariant=True) + +class Bad3(Iterable[T_co], Generic[S_co]): ... # E +class Bad4(Iterable[T_co], Protocol[S_co]): ... # E + +# > The above rule does not apply to a bare ``Protocol`` base class. + +class MyIterator(Iterator[T_co], Protocol): ... # OK # > You can use multiple inheritance with ``Generic`` diff --git a/python/testData/typing/conformance/tests/generics_defaults.py b/python/testData/typing/conformance/tests/generics_defaults.py index 0857b9291ae5..af347b102cfd 100644 --- a/python/testData/typing/conformance/tests/generics_defaults.py +++ b/python/testData/typing/conformance/tests/generics_defaults.py @@ -100,15 +100,15 @@ assert_type(Class_TypeVarTuple[int, bool](), Class_TypeVarTuple[int, bool]) # > subtype of ``bound``. If not, the type checker should generate an # > error. -TypeVar("Ok", bound=float, default=int) # OK -TypeVar("Invalid", bound=str, default=int) # E: the bound and default are incompatible +Ok1 = TypeVar("Ok1", bound=float, default=int) # OK +Invalid1 = TypeVar("Invalid1", bound=str, default=int) # E: the bound and default are incompatible # > For constrained ``TypeVar``\ s, the default needs to be one of the # > constraints. A type checker should generate an error even if it is a # > subtype of one of the constraints. -TypeVar("Ok", float, str, default=float) # OK -TypeVar("Invalid", float, str, default=int) # E: expected one of float or str got int +Ok2 = TypeVar("Ok2", float, str, default=float) # OK +Invalid2 = TypeVar("Invalid2", float, str, default=int) # E: expected one of float or str got int # > In generic functions, type checkers may use a type parameter's default when the diff --git a/python/testData/typing/conformance/tests/generics_defaults_referential.py b/python/testData/typing/conformance/tests/generics_defaults_referential.py index 34b23600731c..a033d9c5caec 100644 --- a/python/testData/typing/conformance/tests/generics_defaults_referential.py +++ b/python/testData/typing/conformance/tests/generics_defaults_referential.py @@ -63,19 +63,19 @@ class Foo3(Generic[S1]): # > ``T1``'s bound must be a subtype of ``T2``'s bound. X1 = TypeVar("X1", bound=int) -TypeVar("Ok1", default=X1, bound=float) # OK -TypeVar("AlsoOk1", default=X1, bound=int) # OK -TypeVar("Invalid1", default=X1, bound=str) # E: int is not a subtype of str +Ok1 = TypeVar("Ok1", default=X1, bound=float) # OK +AlsoOk1 = TypeVar("AlsoOk1", default=X1, bound=int) # OK +Invalid1 = TypeVar("Invalid1", default=X1, bound=str) # E: int is not a subtype of str # > The constraints of ``T2`` must be a superset of the constraints of ``T1``. Y1 = TypeVar("Y1", bound=int) -TypeVar("Invalid2", float, str, default=Y1) # E: upper bound int is incompatible with constraints float or str +Invalid2 = TypeVar("Invalid2", float, str, default=Y1) # E: upper bound int is incompatible with constraints float or str Y2 = TypeVar("Y2", int, str) -TypeVar("AlsoOk2", int, str, bool, default=Y2) # OK -TypeVar("AlsoInvalid2", bool, complex, default=Y2) # E: {bool, complex} is not a superset of {int, str} +AlsoOk2 = TypeVar("AlsoOk2", int, str, bool, default=Y2) # OK +AlsoInvalid2 = TypeVar("AlsoInvalid2", bool, complex, default=Y2) # E: {bool, complex} is not a superset of {int, str} # > Type parameters are valid as parameters to generics inside of a diff --git a/python/testData/typing/conformance/tests/generics_scoping.py b/python/testData/typing/conformance/tests/generics_scoping.py index fcdb1957e765..9b6ec7d181f5 100644 --- a/python/testData/typing/conformance/tests/generics_scoping.py +++ b/python/testData/typing/conformance/tests/generics_scoping.py @@ -77,12 +77,18 @@ class Outer(Generic[T]): class AlsoBad: x: list[T] # E + def __init__(self, x: list[T]) -> None: + self.x = x + class Inner(Iterable[S]): # OK ... attr: Inner[T] # OK alias: TypeAlias = list[T] # E + def __init__(self, attr: Inner[T]) -> None: + self.attr = attr + # Test unbound type variables at global scope global_var1: T # E diff --git a/python/testData/typing/conformance/tests/generics_self_basic.py b/python/testData/typing/conformance/tests/generics_self_basic.py index fd6a78719482..b7da6a38d3e3 100644 --- a/python/testData/typing/conformance/tests/generics_self_basic.py +++ b/python/testData/typing/conformance/tests/generics_self_basic.py @@ -58,6 +58,9 @@ assert_type(Circle.from_config({}), Circle) class Container(Generic[T]): value: T + def __init__(self, value: T) -> None: + self.value = value + def set_value(self, value: T) -> Self: ... # This should generate an error because Self isn't subscriptable. diff --git a/python/testData/typing/conformance/tests/generics_syntax_declarations.py b/python/testData/typing/conformance/tests/generics_syntax_declarations.py index 21ab84cbacc4..62d2d1019800 100644 --- a/python/testData/typing/conformance/tests/generics_syntax_declarations.py +++ b/python/testData/typing/conformance/tests/generics_syntax_declarations.py @@ -7,13 +7,11 @@ Validates the type parameter syntax introduced in PEP 695. # This generic class is parameterized by a TypeVar T, a # TypeVarTuple Ts, and a ParamSpec P. -from typing import Generic, ParamSpec, Protocol, TypeVar, TypeVarTuple, assert_type +from typing import Generic, Protocol class ChildClass[T, *Ts, **P]: - assert_type(T, TypeVar) - assert_type(Ts, TypeVarTuple) - assert_type(P, ParamSpec) + pass class ClassA[T](Generic[T]): # E: Runtime error diff --git a/python/testData/typing/conformance/tests/generics_syntax_infer_variance.py b/python/testData/typing/conformance/tests/generics_syntax_infer_variance.py index f19b6a18108c..62d14ccb5727 100644 --- a/python/testData/typing/conformance/tests/generics_syntax_infer_variance.py +++ b/python/testData/typing/conformance/tests/generics_syntax_infer_variance.py @@ -51,8 +51,15 @@ class ShouldBeCovariant4(Generic[T]): x: T -vo4_1: ShouldBeCovariant4[float] = ShouldBeCovariant4[int](1) # OK -vo4_4: ShouldBeCovariant4[int] = ShouldBeCovariant4[float](1.0) # E +# This test is problematic as of Python 3.13 because of the +# newly synthesized "__replace__" method, which causes the type +# variable to be inferred as invariant rather than covariant. +# See https://github.com/python/mypy/issues/17623#issuecomment-2266312738 +# for details. Until we sort this out, we'll leave this test commented +# out. + +# vo4_1: ShouldBeCovariant4[float] = ShouldBeCovariant4[int](1) # OK +# vo4_4: ShouldBeCovariant4[int] = ShouldBeCovariant4[float](1.0) # E class ShouldBeCovariant5(Generic[T]): @@ -84,7 +91,7 @@ class ShouldBeInvariant1(Generic[T]): self._value = value @property - def value(self): + def value(self) -> T: return self._value @value.setter diff --git a/python/testData/typing/conformance/tests/generics_type_erasure.py b/python/testData/typing/conformance/tests/generics_type_erasure.py index f88af0e598dd..c7462259a471 100644 --- a/python/testData/typing/conformance/tests/generics_type_erasure.py +++ b/python/testData/typing/conformance/tests/generics_type_erasure.py @@ -10,7 +10,9 @@ T = TypeVar("T") class Node(Generic[T]): label: T - def __init__(self, label: T | None = None) -> None: ... + def __init__(self, label: T | None = None) -> None: + if label is not None: + self.label = label assert_type(Node(''), Node[str]) assert_type(Node(0), Node[int]) diff --git a/python/testData/typing/conformance/tests/generics_typevartuple_basic.py b/python/testData/typing/conformance/tests/generics_typevartuple_basic.py index 77a07c1fff2b..daf504fcf440 100644 --- a/python/testData/typing/conformance/tests/generics_typevartuple_basic.py +++ b/python/testData/typing/conformance/tests/generics_typevartuple_basic.py @@ -41,8 +41,8 @@ v3: Array[Time, Batch, Height, Width] = Array( v4: Array[Height, Width] = Array(Height(1)) # E v5: Array[Batch, Height, Width] = Array((Batch(1), Width(1))) # E -v6: Array[Time, Batch, Height, Width] = Array( # E - (Time(1), Batch(1), Width(1), Height(1)) +v6: Array[Time, Batch, Height, Width] = Array( # E[v6] + (Time(1), Batch(1), Width(1), Height(1)) # E[v6] ) diff --git a/python/testData/typing/conformance/tests/generics_variance.py b/python/testData/typing/conformance/tests/generics_variance.py index c4b8682c4505..0b3e732b40db 100644 --- a/python/testData/typing/conformance/tests/generics_variance.py +++ b/python/testData/typing/conformance/tests/generics_variance.py @@ -122,14 +122,14 @@ class CoContra_Child1(CoContra[T_co, T_contra]): # OK ... -class CoContra_Child2( - CoContra[T_co, T_co] # E: Second type arg must be contravariant +class CoContra_Child2( # E[CoContra_Child2]: Second type arg must be contravariant + CoContra[T_co, T_co] # E[CoContra_Child2]: Second type arg must be contravariant ): ... -class CoContra_Child3( - CoContra[T_contra, T_contra] # E: First type arg must be covariant +class CoContra_Child3( # E[CoContra_Child3]: First type arg must be covariant + CoContra[T_contra, T_contra] # E[CoContra_Child3]: First type arg must be covariant ): ... @@ -138,8 +138,8 @@ class CoContra_Child4(CoContra[T, T]): # OK ... -class CoContra_Child5( - CoContra[Co[T_co], Co[T_co]] # E: Second type arg must be contravariant +class CoContra_Child5( # E[CoContra_Child5]: Second type arg must be contravariant + CoContra[Co[T_co], Co[T_co]] # E[CoContra_Child5]: Second type arg must be contravariant ): ... @@ -192,7 +192,7 @@ class CoToContraToContra_WithTA(Contra_TA[Co_TA[Contra_TA[T_contra]]]): # E ... -class ContraToContraToContra_WithTA( - Contra_TA[Contra_TA[Contra_TA[T_co]]] # E +class ContraToContraToContra_WithTA( # E[ContraToContraToContra_WithTA] + Contra_TA[Contra_TA[Contra_TA[T_co]]] # E[ContraToContraToContra_WithTA] ): ... diff --git a/python/testData/typing/conformance/tests/generics_variance_inference.py b/python/testData/typing/conformance/tests/generics_variance_inference.py index c2de93767f6a..c7e09760b5dc 100644 --- a/python/testData/typing/conformance/tests/generics_variance_inference.py +++ b/python/testData/typing/conformance/tests/generics_variance_inference.py @@ -41,7 +41,7 @@ vco1_1: ShouldBeCovariant1[float] = ShouldBeCovariant1[int]() # OK vco1_2: ShouldBeCovariant1[int] = ShouldBeCovariant1[float]() # E -class ShouldBeCovariant2[T](Sequence[T]): +class ShouldBeCovariant2[T](ShouldBeCovariant1[T]): pass @@ -85,7 +85,7 @@ class ShouldBeInvariant1[T]: self._value = value @property - def value(self): + def value(self) -> T: return self._value @value.setter diff --git a/python/testData/typing/conformance/tests/overloads_basic.py b/python/testData/typing/conformance/tests/overloads_basic.py index 86c81765df21..ec0fd0699726 100644 --- a/python/testData/typing/conformance/tests/overloads_basic.py +++ b/python/testData/typing/conformance/tests/overloads_basic.py @@ -1,16 +1,18 @@ """ -Tests the basic typing.overload behavior described in PEP 484. +Tests the behavior of typing.overload. """ # Specification: https://typing.readthedocs.io/en/latest/spec/overload.html#overload -# Note: The behavior of @overload is severely under-specified by PEP 484 leading -# to significant divergence in behavior across type checkers. This is something -# we will likely want to address in a future update to the typing spec. For now, -# this conformance test will cover only the most basic functionality described -# in PEP 484. - -from typing import Any, Callable, Iterable, Iterator, TypeVar, assert_type, overload +from typing import ( + Any, + Callable, + Iterable, + Iterator, + TypeVar, + assert_type, + overload, +) class Bytes: @@ -57,25 +59,3 @@ def map( def map(func: Any, iter1: Any, iter2: Any = ...) -> Any: pass - -# At least two overload signatures should be provided. -@overload # E[func1] -def func1() -> None: # E[func1]: At least two overloads must be present - ... - - -def func1() -> None: - pass - - -# > In regular modules, a series of @overload-decorated definitions must be -# > followed by exactly one non-@overload-decorated definition (for the same -# > function/method). -@overload # E[func2] -def func2(x: int) -> int: # E[func2]: no implementation - ... - - -@overload -def func2(x: str) -> str: - ... diff --git a/python/testData/typing/conformance/tests/overloads_consistency.py b/python/testData/typing/conformance/tests/overloads_consistency.py new file mode 100644 index 000000000000..58533c1255d9 --- /dev/null +++ b/python/testData/typing/conformance/tests/overloads_consistency.py @@ -0,0 +1,118 @@ +""" +Tests consistency of overloads with implementation. +""" + +from typing import Callable, Coroutine, overload +from types import CoroutineType + +# > If an overload implementation is defined, type checkers should validate +# > that it is consistent with all of its associated overload signatures. +# > The implementation should accept all potential sets of arguments +# > that are accepted by the overloads and should produce all potential return +# > types produced by the overloads. In typing terms, this means the input +# > signature of the implementation should be :term:`assignable` to the input +# > signatures of all overloads, and the return type of all overloads should be +# > assignable to the return type of the implementation. + +# Return type of all overloads must be assignable to return type of +# implementation: + +@overload +def return_type(x: int) -> int: + ... + +@overload +def return_type(x: str) -> str: # E[return_type] + ... + +def return_type(x: int | str) -> int: # E[return_type] an overload returns `str`, not assignable to `int` + return 1 + + +# Input signature of implementation must be assignable to signature of each +# overload. We don't attempt a thorough testing of input signature +# assignability here; see `callables_subtyping.py` for that: + +@overload +def parameter_type(x: int) -> int: + ... + +@overload +def parameter_type(x: str) -> str: # E[parameter_type] + ... + +def parameter_type(x: int) -> int | str: # E[parameter_type] impl type of `x` must be assignable from overload types of `x` + return 1 + + +# > Overloads are allowed to use a mixture of ``async def`` and ``def`` statements +# > within the same overload definition. Type checkers should convert +# > ``async def`` statements to a non-async signature (wrapping the return +# > type in a ``Coroutine``) before testing for implementation consistency +# > and overlapping overloads (described below). + +# ...and also... + +# > When a type checker checks the implementation for consistency with overloads, +# > it should first apply any transforms that change the effective type of the +# > implementation including the presence of a ``yield`` statement in the +# > implementation body, the use of ``async def``, and the presence of additional +# > decorators. + +# An overload can explicitly return `Coroutine`, while the implementation is an +# `async def`: + +@overload +def returns_coroutine(x: int) -> CoroutineType[None, None, int]: + ... + +@overload +async def returns_coroutine(x: str) -> str: + ... + +async def returns_coroutine(x: int | str) -> int | str: + return 1 + + +# The implementation can explicitly return `Coroutine`, while overloads are +# `async def`: + +@overload +async def returns_coroutine_2(x: int) -> int: + ... + +@overload +async def returns_coroutine_2(x: str) -> str: + ... + +def returns_coroutine_2(x: int | str) -> Coroutine[None, None, int | str]: + return _wrapped(x) + +async def _wrapped(x: int | str) -> int | str: + return 2 + +# Decorator transforms are applied before checking overload consistency: + +def _deco_1(f: Callable) -> Callable[[int], int]: + def wrapped(_x: int, /) -> int: + return 1 + return wrapped + +def _deco_2(f: Callable) -> Callable[[int | str], int | str]: + def wrapped(_x: int | str, /) -> int | str: + return 1 + return wrapped + +@overload +@_deco_1 +def decorated() -> None: + ... + +@overload +def decorated(x: str, /) -> str: + ... + +@_deco_2 +def decorated(y: bytes, z: bytes) -> bytes: + return b"" + diff --git a/python/testData/typing/conformance/tests/overloads_definitions.py b/python/testData/typing/conformance/tests/overloads_definitions.py new file mode 100644 index 000000000000..bf8be66bb9bb --- /dev/null +++ b/python/testData/typing/conformance/tests/overloads_definitions.py @@ -0,0 +1,229 @@ +""" +Tests valid/invalid definition of overloaded functions. +""" + +from abc import ABC, abstractmethod +from typing import ( + final, + Protocol, + overload, + override, +) + + +# > At least two @overload-decorated definitions must be present. +@overload # E[func1] +def func1() -> None: # E[func1]: At least two overloads must be present + ... + + +def func1() -> None: + pass + + +# > The ``@overload``-decorated definitions must be followed by an overload +# > implementation, which does not include an ``@overload`` decorator. Type +# > checkers should report an error or warning if an implementation is missing. +@overload # E[func2] +def func2(x: int) -> int: # E[func2]: no implementation + ... + + +@overload +def func2(x: str) -> str: ... + + +# > Overload definitions within stub files, protocols, and on abstract methods +# > within abstract base classes are exempt from this check. +class MyProto(Protocol): + @overload + def func3(self, x: int) -> int: ... + + @overload + def func3(self, x: str) -> str: ... + + +class MyAbstractBase(ABC): + @overload + @abstractmethod + def func4(self, x: int) -> int: ... + + @overload + @abstractmethod + def func4(self, x: str) -> str: ... + + # A non-abstract method in an abstract base class still requires an + # implementation: + + @overload # E[not_abstract] + def not_abstract(self, x: int) -> int: # E[not_abstract] no implementation + ... + + @overload + def not_abstract(self, x: str) -> str: ... + + +# > If one overload signature is decorated with ``@staticmethod`` or +# > ``@classmethod``, all overload signatures must be similarly decorated. The +# > implementation, if present, must also have a consistent decorator. Type +# > checkers should report an error if these conditions are not met. +class C: + @overload # E[func5] + @staticmethod + def func5(x: int, /) -> int: # E[func5] + ... + + @overload + @staticmethod + def func5(x: str, /) -> str: # E[func5] + ... + + def func5(*args: object) -> int | str: # E[func5] + return 1 + + @overload # E[func6] + @classmethod + def func6(cls, x: int, /) -> int: # E[func6] + ... + + @overload + def func6(self, x: str, /) -> str: # E[func6] + ... + + @classmethod + def func6(cls, *args: int | str) -> int | str: # E[func6] + return 1 + + +# > If a ``@final`` or ``@override`` decorator is supplied for a function with +# > overloads, the decorator should be applied only to the overload +# > implementation if it is present. If an overload implementation isn't present +# > (for example, in a stub file), the ``@final`` or ``@override`` decorator +# > should be applied only to the first overload. Type checkers should enforce +# > these rules and generate an error when they are violated. If a ``@final`` or +# > ``@override`` decorator follows these rules, a type checker should treat the +# > decorator as if it is present on all overloads. +class Base: + # This is a good definition of an overloaded final method (@final decorator + # on implementation only): + + @overload + def final_method(self, x: int) -> int: ... + + @overload + def final_method(self, x: str) -> str: ... + + @final + def final_method(self, x: int | str) -> int | str: ... + + # The @final decorator should not be on one of the overloads: + + @overload # E[invalid_final] @final should be on implementation only + @final + def invalid_final(self, x: int) -> int: # E[invalid_final] + ... + + @overload + def invalid_final(self, x: str) -> str: # E[invalid_final] + ... + + def invalid_final(self, x: int | str) -> int | str: ... + + # The @final decorator should not be on multiple overloads and + # implementation: + + @overload # E[invalid_final_2+]: @final should be on implementation only + @final # E[invalid_final_2+] + def invalid_final_2(self, x: int) -> int: # E[invalid_final_2+] + ... + + @overload + @final # E[invalid_final_2+] + def invalid_final_2(self, x: str) -> str: # E[invalid_final_2+] + ... + + @final + def invalid_final_2(self, x: int | str) -> int | str: ... + + # These methods are just here for the @override test below. We use an + # overload because mypy doesn't like overriding a non-overloaded method + # with an overloaded one, even if LSP isn't violated. That could be its own + # specification question, but it's not what we're trying to test here: + + @overload + def good_override(self, x: int) -> int: ... + + @overload + def good_override(self, x: str) -> str: ... + + def good_override(self, x: int | str) -> int | str: ... + + @overload + def to_override(self, x: int) -> int: ... + + @overload + def to_override(self, x: str) -> str: ... + + def to_override(self, x: int | str) -> int | str: ... + + +class Child(Base): # E[override-final] + # The correctly-decorated @final method `Base.final_method` should cause an + # error if overridden in a child class (we use an overload here to avoid + # questions of override LSP compatibility and focus only on the override): + + @overload # E[override-final] + def final_method(self, x: int) -> int: ... + + @overload + def final_method(self, x: str) -> str: ... + + def final_method( # E[override-final] can't override final method + self, x: int | str + ) -> int | str: # E[override-final] can't override final method + ... + + # This is the right way to mark an overload as @override (decorate + # implementation only), so the use of @override should cause an error + # (because there's no `Base.bad_override` method): + + @overload # E[bad_override] marked as override but doesn't exist in base + def bad_override(self, x: int) -> int: # E[bad_override] + ... + + @overload + def bad_override(self, x: str) -> str: ... + + @override + def bad_override(self, x: int | str) -> int | str: # E[bad_override] + ... + + # This is also a correctly-decorated overloaded @override, which is + # overriding a method that does exist in the base, so there should be no + # error. We need both this test and the previous one, because in the + # previous test, an incorrect error about the use of @override decorator + # could appear on the same line as the expected error about overriding a + # method that doesn't exist in base: + + @overload + def good_override(self, x: int) -> int: ... + + @overload + def good_override(self, x: str) -> str: ... + + @override + def good_override(self, x: int | str) -> int | str: ... + + # This is the wrong way to use @override with an overloaded method, and + # should emit an error: + + @overload # E[override_impl+]: @override should appear only on implementation + @override # E[override_impl+] + def to_override(self, x: int) -> int: ... # E[override_impl+] + + @overload + @override # E[override_impl+] + def to_override(self, x: str) -> str: ... # E[override_impl+] + + @override + def to_override(self, x: int | str) -> int | str: ... diff --git a/python/testData/typing/conformance/tests/overloads_definitions_stub.pyi b/python/testData/typing/conformance/tests/overloads_definitions_stub.pyi new file mode 100644 index 000000000000..a61bd0593fea --- /dev/null +++ b/python/testData/typing/conformance/tests/overloads_definitions_stub.pyi @@ -0,0 +1,150 @@ +""" +Tests valid/invalid definition of overloaded functions in stub files, where the +rules differ from non-stubs, since an implementation is not required. +""" + +from typing import ( + final, + overload, + override, +) + +# > At least two @overload-decorated definitions must be present. +@overload # E[func1] +def func1() -> None: # E[func1]: At least two overloads must be present + ... + +# > The ``@overload``-decorated definitions must be followed by an overload +# > implementation, which does not include an ``@overload`` decorator. Type +# > checkers should report an error or warning if an implementation is missing. +# > Overload definitions within stub files, protocols, and on abstract methods +# > within abstract base classes are exempt from this check. +@overload +def func2(x: int) -> int: ... +@overload +def func2(x: str) -> str: ... + +# > If one overload signature is decorated with ``@staticmethod`` or +# > ``@classmethod``, all overload signatures must be similarly decorated. The +# > implementation, if present, must also have a consistent decorator. Type +# > checkers should report an error if these conditions are not met. +class C: + @overload # E[func5] + def func5(self, x: int, /) -> int: # E[func5] + ... + @overload + @staticmethod + def func5(x: str, /) -> str: # E[func5] + ... + @overload # E[func6] + @classmethod + def func6(cls, x: int, /) -> int: # E[func6] + ... + @overload + def func6(self, *args: str) -> str: # E[func6] + ... + +# > If a ``@final`` or ``@override`` decorator is supplied for a function with +# > overloads, the decorator should be applied only to the overload +# > implementation if it is present. If an overload implementation isn't present +# > (for example, in a stub file), the ``@final`` or ``@override`` decorator +# > should be applied only to the first overload. Type checkers should enforce +# > these rules and generate an error when they are violated. If a ``@final`` or +# > ``@override`` decorator follows these rules, a type checker should treat the +# > decorator as if it is present on all overloads. +class Base: + # This is a good definition of an overloaded final method in a stub (@final + # decorator on first overload only): + + @overload + @final + def final_method(self, x: int) -> int: ... + @overload + def final_method(self, x: str) -> str: ... + + # The @final decorator should not be on multiple overloads: + + @overload # E[invalid_final] @final should be on first overload + @final + def invalid_final(self, x: int) -> int: # E[invalid_final] + ... + @overload # E[invalid_final] + @final + def invalid_final(self, x: str) -> str: # E[invalid_final] + ... + @overload + def invalid_final(self, x: bytes) -> bytes: ... + + # The @final decorator should not be on all overloads: + + @overload # E[invalid_final_2] @final should be on first overload + @final + def invalid_final_2(self, x: int) -> int: # E[invalid_final_2] + ... + @overload # E[invalid_final_2] + @final + def invalid_final_2(self, x: str) -> str: ... # E[invalid_final_2] + + # These methods are just here for the @override test below. We use an + # overload because mypy doesn't like overriding a non-overloaded method + # with an overloaded one, even if LSP isn't violated. That could be its own + # specification question, but it's not what we're trying to test here: + + @overload + def good_override(self, x: int) -> int: ... + @overload + def good_override(self, x: str) -> str: ... + @overload + def to_override(self, x: int) -> int: ... + @overload + def to_override(self, x: str) -> str: ... + +class Child(Base): # E[override-final] + # The correctly-decorated @final method `Base.final_method` should cause an + # error if overridden in a child class (we use an overload here to avoid + # questions of override LSP compatibility and focus only on the override): + + @overload # E[override-final] + def final_method(self, x: int) -> int: # E[override-final] + ... + @overload + def final_method( # E[override-final] can't override final method + self, x: str + ) -> str: # E[override-final] can't override final method + ... + + # This is the right way to mark an overload as @override (decorate first + # overload only), so the use of @override should cause an error (because + # there's no `Base.bad_override` method): + + @overload # E[bad_override] marked as override but doesn't exist in base + @override + def bad_override(self, x: int) -> int: # E[bad_override] + ... + @overload + def bad_override(self, x: str) -> str: ... + + # This is also a correctly-decorated overloaded @override, which is + # overriding a method that does exist in the base, so there should be no + # error. We need both this test and the previous one, because in the + # previous test, an incorrect error about the use of @override decorator + # could appear on the same line as the expected error about overriding a + # method that doesn't exist in base: + + @overload + @override + def good_override(self, x: int) -> int: ... + @overload + def good_override(self, x: str) -> str: ... + + # This is the wrong way to use @override with an overloaded method, and + # should emit an error: + + @overload # E[override_impl]: @override should appear only on first overload + def to_override(self, x: int) -> int: ... + @overload + @override + def to_override( # E[override_impl]: @override should appear only on first overload + self, x: str + ) -> str: # E[override_impl]: @override should appear only on first overload + ... diff --git a/python/testData/typing/conformance/tests/overloads_evaluation.py b/python/testData/typing/conformance/tests/overloads_evaluation.py new file mode 100644 index 000000000000..094b177edc80 --- /dev/null +++ b/python/testData/typing/conformance/tests/overloads_evaluation.py @@ -0,0 +1,345 @@ +""" +Tests for evaluation of calls to overloaded functions. +""" + +from enum import Enum +from typing import Any, assert_type, Literal, overload, TypeVar + +# mypy: disable-error-code=overload-overlap + + +T = TypeVar("T") + +# > Step 1: Examine the argument list to determine the number of +# > positional and keyword arguments. Use this information to eliminate any +# > overload candidates that are not plausible based on their +# > input signatures. + +# (There is no way to observe via conformance tests whether an implementation +# performs this step separately from the argument-type-testing step 2 below, so +# the separation of step 1 from step 2 is purely a presentation choice for the +# algorithm, not a conformance requirement.) + + +@overload +def example1_1(x: int, y: str) -> int: ... + + +@overload +def example1_1(x: str) -> str: ... + + +def example1_1(x: int | str, y: str = "") -> int | str: + return 1 + + +# > - If no candidate overloads remain, generate an error and stop. + +example1_1() # E: no matching overload + +# > - If only one candidate overload remains, it is the winning match. Evaluate +# > it as if it were a non-overloaded function call and stop. + +ret1 = example1_1(1, "") +assert_type(ret1, int) + +example1_1(1, 1) # E: Literal[1] not assignable to str + +ret3 = example1_1("") +assert_type(ret3, str) + +example1_1(1) # E: Literal[1] not assignable to str + + +@overload +def example1_2(b: Literal[True] = ...) -> int: ... + + +@overload +def example1_2(b: bool) -> float: ... + + +def example1_2(b: bool = True) -> float: ... + + +def check_example1_2() -> None: + assert_type(example1_2(), int) + + +# > Step 2: Evaluate each remaining overload as a regular (non-overloaded) +# > call to determine whether it is compatible with the supplied +# > argument list. Unlike step 1, this step considers the types of the parameters +# > and arguments. During this step, do not generate any user-visible errors. +# > Simply record which of the overloads result in evaluation errors. + + +@overload +def example2(x: int, y: str, z: int) -> str: ... + + +@overload +def example2(x: int, y: int, z: int) -> int: ... + + +def example2(x: int, y: int | str, z: int) -> int | str: + return 1 + + +# > - If only one overload evaluates without error, it is the winning match. +# > Evaluate it as if it were a non-overloaded function call and stop. + +ret5 = example2(1, 2, 3) +assert_type(ret5, int) + +# > Step 3: If step 2 produces errors for all overloads, perform +# > "argument type expansion". Union types can be expanded +# > into their constituent subtypes. For example, the type ``int | str`` can +# > be expanded into ``int`` and ``str``. + +# > - If all argument lists evaluate successfully, combine their +# > respective return types by union to determine the final return type +# > for the call, and stop. + + +def check_expand_union(v: int | str) -> None: + ret1 = example2(1, v, 1) + assert_type(ret1, int | str) + + +# > - If argument expansion has been applied to all arguments and one or +# > more of the expanded argument lists cannot be evaluated successfully, +# > generate an error and stop. + + +def check_expand_union_2(v: int | str) -> None: + example2(v, v, 1) # E: no overload matches (str, ..., ...) + + +# > 2. ``bool`` should be expanded into ``Literal[True]`` and ``Literal[False]``. + + +@overload +def expand_bool(x: Literal[False]) -> Literal[0]: ... + + +@overload +def expand_bool(x: Literal[True]) -> Literal[1]: ... + + +def expand_bool(x: bool) -> int: + return int(x) + + +def check_expand_bool(v: bool) -> None: + ret1 = expand_bool(v) + assert_type(ret1, Literal[0, 1]) + + +# > 3. ``Enum`` types (other than those that derive from ``enum.Flag``) should +# > be expanded into their literal members. + + +class Color(Enum): + RED = 1 + BLUE = 1 + + +@overload +def expand_enum(x: Literal[Color.RED]) -> Literal[0]: ... + + +@overload +def expand_enum(x: Literal[Color.BLUE]) -> Literal[1]: ... + + +def expand_enum(x: Color) -> int: + return x.value + + +def check_expand_enum(v: Color) -> None: + ret1 = expand_enum(v) + assert_type(ret1, Literal[0, 1]) + + +# > 4. ``type[A | B]`` should be expanded into ``type[A]`` and ``type[B]``. + + +@overload +def expand_type_union(x: type[int]) -> int: ... + + +@overload +def expand_type_union(x: type[str]) -> str: ... + + +def expand_type_union(x: type[int] | type[str]) -> int | str: + return 1 + + +def check_expand_type_union(v: type[int | str]) -> None: + ret1 = expand_type_union(v) + assert_type(ret1, int | str) + + +# > 5. Tuples of known length that contain expandable types should be expanded +# > into all possible combinations of their element types. For example, the type +# > ``tuple[int | str, bool]`` should be expanded into ``(int, Literal[True])``, +# > ``(int, Literal[False])``, ``(str, Literal[True])``, and +# > ``(str, Literal[False])``. + + +@overload +def expand_tuple(x: tuple[int, int]) -> int: ... + + +@overload +def expand_tuple(x: tuple[int, str]) -> str: ... + + +def expand_tuple(x: tuple[int, int | str]) -> int | str: + return 1 + + +def check_expand_tuple(v: int | str) -> None: + ret1 = expand_tuple((1, v)) + assert_type(ret1, int | str) + + +# > Step 4: If the argument list is compatible with two or more overloads, +# > determine whether one or more of the overloads has a variadic parameter +# > (either ``*args`` or ``**kwargs``) that maps to a corresponding argument +# > that supplies an indeterminate number of positional or keyword arguments. +# > If so, eliminate overloads that do not have a variadic parameter. + + +@overload +def variadic(x: int, /) -> str: ... + + +@overload +def variadic(x: int, y: int, /, *args: int) -> int: ... + + +def variadic(*args: int) -> int | str: + return 1 + + +# > - If this results in only one remaining candidate overload, it is +# > the winning match. Evaluate it as if it were a non-overloaded function +# > call and stop. + + +def check_variadic(v: list[int]) -> None: + ret1 = variadic(*v) + assert_type(ret1, int) + + +# > Step 5: For all arguments, determine whether all possible +# > :term:`materializations ` of the argument's type are assignable to +# > the corresponding parameter type for each of the remaining overloads. If so, +# > eliminate all of the subsequent remaining overloads. + + +@overload +def example4(x: list[int], y: int) -> list[int]: ... + + +@overload +def example4(x: list[str], y: str) -> list[int]: ... + + +@overload +def example4(x: int, y: int) -> list[str]: ... + + +def example4(x: list[int] | list[str] | int, y: int | str) -> list[int] | list[str]: + return [] + + +def check_example4(v1: list[Any], v2: Any) -> None: + ret1 = example4(v1, v2) + assert_type(ret1, list[int]) + + ret2 = example4(v2, 1) + assert_type(ret2, Any) + + +@overload +def example5(obj: list[int]) -> list[int]: ... + + +@overload +def example5(obj: list[str]) -> list[str]: ... + + +def example5(obj: Any) -> list[Any]: + return [] + + +def check_example5(b: list[Any]) -> None: + assert_type(example5(b), Any) + + +@overload +def example6(a: int, b: Any) -> float: ... + + +@overload +def example6(a: float, b: T) -> T: ... + + +def example6(a: float, b: T) -> T: ... + + +def check_example6(a: list[Any], b: Any, c: str) -> None: + m: list[int] = [] + + # All possible materializations of list[Any] are + # assignable to Any, so this matches the first overload + # and eliminates all subsequent overloads. + v1 = example6(1, a) + assert_type(v1, float) + + # All possible materializations of Any are + # assignable to Any, so this matches the first overload + # and eliminates all subsequent overloads. + v2 = example6(1, b) + assert_type(v2, float) + + # All possible materializations of list[int] are + # assignable to Any, so this matches the first overload + # and eliminates all subsequent overloads. + v3 = example6(1, m) + assert_type(v3, float) + + v4 = example6(1.0, c) + assert_type(v4, str) + + v5 = example6(1.0, b) + assert_type(v5, Any) + + v6 = example6(1.0, m) + assert_type(v6, list[int]) + + +@overload +def example7(x: list[Any], y: int) -> list[int]: ... + + +@overload +def example7(x: list[Any], y: str) -> list[str]: ... + + +def example7(x: list[Any], y: int | str) -> list[int] | list[str]: + return [] + + +def check_example7(v1: list[Any], v2: Any) -> None: + ret1 = example7(v1, 1) + assert_type(ret1, list[int]) + + ret2 = example7(v1, "") + assert_type(ret2, list[str]) + + ret3 = example7(v1, v2) + assert_type(ret3, Any) diff --git a/python/testData/typing/conformance/tests/protocols_class_objects.py b/python/testData/typing/conformance/tests/protocols_class_objects.py index 73dc357752a9..7ffc5e7cb5d2 100644 --- a/python/testData/typing/conformance/tests/protocols_class_objects.py +++ b/python/testData/typing/conformance/tests/protocols_class_objects.py @@ -93,6 +93,9 @@ class ConcreteC2: class CMeta(type): attr1: int + def __init__(self, attr1: int) -> None: + self.attr1 = attr1 + class ConcreteC3(metaclass=CMeta): pass diff --git a/python/testData/typing/conformance/tests/protocols_explicit.py b/python/testData/typing/conformance/tests/protocols_explicit.py index 98f88dce3239..1e2763a61687 100644 --- a/python/testData/typing/conformance/tests/protocols_explicit.py +++ b/python/testData/typing/conformance/tests/protocols_explicit.py @@ -54,6 +54,7 @@ class RGB(Protocol): class Point(RGB): def __init__(self, red: int, green: int, blue: str) -> None: self.rgb = red, green, blue # E: 'blue' must be 'int' + self.other = 0 p = Point(0, 0, "") # E: Cannot instantiate abstract class @@ -80,7 +81,10 @@ class Proto3(Proto2, Protocol): class Concrete1(Proto1): - ... + def __init__(self): + self.cm1 = 1 + self.im1 = 1 + self.im3 = 3 c1 = Concrete1() # E: cannot instantiate abstract class @@ -98,7 +102,9 @@ class Concrete3(Proto1, Proto3): cm1 = 3 def __init__(self): - im1 = 0 + self.im1 = 0 + self.cm10 = 10 + self.cm11 = 11 c3 = Concrete3() # E: cannot instantiate abstract class diff --git a/python/testData/typing/conformance/tests/protocols_generic.py b/python/testData/typing/conformance/tests/protocols_generic.py index d3bbf8a0c4c6..958864285577 100644 --- a/python/testData/typing/conformance/tests/protocols_generic.py +++ b/python/testData/typing/conformance/tests/protocols_generic.py @@ -142,6 +142,6 @@ class ConcreteHasProperty4: hp1: HasPropertyProto = ConcreteHasProperty1() # OK -hp2: HasPropertyProto = ConcreteHasProperty2() # OK +hp2: HasPropertyProto = ConcreteHasProperty2() # E hp3: HasPropertyProto = ConcreteHasProperty3() # E hp4: HasPropertyProto = ConcreteHasProperty4() # E diff --git a/python/testData/typing/conformance/tests/protocols_merging.py b/python/testData/typing/conformance/tests/protocols_merging.py index 6540de7ed97d..e989e4ef8954 100644 --- a/python/testData/typing/conformance/tests/protocols_merging.py +++ b/python/testData/typing/conformance/tests/protocols_merging.py @@ -49,8 +49,8 @@ s3: SizedAndClosable1 = SizedAndClosable3() # OK s4: SizedAndClosable2 = SizedAndClosable3() # OK s5: Sized = SCConcrete1() # OK -s6: SizedAndClosable1 = SCConcrete2() # E: doesn't implement close -s7: SizedAndClosable2 = SCConcrete2() # E: doesn't implement close +s6: SizedAndClosable1 = SCConcrete2() # E: doesn't implement `__len__` +s7: SizedAndClosable2 = SCConcrete2() # E: doesn't implement `__len__` s8: SizedAndClosable3 = SCConcrete2() # E: SizedAndClosable3 is not a protocol diff --git a/python/testData/typing/conformance/tests/qualifiers_final_annotation.py b/python/testData/typing/conformance/tests/qualifiers_final_annotation.py index c20a82528953..822ea564ad3d 100644 --- a/python/testData/typing/conformance/tests/qualifiers_final_annotation.py +++ b/python/testData/typing/conformance/tests/qualifiers_final_annotation.py @@ -138,18 +138,33 @@ N(x="", y="") # E def func2() -> None: global ID1 - ID1 = 2 # E: cannot modify Final value + ID1 = 2 # E: cannot modify Final value x: Final = 3 - x += 1 # E: cannot modify Final value + x += 1 # E: cannot modify Final value a = (x := 4) # E: cannot modify Final value - for x in [1, 2, 3]: # E: cannot modify Final value + for x in [1, 2, 3]: # E: cannot modify Final value pass - with open("FileName") as x: # E: cannot modify Final value + with open("FileName") as x: # E: cannot modify Final value pass (a, x) = (1, 2) # E: cannot modify Final value + + +# > If a module declares a ``Final`` variable and another module imports that +# > variable in an import statement by name or wildcard, the imported symbol +# > inherits the ``Final`` type qualifier. Any attempt to assign a different value +# > to this symbol should be flagged as an error by a type checker. + + +from _qualifiers_final_annotation_1 import TEN + +TEN = 9 # E: Cannot redefine Final value + +from _qualifiers_final_annotation_2 import * + +PI = 3.14159 # E: Cannot redefine Final value diff --git a/python/testData/typing/conformance/tests/specialtypes_never.py b/python/testData/typing/conformance/tests/specialtypes_never.py index 339c6f00097a..41b624e38959 100644 --- a/python/testData/typing/conformance/tests/specialtypes_never.py +++ b/python/testData/typing/conformance/tests/specialtypes_never.py @@ -56,6 +56,10 @@ class ClassA: x: NoReturn y: list[NoReturn] + def __init__(self, x: NoReturn, y: list[NoReturn]) -> None: + self.x = x + self.y = y + # Never is compatible with all types. diff --git a/python/testData/typing/conformance/tests/tuples_type_compat.py b/python/testData/typing/conformance/tests/tuples_type_compat.py index 73f3b3ad5341..27b76921ff5a 100644 --- a/python/testData/typing/conformance/tests/tuples_type_compat.py +++ b/python/testData/typing/conformance/tests/tuples_type_compat.py @@ -7,7 +7,7 @@ Tests type compatibility rules for tuples. # > Because tuple contents are immutable, the element types of a tuple are covariant. -from typing import Any, Iterable, Never, Sequence, TypeVar, assert_type +from typing import Any, Iterable, Never, Sequence, TypeAlias, TypeVar, assert_type def func1(t1: tuple[float, complex], t2: tuple[int, int]): @@ -67,19 +67,23 @@ def func4( # NOTE: This type narrowing functionality is optional, not mandated. +Func5Input: TypeAlias = tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int] -def func5(val: tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]): +def func5(val: Func5Input): if len(val) == 1: # Type can be narrowed to tuple[int]. - assert_type(val, tuple[int]) # tuple[int] + assert_type(val, tuple[int]) # E[func5_1] + assert_type(val, Func5Input) # E[func5_1] if len(val) == 2: # Type can be narrowed to tuple[str, str] | tuple[int, int]. - assert_type(val, tuple[str, str] | tuple[int, int]) + assert_type(val, tuple[str, str] | tuple[int, int]) # E[func5_2] + assert_type(val, Func5Input) # E[func5_2] if len(val) == 3: # Type can be narrowed to tuple[int, str, int]. - assert_type(val, tuple[int, str, int]) + assert_type(val, tuple[int, str, int]) # E[func5_3] + assert_type(val, Func5Input) # E[func5_3] # > This property may also be used to safely narrow tuple types within a match @@ -87,20 +91,25 @@ def func5(val: tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]) # NOTE: This type narrowing functionality is optional, not mandated. +Func6Input: TypeAlias = tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int] -def func6(val: tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]): + +def func6(val: Func6Input): match val: case (x,): - # Type can be narrowed to tuple[int]. - assert_type(val, tuple[int]) # tuple[int] + # Type may be narrowed to tuple[int]. + assert_type(val, tuple[int]) # E[func6_1] + assert_type(val, Func6Input) # E[func6_1] case (x, y): - # Type can be narrowed to tuple[str, str] | tuple[int, int]. - assert_type(val, tuple[str, str] | tuple[int, int]) + # Type may be narrowed to tuple[str, str] | tuple[int, int]. + assert_type(val, tuple[str, str] | tuple[int, int]) # E[func6_2] + assert_type(val, Func6Input) # E[func6_2] case (x, y, z): - # Type can be narrowed to tuple[int, str, int]. - assert_type(val, tuple[int, str, int]) + # Type may be narrowed to tuple[int, str, int]. + assert_type(val, tuple[int, str, int]) # E[func6_3] + assert_type(val, Func6Input) # E[func6_3] # > Type checkers may safely use this equivalency rule (tuple expansion) @@ -108,13 +117,17 @@ def func6(val: tuple[int] | tuple[str, str] | tuple[int, *tuple[str, ...], int]) # NOTE: This type narrowing functionality is optional, not mandated. +Func7Input: TypeAlias = tuple[int | str, int | str] -def func7(subj: tuple[int | str, int | str]): + +def func7(subj: Func7Input): match subj: case x, str(): - assert_type(subj, tuple[int | str, str]) + assert_type(subj, tuple[int | str, str]) # E[func7_1] + assert_type(subj, Func7Input) # E[func7_1] case y: - assert_type(subj, tuple[int | str, int]) + assert_type(subj, tuple[int | str, int]) # E[func7_2] + assert_type(subj, Func7Input) # E[func7_2] # > The tuple class derives from Sequence[T_co] where ``T_co`` is a covariant diff --git a/python/testData/typing/conformance/tests/tuples_unpacked.py b/python/testData/typing/conformance/tests/tuples_unpacked.py index ca58af7a713d..706b58f831fb 100644 --- a/python/testData/typing/conformance/tests/tuples_unpacked.py +++ b/python/testData/typing/conformance/tests/tuples_unpacked.py @@ -57,6 +57,6 @@ def func3(t: tuple[*Ts]): t11: tuple[Unpack[tuple[str]], Unpack[tuple[str]]] # OK t12: tuple[Unpack[tuple[str, Unpack[tuple[str, ...]]]]] # OK t13: tuple[Unpack[tuple[str, ...]], Unpack[tuple[int, ...]]] # E -t14: tuple[ - Unpack[tuple[str, Unpack[tuple[str, ...]]]], Unpack[tuple[int, ...]] # E +t14: tuple[ # E[t14] + Unpack[tuple[str, Unpack[tuple[str, ...]]]], Unpack[tuple[int, ...]] # E[t14] ] diff --git a/python/testData/typing/conformance/tests/typeddicts_class_syntax.py b/python/testData/typing/conformance/tests/typeddicts_class_syntax.py index d2983b76ae80..b83f50bf7067 100644 --- a/python/testData/typing/conformance/tests/typeddicts_class_syntax.py +++ b/python/testData/typing/conformance/tests/typeddicts_class_syntax.py @@ -30,13 +30,13 @@ class BadTypedDict1(TypedDict): pass # Methods are not allowed, so this should generate an error. - @classmethod # E - def method2(cls): + @classmethod # E[method2] + def method2(cls): # E[method2] pass # Methods are not allowed, so this should generate an error. - @staticmethod # E - def method3(): + @staticmethod # E[method3] + def method3(): # E[method3] pass @@ -54,7 +54,7 @@ class BadTypedDict3(TypedDict, other=True): # E T = TypeVar("T") -class GenericTypedDict(Generic[T]): +class GenericTypedDict(TypedDict, Generic[T]): name: str value: T diff --git a/python/testData/typing/conformance/tests/typeddicts_required.py b/python/testData/typing/conformance/tests/typeddicts_required.py index d4cef0142ed1..e38e6df869d9 100644 --- a/python/testData/typing/conformance/tests/typeddicts_required.py +++ b/python/testData/typing/conformance/tests/typeddicts_required.py @@ -11,6 +11,9 @@ from typing import Annotated, NotRequired, Required, TypedDict class NotTypedDict: x: Required[int] # E: Required not allowed in this context + def __init__(self, x: int) -> None: + self.x = x + def func1( x: NotRequired[int], # E: NotRequired not allowed in this context