diff --git a/mypy/checker.py b/mypy/checker.py index 2f99b9b4fece..5697b959bc0e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -870,9 +870,13 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) if isinstance(item, FuncDef): fdef = item # Check if __init__ has an invalid, non-None return type. - if (fdef.info and fdef.name in ('__init__', '__init_subclass__') and - not isinstance(get_proper_type(typ.ret_type), NoneType) and - not self.dynamic_funcs[-1]): + if ( + fdef.info + and fdef.name in ('__init__', '__init_subclass__') + and not isinstance(get_proper_type(typ.ret_type), + (NoneType, UninhabitedType)) + and not self.dynamic_funcs[-1] + ): self.fail(message_registry.MUST_HAVE_NONE_RETURN_TYPE.format(fdef.name), item) @@ -1136,7 +1140,7 @@ def check___new___signature(self, fdef: FuncDef, typ: CallableType) -> None: 'but must return a subtype of' ) elif not isinstance(get_proper_type(bound_type.ret_type), - (AnyType, Instance, TupleType)): + (AnyType, Instance, TupleType, UninhabitedType)): self.fail( message_registry.NON_INSTANCE_NEW_TYPE.format( format_type(bound_type.ret_type)), diff --git a/mypy/typeops.py b/mypy/typeops.py index 57fdfeadad9a..45a395c0ed27 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -5,7 +5,7 @@ since these may assume that MROs are ready. """ -from typing import cast, Optional, List, Sequence, Set, Iterable, TypeVar, Dict, Tuple, Any +from typing import cast, Optional, List, Sequence, Set, Iterable, TypeVar, Dict, Tuple, Any, Union from typing_extensions import Type as TypingType import itertools import sys @@ -45,6 +45,19 @@ def tuple_fallback(typ: TupleType) -> Instance: return Instance(info, [join_type_list(typ.items)]) +def get_self_type(func: CallableType, default_self: Union[Instance, TupleType]) -> Optional[Type]: + 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, @@ -57,8 +70,7 @@ def type_object_type_from_function(signature: FunctionLike, # 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) @@ -106,7 +118,7 @@ def class_callable(init_type: CallableType, info: TypeInfo, type_type: Instance, 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)) # Only use the declared return type from __new__ or declared self in __init__ # if it is actually returning a subtype of what we would return otherwise. and is_subtype(explicit_type, default_ret_type, ignore_type_params=True) diff --git a/mypy/types.py b/mypy/types.py index dadbb84fe1c8..e96f2fb6fd79 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1312,7 +1312,10 @@ 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() diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 65b0e8d69cb5..ca349a8d9378 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7284,3 +7284,71 @@ 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 "" +reveal_type(B()) # N: Revealed type is "" +reveal_type(C()) # N: Revealed type is "" +reveal_type(D()) # N: Revealed type is "" + +[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 "" +reveal_type(A(1)) # N: Revealed type is "__main__.A" +reveal_type(B()) # N: Revealed type is "" +reveal_type(B(1)) # N: Revealed type is "__main__.B" +reveal_type(C()) # N: Revealed type is "" +reveal_type(C(1)) # N: Revealed type is "__main__.C" +reveal_type(D()) # N: Revealed type is "" +reveal_type(D(1)) # N: Revealed type is "__main__.D"