-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Constrained TypeVar fails to narrow on left hand side of conditional expression #4134
Comments
I think this is because you're using Your code should have been written as:
which passes mypy with no errors for me. I don't know why your code gives an error talking about |
Ah... I see. Thanks, I'll leave this open because I think the inference that from typing import Union, Text
def f(x: bytes) -> bytes:
return x.encode("ascii") if isinstance(x, str) else x returns the same MyPy error FWIW. |
Hm, I do think there's a bug somewhere, since this gives no errors -- the key difference being that it uses an def f(x: AnyStr) -> bytes:
if isinstance(x, str):
return x.encode('ascii')
else:
return x UPDATE: Your second example also points in the same direction -- again if that's rewritten to use a statement it gives no errors (presumably treating the True branch as unreachable code). |
I don't think there is any mypy bug here, although this may be a usability problem. What is going on here consists of two parts:
x: int
y = reveal_type(x) if isinstance(x, str) else '' # Revealed type is 'int' I think the solution here should be also twofold:
# Python 3 mode
x: bytes
...
if isinstance(x, str): # This should be an error, this is likely a confusion with Python 2
... |
I think there are several red herrings in the examples (use of AnyStr for a single argument, or isinstance() with a non-overlapping type, but I still think it's a bug that the code with the ternary expression gives an error while the equivalent code with an if-expression. def f(x: AnyStr, y: AnyStr) -> bytes:
x += y
return x.encode('ascii') if isinstance(x, str) else x # Error here
def g(x: AnyStr, y: AnyStr) -> bytes:
x += y
if isinstance(x, str):
return x.encode('ascii') # No error here
else:
return x We used to have more of these, and we solved them by making the binder smarter -- I think there's more of that to be done to make the first version here pass. |
This is not easy, since every part of the ternary expression is an expression. It can't just be unreachable like a statement, and should have some type. A possible strategy is to give that "unreachable" expression |
So just to clarify, generic functions over type variables with "value
constraints" are type-checked multiple times, once for each possible
constraint? (Where for AnyStr the constraints are "str" and "bytes".) And
isinstance() checks involving those constraints can make specific branches
at the statement level unreachable, but there's no concept of an
unreachable expression?
|
I think this is exactly what happens. |
What would be the appropriate place in the docs to document this behavior? https://mypy.readthedocs.io/en/latest/common_issues.html ? I'll submit a PR (unless I'm just missing where this is already documented!) |
@petergaultney This depends on what exactly you want to document (i.e. what is "this behavior"). |
I think the correct section of the docs would be here: https://mypy.readthedocs.io/en/latest/common_issues.html?highlight=isinstance#complex-type-tests And the documentation would be something along the lines of "Ternary expressions will not allow type inference in the same way as using if statements." |
OK, I see yes, this makes sense (this is not a permanent limitation, but likely to be around for a while). You can make a PR and then we can polish the wording. |
I'm wondering is this issue essentially the same as what I'm getting, or should I submit a new one? What I have essentially is - types are inferred correctly when using if, but not when using ternary conditional. Same logic. $ cat conditions.py def conditional(condition: bool = False) -> tuple:
if condition:
return ((1, '', 0),)
return ()
def inline_conditional(condition: bool = False) -> tuple:
return ((1, '', 0),) if condition else () # line 8 is here $ mypy conditions.py
conditions.py:8: error: Incompatible return value type (got "object", expected "Tuple[Any, ...]")
Found 1 error in 1 file (checked 1 source file) My environment:
I know this issue revolves around AnyStr, but from my observations, mypy fails to detect multitude of types when they are returned from the ternary conditional operator, not just AnyStr. Example: #6755 Also wording chosen in #6649 concentrates on isinstance(), while it's not present in my example. |
I think I've run into the same issue working with requests. Since it looks similar enough to these cases I'll leave it here instead of submitting a new issue. Specifically, when working with request.Session. This fails. def send_get(session: Optional[requests.Session] = None) -> requests.Response:
fetch = session.get if session is not None else requests.get
return fetch("https://someurl.com") While this works. def send_get(session: Optional[requests.Session] = None) -> requests.Response:
if session is not None:
fetch = session.get
else:
fetch = requests.get
return fetch("https://someurl.com") I'm not sure this is a mypy problem, or a problem specifically with requests. mypy output:
This is on:
|
A very simple example of Ternary expression that fails with mypy. condition = True
a = [1]
b = [True]
c = a if condition else b
# mypy - Revealed type is "builtins.object"
# pyright - Type of "c" is "list[int] | list[bool]"
reveal_type(c)
# mypy - Value of type "object" is not indexable [index]
# pyright - No errors
print(c[0]) |
The title could be modified to something like "Constrained TypeVar fails to narrow on left hand side of conditional expression" |
When I run
mypy --python-version 3.6
on the following code:I get:
But as far as I can tell, this is backwards!
x.encode("ascii")
is only called whenisinstance(x, str)
, not in theelse
case (where x is abytes
object).This is with the latest
mypy==0.530
The text was updated successfully, but these errors were encountered: