From 97d25cfb31132e9f9ce84056ecd51084ba7a7d97 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 16 Oct 2019 20:01:45 +0100 Subject: [PATCH] Fix __init_subclass__ type check (#7723) --- mypy/checker.py | 12 +++++++++--- test-data/unit/check-classes.test | 11 +++++++++-- test-data/unit/fixtures/dict.pyi | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index cb6242008e3d..fd8bb3b0cfa7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1706,7 +1706,9 @@ def visit_class_def(self, defn: ClassDef) -> None: with self.scope.push_class(defn.info): self.accept(defn.defs) self.binder = old_binder - self.check_init_subclass(defn) + if not (defn.info.typeddict_type or defn.info.tuple_type or defn.info.is_enum): + # If it is not a normal class (not a special form) check class keywords. + self.check_init_subclass(defn) if not defn.has_incompatible_baseclass: # Otherwise we've already found errors; more errors are not useful self.check_multiple_inheritance(typ) @@ -1751,6 +1753,11 @@ def check_init_subclass(self, defn: ClassDef) -> None: Base.__init_subclass__(thing=5) is called at line 4. This is what we simulate here. Child.__init_subclass__ is never called. """ + if (defn.info.metaclass_type and + defn.info.metaclass_type.type.fullname() not in ('builtins.type', 'abc.ABCMeta')): + # We can't safely check situations when both __init_subclass__ and a custom + # metaclass are present. + return # At runtime, only Base.__init_subclass__ will be called, so # we skip the current class itself. for base in defn.info.mro[1:]: @@ -1775,10 +1782,9 @@ def check_init_subclass(self, defn: ClassDef) -> None: self.expr_checker.accept(call_expr, allow_none_return=True, always_allow_any=True) - # We are only interested in the first Base having __init_subclass__ + # We are only interested in the first Base having __init_subclass__, # all other bases have already been checked. break - return def check_protocol_variance(self, defn: ClassDef) -> None: """Check that protocol definition is compatible with declared diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 59b0e528a1f2..b52420f16be9 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6229,14 +6229,21 @@ class Child(Base, thing=5, default_name=""): [builtins fixtures/object_with_init_subclass.pyi] [case testInitSubclassWithMetaclassOK] -class Base(type): +class Base: thing: int def __init_subclass__(cls, thing: int): cls.thing = thing -class Child(Base, metaclass=Base, thing=0): +class Child(Base, metaclass=type, thing=0): pass +[builtins fixtures/object_with_init_subclass.pyi] + +[case testInitSubclassWithCustomMetaclassOK] +class M(type): ... +class Child(metaclass=M, thing=0): + pass +[builtins fixtures/object_with_init_subclass.pyi] [case testTooManyArgsForObject] class A(thing=5): diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index 4d3cbad9c870..9e7970b34705 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -10,6 +10,7 @@ VT = TypeVar('VT') class object: def __init__(self) -> None: pass + def __init_subclass__(cls) -> None: pass def __eq__(self, other: object) -> bool: pass class type: pass