Skip to content

Commit 49316f9

Browse files
authored
Allow super() for mixin protocols (#14082)
Fixes #12344 FWIW this is unsafe (since we don't know where the mixin will appear in the MRO of the actual implementation), but the alternative is having annoying false positives like this issue and e.g. #4335
1 parent f84f00a commit 49316f9

File tree

2 files changed

+32
-2
lines changed

2 files changed

+32
-2
lines changed

mypy/checkexpr.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4321,8 +4321,18 @@ def visit_super_expr(self, e: SuperExpr) -> Type:
43214321
mro = e.info.mro
43224322
index = mro.index(type_info)
43234323
if index is None:
4324-
self.chk.fail(message_registry.SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1, e)
4325-
return AnyType(TypeOfAny.from_error)
4324+
if (
4325+
instance_info.is_protocol
4326+
and instance_info != type_info
4327+
and not type_info.is_protocol
4328+
):
4329+
# A special case for mixins, in this case super() should point
4330+
# directly to the host protocol, this is not safe, since the real MRO
4331+
# is not known yet for mixin, but this feature is more like an escape hatch.
4332+
index = -1
4333+
else:
4334+
self.chk.fail(message_registry.SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1, e)
4335+
return AnyType(TypeOfAny.from_error)
43264336

43274337
if len(mro) == index + 1:
43284338
self.chk.fail(message_registry.TARGET_CLASS_HAS_NO_BASE_CLASS, e)

test-data/unit/check-selftype.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,26 @@ reveal_type(f.copy()) # N: Revealed type is "__main__.File"
792792
b.copy() # E: Invalid self argument "Bad" to attribute function "copy" with type "Callable[[T], T]"
793793
[builtins fixtures/tuple.pyi]
794794

795+
[case testMixinProtocolSuper]
796+
from typing import Protocol
797+
798+
class Base(Protocol):
799+
def func(self) -> int:
800+
...
801+
802+
class TweakFunc:
803+
def func(self: Base) -> int:
804+
return reveal_type(super().func()) # N: Revealed type is "builtins.int"
805+
806+
class Good:
807+
def func(self) -> int: ...
808+
class C(TweakFunc, Good): pass
809+
C().func() # OK
810+
811+
class Bad:
812+
def func(self) -> str: ...
813+
class CC(TweakFunc, Bad): pass # E: Definition of "func" in base class "TweakFunc" is incompatible with definition in base class "Bad"
814+
795815
[case testBadClassLevelDecoratorHack]
796816
from typing_extensions import Protocol
797817
from typing import TypeVar, Any

0 commit comments

Comments
 (0)