Skip to content

Commit 495557c

Browse files
committed
[ty] filter out pre-loop bindings from loop headers
1 parent b2d856f commit 495557c

4 files changed

Lines changed: 41 additions & 27 deletions

File tree

crates/ty_python_semantic/src/place.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,24 +1208,25 @@ fn loop_header_reachability_impl<'db>(
12081208

12091209
match use_def.definition(live_binding.binding) {
12101210
DefinitionState::Defined(def) => {
1211+
debug_assert_ne!(
1212+
def, definition,
1213+
"loop headers only include bindings from within the loop"
1214+
);
12111215
has_defined_bindings = true;
1212-
if def != definition {
1213-
reachable_bindings.insert(ReachableLoopBinding {
1214-
definition: def,
1215-
narrowing_constraint: live_binding.narrowing_constraint,
1216-
});
1217-
}
1216+
reachable_bindings.insert(ReachableLoopBinding {
1217+
definition: def,
1218+
narrowing_constraint: live_binding.narrowing_constraint,
1219+
});
12181220
}
12191221
// `del` in the loop body is always visible to code after the loop via the
12201222
// normal control flow merge. Updating `deleted_reachability` here is
12211223
// necessary for prior uses in the loop to see it.
12221224
DefinitionState::Deleted => {
12231225
deleted_reachability = deleted_reachability.or(reachability);
12241226
}
1225-
// If UNBOUND is visible at loop-back, then it was visible before the loop.
1226-
// Loop header definitions don't shadow preexisting bindings, so we don't
1227-
// need to do anything with this.
1228-
DefinitionState::Undefined => {}
1227+
DefinitionState::Undefined => {
1228+
unreachable!("loop headers only include bindings from within the loop")
1229+
}
12291230
}
12301231
}
12311232

@@ -1242,7 +1243,7 @@ pub(crate) struct LoopHeaderReachability<'db> {
12421243
/// Whether any reachable loop-back binding is a defined binding.
12431244
pub(crate) has_defined_bindings: bool,
12441245
pub(crate) deleted_reachability: Truthiness,
1245-
/// Reachable, defined loop-back bindings (excluding the loop header definition itself).
1246+
/// Reachable loop-back bindings that are not `del`s.
12461247
pub(crate) reachable_bindings: FxIndexSet<ReachableLoopBinding<'db>>,
12471248
}
12481249

crates/ty_python_semantic/src/semantic_index/builder.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ use crate::semantic_index::scope::{
4848
use crate::semantic_index::scope::{Scope, ScopeId, ScopeKind, ScopeLaziness};
4949
use crate::semantic_index::symbol::{ScopedSymbolId, Symbol};
5050
use crate::semantic_index::use_def::{
51-
EnclosingSnapshotKey, FlowSnapshot, PreviousDefinitions, ScopedEnclosingSnapshotId,
52-
UseDefMapBuilder,
51+
EnclosingSnapshotKey, FlowSnapshot, PreviousDefinitions, ScopedDefinitionId,
52+
ScopedEnclosingSnapshotId, UseDefMapBuilder,
5353
};
5454
use crate::semantic_index::{
5555
ExpressionsScopeMap, LoopHeader, LoopToken, SemanticIndex, VisibleAncestorsIter,
@@ -845,12 +845,13 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
845845
}
846846

847847
/// Create loop header definitions for all places that are bound within a loop. Return the
848-
/// `LoopToken` referenced by those definitions, and the set of bound place IDs.
848+
/// `LoopToken` referenced by those definitions, the set of bound place IDs, and the lower
849+
/// bound `ScopedDefinitionId` for definitions created within the loop.
849850
fn synthesize_loop_header_definitions(
850851
&mut self,
851852
loop_stmt: LoopStmtRef<'ast>,
852853
bound_places: Vec<PlaceExpr>,
853-
) -> (LoopToken<'db>, FxHashSet<ScopedPlaceId>) {
854+
) -> (LoopToken<'db>, FxHashSet<ScopedPlaceId>, ScopedDefinitionId) {
854855
let loop_token = LoopToken::new(self.db);
855856
let mut bound_place_ids: FxHashSet<ScopedPlaceId> = FxHashSet::default();
856857
for place_expr in bound_places {
@@ -865,7 +866,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
865866
self.push_additional_definition(place_id, loop_header_ref);
866867
}
867868
}
868-
(loop_token, bound_place_ids)
869+
let loop_min_definition_id = self.current_use_def_map_mut().next_definition_id();
870+
(loop_token, bound_place_ids, loop_min_definition_id)
869871
}
870872

