-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[red-knot] Consider all definitions after terminal statements unreachable #15676
base: main
Are you sure you want to change the base?
Changes from 4 commits
e0d2130
f45af24
70b681b
81a3164
93b149c
912ed62
4102c89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,134 @@ | ||||||
# Terminal statements | ||||||
|
||||||
## Introduction | ||||||
|
||||||
Terminal statements complicate a naive control-flow analysis. | ||||||
|
||||||
As a simple example: | ||||||
|
||||||
```py | ||||||
def f(cond: bool) -> str: | ||||||
if cond: | ||||||
x = "test" | ||||||
else: | ||||||
raise ValueError | ||||||
return x | ||||||
|
||||||
def g(cond: bool): | ||||||
if cond: | ||||||
x = "test" | ||||||
reveal_type(x) # revealed: Literal["test"] | ||||||
else: | ||||||
x = "unreachable" | ||||||
reveal_type(x) # revealed: Literal["unreachable"] | ||||||
raise ValueError | ||||||
reveal_type(x) # revealed: Literal["test"] | ||||||
``` | ||||||
|
||||||
In `f`, we should be able to determine that the `else` branch ends in a terminal statement, and that | ||||||
the `return` statement can only be executed when the condition is true. We should therefore consider | ||||||
the reference always bound, even though `x` is only bound in the true branch. | ||||||
|
||||||
Similarly, in `g`, we should see that the assignment of the value `"unreachable"` can never be seen | ||||||
by the final `reveal_type`. | ||||||
|
||||||
## `return` is terminal | ||||||
|
||||||
```py | ||||||
def f(cond: bool) -> str: | ||||||
if cond: | ||||||
x = "test" | ||||||
else: | ||||||
return "early" | ||||||
return x | ||||||
|
||||||
def g(cond: bool): | ||||||
if cond: | ||||||
x = "test" | ||||||
reveal_type(x) # revealed: Literal["test"] | ||||||
else: | ||||||
x = "unreachable" | ||||||
reveal_type(x) # revealed: Literal["unreachable"] | ||||||
return | ||||||
reveal_type(x) # revealed: Literal["test"] | ||||||
``` | ||||||
|
||||||
## `continue` is terminal within its loop scope | ||||||
|
||||||
```py | ||||||
def f(cond: bool) -> str: | ||||||
while True: | ||||||
if cond: | ||||||
x = "test" | ||||||
else: | ||||||
continue | ||||||
return x | ||||||
|
||||||
def g(cond: bool, i: int): | ||||||
x = "before" | ||||||
while i < 5: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You may want to switch to |
||||||
if cond: | ||||||
x = "loop" | ||||||
reveal_type(x) # revealed: Literal["loop"] | ||||||
else: | ||||||
x = "continue" | ||||||
reveal_type(x) # revealed: Literal["continue"] | ||||||
continue | ||||||
reveal_type(x) # revealed: Literal["loop"] | ||||||
reveal_type(x) # revealed: Literal["before", "loop"] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious in which combination of |
||||||
``` | ||||||
|
||||||
## `break` is terminal within its loop scope | ||||||
|
||||||
```py | ||||||
def f(cond: bool) -> str: | ||||||
while True: | ||||||
if cond: | ||||||
x = "test" | ||||||
else: | ||||||
break | ||||||
return x | ||||||
return "late" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could |
||||||
|
||||||
def g(cond: bool, i: int): | ||||||
x = "before" | ||||||
while i < 5: | ||||||
if cond: | ||||||
x = "loop" | ||||||
reveal_type(x) # revealed: Literal["loop"] | ||||||
else: | ||||||
x = "break" | ||||||
reveal_type(x) # revealed: Literal["break"] | ||||||
break | ||||||
reveal_type(x) # revealed: Literal["loop"] | ||||||
reveal_type(x) # revealed: Literal["before", "loop", "break"] | ||||||
``` | ||||||
|
||||||
## `return` is terminal in nested scopes | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think "nested scopes" is the right term here; Python
Suggested change
|
||||||
|
||||||
```py | ||||||
def f(cond1: bool, cond2: bool) -> str: | ||||||
if cond1: | ||||||
if cond2: | ||||||
x = "test1" | ||||||
else: | ||||||
return "early" | ||||||
else: | ||||||
x = "test2" | ||||||
return x | ||||||
|
||||||
def g(cond1: bool, cond2: bool): | ||||||
if cond1: | ||||||
if cond2: | ||||||
x = "test1" | ||||||
reveal_type(x) # revealed: Literal["test1"] | ||||||
else: | ||||||
x = "unreachable" | ||||||
reveal_type(x) # revealed: Literal["unreachable"] | ||||||
return | ||||||
reveal_type(x) # revealed: Literal["test1"] | ||||||
else: | ||||||
x = "test2" | ||||||
reveal_type(x) # revealed: Literal["test2"] | ||||||
reveal_type(x) # revealed: Literal["test1", "test2"] | ||||||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify, since it took me a moment on first read, even though you just explained it above: