Skip to content

Commit

Permalink
Allow __init__ and __new__ to return NoReturn (#13480)
Browse files Browse the repository at this point in the history
Co-authored-by: Bas van Beek <43369155+BvB93@users.noreply.github.com>
  • Loading branch information
iyanging and BvB93 authored Aug 23, 2022
1 parent e981431 commit 0c0f071
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 13 deletions.
10 changes: 7 additions & 3 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,11 +1037,13 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str | None) ->
# precise type.
if isinstance(item, FuncDef):
fdef = item
# Check if __init__ has an invalid, non-None return type.
# Check if __init__ has an invalid return type.
if (
fdef.info
and fdef.name in ("__init__", "__init_subclass__")
and not isinstance(get_proper_type(typ.ret_type), NoneType)
and not isinstance(
get_proper_type(typ.ret_type), (NoneType, UninhabitedType)
)
and not self.dynamic_funcs[-1]
):
self.fail(
Expand Down Expand Up @@ -1327,7 +1329,9 @@ def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None:
"returns",
"but must return a subtype of",
)
elif not isinstance(get_proper_type(bound_type.ret_type), (AnyType, Instance, TupleType)):
elif not isinstance(
get_proper_type(bound_type.ret_type), (AnyType, Instance, TupleType, UninhabitedType)
):
self.fail(
message_registry.NON_INSTANCE_NEW_TYPE.format(format_type(bound_type.ret_type)),
fdef,
Expand Down
20 changes: 11 additions & 9 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ def tuple_fallback(typ: TupleType) -> Instance:
return Instance(info, [join_type_list(items)])


def get_self_type(func: CallableType, default_self: Instance | TupleType) -> Type | None:
if isinstance(get_proper_type(func.ret_type), UninhabitedType):
return func.ret_type
elif func.arg_types and func.arg_types[0] != default_self and func.arg_kinds[0] == ARG_POS:
return func.arg_types[0]
else:
return None


def type_object_type_from_function(
signature: FunctionLike, info: TypeInfo, def_info: TypeInfo, fallback: Instance, is_new: bool
) -> FunctionLike:
Expand All @@ -117,14 +126,7 @@ def type_object_type_from_function(
# classes such as subprocess.Popen.
default_self = fill_typevars(info)
if not is_new and not info.is_newtype:
orig_self_types = [
(
it.arg_types[0]
if it.arg_types and it.arg_types[0] != default_self and it.arg_kinds[0] == ARG_POS
else None
)
for it in signature.items
]
orig_self_types = [get_self_type(it, default_self) for it in signature.items]
else:
orig_self_types = [None] * len(signature.items)

Expand Down Expand Up @@ -177,7 +179,7 @@ def class_callable(
default_ret_type = fill_typevars(info)
explicit_type = init_ret_type if is_new else orig_self_type
if (
isinstance(explicit_type, (Instance, TupleType))
isinstance(explicit_type, (Instance, TupleType, UninhabitedType))
# We have to skip protocols, because it can be a subtype of a return type
# by accident. Like `Hashable` is a subtype of `object`. See #11799
and isinstance(default_ret_type, Instance)
Expand Down
4 changes: 3 additions & 1 deletion mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1734,7 +1734,9 @@ def is_kw_arg(self) -> bool:
return ARG_STAR2 in self.arg_kinds

def is_type_obj(self) -> bool:
return self.fallback.type.is_metaclass()
return self.fallback.type.is_metaclass() and not isinstance(
get_proper_type(self.ret_type), UninhabitedType
)

def type_object(self) -> mypy.nodes.TypeInfo:
assert self.is_type_obj()
Expand Down
68 changes: 68 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -7296,6 +7296,74 @@ def meth1(self: Any, y: str) -> str: ...
T = TypeVar("T")
def meth2(self: Any, y: T) -> T: ...

[case testNewAndInitNoReturn]
from typing import NoReturn

class A:
def __new__(cls) -> NoReturn: ...

class B:
def __init__(self) -> NoReturn: ...

class C:
def __new__(cls) -> "C": ...
def __init__(self) -> NoReturn: ...

class D:
def __new__(cls) -> NoReturn: ...
def __init__(self) -> NoReturn: ...

reveal_type(A()) # N: Revealed type is "<nothing>"
reveal_type(B()) # N: Revealed type is "<nothing>"
reveal_type(C()) # N: Revealed type is "<nothing>"
reveal_type(D()) # N: Revealed type is "<nothing>"

[case testOverloadedNewAndInitNoReturn]
from typing import NoReturn, overload

class A:
@overload
def __new__(cls) -> NoReturn: ...
@overload
def __new__(cls, a: int) -> "A": ...
def __new__(cls, a: int = ...) -> "A": ...

class B:
@overload
def __init__(self) -> NoReturn: ...
@overload
def __init__(self, a: int) -> None: ...
def __init__(self, a: int = ...) -> None: ...

class C:
def __new__(cls, a: int = ...) -> "C": ...
@overload
def __init__(self) -> NoReturn: ...
@overload
def __init__(self, a: int) -> None: ...
def __init__(self, a: int = ...) -> None: ...

class D:
@overload
def __new__(cls) -> NoReturn: ...
@overload
def __new__(cls, a: int) -> "D": ...
def __new__(cls, a: int = ...) -> "D": ...
@overload
def __init__(self) -> NoReturn: ...
@overload
def __init__(self, a: int) -> None: ...
def __init__(self, a: int = ...) -> None: ...

reveal_type(A()) # N: Revealed type is "<nothing>"
reveal_type(A(1)) # N: Revealed type is "__main__.A"
reveal_type(B()) # N: Revealed type is "<nothing>"
reveal_type(B(1)) # N: Revealed type is "__main__.B"
reveal_type(C()) # N: Revealed type is "<nothing>"
reveal_type(C(1)) # N: Revealed type is "__main__.C"
reveal_type(D()) # N: Revealed type is "<nothing>"
reveal_type(D(1)) # N: Revealed type is "__main__.D"

[case testClassScopeImportWithWrapperAndError]
class Foo:
from mod import foo # E: Unsupported class scoped import
Expand Down

0 comments on commit 0c0f071

Please sign in to comment.