Skip to content

Support for __getattr__ #544

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 19, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
30 changes: 30 additions & 0 deletions mypy/test/data/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
-- ----------------

Expand Down