Skip to content

Commit

Permalink
Allow using super() in methods with self-types (#13488)
Browse files Browse the repository at this point in the history
Fixes #9282 

It looks like `super()` checking is too strict for methods with self-types. Mypy explicitly allows self-type annotation to be a supertype of current class, so to be consistent, I relax the check for `super()` as well.

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
  • Loading branch information
ilevkivskyi and AlexWaygood authored Aug 23, 2022
1 parent d6feadf commit a6e3454
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 2 deletions.
18 changes: 16 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
is_generic_instance,
is_named_instance,
is_optional,
is_self_type_like,
remove_optional,
)
from mypy.typestate import TypeState
Expand Down Expand Up @@ -4268,9 +4269,22 @@ def visit_super_expr(self, e: SuperExpr) -> Type:

# The base is the first MRO entry *after* type_info that has a member
# with the right name
try:
index = None
if type_info in mro:
index = mro.index(type_info)
except ValueError:
else:
method = self.chk.scope.top_function()
assert method is not None
# Mypy explicitly allows supertype upper bounds (and no upper bound at all)
# for annotating self-types. However, if such an annotation is used for
# checking super() we will still get an error. So to be consistent, we also
# allow such imprecise annotations for use with super(), where we fall back
# to the current class MRO instead.
if is_self_type_like(instance_type, is_classmethod=method.is_class):
if e.info and type_info in e.info.mro:
mro = e.info.mro
index = mro.index(type_info)
if index is None:
self.chk.fail(message_registry.SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1, e)
return AnyType(TypeOfAny.from_error)

Expand Down
10 changes: 10 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3313,6 +3313,16 @@ def is_literal_type(typ: ProperType, fallback_fullname: str, value: LiteralValue
return typ.value == value


def is_self_type_like(typ: Type, *, is_classmethod: bool) -> bool:
"""Does this look like a self-type annotation?"""
typ = get_proper_type(typ)
if not is_classmethod:
return isinstance(typ, TypeVarType)
if not isinstance(typ, TypeType):
return False
return isinstance(typ.item, TypeVarType)


names: Final = globals().copy()
names.pop("NOT_READY", None)
deserialize_map: Final = {
Expand Down
29 changes: 29 additions & 0 deletions test-data/unit/check-super.test
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,32 @@ class A:
class B(A):
def h(self, t: Type[None]) -> None:
super(t, self).f # E: Unsupported argument 1 for "super"

[case testSuperSelfTypeInstanceMethod]
from typing import TypeVar, Type

T = TypeVar("T", bound="A")

class A:
def foo(self: T) -> T: ...

class B(A):
def foo(self: T) -> T:
reveal_type(super().foo()) # N: Revealed type is "T`-1"
return super().foo()

[case testSuperSelfTypeClassMethod]
from typing import TypeVar, Type

T = TypeVar("T", bound="A")

class A:
@classmethod
def foo(cls: Type[T]) -> T: ...

class B(A):
@classmethod
def foo(cls: Type[T]) -> T:
reveal_type(super().foo()) # N: Revealed type is "T`-1"
return super().foo()
[builtins fixtures/classmethod.pyi]

0 comments on commit a6e3454

Please sign in to comment.