Skip to content

Fix handling of non-method callable attribute #3227

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

Closed
wants to merge 17 commits into from

Conversation

elazarg
Copy link
Contributor

@elazarg elazarg commented Apr 23, 2017

Rewrite check_method_type:

1. Reverse the subtyping check. It seems to be backwards. This change alone fixes #3223.

2. Pass original_type for the subtype check, which is more precise - in particular, there seems no need to fallback from TupleType. It also yields better error messages.

3. Make the error message clearer (I think. see the tests)

4. The classmethod case goes backwards too - instead of falling back in the parameter, fall back in the argument. I think it's the straightforward thing.



The PR does not pass stub tests due to the following error:

> typeshed/stdlib/2and3/fractions.pyi:80: error: Invalid self argument "Union[int, float, Decimal, Real]" to attribute function Callable[[Real, Any], bool]



For this line:

python <br /> def __gt__(self, other: _ComparableNum) -> bool: ... <br />

And similarly for __ge__. I did not figure out yet why it happens.

@elazarg
Copy link
Contributor Author

elazarg commented Apr 24, 2017

This looks like the minimal repro:

from typing import Union
from abc import abstractmethod

class A:
    @abstractmethod
    def __lt__(self, other) -> bool: ...

class B:
    def __gt__(self, other: Union[int, A]) -> bool: ...  # E: Invalid self argument "Union[str, A]" to attribute function Callable[[B, Any], bool]

I think info.get_method(name) in analyze_member_access does not handle decorated functions properly.

The current fix avoids this case by performing the test for each item of the union separately.

@gvanrossum
Copy link
Member

Repro of what? That snippet gives no errors for me. How did you run it? (Flags, mypy.ini.)

mypy/checker.py Outdated
@@ -796,6 +796,7 @@ def check_reverse_op_method(self, defn: FuncItem, typ: CallableType,
arg_types = list(arg_type.iter_deep())
else:
return
# We check that each method is fine when dispatched on a proper self argument
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI you can make your local commit history more relevant by using git commit --amend for such things (since you did not push to GitHub in between).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand I should put more info in the commit messages, but this comment is needed regardless. Or are you referring to the code below it?

Copy link
Member

@gvanrossum gvanrossum Apr 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither. I meant that I would prefer to see a single commit pushed to the PR that included the previous commit plus this one (which just added a comment that you forgot on the previous commit).

You can do that locally by using git commit --amend for the second commit (that adds the comment). You could also do this after the fact using git rebase -i.

@elazarg
Copy link
Contributor Author

elazarg commented Apr 24, 2017

Sorry for being unclear. This is repro of the issue this PR had (before last commit). I mention it because I think it indicates an error outside this PR, and because it is the reason for noticeable part of this PR.

@elazarg
Copy link
Contributor Author

elazarg commented Apr 24, 2017

All the changes in types.py and checker.py are only needed because of this issue.

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a bunch of mostly minor things and requests for clarification. Also, maybe you should add a test case that includes the original example from #3223?

mypy/types.py Outdated
@@ -1130,6 +1130,13 @@ def relevant_items(self) -> List[Type]:
else:
return [i for i in self.items if not isinstance(i, NoneTyp)]

def iter_deep(self) -> Iterator[Type]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use union_items() instead of adding this method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also currently unions are flattened on creation in __init__ with the help of self.items = flatten_nested_unions(items)

@@ -1975,7 +1975,7 @@ class B:
a = A
bad = lambda: 42

B().bad() # E: Invalid method type
B().bad() # E: Attribute function of type Callable[[], int] does not accept self argument
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd tweak message to be Attribute function with type Callable[...] .... Otherwise this could be understood to refer to an attribute of an object, where the object is callable.

mypy/checker.py Outdated
@@ -827,15 +828,17 @@ def check_reverse_op_method(self, defn: FuncItem, typ: CallableType,
elif isinstance(arg_type, UnionType):
if not arg_type.has_readable_member(other_method):
return
arg_types = list(arg_type.iter_deep())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use union_items() instead of iter_deep().

mypy/checker.py Outdated
typ, method, defn.info,
typ2, other_method, cast(Instance, arg_type),
defn)
# We check that each method is fine when dispatched on a proper self argument
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is unclear. Can you improve it?

mypy/checker.py Outdated
other_method, arg_type, defn)
self.check_overlapping_op_methods(
typ, method, defn.info,
typ2, other_method, cast(Instance, arg_type),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cast is safe (though the unsafe is not introduced by this PR). Is there an easy way to fix it? If not, can you add a TODO comment explaining that it's unsafe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to fix it. Do you think replacing it with an assertion is appropriate, or should we have "get_instance_from(Type)"?

@@ -368,33 +368,23 @@ def lookup_member_var_or_accessor(info: TypeInfo, name: str,
return None


def check_method_type(functype: FunctionLike, itype: Instance, is_classmethod: bool,
def check_method_type(functype: FunctionLike, original_type: Type, is_classmethod: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docstring that at least explains the purpose of this method and the original_type argument.

Also, can you explain why you this change helps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is explained by point (2) above:

  1. Pass original_type for the subtype check, which is more precise - in particular, there seems no need to fallback from TupleType. It also yields better error messages.

if not subtypes.is_equivalent(clsarg, AnyType()):
msg.invalid_class_method_type(item, context)
if is_classmethod:
original_type = TypeType(original_type)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't safe, since TypeType.__init__ doesn't accept Type. TypeType.make_normalized would work around that, though it's unclear what's the correct thing to do.

# (This is sufficient for the current treatment of @classmethod,
# but probably needs to be revisited when we implement Type[C]
# or advanced variants of it like Type[<args>, C].)
clsarg = item.arg_types[0]
if isinstance(clsarg, CallableType) and clsarg.is_type_obj():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these lines no longer needed? Is it because of the changes from itype to original_type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because classmethod is handled differently, falling from the instance to the type (instead of the other way around, bullet (4) above). I think the comment is not needed too.

Actually, I think the entire function can be merged with bind_self() since it performs overlapping checks.

msg.invalid_class_method_type(item, context)
if is_classmethod:
original_type = TypeType(original_type)
if not subtypes.is_subtype(original_type, erase_to_bound(selfarg)):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line needs a comment.

@elazarg elazarg force-pushed the fix-property-selftype branch from aed080c to 950173a Compare July 25, 2017 11:17
@elazarg elazarg force-pushed the fix-property-selftype branch from 950173a to 6703816 Compare July 25, 2017 11:19
@JukkaL
Copy link
Collaborator

JukkaL commented Aug 15, 2017

Can you fix the merge conflict? (You may want to wait until #3132 has been merged since it might cause more conflicts.)

@gvanrossum
Copy link
Member

@elazarg Can you at least fix the test failures before we spend more time reviewing this?

@elazarg
Copy link
Contributor Author

elazarg commented Sep 26, 2017

Sorry, I was too sure that said failure was also a flake (like this last one) that I didn't take the time to check.

I'm going to close this PR, and open two separate ones, which should be easier to review. The review and commit history will not help too much.

@elazarg elazarg closed this Sep 26, 2017
elazarg added a commit to elazarg/mypy that referenced this pull request Sep 26, 2017
1. Reverse subtyping check
2. For classmethod, fallback on the argument instead of on the parameter
3. Mimic dispatch better: meet original_type with static class
4. Better error message
elazarg added a commit to elazarg/mypy that referenced this pull request Sep 26, 2017
1. Reverse subtyping check
2. For classmethod, fallback on the argument instead of on the parameter
3. Mimic dispatch better: meet original_type with static class
4. Better error message
JukkaL pushed a commit that referenced this pull request Oct 12, 2017
…take 2) (#4016)

Fix #3223. Minimal examples:

```
from typing import Callable
class X:
    f: Callable[[object], None]
X().f  # E: Invalid method type
```

```
class X:
    @Property
    def g(self: object): pass
X().g  # E: Invalid method type
```

The problem is that for non-methods the check performed on access 
to self is whether object <: X instead of the other way around.

1. Reverse subtyping check. This change alone fixes the issue described 
   above.
2. For classmethod, fall back on the argument instead of on the parameter.
3. Mimic dispatch better: meet original_type with static class. This handles 
   the case were instead of X() above there was something of type 
   Union[X, Y].
4. Better error message.

Take 2 of #3227, but without the reverse-operator part.
@elazarg elazarg deleted the fix-property-selftype branch November 25, 2017 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

self type on property creates a spurious error
4 participants