8000 Check superclass compatibility of untyped methods if `--check-untyped… · python/mypy@1af7b2c · GitHub
[go: up one dir, main page]

Skip to content

Commit 1af7b2c

Browse files
authored
Check superclass compatibility of untyped methods if --check-untyped-defs is set (#18970)
This PR enables superclass compatibility checks for untyped methods when `--check-untyped-defs` is set. IMO this behavior is correct as `--check-untyped-defs` is essentially "treat everything as if there were `: Any` annotations on all arguments", hence checking arg count and names is sound. 8000 This PR, however, allows `@override` on classes that have Any fallback as those are often coming from unfollowed imports. This PR started as an attempt to reject `@override` on untyped defs not found in superclass, but I think it's better to just run all compatibility checks if the flag is enabled.
1 parent 25b1bb8 commit 1af7b2c

File tree

3 files changed

+85
-2
lines changed

3 files changed

+85
-2
lines changed

mypy/checker.py

Original file line numberDiff line numberDiff line change
@@ -750,6 +750,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
750750
defn.is_explicit_override
751751
and not found_method_base_classes
752752
and found_method_base_classes is not None
753+
# If the class has Any fallback, we can't be certain that a method
754+
# is really missing - it might come from unfollowed import.
755+
and not defn.info.fallback_to_any
753756
):
754757
self.msg.no_overridable_method(defn.name, defn)
755758
self.check_explicit_override_decorator(defn, found_method_base_classes, defn.impl)
@@ -5285,12 +5288,15 @@ def visit_decorator_inner(
52855288
# For overloaded functions/properties we already checked override for overload as a whole.
52865289
if allow_empty or skip_first_item:
52875290
return
5288-
if e.func.info and not e.func.is_dynamic() and not e.is_overload:
5291+
if e.func.info and not e.is_overload:
52895292
found_method_base_classes = self.check_method_override(e)
52905293
if (
52915294
e.func.is_explicit_override
52925295
and not found_method_base_classes
52935296
and found_method_base_classes is not None
5297+
# If the class has Any fallback, we can't be certain that a method
5298+
# is really missing - it might come from unfollowed import.
5299+
and not e.func.info.fallback_to_any
52945300
):
52955301
self.msg.no_overridable_method(e.func.name, e.func)
52965302
self.check_explicit_override_decorator(e.func, found_method_base_classes)
Original file line numberDiff line numberDiff line change
@@ -6651,7 +6651,51 @@ from typing import TypeVar, Tuple, Callable
66516651
T = TypeVar('T')
66526652
def deco(f: Callable[..., T]) -> Callable[..., Tuple[T, int]]: ...
66536653
[builtins fixtures/tuple.pyi]
6654-
[out]
6654+
6655+
[case testOverrideWithUntypedNotChecked]
6656+
class Parent:
6657+
def foo(self, x):
6658+
...
6659+
def bar(self, x):
6660+
...
6661+
def baz(self, x: int) -> str:
6662+
return ""
6663+
6664+
class Child(Parent):
6665+
def foo(self, y): # OK: names not checked
6666+
...
6667+
def bar(self, x, y):
6668+
...
6669+
def baz(self, x, y):
6670+
return ""
6671+
[builtins fixtures/tuple.pyi]
6672+
6673+
[case testOverrideWithUntypedCheckedWithCheckUntypedDefs]
6674+
# flags: --check-untyped-defs
6675+
class Parent:
6676+
def foo(self, x):
6677+
...
6678+
def bar(self, x):
6679+
...
6680+
def baz(self, x: int) -> str:
6681+
return ""
6682+
6683+
class Child(Parent):
6684+
def foo(self, y): # OK: names not checked
6685+
...
6686+
def bar(self, x, y) -> None: # E: Signature of "bar" incompatible with supertype "Parent" \
6687+
# N: Superclass: \
6688+
# N: def bar(self, x: Any) -> Any \
6689+
# N: Subclass: \
6690+
# N: def bar(self, x: Any, y: Any) -> None
6691+
...
6692+
def baz(self, x, y): # E: Signature of "baz" incompatible with supertype "Parent" \
6693+
# N: Superclass: \
6694+
# N: def baz(self, x: int) -> str \
6695+
# N: Subclass: \
6696+
# N: def baz(self, x: Any, y: Any) -> Any
6697+
return ""
6698+
[builtins fixtures/tuple.pyi]
66556699

66566700
[case testOptionalDescriptorsBinder]
66576701
from typing import Type, TypeVar, Optional
Original file line numberDiff line numberDiff line change
@@ -3285,6 +3285,39 @@ class C(B):
32853285
def __f(self, y: int) -> str: pass # OK
32863286
[typing fixtures/typing-override.pyi]
32873287

3288+
[case testOverrideUntypedDef]
3289+
# flags: --python-version 3.12
3290+
from typing import override
3291+
3292+
class Parent: pass
3293+
3294+
class Child(Parent):
3295+
@override
3296+
def foo(self, y): pass # E: Method "foo" is marked as an override, but no base method was found with this name
3297+
3298+
[typing fixtures/typing-override.pyi]
3299+
3300+
[case testOverrideOnUnknownBaseClass]
3301+
# flags: --python-version 3.12
3302+
from typing import overload, override
3303+
3304+
from unknown import UnknownParent # type: ignore[import-not-found]
3305+
3306+
class UnknownChild(UnknownParent):
3307+
@override
3308+
def foo(self, y): pass # OK
3309+
@override
3310+
def bar(self, y: str) -> None: pass # OK
3311+
3312+
@override
3313+
@overload
3314+
def baz(self, y: str) -> None: ...
3315+
@override
3316+
@overload
3317+
def baz(self, y: int) -> None: ...
3318+
def baz(self, y: str | int) -> None: ...
3319+
[typing fixtures/typing-override.pyi]
3320+
32883321
[case testCallableProperty]
32893322
from typing import Callable
32903323