You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Constrained TypeVar fails to narrow on left hand side of conditional expression #4134
test.py:3: error: "bytes" has no attribute "encode"; maybe "decode"?
But as far as I can tell, this is backwards! x.encode("ascii") is only called when isinstance(x, str), not in the else case (where x is a bytes object).
I think this is because you're using AnyStr incorrectly. As a TypeVar, it really makes sense only when used multiple times in a signature (or in a few other cases not relevant here). I believe we're going to introduce a warning for incorrect usages like yours, although I can't find the relevant issue now.
Your code should have been written as:
from typing import Union
def f(x: Union[bytes, str]) -> bytes:
return x.encode("ascii") if isinstance(x, str) else x
which passes mypy with no errors for me.
I don't know why your code gives an error talking about bytes though, and maybe there's a real bug there. I think it's related to the ternary expression, because rewriting the code to use an if-else block removes the error.
Hm, I do think there's a bug somewhere, since this gives no errors -- the key difference being that it uses an if statement instead of an if expression:
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:
For constrained type variables (like AnyStr) the function body is checked for all constraints (str and bytes in this case).
For ternary expression, mypy can't mark first part as "unreachable", but it doesn't perform any type narrowing either if the types are non-overlapping, this can be seen form following:
x: inty=reveal_type(x) ifisinstance(x, str) else''# Revealed type is 'int'
I think the solution here should be also twofold:
Potentially problematic function signatures, like in the original example should get a warning (at least with a note and an exit 0). This is already mentioned.
In addition we should error on attempts of narrowing with non-overlapping types. For user-defined types it is OK to keep current behaviour (there might be custom __instancecheck__ or other dynamic things), but for built-in types - especially for more error-prone situations like str vs bytes vs unicode vs basestring etc. - we should give an error:
# Python 3 modex: bytes
...
ifisinstance(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.
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.
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
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 UninhabitedType (this one has all attributes) and infer a simplified union from ternary expressions, not a join (the second is an independent problem, but I think we already have an issue for this).
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?
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
defconditional(condition: bool=False) ->tuple:
ifcondition:
return ((1, '', 0),)
return ()
definline_conditional(condition: bool=False) ->tuple:
return ((1, '', 0),) ifconditionelse () # line 8 is here
$ mypy conditions.pyconditions.py:8: error: Incompatible return value type (got "object", expected "Tuple[Any, ...]")Found 1 error in 1 file (checked 1 source file)
My environment:
Ubuntu 19.10
Python 3.8.0 (default, Oct 23 2019, 16:52:33) [GCC 9.2.1 20191008]
mypy 0.761
typed-ast 1.4.1
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.
Reacted by Matthew Planchard, Saaket Prakash and Ellis Breen
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.
I'm not sure this is a mypy problem, or a problem specifically with requests.
mypy output:
➜ mypy --show-error-codes request.py
request.py:15: error: Cannot call function of unknown type [operator]
Found 1 error in 1 file (checked 1 source file)
A very simple example of Ternary expression that fails with mypy.
Also comparing with pyright which doesn't complain and does much better inference.
condition=Truea= [1]
b= [True]
c=aifconditionelseb# 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 errorsprint(c[0])
Reacted by Tom Young, Ellis Breen and Beatrix Sirur
changed the title [-]Incorrect Type Inference in Ternary Expression[/-][+]Constrained TypeVar fails to narrow on left hand side of conditional expression[/+]on Aug 19, 2023
Activity
JelleZijlstra commentedon Oct 19, 2017
I think this is because you're using
AnyStrincorrectly. As a TypeVar, it really makes sense only when used multiple times in a signature (or in a few other cases not relevant here). I believe we're going to introduce a warning for incorrect usages like yours, although I can't find the relevant issue now.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
bytesthough, and maybe there's a real bug there. I think it's related to the ternary expression, because rewriting the code to use anif-elseblock removes the error.alanhdu commentedon Oct 19, 2017
Ah... I see. Thanks,
Union[bytes, str]works perfectly!I'll leave this open because I think the inference that
xis abytesobject probably represents a bug somewhere:returns the same MyPy error FWIW.
gvanrossum commentedon Oct 19, 2017
Hm, I do think there's a bug somewhere, since this gives no errors -- the key difference being that it uses an
ifstatement instead of anifexpression: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).
ilevkivskyi commentedon Oct 20, 2017
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:
AnyStr) the function body is checked for all constraints (strandbytesin this case).I think the solution here should be also twofold:
__instancecheck__or other dynamic things), but for built-in types - especially for more error-prone situations likestrvsbytesvsunicodevsbasestringetc. - we should give an error:gvanrossum commentedon Oct 20, 2017
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.
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.
ilevkivskyi commentedon Oct 20, 2017
@gvanrossum
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
UninhabitedType(this one has all attributes) and infer a simplified union from ternary expressions, not a join (the second is an independent problem, but I think we already have an issue for this).gvanrossum commentedon Oct 20, 2017
ilevkivskyi commentedon Oct 20, 2017
I think this is exactly what happens.
petergaultney commentedon Apr 8, 2019
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!)
ilevkivskyi commentedon Apr 8, 2019
@petergaultney This depends on what exactly you want to document (i.e. what is "this behavior").
petergaultney commentedon Apr 8, 2019
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."
ilevkivskyi commentedon Apr 8, 2019
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.
mbdevpl commentedon Feb 4, 2020
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.pyMy 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.
ntn9995 commentedon Jul 2, 2020
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.
While this works.
I'm not sure this is a mypy problem, or a problem specifically with requests.
mypy output:
This is on:
saaketp commentedon Sep 9, 2021
A very simple example of Ternary expression that fails with mypy.
Also comparing with pyright which doesn't complain and does much better inference.
KotlinIsland commentedon Jul 1, 2022
The title could be modified to something like "Constrained TypeVar fails to narrow on left hand side of conditional expression"
[-]Incorrect Type Inference in Ternary Expression[/-][+]Constrained TypeVar fails to narrow on left hand side of conditional expression[/+]Suppress errors for unreachable branches in conditional expressions (#…