diff --git a/mypy/checker.py b/mypy/checker.py index 45da952549ecd..8f55a7c75b7b6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1036,11 +1036,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( @@ -1326,7 +1328,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, diff --git a/mypy/typeops.py b/mypy/typeops.py index 27b629318ea97..8c49b6c870edf 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -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: @@ -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) @@ -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) diff --git a/mypy/types.py b/mypy/types.py index cfb6c62de1470..1cac886c49b5e 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1725,7 +1725,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() diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 36f794dec7805..20a0c4ae80ea4 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -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 "" +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" + [case testClassScopeImportWithWrapperAndError] class Foo: from mod import foo # E: Unsupported class scoped import