-
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
Draft
dcreager
wants to merge
22
commits into
main
Choose a base branch
from
dcreager/static-terminal
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+142
−37
Draft
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
02c4798
Add ALWAYS_FALSE
dcreager 2c0d577
Mark return states
dcreager e198806
Mark live bindings as non-visible when flow is unreachable
dcreager 23b366d
Revert "Mark return states"
dcreager 38f3d99
Handle final return statement specially
dcreager 5c1918a
Customizable binding visibility
dcreager 7504fb7
Reachability is now a vis constraint, not a bool
dcreager ee6f253
Fix ternary AND logic
dcreager 6b53554
These are now unresolved I guess?
dcreager bb11cf8
clippy
dcreager a2ef702
Normalize negations of ALWAYS_{TRUE,FALSE}
dcreager 1c42a2b
scope_start_visibility _is_ reachability
dcreager 289c0c6
No, bindings are always visible
dcreager 8b0899f
TODO for `raise`/`else` unreachability
dcreager 9cd8e68
Fix test failure
dcreager 1a80f81
Expected test case change
dcreager f71325c
Try to skip simplification by checking scope_start_visibility
dcreager 0e06012
And try via a separate `always_reachable` boolean
dcreager b3b4577
Add AlwaysFalse as its own constraint variant
dcreager 46b1ec2
Update always_reachable on merge correctly
dcreager 999188a
Try checking if reachability contains AlwaysFalse in syntax tree
dcreager 0f278da
But that doesn't work either
dcreager File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -265,6 +265,7 @@ use crate::semantic_index::definition::Definition; | |
use crate::semantic_index::symbol::ScopedSymbolId; | ||
use crate::semantic_index::use_def::symbol_state::DeclarationIdWithConstraint; | ||
use crate::visibility_constraints::{VisibilityConstraint, VisibilityConstraints}; | ||
use crate::Db; | ||
use ruff_index::IndexVec; | ||
use rustc_hash::FxHashMap; | ||
|
||
|
@@ -476,11 +477,12 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {} | |
pub(super) struct FlowSnapshot { | ||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>, | ||
scope_start_visibility: ScopedVisibilityConstraintId, | ||
reachable: bool, | ||
always_reachable: bool, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub(super) struct UseDefMapBuilder<'db> { | ||
db: &'db dyn Db, | ||
|
||
/// Append-only array of [`Definition`]. | ||
all_definitions: IndexVec<ScopedDefinitionId, Option<Definition<'db>>>, | ||
|
||
|
@@ -505,27 +507,27 @@ pub(super) struct UseDefMapBuilder<'db> { | |
/// Currently live bindings and declarations for each symbol. | ||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>, | ||
|
||
reachable: bool, | ||
always_reachable: bool, | ||
} | ||
|
||
impl Default for UseDefMapBuilder<'_> { | ||
fn default() -> Self { | ||
impl<'db> UseDefMapBuilder<'db> { | ||
pub(super) fn new(db: &'db dyn Db) -> Self { | ||
Self { | ||
db, | ||
all_definitions: IndexVec::from_iter([None]), | ||
all_constraints: IndexVec::new(), | ||
visibility_constraints: VisibilityConstraints::default(), | ||
scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE, | ||
bindings_by_use: IndexVec::new(), | ||
definitions_by_definition: FxHashMap::default(), | ||
symbol_states: IndexVec::new(), | ||
reachable: true, | ||
always_reachable: true, | ||
} | ||
} | ||
} | ||
|
||
impl<'db> UseDefMapBuilder<'db> { | ||
pub(super) fn mark_unreachable(&mut self) { | ||
self.reachable = false; | ||
self.record_visibility_constraint_id(ScopedVisibilityConstraintId::ALWAYS_FALSE); | ||
self.always_reachable = false; | ||
} | ||
|
||
pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) { | ||
|
@@ -581,6 +583,15 @@ impl<'db> UseDefMapBuilder<'db> { | |
.add_and_constraint(self.scope_start_visibility, constraint); | ||
} | ||
|
||
pub(super) fn record_negated_visibility_constraint_id( | ||
&mut self, | ||
constraint: ScopedVisibilityConstraintId, | ||
) -> ScopedVisibilityConstraintId { | ||
let new_constraint_id = self.visibility_constraints.add_not_constraint(constraint); | ||
self.record_visibility_constraint_id(new_constraint_id); | ||
new_constraint_id | ||
} | ||
|
||
pub(super) fn record_visibility_constraint( | ||
&mut self, | ||
constraint: VisibilityConstraint<'db>, | ||
|
@@ -611,6 +622,10 @@ impl<'db> UseDefMapBuilder<'db> { | |
pub(super) fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) { | ||
debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len()); | ||
|
||
if !self.always_reachable { | ||
return; | ||
} | ||
|
||
self.scope_start_visibility = snapshot.scope_start_visibility; | ||
|
||
// Note that this loop terminates when we reach a symbol not present in the snapshot. | ||
|
@@ -664,7 +679,7 @@ impl<'db> UseDefMapBuilder<'db> { | |
FlowSnapshot { | ||
symbol_states: self.symbol_states.clone(), | ||
scope_start_visibility: self.scope_start_visibility, | ||
reachable: self.reachable, | ||
always_reachable: self.always_reachable, | ||
} | ||
} | ||
|
||
|
@@ -679,6 +694,7 @@ impl<'db> UseDefMapBuilder<'db> { | |
// Restore the current visible-definitions state to the given snapshot. | ||
self.symbol_states = snapshot.symbol_states; | ||
self.scope_start_visibility = snapshot.scope_start_visibility; | ||
self.always_reachable = snapshot.always_reachable; | ||
|
||
// If the snapshot we are restoring is missing some symbols we've recorded since, we need | ||
// to fill them in so the symbol IDs continue to line up. Since they don't exist in the | ||
|
@@ -687,22 +703,34 @@ impl<'db> UseDefMapBuilder<'db> { | |
num_symbols, | ||
SymbolState::undefined(self.scope_start_visibility), | ||
); | ||
|
||
self.reachable = snapshot.reachable; | ||
} | ||
|
||
/// Merge the given snapshot into the current state, reflecting that we might have taken either | ||
/// path to get here. The new state for each symbol should include definitions from both the | ||
/// prior state and the snapshot. | ||
pub(super) fn merge(&mut self, snapshot: FlowSnapshot) { | ||
// Unreachable snapshots should not be merged: If the current snapshot is unreachable, it | ||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. Commenting out these two |
||
// unreachable, we can leave it out of the merged result entirely. Note that we cannot | ||
// perform any type inference at this point, so this is largely limited to unreachability | ||
// via terminal statements. If a flow's reachability depends on an expression in the code, | ||
// we will include the flow in the merged result; the visibility constraints of its | ||
// bindings will include this reachability condition, so that later during type inference, | ||
// we can determine whether any particular binding is non-visible due to unreachability. | ||
if self | ||
.visibility_constraints | ||
.evaluate_without_inference(self.db, snapshot.scope_start_visibility) | ||
.is_always_false() | ||
{ | ||
self.always_reachable = false; | ||
return; | ||
} | ||
if !self.reachable { | ||
if self | ||
.visibility_constraints | ||
.evaluate_without_inference(self.db, self.scope_start_visibility) | ||
.is_always_false() | ||
{ | ||
self.restore(snapshot); | ||
self.always_reachable = false; | ||
return; | ||
} | ||
|
||
|
@@ -727,9 +755,7 @@ impl<'db> UseDefMapBuilder<'db> { | |
self.scope_start_visibility = self | ||
.visibility_constraints | ||
.add_or_constraint(self.scope_start_visibility, snapshot.scope_start_visibility); | ||
|
||
// Both of the snapshots are reachable, so the merged result is too. | ||
self.reachable = true; | ||
self.always_reachable &= snapshot.always_reachable; | ||
} | ||
|
||
pub(super) fn finish(mut self) -> UseDefMap<'db> { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 thatscope_start_visibility
is already what we need: the start of the scope is visible iff the flow is still reachable.