-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Now TypeInfo.get_method
also returns Decorator
nodes
#11150
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
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
362b710
Now decorated `__getattr__` works, refs #10409
sobolevn 96d9ec8
Addresses code review
sobolevn 374ef19
Experiment: `get_method()` returns decorated methods
sobolevn 750eefc
Reworks how `TypeInfo.get_method()` works
sobolevn 500ae57
Improves `__get__` and `__set__` tests
sobolevn 474429c
Improves callable testing
sobolevn 1ea795d
Fixes self type checking
sobolevn 662c342
Fixes mypyc
sobolevn d24b9ae
Typo fixed
sobolevn 7cfb522
Merge branch 'master' into issue-10409
sobolevn 44b565a
Merge into master
sobolevn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,14 +67,17 @@ def not_ready_callback(self, name: str, context: Context) -> None: | |
self.chk.handle_cannot_determine_type(name, context) | ||
|
||
def copy_modified(self, *, messages: Optional[MessageBuilder] = None, | ||
self_type: Optional[Type] = None) -> 'MemberContext': | ||
self_type: Optional[Type] = None, | ||
is_lvalue: Optional[bool] = None) -> 'MemberContext': | ||
mx = MemberContext(self.is_lvalue, self.is_super, self.is_operator, | ||
self.original_type, self.context, self.msg, self.chk, | ||
self.self_type, self.module_symbol_table) | ||
if messages is not None: | ||
mx.msg = messages | ||
if self_type is not None: | ||
mx.self_type = self_type | ||
if is_lvalue is not None: | ||
mx.is_lvalue = is_lvalue | ||
return mx | ||
|
||
|
||
|
@@ -195,7 +198,7 @@ def analyze_instance_member_access(name: str, | |
|
||
# Look up the member. First look up the method dictionary. | ||
method = info.get_method(name) | ||
if method: | ||
if method and not isinstance(method, Decorator): | ||
if method.is_property: | ||
assert isinstance(method, OverloadedFuncDef) | ||
first_item = cast(Decorator, method.items[0]) | ||
|
@@ -385,29 +388,46 @@ def analyze_member_var_access(name: str, | |
if not mx.is_lvalue: | ||
for method_name in ('__getattribute__', '__getattr__'): | ||
method = info.get_method(method_name) | ||
|
||
# __getattribute__ is defined on builtins.object and returns Any, so without | ||
# the guard this search will always find object.__getattribute__ and conclude | ||
# that the attribute exists | ||
if method and method.info.fullname != 'builtins.object': | ||
function = function_type(method, mx.named_type('builtins.function')) | ||
bound_method = bind_self(function, mx.self_type) | ||
if isinstance(method, Decorator): | ||
# https://github.com/python/mypy/issues/10409 | ||
bound_method = analyze_var(method_name, method.var, itype, info, mx) | ||
else: | ||
bound_method = bind_self( | ||
function_type(method, mx.named_type('builtins.function')), | ||
mx.self_type, | ||
) | ||
typ = map_instance_to_supertype(itype, method.info) | ||
getattr_type = get_proper_type(expand_type_by_instance(bound_method, typ)) | ||
if isinstance(getattr_type, CallableType): | ||
result = getattr_type.ret_type | ||
|
||
# Call the attribute hook before returning. | ||
fullname = '{}.{}'.format(method.info.fullname, name) | ||
hook = mx.chk.plugin.get_attribute_hook(fullname) | ||
if hook: | ||
result = hook(AttributeContext(get_proper_type(mx.original_type), | ||
result, mx.context, mx.chk)) | ||
return result | ||
else: | ||
result = getattr_type | ||
|
||
# Call the attribute hook before returning. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now plugin hooks are also triggered for non |
||
fullname = '{}.{}'.format(method.info.fullname, name) | ||
hook = mx.chk.plugin.get_attribute_hook(fullname) | ||
if hook: | ||
result = hook(AttributeContext(get_proper_type(mx.original_type), | ||
result, mx.context, mx.chk)) | ||
return result | ||
else: | ||
setattr_meth = info.get_method('__setattr__') | ||
if setattr_meth and setattr_meth.info.fullname != 'builtins.object': | ||
setattr_func = function_type(setattr_meth, mx.named_type('builtins.function')) | ||
bound_type = bind_self(setattr_func, mx.self_type) | ||
if isinstance(setattr_meth, Decorator): | ||
bound_type = analyze_var( | ||
name, setattr_meth.var, itype, info, | ||
mx.copy_modified(is_lvalue=False), | ||
) | ||
else: | ||
bound_type = bind_self( | ||
function_type(setattr_meth, mx.named_type('builtins.function')), | ||
mx.self_type, | ||
) | ||
typ = map_instance_to_supertype(itype, setattr_meth.info) | ||
setattr_type = get_proper_type(expand_type_by_instance(bound_type, typ)) | ||
if isinstance(setattr_type, CallableType) and len(setattr_type.arg_types) > 0: | ||
|
@@ -436,32 +456,24 @@ def check_final_member(name: str, info: TypeInfo, msg: MessageBuilder, ctx: Cont | |
msg.cant_assign_to_final(name, attr_assign=True, ctx=ctx) | ||
|
||
|
||
def analyze_descriptor_access(instance_type: Type, | ||
descriptor_type: Type, | ||
named_type: Callable[[str], Instance], | ||
msg: MessageBuilder, | ||
context: Context, *, | ||
chk: 'mypy.checker.TypeChecker') -> Type: | ||
def analyze_descriptor_access(descriptor_type: Type, | ||
mx: MemberContext) -> Type: | ||
"""Type check descriptor access. | ||
|
||
Arguments: | ||
instance_type: The type of the instance on which the descriptor | ||
attribute is being accessed (the type of ``a`` in ``a.f`` when | ||
``f`` is a descriptor). | ||
descriptor_type: The type of the descriptor attribute being accessed | ||
(the type of ``f`` in ``a.f`` when ``f`` is a descriptor). | ||
context: The node defining the context of this inference. | ||
mx: The current member access context. | ||
Return: | ||
The return type of the appropriate ``__get__`` overload for the descriptor. | ||
""" | ||
instance_type = get_proper_type(instance_type) | ||
instance_type = get_proper_type(mx.original_type) | ||
descriptor_type = get_proper_type(descriptor_type) | ||
|
||
if isinstance(descriptor_type, UnionType): | ||
# Map the access over union types | ||
return make_simplified_union([ | ||
analyze_descriptor_access(instance_type, typ, named_type, | ||
msg, context, chk=chk) | ||
analyze_descriptor_access(typ, mx) | ||
for typ in descriptor_type.items | ||
]) | ||
elif not isinstance(descriptor_type, Instance): | ||
|
@@ -471,13 +483,21 @@ def analyze_descriptor_access(instance_type: Type, | |
return descriptor_type | ||
|
||
dunder_get = descriptor_type.type.get_method('__get__') | ||
|
||
if dunder_get is None: | ||
msg.fail(message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), context) | ||
mx.msg.fail(message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), | ||
mx.context) | ||
return AnyType(TypeOfAny.from_error) | ||
|
||
function = function_type(dunder_get, named_type('builtins.function')) | ||
bound_method = bind_self(function, descriptor_type) | ||
if isinstance(dunder_get, Decorator): | ||
bound_method = analyze_var( | ||
'__set__', dunder_get.var, descriptor_type, descriptor_type.type, mx, | ||
) | ||
else: | ||
bound_method = bind_self( | ||
function_type(dunder_get, mx.named_type('builtins.function')), | ||
descriptor_type, | ||
) | ||
|
||
typ = map_instance_to_supertype(descriptor_type, dunder_get.info) | ||
dunder_get_type = expand_type_by_instance(bound_method, typ) | ||
|
||
|
@@ -490,19 +510,19 @@ def analyze_descriptor_access(instance_type: Type, | |
else: | ||
owner_type = instance_type | ||
|
||
callable_name = chk.expr_checker.method_fullname(descriptor_type, "__get__") | ||
dunder_get_type = chk.expr_checker.transform_callee_type( | ||
callable_name = mx.chk.expr_checker.method_fullname(descriptor_type, "__get__") | ||
dunder_get_type = mx.chk.expr_checker.transform_callee_type( | ||
callable_name, dunder_get_type, | ||
[TempNode(instance_type, context=context), | ||
TempNode(TypeType.make_normalized(owner_type), context=context)], | ||
[ARG_POS, ARG_POS], context, object_type=descriptor_type, | ||
[TempNode(instance_type, context=mx.context), | ||
TempNode(TypeType.make_normalized(owner_type), context=mx.context)], | ||
[ARG_POS, ARG_POS], mx.context, object_type=descriptor_type, | ||
) | ||
|
||
_, inferred_dunder_get_type = chk.expr_checker.check_call( | ||
_, inferred_dunder_get_type = mx.chk.expr_checker.check_call( | ||
dunder_get_type, | ||
[TempNode(instance_type, context=context), | ||
TempNode(TypeType.make_normalized(owner_type), context=context)], | ||
[ARG_POS, ARG_POS], context, object_type=descriptor_type, | ||
[TempNode(instance_type, context=mx.context), | ||
TempNode(TypeType.make_normalized(owner_type), context=mx.context)], | ||
[ARG_POS, ARG_POS], mx.context, object_type=descriptor_type, | ||
callable_name=callable_name) | ||
|
||
inferred_dunder_get_type = get_proper_type(inferred_dunder_get_type) | ||
|
@@ -511,7 +531,8 @@ def analyze_descriptor_access(instance_type: Type, | |
return inferred_dunder_get_type | ||
|
||
if not isinstance(inferred_dunder_get_type, CallableType): | ||
msg.fail(message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), context) | ||
mx.msg.fail(message_registry.DESCRIPTOR_GET_NOT_CALLABLE.format(descriptor_type), | ||
mx.context) | ||
return AnyType(TypeOfAny.from_error) | ||
|
||
return inferred_dunder_get_type.ret_type | ||
|
@@ -616,8 +637,7 @@ def analyze_var(name: str, | |
fullname = '{}.{}'.format(var.info.fullname, name) | ||
hook = mx.chk.plugin.get_attribute_hook(fullname) | ||
if result and not mx.is_lvalue and not implicit: | ||
result = analyze_descriptor_access(mx.original_type, result, mx.named_type, | ||
mx.msg, mx.context, chk=mx.chk) | ||
result = analyze_descriptor_access(result, mx) | ||
if hook: | ||
result = hook(AttributeContext(get_proper_type(mx.original_type), | ||
result, mx.context, mx.chk)) | ||
|
@@ -793,8 +813,7 @@ def analyze_class_attribute_access(itype: Instance, | |
result = add_class_tvars(t, isuper, is_classmethod, | ||
mx.self_type, original_vars=original_vars) | ||
if not mx.is_lvalue: | ||
result = analyze_descriptor_access(mx.original_type, result, mx.named_type, | ||
mx.msg, mx.context, chk=mx.chk) | ||
result = analyze_descriptor_access(result, mx) | ||
return result | ||
elif isinstance(node.node, Var): | ||
mx.not_ready_callback(name, mx.context) | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code quite similar to this if/else gets repeated several times. I wonder if it would be possible to abstract it into a helper function, for example? If this sounds like a feasible idea, it could be a nice follow-up PR.