diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 3eb54579a050..1963d7c5ac7b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1420,7 +1420,11 @@ def is_generic_decorator_overload_call( return None def handle_decorator_overload_call( - self, callee_type: CallableType, overloaded: Overloaded, ctx: Context + self, + callee_type: CallableType, + overloaded: Overloaded, + ctx: Context, + callee_is_overload_item: bool, ) -> tuple[Type, Type] | None: """Type-check application of a generic callable to an overload. @@ -1432,7 +1436,9 @@ def handle_decorator_overload_call( for item in overloaded.items: arg = TempNode(typ=item) with self.msg.filter_errors() as err: - item_result, inferred_arg = self.check_call(callee_type, [arg], [ARG_POS], ctx) + item_result, inferred_arg = self.check_call( + callee_type, [arg], [ARG_POS], ctx, is_overload_item=callee_is_overload_item + ) if err.has_new_errors(): # This overload doesn't match. continue @@ -1538,6 +1544,7 @@ def check_call( callable_name: str | None = None, object_type: Type | None = None, original_type: Type | None = None, + is_overload_item: bool = False, ) -> tuple[Type, Type]: """Type check a call. @@ -1558,6 +1565,7 @@ def check_call( or None if unavailable (examples: 'builtins.open', 'typing.Mapping.get') object_type: If callable_name refers to a method, the type of the object on which the method is being called + is_overload_item: Whether this check is for an individual overload item """ callee = get_proper_type(callee) @@ -1568,7 +1576,7 @@ def check_call( # Special casing for inline application of generic callables to overloads. # Supporting general case would be tricky, but this should cover 95% of cases. overloaded_result = self.handle_decorator_overload_call( - callee, overloaded, context + callee, overloaded, context, is_overload_item ) if overloaded_result is not None: return overloaded_result @@ -1582,6 +1590,7 @@ def check_call( callable_node, callable_name, object_type, + is_overload_item, ) elif isinstance(callee, Overloaded): return self.check_overload_call( @@ -1659,11 +1668,18 @@ def check_callable_call( callable_node: Expression | None, callable_name: str | None, object_type: Type | None, + is_overload_item: bool = False, ) -> tuple[Type, Type]: """Type check a call that targets a callable value. See the docstring of check_call for more information. """ + # Check implicit calls to deprecated class constructors. + # Only the non-overload case is handled here. Overloaded constructors are handled + # separately during overload resolution. + if (not is_overload_item) and callee.is_type_obj(): + self.chk.warn_deprecated(callee.definition, context) + # Always unpack **kwargs before checking a call. callee = callee.with_unpacked_kwargs().with_normalized_var_args() if callable_name is None and callee.name: @@ -1671,9 +1687,10 @@ def check_callable_call( ret_type = get_proper_type(callee.ret_type) if callee.is_type_obj() and isinstance(ret_type, Instance): callable_name = ret_type.type.fullname - if isinstance(callable_node, RefExpr) and callable_node.fullname in ENUM_BASES: - # An Enum() call that failed SemanticAnalyzerPass2.check_enum_call(). - return callee.ret_type, callee + if isinstance(callable_node, RefExpr) and (callable_node.fullname in ENUM_BASES): + if callable_node.fullname in ENUM_BASES: + # An Enum() call that failed SemanticAnalyzerPass2.check_enum_call(). + return callee.ret_type, callee if ( callee.is_type_obj() @@ -2910,6 +2927,7 @@ def infer_overload_return_type( context=context, callable_name=callable_name, object_type=object_type, + is_overload_item=True, ) is_match = not w.has_new_errors() if is_match: diff --git a/test-data/unit/check-deprecated.test b/test-data/unit/check-deprecated.test index 607e9d767956..4d1870d0029f 100644 --- a/test-data/unit/check-deprecated.test +++ b/test-data/unit/check-deprecated.test @@ -315,18 +315,286 @@ class E: ... [builtins fixtures/tuple.pyi] -[case testDeprecatedClassInitMethod] +[case testDeprecatedClassConstructor] # flags: --enable-error-code=deprecated from typing_extensions import deprecated -@deprecated("use C2 instead") class C: + @deprecated("call `make_c()` instead") def __init__(self) -> None: ... + @classmethod + def make_c(cls) -> C: ... -c: C # E: class __main__.C is deprecated: use C2 instead -C() # E: class __main__.C is deprecated: use C2 instead -C.__init__(c) # E: class __main__.C is deprecated: use C2 instead +class C2(C): ... + +class C3(C): + def __init__(self) -> None: ... + +C() # E: function __main__.C.__init__ is deprecated: call `make_c()` instead +C2() # E: function __main__.C.__init__ is deprecated: call `make_c()` instead +C3() + +class D: + @deprecated("call `make_d()` instead") + def __new__(cls) -> D: ... + @classmethod + def make_d(cls) -> D: ... + +class D2(D): ... + +class D3(D): + def __new__(cls) -> D3: ... + +D() # E: function __main__.D.__new__ is deprecated: call `make_d()` instead +D2() # E: function __main__.D.__new__ is deprecated: call `make_d()` instead +D3() + +[builtins fixtures/tuple.pyi] + + +[case testDeprecatedSuperClassConstructor] +# flags: --enable-error-code=deprecated + +from typing_extensions import deprecated, Self + +class A: + @deprecated("call `self.initialise()` instead") + def __init__(self) -> None: ... + def initialise(self) -> None: ... + +class B(A): + def __init__(self) -> None: + super().__init__() # E: function __main__.A.__init__ is deprecated: call `self.initialise()` instead + +class C: + @deprecated("call `object.__new__(cls)` instead") + def __new__(cls) -> Self: ... + +class D(C): + def __new__(cls) -> Self: + return super().__new__(cls) # E: function __main__.C.__new__ is deprecated: call `object.__new__(cls)` instead + +[builtins fixtures/tuple.pyi] + + +[case testDeprecatedClassConstructorCalledFromTypeAlias] +# flags: --enable-error-code=deprecated + +from typing_extensions import deprecated, TypeAlias + +class A: + @deprecated("do not use") + def __init__(self) -> None: ... + +class B1(A): ... + +class B2(A): + def __init__(self) -> None: ... + +A_alias = A +A_explicit_alias: TypeAlias = A +B1_alias = B1 +B1_explicit_alias: TypeAlias = B1 +B2_alias = B2 +B2_explicit_alias: TypeAlias = B2 + +A_alias() # E: function __main__.A.__init__ is deprecated: do not use +A_explicit_alias() # E: function __main__.A.__init__ is deprecated: do not use +B1_alias() # E: function __main__.A.__init__ is deprecated: do not use +B1_explicit_alias() # E: function __main__.A.__init__ is deprecated: do not use +B2_alias() +B2_explicit_alias() + +A_alias +A_explicit_alias +B1_alias +B1_explicit_alias +B2_alias +B2_explicit_alias + +[builtins fixtures/tuple.pyi] + + +[case testDeprecatedClassConstructorCalledFromTypeType] +# flags: --enable-error-code=deprecated + +from typing_extensions import deprecated + +class A: + @deprecated("do not use") + def __init__(self) -> None: ... + +class B1(A): ... + +class B2(A): + def __init__(self) -> None: ... + +def get_class_A() -> type[A]: ... +def get_class_B1() -> type[B1]: ... +def get_class_B2() -> type[B2]: ... + +A_type_obj: type[A] +B1_type_obj: type[B1] +B2_type_obj: type[B2] +A_type_obj() # E: function __main__.A.__init__ is deprecated: do not use +B1_type_obj() # E: function __main__.A.__init__ is deprecated: do not use +B2_type_obj() +get_class_A()() # E: function __main__.A.__init__ is deprecated: do not use +get_class_B1()() # E: function __main__.A.__init__ is deprecated: do not use +get_class_B2()() + +def call_class_A(cls: type[A]) -> None: + cls() # E: function __main__.A.__init__ is deprecated: do not use + +def call_class_B1(cls: type[B1]) -> None: + cls() # E: function __main__.A.__init__ is deprecated: do not use + +def call_class_B2(cls: type[B2]) -> None: + cls() + +[builtins fixtures/tuple.pyi] + + +[case testDeprecatedClassConstructorCalledFromUnion] +# flags: --enable-error-code=deprecated + +from typing import Callable, Union +from typing_extensions import TypeAlias, deprecated + +def maybe() -> bool: ... + +def dummy_factory() -> Dummy: ... + +class Dummy: ... + +class A: + @deprecated("do not use A()") + def __init__(self) -> None: ... + +class B1(A): + def __init__(self) -> None: ... + +class B2(A): + @deprecated("do not use B2()") + def __init__(self) -> None: ... + +A_alias: TypeAlias = A +A_type_obj: type[A] +B1_alias: TypeAlias = B1 +B1_type_obj: type[B1] + +maybe_A = A if maybe() else None +maybe_A_alias = A_alias if maybe() else None +maybe_A_type_obj = A_type_obj if maybe() else None +maybe_A() # E: function __main__.A.__init__ is deprecated: do not use A() \ + # E: "None" not callable +maybe_A_alias() # E: function __main__.A.__init__ is deprecated: do not use A() \ + # E: "None" not callable +maybe_A_type_obj() # E: function __main__.A.__init__ is deprecated: do not use A() \ + # E: "None" not callable + +maybe_B1 = B1 if maybe() else None +maybe_B1_alias = B1_alias if maybe() else None +maybe_B1_type_obj = B1_type_obj if maybe() else None +maybe_B1() # E: "None" not callable +maybe_B1_alias() # E: "None" not callable +maybe_B1_type_obj() # E: "None" not callable + +A_or_B1: type[Union[A, B1]] +A_or_B2: type[Union[A, B2]] +A_or_dummy: type[Union[A, Dummy]] +A_or_dummy_factory: Union[type[A], Callable[[], Dummy]] +reveal_type(A_or_B1()) # E: function __main__.A.__init__ is deprecated: do not use A() \ + # N: Revealed type is "__main__.A" +reveal_type(A_or_B2()) # E: function __main__.A.__init__ is deprecated: do not use A() \ + # E: function __main__.B2.__init__ is deprecated: do not use B2() \ + # N: Revealed type is "__main__.A" +reveal_type(A_or_dummy()) # E: function __main__.A.__init__ is deprecated: do not use A() \ + # N: Revealed type is "Union[__main__.A, __main__.Dummy]" +reveal_type(A_or_dummy_factory()) # E: function __main__.A.__init__ is deprecated: do not use A() \ + # N: Revealed type is "Union[__main__.A, __main__.Dummy]" + +[builtins fixtures/tuple.pyi] + + +[case testDeprecatedClassConstructorTypeNarrowing] +# flags: --enable-error-code=deprecated + +from typing import TypeVar +from typing_extensions import TypeAlias, TypeIs, deprecated + +T = TypeVar("T") + +class Dummy: ... + +class A: + @deprecated("do not use") + def __init__(self) -> None: ... + +class B(A): + def __init__(self) -> None: ... + +def maybe() -> bool: ... + +# `builtins.issubclass()` does not type-narrow properly +def is_subclass(c1: type, c2: type[T]) -> TypeIs[type[T]]: ... + +A_alias: TypeAlias = A +A_type_obj: type[A] +B_alias: TypeAlias = B +B_type_obj: type[B] + +maybe_A = A if maybe() else None +maybe_A_alias = A_alias if maybe() else None +maybe_A_type_obj = A_type_obj if maybe() else None +if maybe_A is not None: + maybe_A() # E: function __main__.A.__init__ is deprecated: do not use +else: + maybe_A() # E: "None" not callable +if maybe_A_alias is not None: + maybe_A_alias() # E: function __main__.A.__init__ is deprecated: do not use +else: + maybe_A_alias() # E: "None" not callable +if maybe_A_type_obj is not None: + maybe_A_type_obj() # E: function __main__.A.__init__ is deprecated: do not use +else: + maybe_A_type_obj() # E: "None" not callable + +A_or_Dummy = A if maybe() else Dummy +A_type_obj_or_Dummy = A_type_obj if maybe() else Dummy +if is_subclass(A_or_Dummy, A): + A_or_Dummy() # E: function __main__.A.__init__ is deprecated: do not use +else: + A_or_Dummy() +if is_subclass(A_or_Dummy, Dummy): + A_or_Dummy() +else: + A_or_Dummy() # E: function __main__.A.__init__ is deprecated: do not use +if is_subclass(A_type_obj_or_Dummy, A): + A_type_obj_or_Dummy() # E: function __main__.A.__init__ is deprecated: do not use +else: + A_type_obj_or_Dummy() +if is_subclass(A_type_obj_or_Dummy, Dummy): + A_type_obj_or_Dummy() +else: + A_type_obj_or_Dummy() # E: function __main__.A.__init__ is deprecated: do not use + +A_or_B = A if maybe() else B +A_or_B_alias = A if maybe() else B_alias +A_or_B_type_obj = A if maybe() else B_type_obj +if is_subclass(A_or_B, B): + A_or_B() +else: + A_or_B() # E: function __main__.A.__init__ is deprecated: do not use +if is_subclass(A_or_B_alias, B): + A_or_B_alias() +else: + A_or_B_alias() # E: function __main__.A.__init__ is deprecated: do not use +if is_subclass(A_or_B_type_obj, B): + A_or_B_type_obj() +else: + A_or_B_type_obj() # E: function __main__.A.__init__ is deprecated: do not use [builtins fixtures/tuple.pyi] @@ -581,6 +849,85 @@ a += "x" # E: function __main__.A.__iadd__ is deprecated: no A += Any [builtins fixtures/tuple.pyi] +[case testDeprecatedOverloadedClassConstructor] +# flags: --enable-error-code=deprecated --disable-error-code=no-overload-impl + +from typing_extensions import TypeAlias, deprecated, overload + +class A: + @overload + @deprecated("do not pass int") + def __new__(cls, arg: int) -> A: ... + @overload + def __new__(cls, arg: str) -> A: ... + +class B: + @overload + @deprecated("do not pass int") + def __init__(self, arg: int) -> None: ... + @overload + def __init__(self, arg: str) -> None: ... + +A_alias: TypeAlias = A +B_alias: TypeAlias = B + +A(1) # E: overload def (cls: type[__main__.A], arg: builtins.int) -> __main__.A of function __main__.A.__new__ is deprecated: do not pass int +A_alias(1) # E: overload def (cls: type[__main__.A], arg: builtins.int) -> __main__.A of function __main__.A.__new__ is deprecated: do not pass int +A("") +A_alias("") +class A_child(A): + def __new__(cls) -> A_child: + super().__new__(cls, 1) # E: overload def (cls: type[__main__.A], arg: builtins.int) -> __main__.A of function __main__.A.__new__ is deprecated: do not pass int + super().__new__(cls, "") + return object.__new__(cls) + +B(1) # E: overload def (self: __main__.B, arg: builtins.int) of function __main__.B.__init__ is deprecated: do not pass int +B_alias(1) # E: overload def (self: __main__.B, arg: builtins.int) of function __main__.B.__init__ is deprecated: do not pass int +B("") +B_alias("") +class B_child(B): + def __init__(self) -> None: + super().__init__(1) # E: overload def (self: __main__.B, arg: builtins.int) of function __main__.B.__init__ is deprecated: do not pass int + super().__init__("") + +[builtins fixtures/tuple.pyi] + + +[case testDeprecatedOverloadedClassConstructorDecoratingOverloadedFunction] + +# flags: --enable-error-code=deprecated --disable-error-code=no-overload-impl + +from typing import Callable, Generic, TypeVar +from typing_extensions import deprecated, overload + +F = TypeVar("F", bound=Callable[..., object]) + +class decorator(Generic[F]): + __call__: F + + @overload + @deprecated("decorated function must take arguments") + def __init__(self: decorator[Callable[[], None]], f: Callable[[], None]) -> None: ... + @overload + def __init__(self, f: F) -> None: ... + +@overload +@decorator +def f(a: str) -> None: ... +@overload # E: overload def (self: __main__.decorator[def ()], f: def ()) of function __main__.decorator.__init__ is deprecated: decorated function must take arguments +@decorator +def f() -> None: ... + +f() +f("") +f(1) # E: No overload variant of "f" matches argument type "int" \ + # N: Possible overload variants: \ + # N: def f(a: str) -> None \ + # N: def f() -> None + +[builtins fixtures/tuple.pyi] + + [case testDeprecatedMethod] # flags: --enable-error-code=deprecated @@ -616,6 +963,105 @@ C().k() # E: function __main__.C.k is deprecated: use g instead [builtins fixtures/callable.pyi] +[case testDeprecatedMethodAccessedFromMethod] +# flags: --enable-error-code=deprecated + +from typing_extensions import deprecated + +class C: + @deprecated("use g instead") + def f(self) -> None: ... + + def g(self) -> None: ... + + @deprecated("use g instead") + @staticmethod + def h() -> None: ... + + def instance_method(self) -> None: + self.f # E: function __main__.C.f is deprecated: use g instead + self.f() # E: function __main__.C.f is deprecated: use g instead + self.g() + self.h() # E: function __main__.C.h is deprecated: use g instead + t = (self.f, self.f, self.g) # E: function __main__.C.f is deprecated: use g instead + + @classmethod + def class_method(cls) -> None: + c: C + cls.f # E: function __main__.C.f is deprecated: use g instead + cls.f(c) # E: function __main__.C.f is deprecated: use g instead + cls.g(c) + cls.h() # E: function __main__.C.h is deprecated: use g instead + t = (cls.f, cls.f, cls.g) # E: function __main__.C.f is deprecated: use g instead + +[builtins fixtures/callable.pyi] + + +[case testDeprecatedClassConstructorMethodAccessedFromOutsideMethod] +# flags: --enable-error-code=deprecated + +from typing_extensions import TypeAlias, deprecated + +class C: + @deprecated("use make_c() instead") + def __new__(cls) -> C: ... + + @deprecated("use make_c() instead") + def __init__(self) -> None: ... + + @classmethod + def make_c(cls) -> C: ... + +C_alias: TypeAlias = C +C_type_obj: type[C] +c: C + +C.__new__ # E: function __main__.C.__new__ is deprecated: use make_c() instead +C.__new__(C) # E: function __main__.C.__new__ is deprecated: use make_c() instead +C.__init__ # E: function __main__.C.__init__ is deprecated: use make_c() instead +C.__init__(c) # E: function __main__.C.__init__ is deprecated: use make_c() instead +C_alias.__new__ # E: function __main__.C.__new__ is deprecated: use make_c() instead +C_alias.__new__(C) # E: function __main__.C.__new__ is deprecated: use make_c() instead +C_alias.__init__ # E: function __main__.C.__init__ is deprecated: use make_c() instead +C_alias.__init__(c) # E: function __main__.C.__init__ is deprecated: use make_c() instead +C_type_obj.__new__ # E: function __main__.C.__new__ is deprecated: use make_c() instead +C_type_obj.__new__(C) # E: function __main__.C.__new__ is deprecated: use make_c() instead +C_type_obj.__init__ # E: function __main__.C.__init__ is deprecated: use make_c() instead +C_type_obj.__init__(c) # E: function __main__.C.__init__ is deprecated: use make_c() instead + +(C.__new__, C_alias.__new__, C_type_obj.__new__, C.make_c, c.make_c) # E: function __main__.C.__new__ is deprecated: use make_c() instead +(C.__init__, C_alias.__init__, C_type_obj.__init__, C.make_c, c.make_c) # E: function __main__.C.__init__ is deprecated: use make_c() instead + +[builtins fixtures/tuple.pyi] + + +[case testDeprecatedMethodClassConstructorAccessedFromInsideMethod] +# flags: --enable-error-code=deprecated + +from typing_extensions import deprecated + +class C: + @deprecated("use make_c() instead") + def __new__(cls) -> C: ... + @deprecated("use make_c() instead") + def __init__(self) -> None: ... + + @classmethod + def make_c(cls) -> C: ... + + @classmethod + def class_method(cls) -> None: + self: C + cls.__new__ # E: function __main__.C.__new__ is deprecated: use make_c() instead + cls.__new__(cls) # E: function __main__.C.__new__ is deprecated: use make_c() instead + cls.__init__ # E: function __main__.C.__init__ is deprecated: use make_c() instead + cls.__init__(self) # E: function __main__.C.__init__ is deprecated: use make_c() instead + (cls.__new__, cls.__new__, cls.make_c) # E: function __main__.C.__new__ is deprecated: use make_c() instead + (cls.__init__, cls.__init__, cls.make_c) # E: function __main__.C.__init__ is deprecated: use make_c() instead + +[builtins fixtures/tuple.pyi] + + [case testDeprecatedClassWithDeprecatedMethod] # flags: --enable-error-code=deprecated diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 94f65a950062..9b0f5302844c 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -7512,3 +7512,91 @@ tmp/impl.py:31: note: Revealed type is "builtins.object" tmp/impl.py:32: note: Revealed type is "Union[builtins.int, builtins.str, lib.Unrelated]" tmp/impl.py:33: note: Revealed type is "builtins.object" tmp/impl.py:34: note: Revealed type is "builtins.object" + + +[case testIncrementalDeprecatedClassConstructorCallExpressions] +# flags: --enable-error-code=deprecated + +# Tests whether adding/removing `@deprecated()` on class constructors correctly update +# when the call is from a class, the class's children, and from type aliases. +# Addition/removal is emulated by setting up 2 parent classes across 3 versions of `mod.py`. +# `Parent1.__init__` has `@deprecated()`: yes/no/yes in v1/v2/v3, respectively. +# `Parent2.__init__` has `@deprecated()`: no/yes/no in v1/v2/v3, respectively. + +from typing_extensions import TypeAlias +import mod + +Parent1_alias: TypeAlias = mod.Parent1 +Parent1_type_obj: type[mod.Parent1] +Parent2_alias: TypeAlias = mod.Parent2 +Parent2_type_obj: type[mod.Parent2] +class Child1_of_Parent1(mod.Parent1): ... +class Child1_of_Parent2(mod.Parent2): ... + +# Emits errors in v1 and v3 +Parent1_alias() +Parent1_type_obj() +Child1_of_Parent1() +class Child2_of_Parent1(mod.Parent1): + def __init__(self) -> None: + super().__init__() + +# Emits errors in v2 only +Parent2_alias() +Parent2_type_obj() +Child1_of_Parent2() +class Child2_of_Parent2(mod.Parent2): + def __init__(self) -> None: + super().__init__() + +# Never emits errors +Child2_of_Parent1() +Child2_of_Parent2() + +[file mod.py] +from typing_extensions import TypeAlias, deprecated + +class Parent1: + @deprecated("v1: do not use Parent1.__init__") + def __init__(self) -> None: ... + +class Parent2: + def __init__(self) -> None: ... + +[file mod.py.2] +from typing_extensions import TypeAlias, deprecated + +class Parent1: + def __init__(self) -> None: ... + +class Parent2: + @deprecated("v2: do not use Parent2.__init__") + def __init__(self) -> None: ... + +[file mod.py.3] +from typing_extensions import TypeAlias, deprecated + +class Parent1: + @deprecated("v3: do not use Parent1.__init__") + def __init__(self) -> None: ... + +class Parent2: + def __init__(self) -> None: ... + +[builtins fixtures/tuple.pyi] +[stale mod] +[out] +main:20: error: function mod.Parent1.__init__ is deprecated: v1: do not use Parent1.__init__ +main:21: error: function mod.Parent1.__init__ is deprecated: v1: do not use Parent1.__init__ +main:22: error: function mod.Parent1.__init__ is deprecated: v1: do not use Parent1.__init__ +main:25: error: function mod.Parent1.__init__ is deprecated: v1: do not use Parent1.__init__ +[out2] +main:28: error: function mod.Parent2.__init__ is deprecated: v2: do not use Parent2.__init__ +main:29: error: function mod.Parent2.__init__ is deprecated: v2: do not use Parent2.__init__ +main:30: error: function mod.Parent2.__init__ is deprecated: v2: do not use Parent2.__init__ +main:33: error: function mod.Parent2.__init__ is deprecated: v2: do not use Parent2.__init__ +[out3] +main:20: error: function mod.Parent1.__init__ is deprecated: v3: do not use Parent1.__init__ +main:21: error: function mod.Parent1.__init__ is deprecated: v3: do not use Parent1.__init__ +main:22: error: function mod.Parent1.__init__ is deprecated: v3: do not use Parent1.__init__ +main:25: error: function mod.Parent1.__init__ is deprecated: v3: do not use Parent1.__init__