871873
/// Build a `LoopHeader` that tracks all the variables bound in a loop, which will be visible
@@ -876,13 +878,18 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
876878
&mut self,
877879
loop_header_places: &FxHashSet<ScopedPlaceId>,
878880
loop_token: LoopToken<'db>,
881+
loop_min_definition_id: ScopedDefinitionId,
879882
) {
880883
let mut loop_header = LoopHeader::new();
881884
let use_def = self.current_use_def_map_mut();
882-
// Collect bindings.
885+
// Collect all the bindings within the loop that reached a loop back edge. Use the minimum
886+
// definition ID to filter out all the pre-loop bindings. The loop header doesn't shadow
887+
// them, so there's no need to duplicate them.
883888
for place_id in loop_header_places {
884889
for live_binding in use_def.loop_back_bindings(*place_id) {
885-
loop_header.add_binding(*place_id, live_binding);
890+
if live_binding.binding >= loop_min_definition_id {
891+
loop_header.add_binding(*place_id, live_binding);
892+
}
886893
}
887894
}
888895
// Mark the reachability and narrowing constraints as used.
@@ -2225,8 +2232,10 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
22252232

22262233
// Collect all the loop-back bindings (including the `continue` states we just
22272234
// merged) and populate the `LoopHeader`.
2228-
if let Some((loop_token, bound_place_ids)) = maybe_loop_header_info {
2229-
self.populate_loop_header(&bound_place_ids, loop_token);
2235+
if let Some((loop_token, bound_place_ids, loop_min_definition_id)) =
2236+
maybe_loop_header_info
2237+
{
2238+
self.populate_loop_header(&bound_place_ids, loop_token, loop_min_definition_id);
22302239
}
22312240

22322241
// We execute the `else` branch once the condition evaluates to false. This could
@@ -2333,8 +2342,10 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
23332342

23342343
// Collect all the loop-back bindings (including the `continue` states we just
23352344
// merged) and populate the `LoopHeader`.
2336-
if let Some((loop_token, bound_place_ids)) = maybe_loop_header_info {
2337-
self.populate_loop_header(&bound_place_ids, loop_token);
2345+
if let Some((loop_token, bound_place_ids, loop_min_definition_id)) =
2346+
maybe_loop_header_info
2347+
{
2348+
self.populate_loop_header(&bound_place_ids, loop_token, loop_min_definition_id);
23382349
}
23392350

23402351
// We may execute the `else` clause without ever executing the body, so merge in

crates/ty_python_semantic/src/semantic_index/use_def.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,10 @@ impl<'db> UseDefMapBuilder<'db> {
944944
}
945945
}
946946

947+
pub(super) fn next_definition_id(&self) -> ScopedDefinitionId {
948+
self.all_definitions.next_index()
949+
}
950+
947951
pub(super) fn record_binding(
948952
&mut self,
949953
place: ScopedPlaceId,

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5010,11 +5010,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
50105010

50115011
/// Infer the type for a loop header definition.
50125012
///
5013-
/// The loop header sees all bindings that loop-back, either by reaching the end of the loop
5014-
/// body or a `continue` statement. This can include bindings from before the loop too, though
5015-
/// that's technically redundant, since the loop header definition itself doesn't shadow those
5016-
/// bindings. See `struct LoopHeader` in the semantic index for more on how all this fits
5017-
/// together.
5013+
/// The loop header sees all the bindings that originate in the loop and are visible at a
5014+
/// loop-back edge (either the end of the loop body or a `continue` statement). See `struct
5015+
/// LoopHeader` in the semantic index for more on how all this fits together.
50185016
fn infer_loop_header_definition(
50195017
&mut self,
50205018
loop_header_kind: &LoopHeaderDefinitionKind<'db>,

0 commit comments

Comments
 (0)