Skip to content

Commit d6e5fe7

Browse files
JelleZijlstragvanrossum
authored andcommitted
Add support for __init_subclass__ (#2654)
* add tests for init_subclass * enforce that init_subclass must return None * infer the first argument to __init_subclass__ correctly
1 parent 083a1a8 commit d6e5fe7

File tree

6 files changed

+51
-20
lines changed

6 files changed

+51
-20
lines changed

mypy/checker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -550,10 +550,10 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str) -> None:
550550

551551
if fdef:
552552
# Check if __init__ has an invalid, non-None return type.
553-
if (fdef.info and fdef.name() == '__init__' and
553+
if (fdef.info and fdef.name() in ('__init__', '__init_subclass__') and
554554
not isinstance(typ.ret_type, (Void, NoneTyp)) and
555555
not self.dynamic_funcs[-1]):
556-
self.fail(messages.INIT_MUST_HAVE_NONE_RETURN_TYPE,
556+
self.fail(messages.MUST_HAVE_NONE_RETURN_TYPE.format(fdef.name()),
557557
item.type)
558558

559559
show_untyped = not self.is_typeshed_stub or self.options.warn_incomplete_stub
@@ -618,7 +618,7 @@ def is_implicit_any(t: Type) -> bool:
618618
if (isinstance(defn, FuncDef) and ref_type is not None and i == 0
619619
and not defn.is_static
620620
and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]):
621-
if defn.is_class or defn.name() == '__new__':
621+
if defn.is_class or defn.name() in ('__new__', '__init_subclass__'):
622622
ref_type = mypy.types.TypeType(ref_type)
623623
erased = erase_to_bound(arg_type)
624624
if not is_subtype_ignoring_tvars(ref_type, erased):

mypy/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
INCOMPATIBLE_TYPES_IN_YIELD = 'Incompatible types in yield'
4545
INCOMPATIBLE_TYPES_IN_YIELD_FROM = 'Incompatible types in "yield from"'
4646
INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION = 'Incompatible types in string interpolation'
47-
INIT_MUST_HAVE_NONE_RETURN_TYPE = 'The return type of "__init__" must be None'
47+
MUST_HAVE_NONE_RETURN_TYPE = 'The return type of "{}" must be None'
4848
TUPLE_INDEX_MUST_BE_AN_INT_LITERAL = 'Tuple index must be an integer literal'
4949
TUPLE_SLICE_MUST_BE_AN_INT_LITERAL = 'Tuple slice must be an integer literal'
5050
TUPLE_INDEX_OUT_OF_RANGE = 'Tuple index out of range'

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ def prepare_method_signature(self, func: FuncDef) -> None:
335335
elif isinstance(functype, CallableType):
336336
self_type = functype.arg_types[0]
337337
if isinstance(self_type, AnyType):
338-
if func.is_class or func.name() == '__new__':
338+
if func.is_class or func.name() in ('__new__', '__init_subclass__'):
339339
leading_type = self.class_type(self.type)
340340
else:
341341
leading_type = fill_typevars(self.type)

mypy/sharedparse.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"__imod__",
3737
"__imul__",
3838
"__init__",
39+
"__init_subclass__",
3940
"__int__",
4041
"__invert__",
4142
"__ior__",
@@ -84,6 +85,7 @@
8485

8586
MAGIC_METHODS_ALLOWING_KWARGS = {
8687
"__init__",
88+
"__init_subclass__",
8789
"__new__",
8890
}
8991

test-data/unit/check-classes.test

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,20 @@ class A:
382382
[out]
383383
main:3: error: The return type of "__init__" must be None
384384

385+
[case testInitSubclassWithReturnValueType]
386+
import typing
387+
class A:
388+
def __init_subclass__(cls) -> 'A': pass
389+
[out]
390+
main:3: error: The return type of "__init_subclass__" must be None
391+
392+
[case testInitSubclassWithImplicitReturnValueType]
393+
import typing
394+
class A:
395+
def __init_subclass__(cls, x: int=1): pass
396+
[out]
397+
main:3: error: The return type of "__init_subclass__" must be None
398+
385399
[case testGlobalFunctionInitWithReturnType]
386400
import typing
387401
a = __init__() # type: A
@@ -1205,7 +1219,7 @@ class D:
12051219
def __get__(self, inst: Any, own: str) -> Any: pass
12061220
class A:
12071221
f = D()
1208-
A().f # E: Argument 2 to "__get__" of "D" has incompatible type Type[A]; expected "str"
1222+
A().f # E: Argument 2 to "__get__" of "D" has incompatible type Type[A]; expected "str"
12091223

12101224
[case testDescriptorGetSetDifferentTypes]
12111225
from typing import Any

test-data/unit/check-selftype.test

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ reveal_type(B.new()) # E: Revealed type is '__main__.B*'
108108

109109
[case testSelfTypeOverride]
110110
from typing import TypeVar, cast
111-
111+
112112
T = TypeVar('T', bound='A', covariant=True)
113-
113+
114114
class A:
115115
def copy(self: T) -> T: pass
116116

@@ -124,15 +124,15 @@ class C(A):
124124
reveal_type(C().copy) # E: Revealed type is 'def () -> __main__.C*'
125125
reveal_type(C().copy()) # E: Revealed type is '__main__.C*'
126126
reveal_type(cast(A, C()).copy) # E: Revealed type is 'def () -> __main__.A*'
127-
reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A*'
127+
reveal_type(cast(A, C()).copy()) # E: Revealed type is '__main__.A*'
128128

129129
[builtins fixtures/bool.pyi]
130130

131131
[case testSelfTypeSuper]
132132
from typing import TypeVar, cast
133-
133+
134134
T = TypeVar('T', bound='A', covariant=True)
135-
135+
136136
class A:
137137
def copy(self: T) -> T: pass
138138

@@ -147,7 +147,7 @@ class B(A):
147147

148148
[case testSelfTypeRecursiveBinding]
149149
from typing import TypeVar, Callable, Type
150-
150+
151151
T = TypeVar('T', bound='A', covariant=True)
152152
class A:
153153
# 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*) -> __
173173

174174
[case testSelfTypeBound]
175175
from typing import TypeVar, Callable, cast
176-
176+
177177
TA = TypeVar('TA', bound='A', covariant=True)
178178

179179
class A:
@@ -200,9 +200,9 @@ class B(A):
200200
-- # TODO: fail for this
201201
-- [case testSelfTypeBare]
202202
-- from typing import TypeVar, Type
203-
--
203+
--
204204
-- T = TypeVar('T', bound='E')
205-
--
205+
--
206206
-- class E:
207207
-- def copy(self: T, other: T) -> T: pass
208208

@@ -262,7 +262,7 @@ class B:
262262
@classmethod
263263
def cfoo(cls: Type[Q]) -> Q:
264264
return cls()
265-
265+
266266
class C:
267267
def foo(self: C) -> C: return self
268268

@@ -272,7 +272,7 @@ class C:
272272

273273
class D:
274274
def foo(self: str) -> str: # E: The erased type of self 'builtins.str' is not a supertype of its class '__main__.D'
275-
return self
275+
return self
276276

277277
@staticmethod
278278
def bar(self: str) -> str:
@@ -302,28 +302,43 @@ class C:
302302
[case testSelfTypeNew]
303303
from typing import TypeVar, Type
304304

305-
T = TypeVar('T', bound=A)
306-
class A:
305+
T = TypeVar('T', bound=A)
306+
class A:
307307
def __new__(cls: Type[T]) -> T:
308308
return cls()
309309

310+
def __init_subclass__(cls: Type[T]) -> None:
311+
pass
312+
310313
class B:
311314
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]'
312315
return cls()
313316

314-
class C:
317+
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]'
318+
pass
319+
320+
class C:
315321
def __new__(cls: Type[C]) -> C:
316322
return cls()
317323

324+
def __init_subclass__(cls: Type[C]) -> None:
325+
pass
326+
318327
class D:
319328
def __new__(cls: D) -> D: # E: The erased type of self '__main__.D' is not a supertype of its class 'Type[__main__.D]'
320329
return cls
321330

331+
def __init_subclass__(cls: D) -> None: # E: The erased type of self '__main__.D' is not a supertype of its class 'Type[__main__.D]'
332+
pass
333+
322334
class E:
323335
def __new__(cls) -> E:
324336
reveal_type(cls) # E: Revealed type is 'def () -> __main__.E'
325337
return cls()
326338

339+
def __init_subclass__(cls) -> None:
340+
reveal_type(cls) # E: Revealed type is 'def () -> __main__.E'
341+
327342
[case testSelfTypeProperty]
328343
from typing import TypeVar
329344

0 commit comments

Comments
 (0)