diff --git a/mypy/checker.py b/mypy/checker.py index 166ecbd1c7b0..e273dd9dfc42 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -506,6 +506,8 @@ def check_func_def(self, defn: FuncItem, typ: Callable, name: str) -> None: if name in nodes.reverse_op_method_set: self.check_reverse_op_method(item, typ, name) + elif name == '__getattr__': + self.check_getattr_method(typ, defn) # Push return type. self.return_types.append(typ.ret_type) @@ -707,6 +709,15 @@ def check_inplace_operator_method(self, defn: FuncBase) -> None: if fail: self.msg.signatures_incompatible(method, other_method, defn) + def check_getattr_method(self, typ: Callable, context: Context) -> None: + method_type = Callable([AnyType(), self.named_type('builtins.str')], + [nodes.ARG_POS, nodes.ARG_POS], + [None], + AnyType(), + self.named_type('builtins.function')) + if not is_subtype(typ, method_type): + self.msg.invalid_signature(typ, context) + def expand_typevars(self, defn: FuncItem, typ: Callable) -> List[Tuple[FuncItem, Callable]]: # TODO use generator diff --git a/mypy/checkmember.py b/mypy/checkmember.py index c89d45a4c902..f5bbd20faab1 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -55,7 +55,8 @@ def analyse_member_access(name: str, typ: Type, node: Context, is_lvalue: bool, else: # Not a method. return analyse_member_var_access(name, typ, info, node, - is_lvalue, is_super, msg, + is_lvalue, is_super, builtin_type, + msg, report_type=report_type) elif isinstance(typ, AnyType): # The base object has dynamic type. @@ -93,6 +94,7 @@ def analyse_member_access(name: str, typ: Type, node: Context, is_lvalue: bool, def analyse_member_var_access(name: str, itype: Instance, info: TypeInfo, node: Context, is_lvalue: bool, is_super: bool, + builtin_type: Function[[str], Instance], msg: MessageBuilder, report_type: Type = None) -> Type: """Analyse attribute access that does not target a method. @@ -142,6 +144,15 @@ def analyse_member_var_access(name: str, itype: Instance, info: TypeInfo, return AnyType() elif isinstance(v, FuncDef): assert False, "Did not expect a function" + elif not v and name not in ['__getattr__', '__setattr__']: + if not is_lvalue: + method = info.get_method('__getattr__') + if method: + typ = map_instance_to_supertype(itype, method.info) + getattr_type = expand_type_by_instance( + method_type(method, builtin_type('builtins.function')), typ) + if isinstance(getattr_type, Callable): + return getattr_type.ret_type # Could not find the member. if is_super: diff --git a/mypy/messages.py b/mypy/messages.py index 8485c672b310..bf8db9559ae3 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -698,6 +698,9 @@ def yield_from_invalid_operand_type(self, expr: Type, context: Context) -> Type: self.fail('"yield from" can\'t be applied to {}'.format(text), context) return AnyType() + def invalid_signature(self, func_type: Type, context: Context) -> None: + self.fail('Invalid signature "{}"'.format(func_type), context) + def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/mypy/test/data/check-classes.test b/mypy/test/data/check-classes.test index 9f34fb0c057e..5a619ef6ad42 100644 --- a/mypy/test/data/check-classes.test +++ b/mypy/test/data/check-classes.test @@ -1133,6 +1133,36 @@ main, line 8: Argument 1 of "__iadd__" incompatible with "__add__" of supertype main, line 8: Signatures of "__iadd__" and "__add__" are incompatible +[case testGetAttr] +from typing import Undefined +a, b = Undefined, Undefined # type: A, B +class A: + def __getattr__(self, x: str) -> A: + return A() +class B: pass + +a = a.foo +b = a.bar +[out] +main, line 9: Incompatible types in assignment (expression has type "A", variable has type "B") + + +[case testGetAttrSignature] +class A: + def __getattr__(self, x: str) -> A: pass +class B: + def __getattr__(self, x: A) -> B: pass +class C: + def __getattr__(self, x: str, y: str) -> C: pass +class D: + def __getattr__(self, x: str) -> None: pass +[out] +main: In member "__getattr__" of class "B": +main, line 4: Invalid signature "def (self: __main__.B, x: __main__.A) -> __main__.B" +main: In member "__getattr__" of class "C": +main, line 6: Invalid signature "def (self: __main__.C, x: builtins.str, y: builtins.str) -> __main__.C" + + -- Callable objects -- ----------------