Skip to content

Commit a6e3454

Browse files
Allow using super() in methods with self-types (#13488)
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 <[email protected]>
1 parent d6feadf commit a6e3454

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed

mypy/checkexpr.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
is_generic_instance,
154154
is_named_instance,
155155
is_optional,
156+
is_self_type_like,
156157
remove_optional,
157158
)
158159
from mypy.typestate import TypeState
@@ -4268,9 +4269,22 @@ def visit_super_expr(self, e: SuperExpr) -> Type:
42684269

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

mypy/types.py

+10
Original file line numberDiff line numberDiff line change
@@ -3313,6 +3313,16 @@ def is_literal_type(typ: ProperType, fallback_fullname: str, value: LiteralValue
33133313
return typ.value == value
33143314

33153315

3316+
def is_self_type_like(typ: Type, *, is_classmethod: bool) -> bool:
3317+
"""Does this look like a self-type annotation?"""
3318+
typ = get_proper_type(typ)
3319+
if not is_classmethod:
3320+
return isinstance(typ, TypeVarType)
3321+
if not isinstance(typ, TypeType):
3322+
return False
3323+
return isinstance(typ.item, TypeVarType)
3324+
3325+
33163326
names: Final = globals().copy()
33173327
names.pop("NOT_READY", None)
33183328
deserialize_map: Final = {

test-data/unit/check-super.test

+29
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,32 @@ class A:
380380
class B(A):
381381
def h(self, t: Type[None]) -> None:
382382
super(t, self).f # E: Unsupported argument 1 for "super"
383+
384+
[case testSuperSelfTypeInstanceMethod]
385+
from typing import TypeVar, Type
386+
387+
T = TypeVar("T", bound="A")
388+
389+
class A:
390+
def foo(self: T) -> T: ...
391+
392+
class B(A):
393+
def foo(self: T) -> T:
394+
reveal_type(super().foo()) # N: Revealed type is "T`-1"
395+
return super().foo()
396+
397+
[case testSuperSelfTypeClassMethod]
398+
from typing import TypeVar, Type
399+
400+
T = TypeVar("T", bound="A")
401+
402+
class A:
403+
@classmethod
404+
def foo(cls: Type[T]) -> T: ...
405+
406+
class B(A):
407+
@classmethod
408+
def foo(cls: Type[T]) -> T:
409+
reveal_type(super().foo()) # N: Revealed type is "T`-1"
410+
return super().foo()
411+
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)