-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Infer union from conditional definition in if statement #6233
Comments
This pattern occurs a lot in our code, specifically with if condition:
x: Optional[int] = None
else:
x = 5 |
@bluetech Your example already works without the explicit annotation. |
@ilevkivskyi Thanks for letting me know. I didn't realize the order matters. Looking at our code, we indeed usually put the None case in the Maybe this feature can subsume this special case. |
@bluetech This feature would not entirely obsolete the special case, unless we extend it to handle cases like this: x = None
if cond():
x = 5 It's unclear to me whether we should accept code like this: x = 0
if cond():
x = '' # Should this be okay for an arbitrary rvalue type? |
x = 0
if cond():
x = '' # Should this be okay for an arbitrary rvalue type? I think it's okay for an arbitrary rvalue if after end of the |
We've recently started using mypy in some of our Python projects, and we're running into this issue in places where we initialize an optional variable using this pattern: if cond():
x = <some_value>
else:
x = None The change proposed by this issue would solve it, but extending the special case to cover assigning to |
I would certainly accept extending the special case. I think this is actually a huge pain point. The core suggestion in this issue is only being proposed for when |
I agree with @msullivan. In my experience, code like @aperiodic'd example comes up very often, and it would be great if the if cond():
x = None
else:
x = <some_value> # Already supported So there are two sub-issues:
Case (1) seems much more important, but if also supporting (2) is not much more work, it may make sense to implement both at the same time. |
Increased priority to "high" since the |
I think that the general case described here should be supported without the if False:
x = 1
else:
x += 1 results in My proposal is that, when a variable is initialized in all the branches of an |
I would agree with @aperiodic's suggestion, given, of course, that the variable was not defined before the There might be some value to implement another flag, something like |
To summarize, here's what I think should be the behaviour. I see four possible situations: Case A: No union, no redefinition
Case B: Union, no redefinition
Case C: No union, redefinition
Case D: Union, redefinition
By default, cases A and B should be allowed. |
@unordinateur I like your proposal. The main issue I see with changing the default behavior is that this may generate a lot of apparent false positives for existing code, in particular since some inferred types will be different. For example: if cond():
x = [1.0]
else:
x = [1]
reveal_type(x) Currently the type of Another possible problem case: if cond():
x = [1]
else:
x = []
reveal_type(x) If we assume that the two assignments initialize two separate variables, mypy might require an annotation for the second assignment, since it can't infer the list item type. |
I understand! Union inference might be disabled by default then; and be optionally enabled by a flag. ( With that proposal:
The semantics of the special case for
So, these cases would be allowed:
Whereas this code wouldn't work unless
Without the final |
@unordinateur Some comments:
if cond():
x = [1]
else:
x = []
reveal_type(x) # Should continue to be List[int] One potential way to support this would be to link the two definitions so that the inferred type from the first assignment would be used for the type context of the second assignment. Another potential way would be to infer a partial list type in the second assignment and allow it to be merged with a non-partial list type from the first assignment. |
1 and 2. Agreed! Now for 3. I think that the following approach could be taken:
As a special case, in step 2, if any of the other branch contains This should make all your examples work, I believe. It would however fail on my examples above, for example:
In this case, the user would have to explicitly tag I think this makes sense. It would be pretty unusual, I think, for the user to voluntarily want a variable to be either of two unrelated types. Having mypy see that as an error by default until the used explicitly requests it makes sense. To be clear: the behaviour I suggest above is not union inference. It's more like extending the current behaviour to support the cases we identified above as important to support, while preserving the behavior as much as possible for currently valid code. |
Is there any update on this issue? |
This would indeed be really nice. Consider this example: https://mypy-play.net/?mypy=latest&python=3.10&gist=45faed499d453baccdb671b4985facb0 def f(x: bool) -> None:
a = "string" if x else 123
if x:
b = "string"
else:
b = 123
match x:
case True:
c = "string"
case False:
c = 123
case _:
raise ValueError
reveal_type(a) # object
reveal_type(b) # str
reveal_type(c) # str The result should be |
Lowered priority since both the |
Maybe if a variable is initialized both in if and else blocks, the resulting type should be the union of the types of the initializers (only when using
--allow-redefinitions
):Currently the second assignment generates an error, and the type of
x
isint
everywhere.This is a generalization of #6232 and related to #1174 (but not a direct follow-up issue).
The text was updated successfully, but these errors were encountered: