Skip to content

Commit 0c0f071

Browse files
iyangingBvB93
andauthored
Allow __init__ and __new__ to return NoReturn (#13480)
Co-authored-by: Bas van Beek <[email protected]>
1 parent e981431 commit 0c0f071

File tree

4 files changed

+89
-13
lines changed

4 files changed

+89
-13
lines changed

mypy/checker.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,11 +1037,13 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str | None) ->
10371037
# precise type.
10381038
if isinstance(item, FuncDef):
10391039
fdef = item
1040-
# Check if __init__ has an invalid, non-None return type.
1040+
# Check if __init__ has an invalid return type.
10411041
if (
10421042
fdef.info
10431043
and fdef.name in ("__init__", "__init_subclass__")
1044-
and not isinstance(get_proper_type(typ.ret_type), NoneType)
1044+
and not isinstance(
1045+
get_proper_type(typ.ret_type), (NoneType, UninhabitedType)
1046+
)
10451047
and not self.dynamic_funcs[-1]
10461048
):
10471049
self.fail(
@@ -1327,7 +1329,9 @@ def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None:
13271329
"returns",
13281330
"but must return a subtype of",
13291331
)
1330-
elif not isinstance(get_proper_type(bound_type.ret_type), (AnyType, Instance, TupleType)):
1332+
elif not isinstance(
1333+
get_proper_type(bound_type.ret_type), (AnyType, Instance, TupleType, UninhabitedType)
1334+
):
13311335
self.fail(
13321336
message_registry.NON_INSTANCE_NEW_TYPE.format(format_type(bound_type.ret_type)),
13331337
fdef,

mypy/typeops.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,15 @@ def tuple_fallback(typ: TupleType) -> Instance:
107107
return Instance(info, [join_type_list(items)])
108108

109109

110+
def get_self_type(func: CallableType, default_self: Instance | TupleType) -> Type | None:
111+
if isinstance(get_proper_type(func.ret_type), UninhabitedType):
112+
return func.ret_type
113+
elif func.arg_types and func.arg_types[0] != default_self and func.arg_kinds[0] == ARG_POS:
114+
return func.arg_types[0]
115+
else:
116+
return None
117+
118+
110119
def type_object_type_from_function(
111120
signature: FunctionLike, info: TypeInfo, def_info: TypeInfo, fallback: Instance, is_new: bool
112121
) -> FunctionLike:
@@ -117,14 +126,7 @@ def type_object_type_from_function(
117126
# classes such as subprocess.Popen.
118127
default_self = fill_typevars(info)
119128
if not is_new and not info.is_newtype:
120-
orig_self_types = [
121-
(
122-
it.arg_types[0]
123-
if it.arg_types and it.arg_types[0] != default_self and it.arg_kinds[0] == ARG_POS
124-
else None
125-
)
126-
for it in signature.items
127-
]
129+
orig_self_types = [get_self_type(it, default_self) for it in signature.items]
128130
else:
129131
orig_self_types = [None] * len(signature.items)
130132

@@ -177,7 +179,7 @@ def class_callable(
177179
default_ret_type = fill_typevars(info)
178180
explicit_type = init_ret_type if is_new else orig_self_type
179181
if (
180-
isinstance(explicit_type, (Instance, TupleType))
182+
isinstance(explicit_type, (Instance, TupleType, UninhabitedType))
181183
# We have to skip protocols, because it can be a subtype of a return type
182184
# by accident. Like `Hashable` is a subtype of `object`. See #11799
183185
and isinstance(default_ret_type, Instance)

mypy/types.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1734,7 +1734,9 @@ def is_kw_arg(self) -> bool:
17341734
return ARG_STAR2 in self.arg_kinds
17351735

17361736
def is_type_obj(self) -> bool:
1737-
return self.fallback.type.is_metaclass()
1737+
return self.fallback.type.is_metaclass() and not isinstance(
1738+
get_proper_type(self.ret_type), UninhabitedType
1739+
)
17381740

17391741
def type_object(self) -> mypy.nodes.TypeInfo:
17401742
assert self.is_type_obj()

test-data/unit/check-classes.test

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7296,6 +7296,74 @@ def meth1(self: Any, y: str) -> str: ...
72967296
T = TypeVar("T")
72977297
def meth2(self: Any, y: T) -> T: ...
72987298

7299+
[case testNewAndInitNoReturn]
7300+
from typing import NoReturn
7301+
7302+
class A:
7303+
def __new__(cls) -> NoReturn: ...
7304+
7305+
class B:
7306+
def __init__(self) -> NoReturn: ...
7307+
7308+
class C:
7309+
def __new__(cls) -> "C": ...
7310+
def __init__(self) -> NoReturn: ...
7311+
7312+
class D:
7313+
def __new__(cls) -> NoReturn: ...
7314+
def __init__(self) -> NoReturn: ...
7315+
7316+
reveal_type(A()) # N: Revealed type is "<nothing>"
7317+
reveal_type(B()) # N: Revealed type is "<nothing>"
7318+
reveal_type(C()) # N: Revealed type is "<nothing>"
7319+
reveal_type(D()) # N: Revealed type is "<nothing>"
7320+
7321+
[case testOverloadedNewAndInitNoReturn]
7322+
from typing import NoReturn, overload
7323+
7324+
class A:
7325+
@overload
7326+
def __new__(cls) -> NoReturn: ...
7327+
@overload
7328+
def __new__(cls, a: int) -> "A": ...
7329+
def __new__(cls, a: int = ...) -> "A": ...
7330+
7331+
class B:
7332+
@overload
7333+
def __init__(self) -> NoReturn: ...
7334+
@overload
7335+
def __init__(self, a: int) -> None: ...
7336+
def __init__(self, a: int = ...) -> None: ...
7337+
7338+
class C:
7339+
def __new__(cls, a: int = ...) -> "C": ...
7340+
@overload
7341+
def __init__(self) -> NoReturn: ...
7342+
@overload
7343+
def __init__(self, a: int) -> None: ...
7344+
def __init__(self, a: int = ...) -> None: ...
7345+
7346+
class D:
7347+
@overload
7348+
def __new__(cls) -> NoReturn: ...
7349+
@overload
7350+
def __new__(cls, a: int) -> "D": ...
7351+
def __new__(cls, a: int = ...) -> "D": ...
7352+
@overload
7353+
def __init__(self) -> NoReturn: ...
7354+
@overload
7355+
def __init__(self, a: int) -> None: ...
7356+
def __init__(self, a: int = ...) -> None: ...
7357+
7358+
reveal_type(A()) # N: Revealed type is "<nothing>"
7359+
reveal_type(A(1)) # N: Revealed type is "__main__.A"
7360+
reveal_type(B()) # N: Revealed type is "<nothing>"
7361+
reveal_type(B(1)) # N: Revealed type is "__main__.B"
7362+
reveal_type(C()) # N: Revealed type is "<nothing>"
7363+
reveal_type(C(1)) # N: Revealed type is "__main__.C"
7364+
reveal_type(D()) # N: Revealed type is "<nothing>"
7365+
reveal_type(D(1)) # N: Revealed type is "__main__.D"
7366+
72997367
[case testClassScopeImportWithWrapperAndError]
73007368
class Foo:
73017369
from mod import foo # E: Unsupported class scoped import

0 commit comments

Comments
 (0)