diff --git a/mypy/checker.py b/mypy/checker.py index 64cf094558f9..6726c292a0b1 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -550,10 +550,10 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str) -> None: if fdef: # Check if __init__ has an invalid, non-None return type. - if (fdef.info and fdef.name() == '__init__' and + if (fdef.info and fdef.name() in ('__init__', '__init_subclass__') and not isinstance(typ.ret_type, (Void, NoneTyp)) and not self.dynamic_funcs[-1]): - self.fail(messages.INIT_MUST_HAVE_NONE_RETURN_TYPE, + self.fail(messages.MUST_HAVE_NONE_RETURN_TYPE.format(fdef.name()), item.type) show_untyped = not self.is_typeshed_stub or self.options.warn_incomplete_stub @@ -618,7 +618,7 @@ def is_implicit_any(t: Type) -> bool: if (isinstance(defn, FuncDef) and ref_type is not None and i == 0 and not defn.is_static and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]): - if defn.is_class or defn.name() == '__new__': + if defn.is_class or defn.name() in ('__new__', '__init_subclass__'): ref_type = mypy.types.TypeType(ref_type) erased = erase_to_bound(arg_type) if not is_subtype_ignoring_tvars(ref_type, erased): diff --git a/mypy/messages.py b/mypy/messages.py index fb5923dc52c4..210147c14ba5 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -44,7 +44,7 @@ INCOMPATIBLE_TYPES_IN_YIELD = 'Incompatible types in yield' INCOMPATIBLE_TYPES_IN_YIELD_FROM = 'Incompatible types in "yield from"' INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION = 'Incompatible types in string interpolation' -INIT_MUST_HAVE_NONE_RETURN_TYPE = 'The return type of "__init__" must be None' +MUST_HAVE_NONE_RETURN_TYPE = 'The return type of "{}" must be None' TUPLE_INDEX_MUST_BE_AN_INT_LITERAL = 'Tuple index must be an integer literal' TUPLE_SLICE_MUST_BE_AN_INT_LITERAL = 'Tuple slice must be an integer literal' TUPLE_INDEX_OUT_OF_RANGE = 'Tuple index out of range' diff --git a/mypy/semanal.py b/mypy/semanal.py index 8771fb768d80..539969c35bae 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -335,7 +335,7 @@ def prepare_method_signature(self, func: FuncDef) -> None: elif isinstance(functype, CallableType): self_type = functype.arg_types[0] if isinstance(self_type, AnyType): - if func.is_class or func.name() == '__new__': + if func.is_class or func.name() in ('__new__', '__init_subclass__'): leading_type = self.class_type(self.type) else: leading_type = fill_typevars(self.type) diff --git a/mypy/sharedparse.py b/mypy/sharedparse.py index ba1da1eb17db..1643aaccf321 100644 --- a/mypy/sharedparse.py +++ b/mypy/sharedparse.py @@ -36,6 +36,7 @@ "__imod__", "__imul__", "__init__", + "__init_subclass__", "__int__", "__invert__", "__ior__", @@ -84,6 +85,7 @@ MAGIC_METHODS_ALLOWING_KWARGS = { "__init__", + "__init_subclass__", "__new__", } diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index bdfa2de9b340..2f6e0298c2cb 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -382,6 +382,20 @@ class A: [out] main:3: error: The return type of "__init__" must be None +[case testInitSubclassWithReturnValueType] +import typing +class A: + def __init_subclass__(cls) -> 'A': pass +[out] +main:3: error: The return type of "__init_subclass__" must be None + +[case testInitSubclassWithImplicitReturnValueType] +import typing +class A: + def __init_subclass__(cls, x: int=1): pass +[out] +main:3: error: The return type of "__init_subclass__" must be None + [case testGlobalFunctionInitWithReturnType] import typing a = __init__() # type: A @@ -1205,7 +1219,7 @@ class D: def __get__(self, inst: Any, own: str) -> Any: pass class A: f = D() -A().f # E: Argument 2 to "__get__" of "D" has incompatible type Type[A]; expected "str" +A().f # E: Argument 2 to "__get__" of "D" has incompatible type Type[A]; expected "str" [case testDescriptorGetSetDifferentTypes] from typing import Any diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 42719512e133..98bcfa1e9e65 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -108,9 +108,9 @@ reveal_type(B.new()) # E: Revealed type is '__main__.B*' [case testSelfTypeOverride] from typing import TypeVar, cast - + T = TypeVar('T', bound='A', covariant=True) - + class A: def copy(self: T) -> T: pass @@ -124,15 +124,15 @@ class C(A): reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C*' reveal_type(C().copy()) # E: Revealed type is '__main__.C*' reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A*' -reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A*' +reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A*' [builtins fixtures/bool.pyi] [case testSelfTypeSuper] from typing import TypeVar, cast - + T = TypeVar('T', bound='A', covariant=True) - + class A: def copy(self: T) -> T: pass @@ -147,7 +147,7 @@ class B(A): [case testSelfTypeRecursiveBinding] from typing import TypeVar, Callable, Type - + T = TypeVar('T', bound='A', covariant=True) class A: # TODO: This is potentially unsafe, as we use T in an argument type @@ -173,7 +173,7 @@ reveal_type(B.new) # E: Revealed type is 'def (factory: def (__main__.B*) -> __ [case testSelfTypeBound] from typing import TypeVar, Callable, cast - + TA = TypeVar('TA', bound='A', covariant=True) class A: @@ -200,9 +200,9 @@ class B(A): -- # TODO: fail for this -- [case testSelfTypeBare] -- from typing import TypeVar, Type --- +-- -- T = TypeVar('T', bound='E') --- +-- -- class E: -- def copy(self: T, other: T) -> T: pass @@ -262,7 +262,7 @@ class B: @classmethod def cfoo(cls: Type[Q]) -> Q: return cls() - + class C: def foo(self: C) -> C: return self @@ -272,7 +272,7 @@ class C: class D: def foo(self: str) -> str: # E: The erased type of self 'builtins.str' is not a supertype of its class '__main__.D' - return self + return self @staticmethod def bar(self: str) -> str: @@ -302,28 +302,43 @@ class C: [case testSelfTypeNew] from typing import TypeVar, Type -T = TypeVar('T', bound=A) -class A: +T = TypeVar('T', bound=A) +class A: def __new__(cls: Type[T]) -> T: return cls() + def __init_subclass__(cls: Type[T]) -> None: + pass + class B: def __new__(cls: Type[T]) -> T: # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class 'Type[__main__.B]' return cls() -class C: + def __init_subclass__(cls: Type[T]) -> None: # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class 'Type[__main__.B]' + pass + +class C: def __new__(cls: Type[C]) -> C: return cls() + def __init_subclass__(cls: Type[C]) -> None: + pass + class D: def __new__(cls: D) -> D: # E: The erased type of self '__main__.D' is not a supertype of its class 'Type[__main__.D]' return cls + def __init_subclass__(cls: D) -> None: # E: The erased type of self '__main__.D' is not a supertype of its class 'Type[__main__.D]' + pass + class E: def __new__(cls) -> E: reveal_type(cls) # E: Revealed type is 'def () -> __main__.E' return cls() + def __init_subclass__(cls) -> None: + reveal_type(cls) # E: Revealed type is 'def () -> __main__.E' + [case testSelfTypeProperty] from typing import TypeVar