Skip to content

Commit eab1bb6

Browse files
committed
[ty] filter out pre-loop bindings from loop headers
1 parent dec65ad commit eab1bb6

4 files changed

Lines changed: 32 additions & 20 deletions

File tree

crates/ty_python_semantic/src/place.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,10 +1309,9 @@ fn place_from_bindings_impl<'db>(
13091309
loop_back.reachability_constraint,
13101310
));
13111311
}
1312-
// If UNBOUND is visible at loop-back, then it was visible before the loop.
1313-
// Loop header definitions don't shadow preexisting bindings, so we don't
1314-
// need to do anything with this.
1315-
DefinitionState::Undefined => {}
1312+
DefinitionState::Undefined => {
1313+
unreachable!("loop headers only include bindings from within the loop")
1314+
}
13161315
}
13171316
}
13181317
// If all the bindings in the loop are in statically false branches, it might be

crates/ty_python_semantic/src/semantic_index/builder.rs

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

842842
/// Create loop header definitions for all places that are bound within a loop. Return the
843-
/// `LoopToken` referenced by those definitions, and the set of bound place IDs.
843+
/// `LoopToken` referenced by those definitions, the set of bound place IDs, and the lower
844+
/// bound `ScopedDefinitionId` for definitions created within the loop.
844845
fn synthesize_loop_header_definitions(
845846
&mut self,
846847
loop_stmt: LoopStmtRef<'ast>,
847848
bound_places: Vec<PlaceExpr>,
848-
) -> (LoopToken<'db>, FxHashSet<ScopedPlaceId>) {
849+
) -> (LoopToken<'db>, FxHashSet<ScopedPlaceId>, ScopedDefinitionId) {
849850
let loop_token = LoopToken::new(self.db);
850851
let mut bound_place_ids: FxHashSet<ScopedPlaceId> = FxHashSet::default();
851852
for place_expr in bound_places {
@@ -860,7 +861,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
860861
self.push_additional_definition(place_id, loop_header_ref);
861862
}
862863
}
863-
(loop_token, bound_place_ids)
864+
let loop_min_definition_id = self.current_use_def_map_mut().next_definition_id();
865+
(loop_token, bound_place_ids, loop_min_definition_id)
864866
}
865867

866868
/// Build a `LoopHeader` that tracks all the variables bound in a loop, which will be visible
@@ -871,13 +873,18 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
871873
&mut self,
872874
loop_header_places: &FxHashSet<ScopedPlaceId>,
873875
loop_token: LoopToken<'db>,
876+
loop_min_definition_id: ScopedDefinitionId,
874877
) {
875878
let mut loop_header = LoopHeader::new();
876879
let use_def = self.current_use_def_map_mut();
877-
// Collect bindings.
880+
// Collect all the bindings within the loop that reached a loop back edge. Use the minimum
881+
// definition ID to filter out all the pre-loop bindings. The loop header doesn't shadow
882+
// them, so there's no need to duplicate them.
878883
for place_id in loop_header_places {
879884
for live_binding in use_def.loop_back_bindings(*place_id) {
880-
loop_header.add_binding(*place_id, live_binding);
885+
if live_binding.binding >= loop_min_definition_id {
886+
loop_header.add_binding(*place_id, live_binding);
887+
}
881888
}
882889
}
883890
// Mark the reachability and narrowing constraints as used.
@@ -2210,8 +2217,10 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
22102217

22112218
// Collect all the loop-back bindings (including the `continue` states we just
22122219
// merged) and populate the `LoopHeader`.
2213-
if let Some((loop_token, bound_place_ids)) = maybe_loop_header_info {
2214-
self.populate_loop_header(&bound_place_ids, loop_token);
2220+
if let Some((loop_token, bound_place_ids, loop_min_definition_id)) =
2221+
maybe_loop_header_info
2222+
{
2223+
self.populate_loop_header(&bound_place_ids, loop_token, loop_min_definition_id);
22152224
}
22162225

22172226
// We execute the `else` branch once the condition evaluates to false. This could
@@ -2318,8 +2327,10 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
23182327

23192328
// Collect all the loop-back bindings (including the `continue` states we just
23202329
// merged) and populate the `LoopHeader`.
2321-
if let Some((loop_token, bound_place_ids)) = maybe_loop_header_info {
2322-
self.populate_loop_header(&bound_place_ids, loop_token);
2330+
if let Some((loop_token, bound_place_ids, loop_min_definition_id)) =
2331+
maybe_loop_header_info
2332+
{
2333+
self.populate_loop_header(&bound_place_ids, loop_token, loop_min_definition_id);
23232334
}
23242335

23252336
// 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
@@ -937,6 +937,10 @@ impl<'db> UseDefMapBuilder<'db> {
937937
}
938938
}
939939

940+
pub(super) fn next_definition_id(&self) -> ScopedDefinitionId {
941+
self.all_definitions.next_index()
942+
}
943+
940944
pub(super) fn record_binding(
941945
&mut self,
942946
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
@@ -5009,11 +5009,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
50095009

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

0 commit comments

Comments
 (0)