Skip to content

Incorrect inference with isinstance() #2993

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
pkch opened this issue Mar 14, 2017 · 7 comments
Closed

Incorrect inference with isinstance() #2993

pkch opened this issue Mar 14, 2017 · 7 comments

Comments

@pkch
Copy link
Contributor

pkch commented Mar 14, 2017

I think there are a few problems with isinstance inference.

  1. This code type checks with mypy --strcit --python-version 3.6, but it should not:
from typing import *
def f(x: Union[int, str], typ: type) -> None:
    if isinstance(x, (typ, int)):
        print(x + 1)

The semantic analyzer concludes that within the body of if isinstance(), the type of x must be int. It's not, however. For example, in this case f('a', str), the if branch will be followed even though x is not an int (and this will cause runtime TypeError without any mypy warnings).

The reason this happens is that type annotation is represented as an Instance rather than as a FunctionLike inside checker.py:get_isinstance_type(). As a result, it's skipped entirely in the loop, and only int is added to types, so sem analyzer thinks x must absolutely be int.

One solution is to make type represented with an instance of CallableType (why isn't it btw?!). Another solution is to simply add a bit of logic to the loop to properly handle this case.

  1. This code:
def f(x: Union[int, str, List, Tuple]) -> None:
    if isinstance(x, (str, (list, tuple))):
        print(x[1])

results in error: Argument 2 to "isinstance" has incompatible type "Tuple[str, Tuple[List[_T], Tuple[_T_co, ...]]]"; expected "Union[type, Tuple[type, ...]]".

This is because there's this weird thing in python that allows to put nested tuples as the second argument to isinstance, and mypy (along with most other people) doesn't know about it. I don't know if it's worth fixing, but it's super easy (just flatten the second argument if it's a tuple).

If my understanding is correct, I'll make a PR.

@JukkaL
Copy link
Collaborator

JukkaL commented Mar 14, 2017

Yes, the way type objects are represented right now is kind of messy. Until we clean up the implementation, mypy should support all the possible representations of type objects in all contexts.

The second case would be a reasonable thing to fix if the fix is simple enough, though clearly it's not a high priority thing. Could you actually create a separate issue for the second case, since it seems like an independent issue?

@pkch
Copy link
Contributor Author

pkch commented Mar 14, 2017

Actually, I was wrong: the 1) issue has nothing to do with whether type is a CallableType or an Instance (and I understand now why it's not quite a good fit for CallableType). The problem is that from the perspective of type checker, the second argument to isinstance can be either a literal type (when the source code directly refers to a class name), or a variable that contains the type (when the source code uses a function argument or something else). In the second case, the type checker cannot really infer anything since the value inside the variable won't be known statically. So I don't think there's any need to fix the representation of type, just a small logic change to the function will fix the problem.

And yes, I can separate them out.

@JukkaL
Copy link
Collaborator

JukkaL commented Mar 14, 2017

Ah yeah, I didn't read the first example correctly. Yes, if one the types is a type instance, it can match any type, but it could also not match any type. For example:

from typing import *
def f(x: Union[int, str], typ: type) -> None:
    if isinstance(x, (typ, int)):
        reveal_type(x)  # should be Union[int, str]
    else:
        reveal_type(x)  # should be str

@pkch
Copy link
Contributor Author

pkch commented Mar 14, 2017

I'm moving item 2) into a separate issue as you suggested.

@gvanrossum
Copy link
Member

What's the issue# for the separate issue you created for (2)? Also does #2997 fix this or (2) or both?

@pkch
Copy link
Contributor Author

pkch commented Mar 14, 2017

#2997 would only fix (1). The separate issue I created for (2) is #2994.

@pkch
Copy link
Contributor Author

pkch commented Apr 21, 2017

To clarify: this issue is completely fixed in 2 merged PRs: #2997 and #2995.

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

No branches or pull requests

3 participants