diff --git a/mypy/checker.py b/mypy/checker.py index 81c6322dcb0a..6390b381d918 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3124,6 +3124,10 @@ def visit_decorator(self, e: Decorator) -> None: if e.func.info and not e.func.is_dynamic(): self.check_method_override(e) + if e.func.info and e.func.name() in ('__init__', '__new__'): + if e.type and not isinstance(e.type, (FunctionLike, AnyType)): + self.fail(message_registry.BAD_CONSTRUCTOR_TYPE, e) + def check_for_untyped_decorator(self, func: FuncDef, dec_type: Type, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 1a304fc9cb8b..e3ac94030e8a 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -1,6 +1,6 @@ """Type checking of attribute access""" -from typing import cast, Callable, List, Optional, TypeVar +from typing import cast, Callable, List, Optional, TypeVar, Union from mypy.types import ( Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarDef, @@ -764,25 +764,32 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> # We take the type from whichever of __init__ and __new__ is first # in the MRO, preferring __init__ if there is a tie. - init_method = info.get_method('__init__') - new_method = info.get_method('__new__') - if not init_method: + init_method = info.get('__init__') + new_method = info.get('__new__') + if not init_method or not is_valid_constructor(init_method.node): # Must be an invalid class definition. return AnyType(TypeOfAny.from_error) # There *should* always be a __new__ method except the test stubs # lack it, so just copy init_method in that situation new_method = new_method or init_method + if not is_valid_constructor(new_method.node): + # Must be an invalid class definition. + return AnyType(TypeOfAny.from_error) + + # The two is_valid_constructor() checks ensure this. + assert isinstance(new_method.node, (FuncBase, Decorator)) + assert isinstance(init_method.node, (FuncBase, Decorator)) - init_index = info.mro.index(init_method.info) - new_index = info.mro.index(new_method.info) + init_index = info.mro.index(init_method.node.info) + new_index = info.mro.index(new_method.node.info) fallback = info.metaclass_type or builtin_type('builtins.type') if init_index < new_index: - method = init_method + method = init_method.node # type: Union[FuncBase, Decorator] elif init_index > new_index: - method = new_method + method = new_method.node else: - if init_method.info.fullname() == 'builtins.object': + if init_method.node.info.fullname() == 'builtins.object': # Both are defined by object. But if we've got a bogus # base class, we can't know for sure, so check for that. if info.fallback_to_any: @@ -798,17 +805,34 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> # Otherwise prefer __init__ in a tie. It isn't clear that this # is the right thing, but __new__ caused problems with # typeshed (#5647). - method = init_method + method = init_method.node # Construct callable type based on signature of __init__. Adjust # return type and insert type arguments. - return type_object_type_from_function(method, info, fallback) + if isinstance(method, FuncBase): + t = function_type(method, fallback) + else: + assert isinstance(method.type, FunctionLike) # is_valid_constructor() ensures this + t = method.type + return type_object_type_from_function(t, info, method.info, fallback) + +def is_valid_constructor(n: Optional[SymbolNode]) -> bool: + """Does this node represents a valid constructor method? -def type_object_type_from_function(init_or_new: FuncBase, + This includes normal functions, overloaded functions, and decorators + that return a callable type. + """ + if isinstance(n, FuncBase): + return True + if isinstance(n, Decorator): + return isinstance(n.type, FunctionLike) + return False + + +def type_object_type_from_function(signature: FunctionLike, info: TypeInfo, + def_info: TypeInfo, fallback: Instance) -> FunctionLike: - signature = bind_self(function_type(init_or_new, fallback)) - # The __init__ method might come from a generic superclass # (init_or_new.info) with type variables that do not map # identically to the type variables of the class being constructed @@ -818,10 +842,11 @@ def type_object_type_from_function(init_or_new: FuncBase, # class B(A[List[T]], Generic[T]): pass # # We need to first map B's __init__ to the type (List[T]) -> None. + signature = bind_self(signature) signature = cast(FunctionLike, - map_type_from_supertype(signature, info, init_or_new.info)) + map_type_from_supertype(signature, info, def_info)) special_sig = None # type: Optional[str] - if init_or_new.info.fullname() == 'builtins.dict': + if def_info.fullname() == 'builtins.dict': # Special signature! special_sig = 'dict' diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 9cfdfef940c9..311e06e2a3ae 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -57,6 +57,7 @@ INVALID_SLICE_INDEX = 'Slice index must be an integer or None' # type: Final CANNOT_INFER_LAMBDA_TYPE = 'Cannot infer type of lambda' # type: Final CANNOT_ACCESS_INIT = 'Cannot access "__init__" directly' # type: Final +BAD_CONSTRUCTOR_TYPE = 'Unsupported decorated constructor type' # type: Final CANNOT_ASSIGN_TO_METHOD = 'Cannot assign to a method' # type: Final CANNOT_ASSIGN_TO_TYPE = 'Cannot assign to a type' # type: Final INCONSISTENT_ABSTRACT_OVERLOAD = \ diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e1e9e0d27517..b8e9cda74ee3 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -5837,3 +5837,53 @@ def test() -> None: return reveal_type(x) # E: Revealed type is 'Union[Type[__main__.One], Type[__main__.Other]]' [builtins fixtures/isinstancelist.pyi] + +[case testAbstractInit] +from abc import abstractmethod, ABCMeta +class A(metaclass=ABCMeta): + @abstractmethod + def __init__(self, a: int) -> None: + pass +class B(A): + pass +class C(B): + def __init__(self, a: int) -> None: + self.c = a +a = A(1) # E: Cannot instantiate abstract class 'A' with abstract attribute '__init__' +A.c # E: "Type[A]" has no attribute "c" +b = B(2) # E: Cannot instantiate abstract class 'B' with abstract attribute '__init__' +B.c # E: "Type[B]" has no attribute "c" +c = C(3) +c.c +C.c + +[case testDecoratedConstructors] +from typing import TypeVar, Callable, Any + +F = TypeVar('F', bound=Callable[..., Any]) + +def dec(f: F) -> F: ... + +class A: + @dec + def __init__(self, x: int) -> None: ... + +class B: + @dec + def __new__(cls, x: int) -> B: ... + +reveal_type(A) # E: Revealed type is 'def (x: builtins.int) -> __main__.A' +reveal_type(B) # E: Revealed type is 'def (x: builtins.int) -> __main__.B' + +[case testDecoratedConstructorsBad] +from typing import Callable, Any + +def dec(f: Callable[[Any, int], Any]) -> int: ... + +class A: + @dec # E: Unsupported decorated constructor type + def __init__(self, x: int) -> None: ... + +class B: + @dec # E: Unsupported decorated constructor type + def __new__(cls, x: int) -> B: ...