Skip to content

Commit 6af7027

Browse files
committed
Add testDeprecatedOverriddenMethod and implement the necessary changes in a prototype manner for first discussions.
1 parent b59878e commit 6af7027

File tree

2 files changed

+125
-38
lines changed

2 files changed

+125
-38
lines changed

mypy/checker.py

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1981,22 +1981,26 @@ def check_method_override(
19811981
"__post_init__",
19821982
) and (self.options.check_untyped_defs or not defn.is_dynamic())
19831983
found_method_base_classes: list[TypeInfo] = []
1984-
for base in defn.info.mro[1:]:
1985-
result = self.check_method_or_accessor_override_for_base(
1986-
defn, base, check_override_compatibility
1987-
)
1988-
if result is None:
1989-
# Node was deferred, we will have another attempt later.
1990-
return None
1991-
if result:
1992-
found_method_base_classes.append(base)
1984+
for directbase in defn.info.bases:
1985+
first_baseclass = True
1986+
for base in directbase.type.mro:
1987+
result = self.check_method_or_accessor_override_for_base(
1988+
defn, base, check_override_compatibility, first_baseclass
1989+
)
1990+
if result is None:
1991+
# Node was deferred, we will have another attempt later.
1992+
return None
1993+
if result:
1994+
found_method_base_classes.append(base)
1995+
first_baseclass = False
19931996
return found_method_base_classes
19941997

