Skip to content

Commit 97d25cf

Browse files
ilevkivskyimsullivan
authored andcommitted
Fix __init_subclass__ type check (#7723)
1 parent 9751449 commit 97d25cf

File tree

3 files changed

+19
-5
lines changed

3 files changed

+19
-5
lines changed

mypy/checker.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,7 +1706,9 @@ def visit_class_def(self, defn: ClassDef) -> None:
17061706
with self.scope.push_class(defn.info):
17071707
self.accept(defn.defs)
17081708
self.binder = old_binder
1709-
self.check_init_subclass(defn)
1709+
if not (defn.info.typeddict_type or defn.info.tuple_type or defn.info.is_enum):
1710+
# If it is not a normal class (not a special form) check class keywords.
1711+
self.check_init_subclass(defn)
17101712
if not defn.has_incompatible_baseclass:
17111713
# Otherwise we've already found errors; more errors are not useful
17121714
self.check_multiple_inheritance(typ)
@@ -1751,6 +1753,11 @@ def check_init_subclass(self, defn: ClassDef) -> None:
17511753
Base.__init_subclass__(thing=5) is called at line 4. This is what we simulate here.
17521754
Child.__init_subclass__ is never called.
17531755
"""
1756+
if (defn.info.metaclass_type and
1757+
defn.info.metaclass_type.type.fullname() not in ('builtins.type', 'abc.ABCMeta')):
1758+
# We can't safely check situations when both __init_subclass__ and a custom
1759+
# metaclass are present.
1760+
return
17541761
# At runtime, only Base.__init_subclass__ will be called, so
17551762
# we skip the current class itself.
17561763
for base in defn.info.mro[1:]:
@@ -1775,10 +1782,9 @@ def check_init_subclass(self, defn: ClassDef) -> None:
17751782
self.expr_checker.accept(call_expr,
17761783
allow_none_return=True,
17771784
always_allow_any=True)
1778-
# We are only interested in the first Base having __init_subclass__
1785+
# We are only interested in the first Base having __init_subclass__,
17791786
# all other bases have already been checked.
17801787
break
1781-
return
17821788

17831789
def check_protocol_variance(self, defn: ClassDef) -> None:
17841790
"""Check that protocol definition is compatible with declared

test-data/unit/check-classes.test

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6229,14 +6229,21 @@ class Child(Base, thing=5, default_name=""):
62296229
[builtins fixtures/object_with_init_subclass.pyi]
62306230

62316231
[case testInitSubclassWithMetaclassOK]
6232-
class Base(type):
6232+
class Base:
62336233
thing: int
62346234

62356235
def __init_subclass__(cls, thing: int):
62366236
cls.thing = thing
62376237

6238-
class Child(Base, metaclass=Base, thing=0):
6238+
class Child(Base, metaclass=type, thing=0):
62396239
pass
6240+
[builtins fixtures/object_with_init_subclass.pyi]
6241+
6242+
[case testInitSubclassWithCustomMetaclassOK]
6243+
class M(type): ...
6244+
class Child(metaclass=M, thing=0):
6245+
pass
6246+
[builtins fixtures/object_with_init_subclass.pyi]
62406247

62416248
[case testTooManyArgsForObject]
62426249
class A(thing=5):

test-data/unit/fixtures/dict.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ VT = TypeVar('VT')
1010

1111
class object:
1212
def __init__(self) -> None: pass
13+
def __init_subclass__(cls) -> None: pass
1314
def __eq__(self, other: object) -> bool: pass
1415

1516
class type: pass

0 commit comments

Comments
 (0)