Skip to content

Fix __init_subclass__ type check #7723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:]:
Expand All @@ -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
Expand Down
11 changes: 9 additions & 2 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -6339,14 +6339,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):
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/fixtures/dict.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down