19951998
def check_method_or_accessor_override_for_base(
19961999
self,
19972000
defn: FuncDef | OverloadedFuncDef | Decorator,
19982001
base: TypeInfo,
19992002
check_override_compatibility: bool,
2003+
first_baseclass: bool,
20002004
) -> bool | None:
20012005
"""Check if method definition is compatible with a base class.
20022006
@@ -2020,7 +2024,7 @@ def check_method_or_accessor_override_for_base(
20202024
if check_override_compatibility:
20212025
# Check compatibility of the override signature
20222026
# (__init__, __new__, __init_subclass__ are special).
2023-
if self.check_method_override_for_base_with_name(defn, name, base):
2027+
if self.check_method_override_for_base_with_name(defn, name, base, first_baseclass):
20242028
return None
20252029
if name in operators.inplace_operator_methods:
20262030
# Figure out the name of the corresponding operator method.
@@ -2029,12 +2033,12 @@ def check_method_or_accessor_override_for_base(
20292033
# always introduced safely if a base class defined __add__.
20302034
# TODO can't come up with an example where this is
20312035
# necessary; now it's "just in case"
2032-
if self.check_method_override_for_base_with_name(defn, method, base):
2036+
if self.check_method_override_for_base_with_name(defn, method, base, first_baseclass):
20332037
return None
20342038
return found_base_method
20352039

20362040
def check_method_override_for_base_with_name(
2037-
self, defn: FuncDef | OverloadedFuncDef | Decorator, name: str, base: TypeInfo
2041+
self, defn: FuncDef | OverloadedFuncDef | Decorator, name: str, base: TypeInfo, first_baseclass: bool
20382042
) -> bool:
20392043
"""Check if overriding an attribute `name` of `base` with `defn` is valid.
20402044
@@ -2135,33 +2139,43 @@ def check_method_override_for_base_with_name(
21352139
if isinstance(original_type, AnyType) or isinstance(typ, AnyType):
21362140
pass
21372141
elif isinstance(original_type, FunctionLike) and isinstance(typ, FunctionLike):
2138-
# Check that the types are compatible.
2139-
ok = self.check_override(
2140-
typ,
2141-
original_type,
2142-
defn.name,
2143-
name,
2144-
base.name,
2145-
original_class_or_static,
2146-
override_class_or_static,
2147-
context,
2148-
)
2149-
# Check if this override is covariant.
2150-
if (
2151-
ok
2152-
and original_node
2153-
and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes
2154-
and self.is_writable_attribute(original_node)
2155-
and not is_subtype(original_type, typ, ignore_pos_arg_names=True)
2156-
):
2157-
base_str, override_str = format_type_distinctly(
2158-
original_type, typ, options=self.options
2159-
)
2160-
msg = message_registry.COVARIANT_OVERRIDE_OF_MUTABLE_ATTRIBUTE.with_additional_msg(
2161-
f' (base class "{base.name}" defined the type as {base_str},'
2162-
f" override has type {override_str})"
2142+
if isinstance(original_node, Decorator):
2143+
deprecated = original_node.func.deprecated
2144+
elif isinstance(original_node, OverloadedFuncDef):
2145+
deprecated = original_node.deprecated
2146+
else:
2147+
deprecated = None
2148+
if deprecated is None:
2149+
# Check that the types are compatible.
2150+
ok = self.check_override(
2151+
typ,
2152+
original_type,
2153+
defn.name,
2154+
name,
2155+
base.name,
2156+
original_class_or_static,
2157+
override_class_or_static,
2158+
context,
21632159
)
2164-
self.fail(msg, context)
2160+
# Check if this override is covariant.
2161+
if (
2162+
ok
2163+
and original_node
2164+
and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes
2165+
and self.is_writable_attribute(original_node)
2166+
and not is_subtype(original_type, typ, ignore_pos_arg_names=True)
2167+
):
2168+
base_str, override_str = format_type_distinctly(
2169+
original_type, typ, options=self.options
2170+
)
2171+
msg = message_registry.COVARIANT_OVERRIDE_OF_MUTABLE_ATTRIBUTE.with_additional_msg(
2172+
f' (base class "{base.name}" defined the type as {base_str},'
2173+
f" override has type {override_str})"
2174+
)
2175+
self.fail(msg, context)
2176+
elif context.is_explicit_override and first_baseclass and not is_private(context.name):
2177+
warn = self.fail if self.options.report_deprecated_as_error else self.note
2178+
warn(deprecated, context, code=codes.DEPRECATED)
21652179
elif isinstance(original_type, UnionType) and any(
21662180
is_subtype(typ, orig_typ, ignore_pos_arg_names=True)
21672181
for orig_typ in original_type.items

test-data/unit/check-deprecated.test

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,3 +542,76 @@ h(1.0) # E: No overload variant of "h" matches argument type "float" \
542542
# N: def h(x: str) -> str
543543

544544
[builtins fixtures/tuple.pyi]
545+
546+
547+
[case testDeprecatedOverriddenMethod]
548+
549+
from typing_extensions import deprecated, override
550+
551+
class A:
552+
@deprecated("replaced by g1")
553+
def f1(self) -> int: ...
554+
@deprecated("replaced by g2")
555+
def f2(self) -> int: ...
556+
@deprecated("replaced by g3")
557+
def __f3__(self) -> int: ...
558+
@deprecated("replaced by g4")
559+
def __f4__(self) -> int: ...
560+
561+
# no notes about incompatibilities
562+
# overriding a deprecated method is like defining a new one
563+
class B1(A):
564+
def f1(self) -> int: ...
565+
def f2(self) -> str: ...
566+
def __f3(self) -> int: ...
567+
def __f4(self) -> str: ...
568+
569+
# no notes about deprecations
570+
# the directly overriden class is okay
571+
class B2(B1):
572+
@override
573+
def f1(self) -> int: ...
574+
@override
575+
def f2(self) -> str: ...
576+
@override
577+
def __f3(self) -> int: ...
578+
@override
579+
def __f4(self) -> str: ...
580+
581+
# deprecation notes
582+
# single inheritance
583+
class C(A):
584+
@override
585+
def f1(self) -> int: ... # N: function __main__.A.f1 is deprecated: replaced by g1
586+
@override
587+
def f2(self) -> str: ... # N: function __main__.A.f2 is deprecated: replaced by g2
588+
@override
589+
def __f3(self) -> int: ... # E: Method "__f3" is marked as an override, but no base method was found with this name
590+
@override
591+
def __f4(self) -> str: ... # E: Method "__f4" is marked as an override, but no base method was found with this name
592+
593+
# deprecation notes
594+
# multiple inheritance
595+
class D(B1, A):
596+
@override
597+
def f1(self) -> int: ... # N: function __main__.A.f1 is deprecated: replaced by g1
598+
@override
599+
def f2(self) -> str: ... # N: function __main__.A.f2 is deprecated: replaced by g2
600+
@override
601+
def __f3(self) -> int: ... # No error?
602+
@override
603+
def __f4(self) -> str: ... # No error?
604+
605+
# no deprecation notes
606+
# multiple inheritance
607+
class E(B1, C):
608+
@override
609+
def f1(self) -> int: ...
610+
@override
611+
def f2(self) -> str: ...
612+
@override
613+
def __f3(self) -> int: ... # No error?
614+
@override
615+
def __f4(self) -> str: ... # No error?
616+
617+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)