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. 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

Lines changed: 7 additions & 1 deletion
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)

test-data/unit/check-classes.test

Lines changed: 45 additions & 1 deletion
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

test-data/unit/check-functions.test

Lines changed: 33 additions & 0 deletions
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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy