Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cf90a36

Browse files
[ty] Omit retained unbound definition sentinel (#26348)
## Summary Stacked on #26019. We reserve `ScopedDefinitionId` zero for the implicit unbound or undeclared definition. The mutable use-def builder needs that sentinel while constructing its indexed flow state, but retaining an explicit `Undefined` entry in every frozen `UseDefMap` is unnecessary. This introduces a small retained-definition wrapper that omits the sentinel when freezing the map and synthesizes it for lookup and enumeration. Definition IDs and the builder representation remain unchanged. Coverage for an empty scope verifies that the implicit definition remains observable through both APIs.
1 parent 825509f commit cf90a36

1 file changed

Lines changed: 69 additions & 21 deletions

File tree

crates/ty_python_core/src/use_def.rs

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,10 @@
177177
//!
178178
//! There is another special kind of possible "definition" for a place: there might be a path from
179179
//! the scope entry to a given use in which the place is never bound. We model this with a special
180-
//! "unbound/undeclared" definition (a [`DefinitionState::Undefined`] entry at the start of the
181-
//! `all_definitions` vector). If that sentinel definition is present in the live bindings at a
182-
//! given use, it means that there is a possible path through control flow in which that place is
183-
//! unbound. Similarly, if that sentinel is present in the live declarations, it means that the
184-
//! place is (possibly) undeclared.
180+
//! "unbound/undeclared" definition at logical index zero. If that sentinel definition is present
181+
//! in the live bindings at a given use, it means that there is a possible path through control
182+
//! flow in which that place is unbound. Similarly, if that sentinel is present in the live
183+
//! declarations, it means that the place is (possibly) undeclared.
185184
//!
186185
//! To build a [`UseDefMap`], the [`UseDefMapBuilder`] is notified of each new use, definition, and
187186
//! constraint as they are encountered by the
@@ -245,7 +244,7 @@ use std::ops::Index;
245244
use std::rc::Rc;
246245
use std::sync::LazyLock;
247246

248-
use ruff_index::{FrozenIndexVec, Idx, IndexSlice, IndexVec, newtype_index};
247+
use ruff_index::{FrozenIndexVec, Idx, IndexVec, newtype_index};
249248
use ruff_text_size::TextRange;
250249
use rustc_hash::{FxBuildHasher, FxHashMap, FxHasher};
251250
use smallvec::SmallVec;
@@ -667,12 +666,66 @@ impl<'db> RetainedDefinitionState<'db> {
667666

668667
static_assertions::assert_eq_size!(RetainedDefinitionState<'static>, DefinitionState<'static>);
669668

669+
/// Retained definition states, excluding the implicit unbound definition at index zero.
670+
#[derive(Debug, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
671+
struct RetainedDefinitions<'db> {
672+
states: Box<[RetainedDefinitionState<'db>]>,
673+
}
674+
675+
impl<'db> RetainedDefinitions<'db> {
676+
fn new(
677+
states: IndexVec<ScopedDefinitionId, DefinitionState<'db>>,
678+
used: IndexVec<ScopedDefinitionId, bool>,
679+
) -> Self {
680+
let mut states = states.into_iter();
681+
let mut used = used.into_iter();
682+
683+
let unbound_state = states.next();
684+
let unbound_used = used.next();
685+
debug_assert_eq!(unbound_state, Some(DefinitionState::Undefined));
686+
debug_assert_eq!(unbound_used, Some(false));
687+
688+
Self {
689+
states: states
690+
.zip(used)
691+
.map(|(state, used)| RetainedDefinitionState::new(state, used))
692+
.collect(),
693+
}
694+
}
695+
696+
#[inline]
697+
fn get(&self, id: ScopedDefinitionId) -> RetainedDefinitionState<'db> {
698+
let index = id.index();
699+
if index == 0 {
700+
RetainedDefinitionState::Undefined
701+
} else {
702+
self.states[index - 1]
703+
}
704+
}
705+
706+
fn iter_enumerated(
707+
&self,
708+
) -> impl Iterator<Item = (ScopedDefinitionId, RetainedDefinitionState<'db>)> + '_ {
709+
std::iter::once((
710+
ScopedDefinitionId::UNBOUND,
711+
RetainedDefinitionState::Undefined,
712+
))
713+
.chain(
714+
self.states
715+
.iter()
716+
.copied()
717+
.enumerate()
718+
.map(|(index, state)| (ScopedDefinitionId::new(index + 1), state)),
719+
)
720+
}
721+
}
722+
670723
/// Applicable definitions and constraints for every use of a name.
671724
#[derive(Debug, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
672725
pub struct UseDefMap<'db> {
673-
/// Array of [`Definition`] in this scope. Only the first entry should be [`DefinitionState::Undefined`];
674-
/// this represents the implicit "unbound"/"undeclared" definition of every place.
675-
all_definitions: FrozenIndexVec<ScopedDefinitionId, RetainedDefinitionState<'db>>,
726+
/// Definition states in this scope, plus an implicit "unbound"/"undeclared" definition at
727+
/// index zero.
728+
all_definitions: RetainedDefinitions<'db>,
676729

677730
/// Constraint lookup tables, absent when all retained constraints are built-in terminal
678731
/// values that require no table lookup.
@@ -811,7 +864,7 @@ impl<'db> UseDefMap<'db> {
811864
) -> impl Iterator<Item = (ScopedDefinitionId, DefinitionState<'db>, bool)> + '_ {
812865
self.all_definitions
813866
.iter_enumerated()
814-
.map(|(id, &state)| (id, state.state(), state.is_used()))
867+
.map(|(id, state)| (id, state.state(), state.is_used()))
815868
}
816869

817870
pub fn bindings_at_use(&self, use_id: ScopedUseId) -> BindingWithConstraintsIterator<'_, 'db> {
@@ -872,7 +925,7 @@ impl<'db> UseDefMap<'db> {
872925
}
873926

874927
pub fn definition(&self, id: ScopedDefinitionId) -> DefinitionState<'db> {
875-
self.all_definitions[id].state()
928+
self.all_definitions.get(id).state()
876929
}
877930

878931
pub fn narrowing_evaluator(
@@ -1162,7 +1215,7 @@ type EnclosingSnapshots = IndexVec<ScopedEnclosingSnapshotId, EnclosingSnapshot>
11621215

11631216
#[derive(Clone, Debug)]
11641217
pub struct BindingWithConstraintsIterator<'map, 'db> {
1165-
all_definitions: &'map IndexSlice<ScopedDefinitionId, RetainedDefinitionState<'db>>,
1218+
all_definitions: &'map RetainedDefinitions<'db>,
11661219
constraint_tables: &'map ConstraintTables<'db>,
11671220
boundness_analysis: BoundnessAnalysis,
11681221
inner: LiveBindingsIterator<'map>,
@@ -1189,7 +1242,7 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
11891242
self.inner
11901243
.next()
11911244
.map(|live_binding| BindingWithConstraints {
1192-
binding: self.all_definitions[live_binding.binding()].state(),
1245+
binding: self.all_definitions.get(live_binding.binding()).state(),
11931246
binding_order: live_binding.binding(),
11941247
narrowing_constraint: NarrowingEvaluator {
11951248
constraint: live_binding.narrowing_constraint(),
@@ -1231,7 +1284,7 @@ impl<'map, 'db> NarrowingEvaluator<'map, 'db> {
12311284

12321285
#[derive(Clone)]
12331286
pub struct DeclarationsIterator<'map, 'db> {
1234-
all_definitions: &'map IndexSlice<ScopedDefinitionId, RetainedDefinitionState<'db>>,
1287+
all_definitions: &'map RetainedDefinitions<'db>,
12351288
constraint_tables: &'map ConstraintTables<'db>,
12361289
boundness_analysis: BoundnessAnalysis,
12371290
inner: LiveDeclarationsIterator<'map>,
@@ -1269,7 +1322,7 @@ impl<'db> Iterator for DeclarationsIterator<'_, 'db> {
12691322
reachability_constraint,
12701323
}| {
12711324
DeclarationWithConstraint {
1272-
declaration: self.all_definitions[*declaration].state(),
1325+
declaration: self.all_definitions.get(*declaration).state(),
12731326
declaration_order: *declaration,
12741327
reachability_constraint: *reachability_constraint,
12751328
}
@@ -2571,12 +2624,7 @@ impl<'db> UseDefMapBuilder<'db> {
25712624
narrowing_constraints,
25722625
})
25732626
});
2574-
let all_definitions = self
2575-
.all_definitions
2576-
.into_iter()
2577-
.zip(self.used_bindings)
2578-
.map(|(state, used)| RetainedDefinitionState::new(state, used))
2579-
.collect();
2627+
let all_definitions = RetainedDefinitions::new(self.all_definitions, self.used_bindings);
25802628

25812629
UseDefMap {
25822630
all_definitions,

0 commit comments

Comments
 (0)