Skip to content
Merged
2 changes: 1 addition & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7746,7 +7746,7 @@ def warn_deprecated_overload_item(
if isinstance(item, Decorator) and isinstance(
candidate := item.func.type, CallableType
):
if selftype is not None:
if (selftype is not None) and (not node.is_static):
candidate = bind_self(candidate, selftype)
if candidate == target:
self.warn_deprecated(item.func, context)
Expand Down
10 changes: 9 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1485,7 +1485,15 @@ def check_call_expr_with_callee_type(
)
proper_callee = get_proper_type(callee_type)
if isinstance(e.callee, (NameExpr, MemberExpr)):
self.chk.warn_deprecated_overload_item(e.callee.node, e, target=callee_type)
node = e.callee.node
if (node is None) and (member is not None) and isinstance(object_type, Instance):
for base in object_type.type.mro:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we can use object_type.type.get(member)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I adjusted it.

if (symbol := base.names.get(member)) is not None:
node = symbol.node
break
self.chk.warn_deprecated_overload_item(
node, e, target=callee_type, selftype=object_type
)
if isinstance(e.callee, RefExpr) and isinstance(proper_callee, CallableType):
# Cache it for find_isinstance_check()
if proper_callee.type_guard is not None:
Expand Down
165 changes: 165 additions & 0 deletions test-data/unit/check-deprecated.test
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,171 @@ for i in a: # E: function __main__.A.__iter__ is deprecated: no iteration
[builtins fixtures/tuple.pyi]


[case testDeprecatedOverloadedInstanceMethods]
# flags: --enable-error-code=deprecated

from typing import Iterator, Union
from typing_extensions import deprecated, overload

class A:
@overload
@deprecated("pass `str` instead")
def f(self, v: int) -> None: ...
@overload
def f(self, v: str) -> None: ...
def f(self, v: Union[int, str]) -> None: ...

@overload
def g(self, v: int) -> None: ...
@overload
@deprecated("pass `int` instead")
def g(self, v: str) -> None: ...
def g(self, v: Union[int, str]) -> None: ...

@overload
def h(self, v: int) -> A: ...
@overload
def h(self, v: str) -> A: ...
@deprecated("use `h2` instead")
def h(self, v: Union[int, str]) -> A: ...

class B(A): ...

a = A()
a.f(1) # E: overload def (self: __main__.A, v: builtins.int) of function __main__.A.f is deprecated: pass `str` instead
a.f("x")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also test this case:

Suggested change
a.f("x")
a.f("x")
int_or_str: Union[int, str]
a.f(int_or_str)

It should not raise if all is good.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not raise a warning, but why do you think it should not?

(There is not even a warning for a.h, where the implementation is marked as deprecated, which is inconsistent with how functions are handled.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(There is not even a warning for a.h, where the implementation is marked as deprecated, which is inconsistent with how functions are handled.)

I made it consistent in c95f936. However, I added the in my opinion missing warnings to the test case, to prevent us from merging this too early by accident. I am curious to hear why you think the current behaviour is correct. (regarding int_or_str).

a.g(1)
a.g("x") # E: overload def (self: __main__.A, v: builtins.str) of function __main__.A.g is deprecated: pass `int` instead
a.h(1) # E: function __main__.A.h is deprecated: use `h2` instead
a.h("x") # E: function __main__.A.h is deprecated: use `h2` instead

b = B()
b.f(1) # E: overload def (self: __main__.A, v: builtins.int) of function __main__.A.f is deprecated: pass `str` instead
b.f("x")
b.g(1)
b.g("x") # E: overload def (self: __main__.A, v: builtins.str) of function __main__.A.g is deprecated: pass `int` instead
b.h(1) # E: function __main__.A.h is deprecated: use `h2` instead
b.h("x") # E: function __main__.A.h is deprecated: use `h2` instead

[builtins fixtures/tuple.pyi]


[case testDeprecatedOverloadedClassMethods]
# flags: --enable-error-code=deprecated

from typing import Iterator, Union
from typing_extensions import deprecated, overload

class A:
@overload
@classmethod
@deprecated("pass `str` instead")
def f(self, v: int) -> None: ...
@overload
@classmethod
def f(self, v: str) -> None: ...
@classmethod
def f(self, v: Union[int, str]) -> None: ...

@overload
@classmethod
def g(self, v: int) -> None: ...
@overload
@classmethod
@deprecated("pass `int` instead")
def g(self, v: str) -> None: ...
@classmethod
def g(self, v: Union[int, str]) -> None: ...

@overload
@classmethod
def h(self, v: int) -> A: ...
@overload
@classmethod
def h(self, v: str) -> A: ...
@deprecated("use `h2` instead")
@classmethod
def h(self, v: Union[int, str]) -> A: ...

class B(A): ...

a = A()
a.f(1) # E: overload def (self: type[__main__.A], v: builtins.int) of function __main__.A.f is deprecated: pass `str` instead
a.f("x")
a.g(1)
a.g("x") # E: overload def (self: type[__main__.A], v: builtins.str) of function __main__.A.g is deprecated: pass `int` instead
a.h(1) # E: function __main__.A.h is deprecated: use `h2` instead
a.h("x") # E: function __main__.A.h is deprecated: use `h2` instead

b = B()
b.f(1) # E: overload def (self: type[__main__.A], v: builtins.int) of function __main__.A.f is deprecated: pass `str` instead
b.f("x")
b.g(1)
b.g("x") # E: overload def (self: type[__main__.A], v: builtins.str) of function __main__.A.g is deprecated: pass `int` instead
b.h(1) # E: function __main__.A.h is deprecated: use `h2` instead
b.h("x") # E: function __main__.A.h is deprecated: use `h2` instead

[builtins fixtures/tuple.pyi]


[case testDeprecatedOverloadedStaticMethods]
# flags: --enable-error-code=deprecated

from typing import Iterator, Union
from typing_extensions import deprecated, overload

class A:
@overload
@staticmethod
@deprecated("pass `str` instead")
def f(v: int) -> None: ...
@overload
@staticmethod
def f(v: str) -> None: ...
@staticmethod
def f(v: Union[int, str]) -> None: ...

@overload
@staticmethod
def g(v: int) -> None: ...
@overload
@staticmethod
@deprecated("pass `int` instead")
def g(v: str) -> None: ...
@staticmethod
def g(v: Union[int, str]) -> None: ...

@overload
@staticmethod
def h(v: int) -> A: ...
@overload
@staticmethod
def h(v: str) -> A: ...
@deprecated("use `h2` instead")
@staticmethod
def h(v: Union[int, str]) -> A: ...

class B(A): ...

a = A()
a.f(1) # E: overload def (v: builtins.int) of function __main__.A.f is deprecated: pass `str` instead
a.f("x")
a.g(1)
a.g("x") # E: overload def (v: builtins.str) of function __main__.A.g is deprecated: pass `int` instead
a.h(1) # E: function __main__.A.h is deprecated: use `h2` instead
a.h("x") # E: function __main__.A.h is deprecated: use `h2` instead

b = B()
b.f(1) # E: overload def (v: builtins.int) of function __main__.A.f is deprecated: pass `str` instead
b.f("x")
b.g(1)
b.g("x") # E: overload def (v: builtins.str) of function __main__.A.g is deprecated: pass `int` instead
b.h(1) # E: function __main__.A.h is deprecated: use `h2` instead
b.h("x") # E: function __main__.A.h is deprecated: use `h2` instead

[builtins fixtures/classmethod.pyi]


[case testDeprecatedOverloadedSpecialMethods]
# flags: --enable-error-code=deprecated

Expand Down
Loading