Skip to content

Commit fb778ee

Browse files
carljmAlexWaygood
andauthored
[red-knot] unify LoopState and saved_break_states (#16406)
We currently keep two separate pieces of state regarding the current loop on `SemanticIndexBuilder`. One is an enum simply reflecting whether we are currently inside a loop, and the other is the saved flow states for `break` statements found in the current loop. For adding loopy control flow, I'll need to add some additional loop state (`continue` states, for example). Prepare for this by consolidating our existing loop state into a single struct and simplifying the API for pushing and popping a loop. This is purely a refactor, so tests are not changed. --------- Co-authored-by: Alex Waygood <[email protected]>
1 parent 671494a commit fb778ee

File tree

1 file changed

+42
-52
lines changed
  • crates/red_knot_python_semantic/src/semantic_index

1 file changed

+42
-52
lines changed

crates/red_knot_python_semantic/src/semantic_index/builder.rs

+42-52
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,22 @@ use crate::Db;
4141

4242
mod except_handlers;
4343

44-
/// Are we in a state where a `break` statement is allowed?
45-
#[derive(Clone, Copy, Debug)]
46-
enum LoopState {
47-
InLoop,
48-
NotInLoop,
44+
#[derive(Clone, Debug, Default)]
45+
struct Loop {
46+
/// Flow states at each `break` in the current loop.
47+
break_states: Vec<FlowSnapshot>,
4948
}
5049

51-
impl LoopState {
52-
fn is_inside(self) -> bool {
53-
matches!(self, LoopState::InLoop)
50+
impl Loop {
51+
fn push_break(&mut self, state: FlowSnapshot) {
52+
self.break_states.push(state);
5453
}
5554
}
5655

5756
struct ScopeInfo {
5857
file_scope_id: FileScopeId,
59-
loop_state: LoopState,
58+
/// Current loop state; None if we are not currently visiting a loop
59+
current_loop: Option<Loop>,
6060
}
6161

6262
pub(super) struct SemanticIndexBuilder<'db> {
@@ -73,8 +73,6 @@ pub(super) struct SemanticIndexBuilder<'db> {
7373
/// The name of the first function parameter of the innermost function that we're currently visiting.
7474
current_first_parameter_name: Option<&'db str>,
7575

76-
/// Flow states at each `break` in the current loop.
77-
loop_break_states: Vec<FlowSnapshot>,
7876
/// Per-scope contexts regarding nested `try`/`except` statements
7977
try_node_context_stack_manager: TryNodeContextStackManager,
8078

@@ -106,7 +104,6 @@ impl<'db> SemanticIndexBuilder<'db> {
106104
current_assignments: vec![],
107105
current_match_case: None,
108106
current_first_parameter_name: None,
109-
loop_break_states: vec![],
110107
try_node_context_stack_manager: TryNodeContextStackManager::default(),
111108

112109
has_future_annotations: false,
@@ -134,19 +131,20 @@ impl<'db> SemanticIndexBuilder<'db> {
134131
builder
135132
}
136133

137-
fn current_scope(&self) -> FileScopeId {
138-
*self
139-
.scope_stack
134+
fn current_scope_info(&self) -> &ScopeInfo {
135+
self.scope_stack
140136
.last()
141-
.map(|ScopeInfo { file_scope_id, .. }| file_scope_id)
142137
.expect("SemanticIndexBuilder should have created a root scope")
143138
}
144139

145-
fn loop_state(&self) -> LoopState {
140+
fn current_scope_info_mut(&mut self) -> &mut ScopeInfo {
146141
self.scope_stack
147-
.last()
142+
.last_mut()
148143
.expect("SemanticIndexBuilder should have created a root scope")
149-
.loop_state
144+
}
145+
146+
fn current_scope(&self) -> FileScopeId {
147+
self.current_scope_info().file_scope_id
150148
}
151149

152150
/// Returns the scope ID of the surrounding class body scope if the current scope
@@ -167,11 +165,21 @@ impl<'db> SemanticIndexBuilder<'db> {
167165
}
168166
}
169167

170-
fn set_inside_loop(&mut self, state: LoopState) {
171-
self.scope_stack
172-
.last_mut()
173-
.expect("Always to have a root scope")
174-
.loop_state = state;
168+
/// Push a new loop, returning the outer loop, if any.
169+
fn push_loop(&mut self) -> Option<Loop> {
170+
self.current_scope_info_mut()
171+
.current_loop
172+
.replace(Loop::default())
173+
}
174+
175+
/// Pop a loop, replacing with the previous saved outer loop, if any.
176+
fn pop_loop(&mut self, outer_loop: Option<Loop>) -> Loop {
177+
std::mem::replace(&mut self.current_scope_info_mut().current_loop, outer_loop)
178+
.expect("pop_loop() should not be called without a prior push_loop()")
179+
}
180+
181+
fn current_loop_mut(&mut self) -> Option<&mut Loop> {
182+
self.current_scope_info_mut().current_loop.as_mut()
175183
}
176184

177185
fn push_scope(&mut self, node: NodeWithScopeRef) {
@@ -204,7 +212,7 @@ impl<'db> SemanticIndexBuilder<'db> {
204212

205213
self.scope_stack.push(ScopeInfo {
206214
file_scope_id,
207-
loop_state: LoopState::NotInLoop,
215+
current_loop: None,
208216
});
209217
}
210218

@@ -1208,15 +1216,9 @@ where
12081216
.current_visibility_constraints_mut()
12091217
.add_atom(later_predicate_id);
12101218

1211-
// Save aside any break states from an outer loop
1212-
let saved_break_states = std::mem::take(&mut self.loop_break_states);
1213-
1214-
// TODO: definitions created inside the body should be fully visible
1215-
// to other statements/expressions inside the body --Alex/Carl
1216-
let outer_loop_state = self.loop_state();
1217-
self.set_inside_loop(LoopState::InLoop);
1219+
let outer_loop = self.push_loop();
12181220
self.visit_body(body);
1219-
self.set_inside_loop(outer_loop_state);
1221+
let this_loop = self.pop_loop(outer_loop);
12201222

12211223
// If the body is executed, we know that we've evaluated the condition at least
12221224
// once, and that the first evaluation was True. We might not have evaluated the
@@ -1225,11 +1227,6 @@ where
12251227
let body_vis_constraint_id = first_vis_constraint_id;
12261228
self.record_visibility_constraint_id(body_vis_constraint_id);
12271229

1228-
// Get the break states from the body of this loop, and restore the saved outer
1229-
// ones.
1230-
let break_states =
1231-
std::mem::replace(&mut self.loop_break_states, saved_break_states);
1232-
12331230
// We execute the `else` once the condition evaluates to false. This could happen
12341231
// without ever executing the body, if the condition is false the first time it's
12351232
// tested. So the starting flow state of the `else` clause is the union of:
@@ -1250,7 +1247,7 @@ where
12501247

12511248
// Breaking out of a while loop bypasses the `else` clause, so merge in the break
12521249
// states after visiting `else`.
1253-
for break_state in break_states {
1250+
for break_state in this_loop.break_states {
12541251
let snapshot = self.flow_snapshot();
12551252
self.flow_restore(break_state);
12561253
self.record_visibility_constraint_id(body_vis_constraint_id);
@@ -1298,7 +1295,6 @@ where
12981295
self.record_ambiguous_visibility();
12991296

13001297
let pre_loop = self.flow_snapshot();
1301-
let saved_break_states = std::mem::take(&mut self.loop_break_states);
13021298

13031299
let current_assignment = match &**target {
13041300
ast::Expr::List(_) | ast::Expr::Tuple(_) => Some(CurrentAssignment::For {
@@ -1346,16 +1342,9 @@ where
13461342
self.pop_assignment();
13471343
}
13481344

1349-
// TODO: Definitions created by loop variables
1350-
// (and definitions created inside the body)
1351-
// are fully visible to other statements/expressions inside the body --Alex/Carl
1352-
let outer_loop_state = self.loop_state();
1353-
self.set_inside_loop(LoopState::InLoop);
1345+
let outer_loop = self.push_loop();
13541346
self.visit_body(body);
1355-
self.set_inside_loop(outer_loop_state);
1356-
1357-
let break_states =
1358-
std::mem::replace(&mut self.loop_break_states, saved_break_states);
1347+
let this_loop = self.pop_loop(outer_loop);
13591348

13601349
// We may execute the `else` clause without ever executing the body, so merge in
13611350
// the pre-loop state before visiting `else`.
@@ -1364,7 +1353,7 @@ where
13641353

13651354
// Breaking out of a `for` loop bypasses the `else` clause, so merge in the break
13661355
// states after visiting `else`.
1367-
for break_state in break_states {
1356+
for break_state in this_loop.break_states {
13681357
self.flow_merge(break_state);
13691358
}
13701359
}
@@ -1547,8 +1536,9 @@ where
15471536
}
15481537

15491538
ast::Stmt::Break(_) => {
1550-
if self.loop_state().is_inside() {
1551-
self.loop_break_states.push(self.flow_snapshot());
1539+
let snapshot = self.flow_snapshot();
1540+
if let Some(current_loop) = self.current_loop_mut() {
1541+
current_loop.push_break(snapshot);
15521542
}
15531543
// Everything in the current block after a terminal statement is unreachable.
15541544
self.mark_unreachable();

0 commit comments

Comments
 (0)