-
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] [WIP] Combine terminal statement support with statically known branches #15817
base: main
Are you sure you want to change the base?
Conversation
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.
This is not currently working because of visibility constraint simplification:
def _(cond: bool):
x = "a"
if cond:
x = "b"
# ← {0}
if True:
return
# ← {1}
# ← {2}
reveal_type(x) # revealed: Literal["a"]
At point {1}
, we're marking the x = "b"
binding as non-visible (by setting a visibility constraint of ALWAYS_FALSE
).
But at point {2}
, after we merge the two flows back together (the then
flow and the artifically inserted else
flow), we simplify the result relative to point {0}
. That sees that there weren't any new bindings of x
, and resets the visibility of x = "b"
back to what it was at point {0}
, forgetting the unreachability that we just introduced.
So I think I need to skip the simplification step when a flow contains a terminal statement.
@@ -504,28 +505,24 @@ pub(super) struct UseDefMapBuilder<'db> { | |||
|
|||
/// Currently live bindings and declarations for each symbol. | |||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>, | |||
|
|||
reachable: bool, |
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.
At first I replaced this with reachability: ScopedVisibilityConstraintId
, but I realized that scope_start_visibility
is already what we need: the start of the scope is visible iff the flow is still reachable.
// should be completely overwritten by the snapshot we're merging in. If the other snapshot | ||
// is unreachable, we should return without merging. | ||
if !snapshot.reachable { | ||
// As an optimization, if we know statically that either of the snapshots is always |
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.
Commenting out these two if
clauses is how we verify that this is truly an optimization — we should get the same results for the tests with and without it
This sounds right. The simplification step is a bit of a performance hack. I think it could be eliminated if we used BDDs instead of syntax trees to represent visibility constraints (since BDDs self-simplify). But I think it should never be wrong to skip the simplification, just potentially hurt performance. Which in this case shouldn't be too bad, since it would only occur when there's a terminal statement in the branch, which won't be the common case. |
CodSpeed Performance ReportMerging #15817 will degrade performances by 14.12%Comparing Summary
Benchmarks breakdown
|
(because OR-ing reachability on merge throws away the AlwaysFalse)
This example from @sharkdp shows how terminal statements can appear in statically known branches: #15676 (comment)
We currently don't handle that correctly, since we don't apply the
True
visibility constraint to the reachability flag that we set when we encounter a terminal statement.Work in progress