Skip to main content

ty_python_core/
use_def.rs

1//! First, some terminology:
2//!
3//! * A "place" is semantically a location where a value can be read or written, and syntactically,
4//!   an expression that can be the target of an assignment, e.g. `x`, `x[0]`, `x.y`. (The term is
5//!   borrowed from Rust). In Python syntax, an expression like `f().x` is also allowed as the
6//!   target so it can be called a place, but we do not record declarations / bindings like `f().x:
7//!   int`, `f().x = ...`. Type checking itself can be done by recording only assignments to names,
8//!   but in order to perform type narrowing by attribute/subscript assignments, they must also be
9//!   recorded.
10//!
11//! * A "binding" gives a new value to a place. This includes many different Python statements
12//!   (assignment statements of course, but also imports, `def` and `class` statements, `as`
13//!   clauses in `with` and `except` statements, match patterns, and others) and even one
14//!   expression kind (named expressions). It notably does not include annotated assignment
15//!   statements without a right-hand side value; these do not assign any new value to the place.
16//!   We consider function parameters to be bindings as well, since (from the perspective of the
17//!   function's internal scope), a function parameter begins the scope bound to a value.
18//!
19//! * A "declaration" establishes an upper bound type for the values that a variable may be
20//!   permitted to take on. Annotated assignment statements (with or without an RHS value) are
21//!   declarations; annotated function parameters are also declarations. We consider `def` and
22//!   `class` statements to also be declarations, so as to prohibit accidentally shadowing them.
23//!
24//! Annotated assignments with a right-hand side, and annotated function parameters, are both
25//! bindings and declarations.
26//!
27//! We use [`Definition`] as the universal term (and Salsa tracked struct) encompassing both
28//! bindings and declarations. (This sacrifices a bit of type safety in exchange for improved
29//! performance via fewer Salsa tracked structs and queries, since most declarations -- typed
30//! parameters and annotated assignments with RHS -- are both bindings and declarations.)
31//!
32//! At any given use of a variable, we can ask about both its "declared type" and its "inferred
33//! type". These may be different, but the inferred type must always be assignable to the declared
34//! type; that is, the declared type is always wider, and the inferred type may be more precise. If
35//! we see an invalid assignment, we emit a diagnostic and abandon our inferred type, deferring to
36//! the declared type (this allows an explicit annotation to override bad inference, without a
37//! cast), maintaining the invariant.
38//!
39//! The **inferred type** represents the most precise type we believe encompasses all possible
40//! values for the variable at a given use. It is based on a union of the bindings which can reach
41//! that use through some control flow path, and the narrowing constraints that control flow must
42//! have passed through between the binding and the use. For example, in this code:
43//!
44//! ```python
45//! x = 1 if flag else None
46//! if x is not None:
47//!     use(x)
48//! ```
49//!
50//! For the use of `x` on the third line, the inferred type should be `Literal[1]`. This is based
51//! on the binding on the first line, which assigns the type `Literal[1] | None`, and the narrowing
52//! constraint on the second line, which rules out the type `None`, since control flow must pass
53//! through this constraint to reach the use in question.
54//!
55//! The **declared type** represents the code author's declaration (usually through a type
56//! annotation) that a given variable should not be assigned any type outside the declared type. In
57//! our model, declared types are also control-flow-sensitive; we allow the code author to
58//! explicitly redeclare the same variable with a different type. So for a given binding of a
59//! variable, we will want to ask which declarations of that variable can reach that binding, in
60//! order to determine whether the binding is permitted, or should be a type error. For example:
61//!
62//! ```python
63//! from pathlib import Path
64//! def f(path: str):
65//!     path: Path = Path(path)
66//! ```
67//!
68//! In this function, the initial declared type of `path` is `str`, meaning that the assignment
69//! `path = Path(path)` would be a type error, since it assigns to `path` a value whose type is not
70//! assignable to `str`. This is the purpose of declared types: they prevent accidental assignment
71//! of the wrong type to a variable.
72//!
73//! But in some cases it is useful to "shadow" or "redeclare" a variable with a new type, and we
74//! permit this, as long as it is done with an explicit re-annotation. So `path: Path =
75//! Path(path)`, with the explicit `: Path` annotation, is permitted.
76//!
77//! The general rule is that whatever declaration(s) can reach a given binding determine the
78//! validity of that binding. If there is a path in which the place is not declared, that is a
79//! declaration of `Unknown`. If multiple declarations can reach a binding, we union them, but by
80//! default we also issue a type error, since this implicit union of declared types may hide an
81//! error.
82//!
83//! To support type inference, we build a map from each use of a place to the bindings live at
84//! that use, and the type narrowing constraints that apply to each binding.
85//!
86//! Let's take this code sample:
87//!
88//! ```python
89//! x = 1
90//! x = 2
91//! y = x
92//! if flag:
93//!     x = 3
94//! else:
95//!     x = 4
96//! z = x
97//! ```
98//!
99//! In this snippet, we have four bindings of `x` (the statements assigning `1`, `2`, `3`, and `4`
100//! to it), and two uses of `x` (the `y = x` and `z = x` assignments). The first binding of `x`
101//! does not reach any use, because it's immediately replaced by the second binding, before any use
102//! happens. (A linter could thus flag the statement `x = 1` as likely superfluous.)
103//!
104//! The first use of `x` has one live binding: the assignment `x = 2`.
105//!
106//! Things get a bit more complex when we have branches. We will definitely take either the `if` or
107//! the `else` branch. Thus, the second use of `x` has two live bindings: `x = 3` and `x = 4`. The
108//! `x = 2` assignment is no longer visible, because it must be replaced by either `x = 3` or `x =
109//! 4`, no matter which branch was taken. We don't know which branch was taken, so we must consider
110//! both bindings as live, which means eventually we would (in type inference) look at these two
111//! bindings and infer a type of `Literal[3, 4]` -- the union of `Literal[3]` and `Literal[4]` --
112//! for the second use of `x`.
113//!
114//! So that's one question our use-def map needs to answer: given a specific use of a place, which
115//! binding(s) can reach that use. In [`crate::ast_ids::AstIds`] we number
116//! all uses (that means a `Name`/`ExprAttribute`/`ExprSubscript` node with `Load` context)
117//! so we have a `ScopedUseId` to efficiently represent each use.
118//!
119//! We also need to know, for a given definition of a place, what type narrowing constraints apply
120//! to it. For instance, in this code sample:
121//!
122//! ```python
123//! x = 1 if flag else None
124//! if x is not None:
125//!     use(x)
126//! ```
127//!
128//! At the use of `x`, the live binding of `x` is `1 if flag else None`, which would infer as the
129//! type `Literal[1] | None`. But the constraint `x is not None` dominates this use, which means we
130//! can rule out the possibility that `x` is `None` here, which should give us the type
131//! `Literal[1]` for this use.
132//!
133//! For declared types, we need to be able to answer the question "given a binding to a place,
134//! which declarations of that place can reach the binding?" This allows us to emit a diagnostic
135//! if the binding is attempting to bind a value of a type that is not assignable to the declared
136//! type for that place, at that point in control flow.
137//!
138//! We also need to know, given a declaration of a place, what the inferred type of that place is
139//! at that point. This allows us to emit a diagnostic in a case like `x = "foo"; x: int`. The
140//! binding `x = "foo"` occurs before the declaration `x: int`, so according to our
141//! control-flow-sensitive interpretation of declarations, the assignment is not an error. But the
142//! declaration is an error, since it would violate the "inferred type must be assignable to
143//! declared type" rule.
144//!
145//! Another case we need to handle is when a place is referenced from a different scope (for
146//! example, an import or a nonlocal reference). We call this "public" use of a place. For public
147//! use of a place, we prefer the declared type, if there are any declarations of that place; if
148//! not, we fall back to the inferred type. So we also need to know which declarations and bindings
149//! can reach the end of the scope.
150//!
151//! Technically, public use of a place could occur from any point in control flow of the scope
152//! where the place is defined (via inline imports and import cycles, in the case of an import, or
153//! via a function call partway through the local scope that ends up using a place from the scope
154//! via a global or nonlocal reference.) But modeling this fully accurately requires whole-program
155//! analysis that isn't tractable for an efficient analysis, since it means a given place could
156//! have a different type every place it's referenced throughout the program, depending on the
157//! shape of arbitrarily-sized call/import graphs. So we follow other Python type checkers in
158//! making the simplifying assumption that usually the scope will finish execution before its
159//! places are made visible to other scopes; for instance, most imports will import from a
160//! complete module, not a partially-executed module. (We may want to get a little smarter than
161//! this in the future for some closures, but for now this is where we start.)
162//!
163//! The data structure we build to answer these questions is the `UseDefMap`. It has a
164//! `bindings_by_use` vector of [`InternedBindingsId`] indexed by [`ScopedUseId`]
165//! (plus an interned bindings table), a
166//! `definitions_by_definition` map of [`DefinitionsAtDefinition`], and `symbol_states` and
167//! `member_states` vectors indexed by [`ScopedSymbolId`]/[`ScopedMemberId`]. The values are (in
168//! principle) a list of live bindings at that use/definition, or at the end of the scope for that
169//! place, with a list of the dominating constraints for each binding.
170//!
171//! In order to avoid vectors-of-vectors-of-vectors and all the allocations that would entail, we
172//! don't actually store these "list of visible definitions" as a vector of [`Definition`].
173//! Instead, [`Bindings`] and [`Declarations`] are structs which use bit-sets to track
174//! definitions (and constraints, in the case of bindings) in terms of [`ScopedDefinitionId`] and
175//! [`ScopedPredicateId`], which are indices into the `all_definitions` and `predicates`
176//! indexvecs in the [`UseDefMap`].
177//!
178//! There is another special kind of possible "definition" for a place: there might be a path from
179//! the scope entry to a given use in which the place is never bound. We model this with a special
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.
184//!
185//! To build a [`UseDefMap`], the [`UseDefMapBuilder`] is notified of each new use, definition, and
186//! constraint as they are encountered by the
187//! [`crate::builder::SemanticIndexBuilder`] AST visit. For
188//! each place, the builder tracks the `PlaceState` (`Bindings` and `Declarations`) for that place.
189//! When we hit a use or definition of a place, we record the necessary parts of the current state
190//! for that place that we need for that use or definition. When we reach the end of the scope, it
191//! records the state for each place as the public definitions of that place.
192//!
193//! ```python
194//! x = 1
195//! x = 2
196//! y = x
197//! if flag:
198//!     x = 3
199//! else:
200//!     x = 4
201//! z = x
202//! ```
203//!
204//! Let's walk through the above example. Initially we do not have any record of `x`. When we add
205//! the new place (before we process the first binding), we create a new undefined `PlaceState`
206//! which has a single live binding (the "unbound" definition) and a single live declaration (the
207//! "undeclared" definition). When we see `x = 1`, we record that as the sole live binding of `x`.
208//! The "unbound" binding is no longer visible. Then we see `x = 2`, and we replace `x = 1` as the
209//! sole live binding of `x`. When we get to `y = x`, we record that the live bindings for that use
210//! of `x` are just the `x = 2` definition.
211//!
212//! Then we hit the `if` branch. We visit the `test` node (`flag` in this case), since that will
213//! happen regardless. Then we take a pre-branch snapshot of the current state for all places,
214//! which we'll need later. Then we record `flag` as a possible constraint on the current binding
215//! (`x = 2`), and go ahead and visit the `if` body. When we see `x = 3`, it replaces `x = 2`
216//! (constrained by `flag`) as the sole live binding of `x`. At the end of the `if` body, we take
217//! another snapshot of the current place state; we'll call this the post-if-body snapshot.
218//!
219//! Now we need to visit the `else` clause. The conditions when entering the `else` clause should
220//! be the pre-if conditions; if we are entering the `else` clause, we know that the `if` test
221//! failed and we didn't execute the `if` body. So we first reset the builder to the pre-if state,
222//! using the snapshot we took previously (meaning we now have `x = 2` as the sole binding for `x`
223//! again), and record a *negative* `flag` constraint for all live bindings (`x = 2`). We then
224//! visit the `else` clause, where `x = 4` replaces `x = 2` as the sole live binding of `x`.
225//!
226//! Now we reach the end of the if/else, and want to visit the following code. The state here needs
227//! to reflect that we might have gone through the `if` branch, or we might have gone through the
228//! `else` branch, and we don't know which. So we need to "merge" our current builder state
229//! (reflecting the end-of-else state, with `x = 4` as the only live binding) with our post-if-body
230//! snapshot (which has `x = 3` as the only live binding). The result of this merge is that we now
231//! have two live bindings of `x`: `x = 3` and `x = 4`.
232//!
233//! Another piece of information that the `UseDefMap` needs to provide are reachability constraints.
234//! See `reachability_constraints.rs` for more details, in particular how they apply to bindings.
235//!
236//! The [`UseDefMapBuilder`] itself just exposes methods for taking a snapshot, resetting to a
237//! snapshot, and merging a snapshot into the current state. The logic using these methods lives in
238//! [`SemanticIndexBuilder`](crate::builder::SemanticIndexBuilder), e.g. where it
239//! visits a `StmtIf` node.
240
241use std::collections::hash_map::Entry;
242use std::hash::{Hash as _, Hasher as _};
243use std::ops::Index;
244use std::rc::Rc;
245use std::sync::LazyLock;
246
247use ruff_index::{FrozenIndexVec, Idx, IndexVec, newtype_index};
248use ruff_text_size::TextRange;
249use rustc_hash::{FxBuildHasher, FxHashMap, FxHasher};
250use smallvec::SmallVec;
251use thin_vec::ThinVec;
252
253use crate::ast_ids::ScopedUseId;
254use crate::definition::{Definition, DefinitionCategory, DefinitionState};
255use crate::frozen::FrozenMap;
256use crate::member::ScopedMemberId;
257use crate::narrowing_constraints::{
258    ConstraintKey, NarrowingConstraints, NarrowingConstraintsBuilder, ScopedNarrowingConstraint,
259};
260use crate::place::{PlaceExprRef, ScopedPlaceId};
261use crate::predicate::{PredicateOrLiteral, Predicates, PredicatesBuilder, ScopedPredicateId};
262use crate::reachability_constraints::{
263    ReachabilityConstraints, ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId,
264};
265use crate::scope::{FileScopeId, ScopeKind, ScopeLaziness};
266use crate::symbol::ScopedSymbolId;
267use crate::use_def::place_state::{
268    Bindings, Declarations, EnclosingSnapshot, LiveBindingsIterator, LiveDeclaration,
269    LiveDeclarationsIterator, PlaceState,
270};
271use crate::{
272    BoundnessAnalysis, EnclosingSnapshotResult, LoopHeader, PossiblyNarrowedPlaces, SemanticIndex,
273};
274
275mod exception_checkpoint;
276mod place_state;
277
278pub(super) use exception_checkpoint::ExceptionCheckpointKey;
279use exception_checkpoint::{ExceptionCheckpointSnapshot, ExceptionCheckpointState};
280pub use place_state::LiveBinding;
281pub use place_state::ScopedDefinitionId;
282pub(super) use place_state::{FutureDefinitions, PreviousDefinitions};
283
284/// Summarizes whether the live control-flow paths leave a symbol bound.
285#[derive(Copy, Clone, Debug, Eq, PartialEq)]
286pub(super) enum LiveBindingStatus {
287    /// No live path contains a binding.
288    Unbound,
289    /// Some live paths contain a binding and others leave the symbol unbound.
290    PossiblyBound,
291    /// Every live path contains a binding.
292    Bound,
293}
294
295/// Identifies a [`LoopHeader`] within a single scope's [`UseDefMap`].
296#[newtype_index]
297#[derive(get_size2::GetSize)]
298pub struct LoopHeaderId;
299
300/// Uniquely identifies an interned [`Bindings`] entry in [`UseDefMap::interned_bindings`].
301#[newtype_index]
302#[derive(get_size2::GetSize, salsa::SalsaValue)]
303struct InternedBindingsId;
304
305/// Uniquely identifies an interned [`Declarations`] entry in [`UseDefMap::interned_declarations`].
306#[newtype_index]
307#[derive(get_size2::GetSize, salsa::SalsaValue)]
308struct InternedDeclarationsId;
309
310#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
311struct InternedPlaceStateId(InternedBindingsId, InternedDeclarationsId);
312
313impl InternedPlaceStateId {
314    fn bindings_id(self) -> InternedBindingsId {
315        self.0
316    }
317
318    fn declarations_id(self) -> InternedDeclarationsId {
319        self.1
320    }
321}
322
323struct PlaceStateInterner {
324    interned_bindings: RetainedBindingsBuilder,
325    interned_ids_by_bindings: hashbrown::HashTable<InternedBindingsId>,
326    interned_declarations: RetainedDeclarationsBuilder,
327    interned_ids_by_declarations: FxHashMap<Declarations, InternedDeclarationsId>,
328    // Undeclared states are common and can be interned by their dense constraint IDs.
329    undeclared_declarations_by_constraint:
330        IndexVec<ScopedReachabilityConstraintId, Option<InternedDeclarationsId>>,
331    // These values are extremely common, so avoid repeatedly hashing their small vectors.
332    always_unbound_bindings: Option<InternedBindingsId>,
333    always_undeclared_declarations: Option<InternedDeclarationsId>,
334}
335
336impl PlaceStateInterner {
337    fn with_capacity(bindings: usize, declaration_map: usize, declarations: usize) -> Self {
338        Self {
339            interned_bindings: RetainedBindingsBuilder::with_capacity(bindings),
340            interned_ids_by_bindings: hashbrown::HashTable::with_capacity(bindings),
341            interned_declarations: RetainedDeclarationsBuilder::with_capacity(declarations),
342            interned_ids_by_declarations: FxHashMap::with_capacity_and_hasher(
343                declaration_map,
344                FxBuildHasher,
345            ),
346            undeclared_declarations_by_constraint: IndexVec::new(),
347            always_unbound_bindings: None,
348            always_undeclared_declarations: None,
349        }
350    }
351
352    fn intern_bindings(&mut self, bindings: &Bindings) -> InternedBindingsId {
353        if bindings.is_always_unbound() {
354            if let Some(interned_id) = self.always_unbound_bindings {
355                return interned_id;
356            }
357
358            let interned_id = self.interned_bindings.push(bindings);
359            self.always_unbound_bindings = Some(interned_id);
360            return interned_id;
361        }
362
363        // The retained representation discards the unbound narrowing constraint, so it isn't
364        // part of the interned identity.
365        let hash = Self::hash_bindings(bindings.as_slice());
366        let interned_bindings = &mut self.interned_bindings;
367        let entry = self.interned_ids_by_bindings.entry(
368            hash,
369            |id| interned_bindings.get(*id) == bindings.as_slice(),
370            |id| Self::hash_bindings(interned_bindings.get(*id)),
371        );
372        match entry {
373            hashbrown::hash_table::Entry::Occupied(entry) => *entry.get(),
374            hashbrown::hash_table::Entry::Vacant(entry) => {
375                let interned_id = interned_bindings.push(bindings);
376                entry.insert(interned_id);
377                interned_id
378            }
379        }
380    }
381
382    fn hash_bindings(live_bindings: &[LiveBinding]) -> u64 {
383        let mut hasher = FxHasher::default();
384        live_bindings.hash(&mut hasher);
385        hasher.finish()
386    }
387
388    fn intern_declarations(&mut self, declarations: Declarations) -> InternedDeclarationsId {
389        if declarations.is_always_undeclared() {
390            if let Some(interned_id) = self.always_undeclared_declarations {
391                return interned_id;
392            }
393
394            let interned_id = self.interned_declarations.push(&declarations);
395            self.always_undeclared_declarations = Some(interned_id);
396            return interned_id;
397        }
398
399        if let Some(reachability_constraint) = declarations.undeclared_reachability_constraint()
400            && !reachability_constraint.is_terminal()
401        {
402            let index = reachability_constraint.index();
403            let len = self.undeclared_declarations_by_constraint.len();
404            if index >= len {
405                self.undeclared_declarations_by_constraint
406                    .resize(index + 1, None);
407            } else if let Some(interned_id) =
408                self.undeclared_declarations_by_constraint[reachability_constraint]
409            {
410                return interned_id;
411            }
412
413            let interned_id = self.interned_declarations.push(&declarations);
414            self.undeclared_declarations_by_constraint[reachability_constraint] = Some(interned_id);
415            return interned_id;
416        }
417
418        match self.interned_ids_by_declarations.entry(declarations) {
419            Entry::Occupied(entry) => *entry.get(),
420            Entry::Vacant(entry) => {
421                let interned_id = self.interned_declarations.push(entry.key());
422                entry.insert(interned_id);
423                interned_id
424            }
425        }
426    }
427
428    fn intern_place_state(
429        &mut self,
430        bindings: &Bindings,
431        declarations: Declarations,
432    ) -> InternedPlaceStateId {
433        InternedPlaceStateId(
434            self.intern_bindings(bindings),
435            self.intern_declarations(declarations),
436        )
437    }
438
439    fn retain_place_state(
440        &mut self,
441        bindings: &Bindings,
442        declarations: Declarations,
443    ) -> InternedPlaceStateId {
444        // Other retained declarations rarely repeat. Keep the compact IDs without hashing every
445        // declaration vector to find the occasional duplicate.
446        let declarations_id = if declarations.undeclared_reachability_constraint().is_some() {
447            self.intern_declarations(declarations)
448        } else {
449            self.interned_declarations.push(&declarations)
450        };
451        InternedPlaceStateId(self.intern_bindings(bindings), declarations_id)
452    }
453}
454
455/// Compact, retained representation of the interned binding vectors for a scope.
456///
457/// The builder needs a `SmallVec` and an optional unbound constraint while constructing each
458/// binding state. Neither is needed after the semantic index is built, so the retained map stores
459/// cumulative end offsets into one contiguous array instead.
460#[derive(Debug, PartialEq, Eq, get_size2::GetSize)]
461struct RetainedBindings {
462    ends: FrozenIndexVec<InternedBindingsId, u32>,
463    live_bindings: Box<[LiveBinding]>,
464}
465
466struct RetainedBindingsBuilder {
467    ends: IndexVec<InternedBindingsId, u32>,
468    live_bindings: Vec<LiveBinding>,
469}
470
471impl RetainedBindingsBuilder {
472    fn with_capacity(bindings: usize) -> Self {
473        Self {
474            ends: IndexVec::with_capacity(bindings),
475            live_bindings: Vec::with_capacity(bindings),
476        }
477    }
478
479    fn push(&mut self, bindings: &Bindings) -> InternedBindingsId {
480        // Definition IDs are also 32-bit and a single scope cannot practically approach this
481        // limit. Keeping one cumulative end offset per state halves the retained range metadata.
482        self.live_bindings.extend_from_slice(bindings.as_slice());
483        let end = u32::try_from(self.live_bindings.len())
484            .expect("Expected live-bindings length to fit into a u32");
485        self.ends.push(end)
486    }
487
488    fn get(&self, index: InternedBindingsId) -> &[LiveBinding] {
489        let end = self.ends[index];
490        let start = if index.index() == 0 {
491            0
492        } else {
493            self.ends[InternedBindingsId::new(index.index() - 1)]
494        };
495        &self.live_bindings[start as usize..end as usize]
496    }
497
498    fn finish(
499        self,
500        narrowing_constraints: &mut NarrowingConstraintsBuilder,
501        reachability_constraints: &mut ReachabilityConstraintsBuilder,
502    ) -> RetainedBindings {
503        for binding in &self.live_bindings {
504            reachability_constraints.mark_used(binding.reachability_constraint());
505            narrowing_constraints.mark_used(binding.narrowing_constraint());
506        }
507        RetainedBindings {
508            ends: self.ends.into(),
509            live_bindings: self.live_bindings.into_boxed_slice(),
510        }
511    }
512}
513
514impl Index<InternedBindingsId> for RetainedBindings {
515    type Output = [LiveBinding];
516
517    fn index(&self, index: InternedBindingsId) -> &Self::Output {
518        let end = self.ends[index];
519        let start = if index.index() == 0 {
520            0
521        } else {
522            self.ends[InternedBindingsId::new(index.index() - 1)]
523        };
524        &self.live_bindings[start as usize..end as usize]
525    }
526}
527
528/// Compact, retained representation of the interned declaration vectors for a scope.
529#[derive(Debug, PartialEq, Eq, get_size2::GetSize)]
530struct RetainedDeclarations {
531    /// The exclusive end of each state in `live_declarations`; its start is the previous end.
532    ends: FrozenIndexVec<InternedDeclarationsId, u32>,
533    live_declarations: Box<[LiveDeclaration]>,
534}
535
536struct RetainedDeclarationsBuilder {
537    ends: IndexVec<InternedDeclarationsId, u32>,
538    live_declarations: Vec<LiveDeclaration>,
539}
540
541impl RetainedDeclarationsBuilder {
542    fn with_capacity(declarations: usize) -> Self {
543        Self {
544            ends: IndexVec::with_capacity(declarations),
545            live_declarations: Vec::with_capacity(declarations),
546        }
547    }
548
549    fn push(&mut self, declarations: &Declarations) -> InternedDeclarationsId {
550        self.live_declarations.extend(declarations.iter().cloned());
551        let end = u32::try_from(self.live_declarations.len())
552            .expect("Expected live-declarations length to fit into a u32");
553        self.ends.push(end)
554    }
555
556    fn finish(
557        self,
558        reachability_constraints: &mut ReachabilityConstraintsBuilder,
559    ) -> RetainedDeclarations {
560        for declaration in &self.live_declarations {
561            reachability_constraints.mark_used(declaration.reachability_constraint);
562        }
563        RetainedDeclarations {
564            ends: self.ends.into(),
565            live_declarations: self.live_declarations.into_boxed_slice(),
566        }
567    }
568}
569
570impl Index<InternedDeclarationsId> for RetainedDeclarations {
571    type Output = [LiveDeclaration];
572
573    fn index(&self, index: InternedDeclarationsId) -> &Self::Output {
574        let end = self.ends[index];
575        let start = if index.index() == 0 {
576            0
577        } else {
578            self.ends[InternedDeclarationsId::new(index.index() - 1)]
579        };
580        &self.live_declarations[start as usize..end as usize]
581    }
582}
583
584#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
585struct RetainedPlaceStates<T> {
586    end_of_scope: T,
587    reachable: T,
588}
589
590#[derive(Debug, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
591struct DefinitionsAtDefinition<B, D> {
592    bindings: B,
593    declarations: Option<D>,
594}
595
596#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
597enum InternedEnclosingSnapshotId {
598    Constraint(ScopedNarrowingConstraint),
599    Bindings(InternedBindingsId),
600}
601
602/// Lookup tables needed to evaluate reachability and narrowing constraints.
603#[derive(Debug, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
604struct ConstraintTables<'db> {
605    predicates: Predicates<'db>,
606    predicate_narrowing_targets: PredicateNarrowingTargets,
607    reachability_constraints: ReachabilityConstraints,
608    narrowing_constraints: NarrowingConstraints,
609}
610
611/// Predicate-place pairs for which type narrowing may produce a constraint.
612///
613/// Reachability gates can contain predicates that are unrelated to the place being narrowed.
614/// Keeping the conservative targets computed while building the semantic index lets type
615/// inference skip constructing those predicates' full narrowing maps.
616#[derive(Debug, Default, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
617pub struct PredicateNarrowingTargets(Box<[(ScopedPredicateId, ScopedPlaceId)]>);
618
619impl PredicateNarrowingTargets {
620    fn from_entries(mut entries: Vec<(ScopedPredicateId, ScopedPlaceId)>) -> Self {
621        entries.sort_unstable_by_key(|&(predicate, place)| (place, predicate));
622        entries.dedup();
623
624        Self(entries.into_boxed_slice())
625    }
626
627    /// Returns whether `predicate` may narrow `place`.
628    pub fn contains(&self, predicate: ScopedPredicateId, place: ScopedPlaceId) -> bool {
629        self.0
630            .binary_search_by_key(&(place, predicate), |&(predicate, place)| {
631                (place, predicate)
632            })
633            .is_ok()
634    }
635
636    /// Returns whether any predicate may narrow `place`.
637    pub fn contains_place(&self, place: ScopedPlaceId) -> bool {
638        self.0
639            .binary_search_by_key(&place, |&(_, target)| target)
640            .is_ok()
641    }
642}
643
644/// Fields that are empty in most use-def maps.
645///
646/// These fields share an allocation to avoid storing five collection headers in every
647/// [`UseDefMap`]. They are not otherwise semantically related.
648#[derive(Debug, PartialEq, Eq, get_size2::GetSize)]
649struct UseDefMapExtra {
650    /// [`Bindings`] reaching a [`ScopedUseId`].
651    bindings_by_use: FrozenIndexVec<ScopedUseId, InternedBindingsId>,
652
653    /// [`Bindings`] for each member reaching a [`ScopedUseId`].
654    ///
655    /// This is only used for kwargs expressions, whose corresponding `bindings_by_use` entry
656    /// is empty.
657    multi_bindings_by_use: MultiBindingsByUse,
658
659    /// Retained [`PlaceState`] values for each member.
660    member_states: FrozenIndexVec<ScopedMemberId, RetainedPlaceStates<InternedPlaceStateId>>,
661
662    /// Snapshots of bindings used to resolve references from nested scopes.
663    enclosing_snapshots: FrozenIndexVec<ScopedEnclosingSnapshotId, InternedEnclosingSnapshotId>,
664
665    /// Completed loop headers in this scope.
666    loop_headers: FrozenIndexVec<LoopHeaderId, LoopHeader>,
667}
668
669static EMPTY_CONSTRAINT_TABLES: LazyLock<ConstraintTables<'static>> =
670    LazyLock::new(|| ConstraintTables {
671        predicates: IndexVec::new().into(),
672        predicate_narrowing_targets: PredicateNarrowingTargets::default(),
673        reachability_constraints: ReachabilityConstraintsBuilder::default().build(),
674        narrowing_constraints: NarrowingConstraintsBuilder::default().build(),
675    });
676
677static ALWAYS_UNBOUND_BINDINGS: LazyLock<Bindings> =
678    LazyLock::new(|| Bindings::unbound(ScopedReachabilityConstraintId::ALWAYS_TRUE));
679
680static ALWAYS_UNDECLARED_DECLARATIONS: LazyLock<Declarations> =
681    LazyLock::new(|| Declarations::undeclared(ScopedReachabilityConstraintId::ALWAYS_TRUE));
682
683/// One event in a scope's use-def history.
684#[derive(Clone, Copy, Debug, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
685enum DefinitionEntry<'db> {
686    /// The early declaration of a combined definition whose binding is recorded separately.
687    /// It participates in declaration lookup, but not in binding-usage analysis.
688    DeclarationPart(Definition<'db>),
689    /// A binding or standalone declaration with no recorded use.
690    Unused(Definition<'db>),
691    Used(Definition<'db>),
692    Undefined,
693    Deleted,
694}
695
696impl<'db> DefinitionEntry<'db> {
697    fn state(self) -> DefinitionState<'db> {
698        match self {
699            Self::DeclarationPart(definition)
700            | Self::Unused(definition)
701            | Self::Used(definition) => DefinitionState::Defined(definition),
702            Self::Undefined => DefinitionState::Undefined,
703            Self::Deleted => DefinitionState::Deleted,
704        }
705    }
706}
707
708static_assertions::assert_eq_size!(DefinitionEntry<'static>, DefinitionState<'static>);
709
710/// Retained definition states, excluding the implicit unbound definition at index zero.
711#[derive(Debug, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
712struct RetainedDefinitions<'db> {
713    states: Box<[DefinitionEntry<'db>]>,
714}
715
716impl<'db> RetainedDefinitions<'db> {
717    fn new(states: IndexVec<ScopedDefinitionId, DefinitionEntry<'db>>) -> Self {
718        let mut states = states.into_iter();
719
720        let unbound_state = states.next();
721        debug_assert_eq!(unbound_state, Some(DefinitionEntry::Undefined));
722
723        Self {
724            states: states.collect(),
725        }
726    }
727
728    #[inline]
729    fn get(&self, id: ScopedDefinitionId) -> DefinitionEntry<'db> {
730        let index = id.index();
731        if index == 0 {
732            DefinitionEntry::Undefined
733        } else {
734            self.states[index - 1]
735        }
736    }
737
738    fn iter_enumerated(
739        &self,
740    ) -> impl Iterator<Item = (ScopedDefinitionId, DefinitionEntry<'db>)> + '_ {
741        self.states
742            .iter()
743            .copied()
744            .enumerate()
745            .map(|(index, entry)| (ScopedDefinitionId::new(index + 1), entry))
746    }
747}
748
749/// Applicable definitions and constraints for every use of a name.
750#[derive(Debug, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
751pub struct UseDefMap<'db> {
752    /// Definition states in this scope, plus an implicit "unbound"/"undeclared" definition at
753    /// index zero.
754    all_definitions: RetainedDefinitions<'db>,
755
756    /// Constraint lookup tables, absent when all retained constraints are built-in terminal
757    /// values that require no table lookup.
758    constraint_tables: Option<Box<ConstraintTables<'db>>>,
759
760    /// Interned [`Bindings`] values.
761    interned_bindings: RetainedBindings,
762    /// Interned [`Declarations`] values.
763    interned_declarations: RetainedDeclarations,
764
765    /// Tracks the reachability constraint for statements and certain sub-expressions
766    /// (e.g. ternary branches, boolean operator operands), keyed by their text range.
767    /// Used to suppress diagnostics in unreachable code.
768    range_reachability: Box<[(TextRange, RangeInfo)]>,
769
770    /// If the definition is a binding (only) -- `x = 1` for example -- then we need
771    /// [`Declarations`] to know whether this binding is permitted by the live declarations.
772    ///
773    /// If the definition is both a declaration and a binding -- `x: int = 1` for example -- then
774    /// we don't actually need anything here, all we'll need to validate is that our own RHS is a
775    /// valid assignment to our own annotation.
776    ///
777    /// If the definition is a declaration (only) -- `x: int` for example -- then we need
778    /// [`Bindings`] to know whether this declaration is consistent with the previously
779    /// inferred type.
780    ///
781    /// If we see a binding to a `Final`-qualified symbol, we also need the bindings to find
782    /// previous bindings to that symbol. If there are any, the assignment is invalid.
783    ///
784    /// Entries whose prior state is the start-of-scope default (always unbound and, if present,
785    /// always undeclared) are omitted. Lookups use [`ALWAYS_UNBOUND_BINDINGS`] and
786    /// [`ALWAYS_UNDECLARED_DECLARATIONS`], which are initialized lazily and shared by every map.
787    definitions_by_definition: FrozenMap<
788        Definition<'db>,
789        DefinitionsAtDefinition<InternedBindingsId, InternedDeclarationsId>,
790    >,
791
792    /// Retained [`PlaceState`] values for each symbol.
793    symbol_states: FrozenIndexVec<ScopedSymbolId, RetainedPlaceStates<InternedPlaceStateId>>,
794
795    /// Collection fields omitted when they would all be empty.
796    extra: Option<Box<UseDefMapExtra>>,
797
798    /// Whether or not the end of the scope is reachable.
799    ///
800    /// This is used to check if the function can implicitly return `None`.
801    /// For example:
802    /// ```py
803    /// def f(cond: bool) -> int | None:
804    ///     if cond:
805    ///        return 1
806    ///
807    /// def g() -> int:
808    ///     if True:
809    ///        return 1
810    /// ```
811    ///
812    /// Function `f` may implicitly return `None`, but `g` cannot.
813    ///
814    /// This is used by `can_implicitly_return_none` in the `ty_python_semantic` crate.
815    end_of_scope_reachability: ScopedReachabilityConstraintId,
816}
817
818/// Information about a given range of source code.
819#[derive(Debug, Copy, Clone, PartialEq, Eq, get_size2::GetSize)]
820struct RangeInfo {
821    reachability: ScopedReachabilityConstraintId,
822    in_type_checking_block: bool,
823}
824
825impl Default for RangeInfo {
826    fn default() -> Self {
827        Self {
828            reachability: ScopedReachabilityConstraintId::ALWAYS_TRUE,
829            in_type_checking_block: false,
830        }
831    }
832}
833
834#[derive(Debug, PartialEq, Eq, get_size2::GetSize)]
835struct MultiBindingsByUse(ThinVec<(ScopedUseId, Box<[Bindings]>)>);
836
837impl MultiBindingsByUse {
838    fn from_map(map: FxHashMap<ScopedUseId, Vec<Bindings>>) -> Self {
839        let mut entries = map
840            .into_iter()
841            .map(|(use_id, bindings)| (use_id, bindings.into_boxed_slice()))
842            .collect::<Vec<_>>();
843        entries.sort_unstable_by_key(|(use_id, _)| *use_id);
844        Self(entries.into_iter().collect())
845    }
846
847    fn get(&self, use_id: ScopedUseId) -> Option<&[Bindings]> {
848        self.0
849            .binary_search_by_key(&use_id, |(candidate, _)| *candidate)
850            .ok()
851            .map(|index| self.0[index].1.as_ref())
852    }
853}
854
855pub enum ApplicableConstraints<'map, 'db> {
856    UnboundBinding(NarrowingEvaluator<'map, 'db>),
857    ConstrainedBindings(BindingWithConstraintsIterator<'map, 'db>),
858}
859
860impl<'db> UseDefMap<'db> {
861    fn constraint_tables(&self) -> &ConstraintTables<'db> {
862        self.constraint_tables
863            .as_deref()
864            .map_or(&EMPTY_CONSTRAINT_TABLES, |tables| tables)
865    }
866
867    fn extra(&self) -> &UseDefMapExtra {
868        self.extra
869            .as_deref()
870            .expect("extra use-def data should have been retained")
871    }
872
873    pub fn loop_header(&self, id: LoopHeaderId) -> &LoopHeader {
874        &self.extra().loop_headers[id]
875    }
876
877    pub fn reachability_constraints(&self) -> &ReachabilityConstraints {
878        &self.constraint_tables().reachability_constraints
879    }
880
881    pub fn predicates(&self) -> &Predicates<'db> {
882        &self.constraint_tables().predicates
883    }
884
885    pub fn range_reachability(
886        &self,
887    ) -> impl Iterator<Item = (TextRange, ScopedReachabilityConstraintId)> + '_ {
888        self.range_reachability
889            .iter()
890            .map(|&(range, RangeInfo { reachability, .. })| (range, reachability))
891    }
892
893    pub fn end_of_scope_reachability(&self) -> ScopedReachabilityConstraintId {
894        self.end_of_scope_reachability
895    }
896
897    /// Definitions relevant to usage analysis, including standalone declarations.
898    ///
899    /// The early declaration part of a combined definition is omitted: its later binding entry
900    /// carries the usage information for that definition.
901    pub fn definitions_with_usage(
902        &self,
903    ) -> impl Iterator<Item = (ScopedDefinitionId, Definition<'db>, bool)> + '_ {
904        self.all_definitions
905            .iter_enumerated()
906            .filter_map(|(id, entry)| match entry {
907                DefinitionEntry::Unused(definition) => Some((id, definition, false)),
908                DefinitionEntry::Used(definition) => Some((id, definition, true)),
909                DefinitionEntry::DeclarationPart(_)
910                | DefinitionEntry::Undefined
911                | DefinitionEntry::Deleted => None,
912            })
913    }
914
915    pub fn bindings_at_use(&self, use_id: ScopedUseId) -> BindingWithConstraintsIterator<'_, 'db> {
916        let bindings_id = self.extra().bindings_by_use[use_id];
917        self.bindings_iterator(
918            &self.interned_bindings[bindings_id],
919            BoundnessAnalysis::BasedOnUnboundVisibility,
920        )
921    }
922
923    pub fn multi_bindings_at_use(
924        &self,
925        use_id: ScopedUseId,
926    ) -> impl Iterator<Item = BindingWithConstraintsIterator<'_, 'db>> {
927        self.extra
928            .as_deref()
929            .and_then(|extra| extra.multi_bindings_by_use.get(use_id))
930            .map(|member_bindings| {
931                member_bindings.iter().map(|bindings| {
932                    self.bindings_iterator(
933                        bindings.as_slice(),
934                        BoundnessAnalysis::BasedOnUnboundVisibility,
935                    )
936                })
937            })
938            .into_iter()
939            .flatten()
940    }
941
942    pub fn applicable_constraints(
943        &self,
944        constraint_key: ConstraintKey,
945        enclosing_scope: FileScopeId,
946        expr: PlaceExprRef,
947        index: &'db SemanticIndex,
948    ) -> ApplicableConstraints<'_, 'db> {
949        match constraint_key {
950            ConstraintKey::NarrowingConstraint(constraint) => {
951                ApplicableConstraints::UnboundBinding(NarrowingEvaluator {
952                    constraint,
953                    constraint_tables: self.constraint_tables(),
954                })
955            }
956            ConstraintKey::NestedScope(nested_scope) => {
957                let EnclosingSnapshotResult::FoundBindings(bindings) =
958                    index.enclosing_snapshot(enclosing_scope, expr, nested_scope)
959                else {
960                    unreachable!(
961                        "The result of `SemanticIndex::eager_snapshot` must be `FoundBindings`"
962                    )
963                };
964                ApplicableConstraints::ConstrainedBindings(bindings)
965            }
966            ConstraintKey::UseId(use_id) => {
967                ApplicableConstraints::ConstrainedBindings(self.bindings_at_use(use_id))
968            }
969        }
970    }
971
972    pub fn definition(&self, id: ScopedDefinitionId) -> DefinitionState<'db> {
973        self.all_definitions.get(id).state()
974    }
975
976    pub fn narrowing_evaluator(
977        &self,
978        constraint: ScopedNarrowingConstraint,
979    ) -> NarrowingEvaluator<'_, 'db> {
980        NarrowingEvaluator {
981            constraint,
982            constraint_tables: self.constraint_tables(),
983        }
984    }
985
986    pub(crate) fn is_range_in_type_checking_block(&self, range: TextRange) -> bool {
987        self.range_reachability
988            .iter()
989            .take_while(|(entry_range, _)| entry_range.start() <= range.start())
990            .any(|&(entry_range, block)| {
991                block.in_type_checking_block && entry_range.contains_range(range)
992            })
993    }
994    pub fn end_of_scope_bindings(
995        &self,
996        place: ScopedPlaceId,
997    ) -> BindingWithConstraintsIterator<'_, 'db> {
998        match place {
999            ScopedPlaceId::Symbol(symbol) => self.end_of_scope_symbol_bindings(symbol),
1000            ScopedPlaceId::Member(member) => self.end_of_scope_member_bindings(member),
1001        }
1002    }
1003
1004    pub fn end_of_scope_symbol_bindings(
1005        &self,
1006        symbol: ScopedSymbolId,
1007    ) -> BindingWithConstraintsIterator<'_, 'db> {
1008        let place_state_id = self.symbol_states[symbol].end_of_scope;
1009        self.bindings_iterator(
1010            &self.interned_bindings[place_state_id.bindings_id()],
1011            BoundnessAnalysis::BasedOnUnboundVisibility,
1012        )
1013    }
1014
1015    fn end_of_scope_member_bindings(
1016        &self,
1017        member: ScopedMemberId,
1018    ) -> BindingWithConstraintsIterator<'_, 'db> {
1019        let place_state_id = self.extra().member_states[member].end_of_scope;
1020        self.bindings_iterator(
1021            &self.interned_bindings[place_state_id.bindings_id()],
1022            BoundnessAnalysis::BasedOnUnboundVisibility,
1023        )
1024    }
1025
1026    pub fn reachable_bindings(
1027        &self,
1028        place: ScopedPlaceId,
1029    ) -> BindingWithConstraintsIterator<'_, 'db> {
1030        match place {
1031            ScopedPlaceId::Symbol(symbol) => self.reachable_symbol_bindings(symbol),
1032            ScopedPlaceId::Member(member) => self.reachable_member_bindings(member),
1033        }
1034    }
1035
1036    pub fn reachable_symbol_bindings(
1037        &self,
1038        symbol: ScopedSymbolId,
1039    ) -> BindingWithConstraintsIterator<'_, 'db> {
1040        let place_state_id = self.symbol_states[symbol].reachable;
1041        let bindings = &self.interned_bindings[place_state_id.bindings_id()];
1042        self.bindings_iterator(bindings, BoundnessAnalysis::AssumeBound)
1043    }
1044
1045    pub fn reachable_member_bindings(
1046        &self,
1047        member: ScopedMemberId,
1048    ) -> BindingWithConstraintsIterator<'_, 'db> {
1049        let place_state_id = self.extra().member_states[member].reachable;
1050        let bindings = &self.interned_bindings[place_state_id.bindings_id()];
1051        self.bindings_iterator(bindings, BoundnessAnalysis::AssumeBound)
1052    }
1053
1054    pub(crate) fn enclosing_snapshot(
1055        &self,
1056        snapshot_id: ScopedEnclosingSnapshotId,
1057        nested_laziness: ScopeLaziness,
1058    ) -> EnclosingSnapshotResult<'_, 'db> {
1059        let boundness_analysis = if nested_laziness.is_eager() {
1060            BoundnessAnalysis::BasedOnUnboundVisibility
1061        } else {
1062            // TODO: We haven't implemented proper boundness analysis for nonlocal symbols, so we assume the boundness is bound for now.
1063            BoundnessAnalysis::AssumeBound
1064        };
1065
1066        let Some(extra) = self.extra.as_deref() else {
1067            return EnclosingSnapshotResult::NotFound;
1068        };
1069
1070        match extra.enclosing_snapshots.get(snapshot_id) {
1071            Some(InternedEnclosingSnapshotId::Constraint(constraint)) => {
1072                EnclosingSnapshotResult::FoundConstraint(*constraint)
1073            }
1074            Some(InternedEnclosingSnapshotId::Bindings(bindings_id)) => {
1075                EnclosingSnapshotResult::FoundBindings(
1076                    self.bindings_iterator(
1077                        &self.interned_bindings[*bindings_id],
1078                        boundness_analysis,
1079                    ),
1080                )
1081            }
1082            None => EnclosingSnapshotResult::NotFound,
1083        }
1084    }
1085
1086    pub fn bindings_at_definition(
1087        &self,
1088        definition: Definition<'db>,
1089    ) -> BindingWithConstraintsIterator<'_, 'db> {
1090        let bindings = self.definitions_by_definition.get(&definition).map_or_else(
1091            || ALWAYS_UNBOUND_BINDINGS.as_slice(),
1092            |definitions| &self.interned_bindings[definitions.bindings],
1093        );
1094        self.bindings_iterator(bindings, BoundnessAnalysis::BasedOnUnboundVisibility)
1095    }
1096
1097    pub fn declarations_at_binding(
1098        &self,
1099        binding: Definition<'db>,
1100    ) -> DeclarationsIterator<'_, 'db> {
1101        let declarations = self.definitions_by_definition.get(&binding).map_or_else(
1102            || ALWAYS_UNDECLARED_DECLARATIONS.as_slice(),
1103            |definitions| {
1104                &self.interned_declarations[definitions
1105                    .declarations
1106                    .expect("binding definition should have retained declarations")]
1107            },
1108        );
1109        self.declarations_iterator(declarations, BoundnessAnalysis::BasedOnUnboundVisibility)
1110    }
1111
1112    pub fn end_of_scope_declarations<'map>(
1113        &'map self,
1114        place: ScopedPlaceId,
1115    ) -> DeclarationsIterator<'map, 'db> {
1116        match place {
1117            ScopedPlaceId::Symbol(symbol) => self.end_of_scope_symbol_declarations(symbol),
1118            ScopedPlaceId::Member(member) => self.end_of_scope_member_declarations(member),
1119        }
1120    }
1121
1122    pub fn end_of_scope_symbol_declarations<'map>(
1123        &'map self,
1124        symbol: ScopedSymbolId,
1125    ) -> DeclarationsIterator<'map, 'db> {
1126        let place_state_id = self.symbol_states[symbol].end_of_scope;
1127        let declarations = &self.interned_declarations[place_state_id.declarations_id()];
1128        self.declarations_iterator(declarations, BoundnessAnalysis::BasedOnUnboundVisibility)
1129    }
1130
1131    fn end_of_scope_member_declarations<'map>(
1132        &'map self,
1133        member: ScopedMemberId,
1134    ) -> DeclarationsIterator<'map, 'db> {
1135        let place_state_id = self.extra().member_states[member].end_of_scope;
1136        let declarations = &self.interned_declarations[place_state_id.declarations_id()];
1137        self.declarations_iterator(declarations, BoundnessAnalysis::BasedOnUnboundVisibility)
1138    }
1139
1140    pub fn reachable_symbol_declarations(
1141        &self,
1142        symbol: ScopedSymbolId,
1143    ) -> DeclarationsIterator<'_, 'db> {
1144        let place_state_id = self.symbol_states[symbol].reachable;
1145        let declarations = &self.interned_declarations[place_state_id.declarations_id()];
1146        self.declarations_iterator(declarations, BoundnessAnalysis::AssumeBound)
1147    }
1148
1149    pub fn reachable_member_declarations(
1150        &self,
1151        member: ScopedMemberId,
1152    ) -> DeclarationsIterator<'_, 'db> {
1153        let place_state_id = self.extra().member_states[member].reachable;
1154        let declarations = &self.interned_declarations[place_state_id.declarations_id()];
1155        self.declarations_iterator(declarations, BoundnessAnalysis::AssumeBound)
1156    }
1157
1158    pub fn reachable_declarations(&self, place: ScopedPlaceId) -> DeclarationsIterator<'_, 'db> {
1159        match place {
1160            ScopedPlaceId::Symbol(symbol) => self.reachable_symbol_declarations(symbol),
1161            ScopedPlaceId::Member(member) => self.reachable_member_declarations(member),
1162        }
1163    }
1164
1165    pub fn all_end_of_scope_symbol_declarations<'map>(
1166        &'map self,
1167    ) -> impl Iterator<Item = (ScopedSymbolId, DeclarationsIterator<'map, 'db>)> + 'map {
1168        self.symbol_states
1169            .indices()
1170            .map(|symbol_id| (symbol_id, self.end_of_scope_symbol_declarations(symbol_id)))
1171    }
1172
1173    pub fn all_end_of_scope_symbol_bindings<'map>(
1174        &'map self,
1175    ) -> impl Iterator<Item = (ScopedSymbolId, BindingWithConstraintsIterator<'map, 'db>)> + 'map
1176    {
1177        self.symbol_states
1178            .indices()
1179            .map(|symbol_id| (symbol_id, self.end_of_scope_symbol_bindings(symbol_id)))
1180    }
1181
1182    pub fn all_reachable_symbols<'map>(
1183        &'map self,
1184    ) -> impl Iterator<
1185        Item = (
1186            ScopedSymbolId,
1187            DeclarationsIterator<'map, 'db>,
1188            BindingWithConstraintsIterator<'map, 'db>,
1189        ),
1190    > + 'map {
1191        self.symbol_states.iter_enumerated().map(
1192            |(symbol_id, RetainedPlaceStates { reachable, .. })| {
1193                let declarations = self.declarations_iterator(
1194                    &self.interned_declarations[reachable.declarations_id()],
1195                    BoundnessAnalysis::AssumeBound,
1196                );
1197                let bindings = self.bindings_iterator(
1198                    &self.interned_bindings[reachable.bindings_id()],
1199                    BoundnessAnalysis::AssumeBound,
1200                );
1201                (symbol_id, declarations, bindings)
1202            },
1203        )
1204    }
1205
1206    fn bindings_iterator<'map>(
1207        &'map self,
1208        bindings: &'map [LiveBinding],
1209        boundness_analysis: BoundnessAnalysis,
1210    ) -> BindingWithConstraintsIterator<'map, 'db> {
1211        BindingWithConstraintsIterator {
1212            all_definitions: &self.all_definitions,
1213            constraint_tables: self.constraint_tables(),
1214            boundness_analysis,
1215            inner: bindings.iter(),
1216        }
1217    }
1218
1219    fn declarations_iterator<'map>(
1220        &'map self,
1221        declarations: &'map [LiveDeclaration],
1222        boundness_analysis: BoundnessAnalysis,
1223    ) -> DeclarationsIterator<'map, 'db> {
1224        DeclarationsIterator {
1225            all_definitions: &self.all_definitions,
1226            constraint_tables: self.constraint_tables(),
1227            boundness_analysis,
1228            inner: declarations.iter(),
1229        }
1230    }
1231}
1232
1233/// Uniquely identifies a snapshot of an enclosing scope place state that can be used to resolve a
1234/// reference in a nested scope.
1235///
1236/// An eager scope has its entire body executed immediately at the location where it is defined.
1237/// For any free references in the nested scope, we use the bindings that are visible at the point
1238/// where the nested scope is defined, instead of using the public type of the place.
1239///
1240/// There is a unique ID for each distinct [`EnclosingSnapshotKey`] in the file.
1241#[newtype_index]
1242#[derive(get_size2::GetSize)]
1243pub(crate) struct ScopedEnclosingSnapshotId;
1244
1245#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, get_size2::GetSize)]
1246pub(crate) struct EnclosingSnapshotKey {
1247    /// The enclosing scope containing the bindings
1248    pub(crate) enclosing_scope: FileScopeId,
1249    /// The referenced place (in the enclosing scope)
1250    pub(crate) enclosing_place: ScopedPlaceId,
1251    /// The nested scope containing the reference
1252    pub(crate) nested_scope: FileScopeId,
1253    /// Laziness of the nested scope (technically redundant, but convenient to have here)
1254    pub(crate) nested_laziness: ScopeLaziness,
1255}
1256
1257/// Snapshots of enclosing scope place states for resolving a reference in a nested scope.
1258/// If the nested scope is eager, the snapshot is simply recorded and used as is.
1259/// If it is lazy, every time the outer symbol is reassigned, the snapshot is updated to add the
1260/// new binding.
1261type EnclosingSnapshots = IndexVec<ScopedEnclosingSnapshotId, EnclosingSnapshot>;
1262
1263#[derive(Clone, Debug)]
1264pub struct BindingWithConstraintsIterator<'map, 'db> {
1265    all_definitions: &'map RetainedDefinitions<'db>,
1266    constraint_tables: &'map ConstraintTables<'db>,
1267    boundness_analysis: BoundnessAnalysis,
1268    inner: LiveBindingsIterator<'map>,
1269}
1270
1271impl<'map, 'db> BindingWithConstraintsIterator<'map, 'db> {
1272    pub const fn predicates(&self) -> &'map Predicates<'db> {
1273        &self.constraint_tables.predicates
1274    }
1275
1276    pub const fn reachability_constraints(&self) -> &'map ReachabilityConstraints {
1277        &self.constraint_tables.reachability_constraints
1278    }
1279
1280    pub const fn boundness_analysis(&self) -> BoundnessAnalysis {
1281        self.boundness_analysis
1282    }
1283}
1284
1285impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
1286    type Item = BindingWithConstraints<'map, 'db>;
1287
1288    fn next(&mut self) -> Option<Self::Item> {
1289        self.inner
1290            .next()
1291            .map(|live_binding| BindingWithConstraints {
1292                binding: self.all_definitions.get(live_binding.binding()).state(),
1293                binding_order: live_binding.binding(),
1294                narrowing_constraint: NarrowingEvaluator {
1295                    constraint: live_binding.narrowing_constraint(),
1296                    constraint_tables: self.constraint_tables,
1297                },
1298                reachability_constraint: live_binding.reachability_constraint(),
1299            })
1300    }
1301}
1302
1303impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
1304
1305pub struct BindingWithConstraints<'map, 'db> {
1306    pub binding: DefinitionState<'db>,
1307    /// Stable binding order within the containing scope.
1308    pub binding_order: ScopedDefinitionId,
1309    pub narrowing_constraint: NarrowingEvaluator<'map, 'db>,
1310    pub reachability_constraint: ScopedReachabilityConstraintId,
1311}
1312
1313pub struct NarrowingEvaluator<'map, 'db> {
1314    constraint: ScopedNarrowingConstraint,
1315    constraint_tables: &'map ConstraintTables<'db>,
1316}
1317
1318impl<'map, 'db> NarrowingEvaluator<'map, 'db> {
1319    pub fn constraint(&self) -> ScopedNarrowingConstraint {
1320        self.constraint
1321    }
1322
1323    pub fn predicates(&self) -> &'map Predicates<'db> {
1324        &self.constraint_tables.predicates
1325    }
1326
1327    pub fn predicate_narrowing_targets(&self) -> &'map PredicateNarrowingTargets {
1328        &self.constraint_tables.predicate_narrowing_targets
1329    }
1330
1331    pub fn narrowing_constraints(&self) -> &'map NarrowingConstraints {
1332        &self.constraint_tables.narrowing_constraints
1333    }
1334}
1335
1336#[derive(Clone)]
1337pub struct DeclarationsIterator<'map, 'db> {
1338    all_definitions: &'map RetainedDefinitions<'db>,
1339    constraint_tables: &'map ConstraintTables<'db>,
1340    boundness_analysis: BoundnessAnalysis,
1341    inner: LiveDeclarationsIterator<'map>,
1342}
1343
1344impl<'map, 'db> DeclarationsIterator<'map, 'db> {
1345    pub const fn predicates(&self) -> &'map Predicates<'db> {
1346        &self.constraint_tables.predicates
1347    }
1348
1349    pub const fn reachability_constraints(&self) -> &'map ReachabilityConstraints {
1350        &self.constraint_tables.reachability_constraints
1351    }
1352
1353    pub const fn boundness_analysis(&self) -> BoundnessAnalysis {
1354        self.boundness_analysis
1355    }
1356}
1357
1358#[derive(Debug, Clone)]
1359pub struct DeclarationWithConstraint<'db> {
1360    pub declaration: DefinitionState<'db>,
1361    /// Stable declaration order within the containing scope.
1362    pub declaration_order: ScopedDefinitionId,
1363    pub reachability_constraint: ScopedReachabilityConstraintId,
1364}
1365
1366impl<'db> Iterator for DeclarationsIterator<'_, 'db> {
1367    type Item = DeclarationWithConstraint<'db>;
1368
1369    fn next(&mut self) -> Option<Self::Item> {
1370        self.inner.next().map(
1371            |LiveDeclaration {
1372                 declaration,
1373                 reachability_constraint,
1374             }| {
1375                DeclarationWithConstraint {
1376                    declaration: self.all_definitions.get(*declaration).state(),
1377                    declaration_order: *declaration,
1378                    reachability_constraint: *reachability_constraint,
1379                }
1380            },
1381        )
1382    }
1383}
1384
1385impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {}
1386
1387#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
1388struct ReachableDefinitions {
1389    bindings: Bindings,
1390    declarations: Declarations,
1391}
1392
1393/// A snapshot of the definitions and constraints state at a particular point in control flow.
1394#[derive(Clone, Debug)]
1395pub(super) struct FlowSnapshot {
1396    symbol_states: IndexVec<ScopedSymbolId, PendingPlaceState>,
1397    member_states: IndexVec<ScopedMemberId, PendingPlaceState>,
1398    reachability: ScopedReachabilityConstraintId,
1399    checkpoint_flow: ScopedReachabilityConstraintId,
1400    checkpoint_state: ExceptionCheckpointSnapshot,
1401    pending_reachability: PendingReachabilityId,
1402}
1403
1404impl FlowSnapshot {
1405    pub(super) fn is_always_unreachable(&self) -> bool {
1406        self.reachability == ScopedReachabilityConstraintId::ALWAYS_FALSE
1407    }
1408}
1409
1410/// Identifies a node in the tree of pending reachability constraints.
1411#[newtype_index]
1412struct PendingReachabilityId;
1413
1414#[derive(Debug)]
1415struct PendingReachabilityConstraint {
1416    parent: PendingReachabilityId,
1417    reachability_constraint: ScopedReachabilityConstraintId,
1418    narrowing_constraint: ScopedNarrowingConstraint,
1419}
1420
1421/// An append-only tree of scope-wide reachability constraints and narrowing gates.
1422///
1423/// Each [`PendingPlaceState`] remembers the last node applied for each constraint kind, so
1424/// snapshots can share place states and defer applying subsequent constraints until needed.
1425#[derive(Debug)]
1426struct PendingReachability {
1427    constraints: IndexVec<PendingReachabilityId, PendingReachabilityConstraint>,
1428    current: PendingReachabilityId,
1429}
1430
1431impl Default for PendingReachability {
1432    fn default() -> Self {
1433        let mut constraints = IndexVec::new();
1434        let root = constraints.next_index();
1435        constraints.push(PendingReachabilityConstraint {
1436            parent: root,
1437            reachability_constraint: ScopedReachabilityConstraintId::ALWAYS_TRUE,
1438            narrowing_constraint: ScopedNarrowingConstraint::ALWAYS_TRUE,
1439        });
1440        Self {
1441            constraints,
1442            current: root,
1443        }
1444    }
1445}
1446
1447impl PendingReachability {
1448    fn push(
1449        &mut self,
1450        reachability_constraint: ScopedReachabilityConstraintId,
1451        narrowing_constraint: ScopedNarrowingConstraint,
1452    ) {
1453        self.current = self.constraints.push(PendingReachabilityConstraint {
1454            parent: self.current,
1455            reachability_constraint,
1456            narrowing_constraint,
1457        });
1458    }
1459
1460    /// Applies both constraint kinds between the place's last materialized nodes and `target`.
1461    ///
1462    /// The place's node must be an ancestor of `target`. After materialization, the place is
1463    /// uniquely owned for mutation and records `target` as its last applied node.
1464    fn materialize<'a>(
1465        &self,
1466        pending: &'a mut PendingPlaceState,
1467        target: PendingReachabilityId,
1468        narrowing_constraints: &mut NarrowingConstraintsBuilder,
1469        reachability_constraints: &mut ReachabilityConstraintsBuilder,
1470    ) -> &'a mut PlaceState {
1471        self.materialize_reachability(pending, target, reachability_constraints);
1472        self.materialize_narrowing(pending, target, narrowing_constraints);
1473
1474        Rc::make_mut(&mut pending.state)
1475    }
1476
1477    fn materialize_narrowing(
1478        &self,
1479        pending: &mut PendingPlaceState,
1480        target: PendingReachabilityId,
1481        narrowing_constraints: &mut NarrowingConstraintsBuilder,
1482    ) {
1483        if pending.narrowing != target {
1484            let mut unapplied = SmallVec::<[ScopedNarrowingConstraint; 4]>::new();
1485            let mut current = target;
1486            while current != pending.narrowing {
1487                let event = &self.constraints[current];
1488                if event.narrowing_constraint != ScopedNarrowingConstraint::ALWAYS_TRUE {
1489                    unapplied.push(event.narrowing_constraint);
1490                }
1491                assert_ne!(
1492                    current, event.parent,
1493                    "pending narrowing must be an ancestor"
1494                );
1495                current = event.parent;
1496            }
1497
1498            if !unapplied.is_empty() {
1499                let state = Rc::make_mut(&mut pending.state);
1500                for constraint in unapplied.into_iter().rev() {
1501                    state.record_narrowing_constraint(narrowing_constraints, constraint);
1502                }
1503            }
1504            pending.narrowing = target;
1505        }
1506    }
1507
1508    fn materialize_reachability<'a>(
1509        &self,
1510        pending: &'a mut PendingPlaceState,
1511        target: PendingReachabilityId,
1512        reachability_constraints: &mut ReachabilityConstraintsBuilder,
1513    ) -> &'a mut PlaceState {
1514        if pending.reachability != target {
1515            let mut unapplied = SmallVec::<[ScopedReachabilityConstraintId; 4]>::new();
1516            let mut current = target;
1517            while current != pending.reachability {
1518                let event = &self.constraints[current];
1519                unapplied.push(event.reachability_constraint);
1520                assert_ne!(
1521                    current, event.parent,
1522                    "pending reachability must be an ancestor"
1523                );
1524                current = event.parent;
1525            }
1526
1527            let state = Rc::make_mut(&mut pending.state);
1528            for constraint in unapplied.into_iter().rev() {
1529                state.record_reachability_constraint(reachability_constraints, constraint);
1530            }
1531            pending.reachability = target;
1532        }
1533
1534        Rc::make_mut(&mut pending.state)
1535    }
1536
1537    /// Returns the materialized place state for immutable access.
1538    ///
1539    /// Call this instead of [`Self::materialize`] when the state will only be read. If the pending
1540    /// constraints are already materialized, this preserves the shared [`Rc`] instead of making
1541    /// the state uniquely owned.
1542    fn materialize_ref<'a>(
1543        &self,
1544        pending: &'a mut PendingPlaceState,
1545        target: PendingReachabilityId,
1546        narrowing_constraints: &mut NarrowingConstraintsBuilder,
1547        reachability_constraints: &mut ReachabilityConstraintsBuilder,
1548    ) -> &'a PlaceState {
1549        if pending.reachability != target || pending.narrowing != target {
1550            self.materialize(
1551                pending,
1552                target,
1553                narrowing_constraints,
1554                reachability_constraints,
1555            );
1556        }
1557        &pending.state
1558    }
1559
1560    /// Returns the place state needed to resolve a use.
1561    ///
1562    /// Pending narrowing gates are only needed to preserve path correlations across a later place
1563    /// change or merge, so they are not materialized here.
1564    fn materialize_ref_at_use<'a>(
1565        &self,
1566        pending: &'a mut PendingPlaceState,
1567        target: PendingReachabilityId,
1568        reachability_constraints: &mut ReachabilityConstraintsBuilder,
1569    ) -> &'a PlaceState {
1570        self.materialize_reachability(pending, target, reachability_constraints);
1571        &pending.state
1572    }
1573
1574    /// Combines the constraints after `ancestor` through `target` into a single constraint.
1575    ///
1576    /// `ancestor` must be an ancestor of `target`.
1577    fn constraint_between(
1578        &self,
1579        ancestor: PendingReachabilityId,
1580        target: PendingReachabilityId,
1581        reachability_constraints: &mut ReachabilityConstraintsBuilder,
1582    ) -> ScopedReachabilityConstraintId {
1583        let mut constraint = ScopedReachabilityConstraintId::ALWAYS_TRUE;
1584        let mut current = target;
1585        while current != ancestor {
1586            let event = &self.constraints[current];
1587            constraint = reachability_constraints
1588                .add_and_constraint(constraint, event.reachability_constraint);
1589            assert_ne!(
1590                current, event.parent,
1591                "pending reachability must be an ancestor"
1592            );
1593            current = event.parent;
1594        }
1595        constraint
1596    }
1597
1598    /// Combines the narrowing gates after `ancestor` through `target` into one constraint.
1599    ///
1600    /// `ancestor` must be an ancestor of `target`.
1601    fn narrowing_constraint_between(
1602        &self,
1603        ancestor: PendingReachabilityId,
1604        target: PendingReachabilityId,
1605        narrowing_constraints: &mut NarrowingConstraintsBuilder,
1606    ) -> ScopedNarrowingConstraint {
1607        let mut unapplied = SmallVec::<[ScopedNarrowingConstraint; 4]>::new();
1608        let mut current = target;
1609        while current != ancestor {
1610            let event = &self.constraints[current];
1611            if event.narrowing_constraint != ScopedNarrowingConstraint::ALWAYS_TRUE {
1612                unapplied.push(event.narrowing_constraint);
1613            }
1614            assert_ne!(
1615                current, event.parent,
1616                "pending narrowing must be an ancestor"
1617            );
1618            current = event.parent;
1619        }
1620
1621        let mut constraint = ScopedNarrowingConstraint::ALWAYS_TRUE;
1622        for pending in unapplied.into_iter().rev() {
1623            constraint = narrowing_constraints.add_and_constraint(constraint, pending);
1624        }
1625        constraint
1626    }
1627
1628    /// Returns the lowest common ancestor of two nodes in the pending-constraint tree.
1629    fn common_ancestor(
1630        &self,
1631        mut left: PendingReachabilityId,
1632        mut right: PendingReachabilityId,
1633    ) -> PendingReachabilityId {
1634        while left != right {
1635            if left.index() > right.index() {
1636                left = self.constraints[left].parent;
1637            } else {
1638                right = self.constraints[right].parent;
1639            }
1640        }
1641        left
1642    }
1643}
1644
1645/// A copy-on-write place state and the last reachability node materialized into it.
1646#[derive(Clone, Debug)]
1647struct PendingPlaceState {
1648    state: Rc<PlaceState>,
1649    reachability: PendingReachabilityId,
1650    narrowing: PendingReachabilityId,
1651}
1652
1653impl PendingPlaceState {
1654    fn new(state: PlaceState, reachability: PendingReachabilityId) -> Self {
1655        Self {
1656            state: Rc::new(state),
1657            reachability,
1658            narrowing: reachability,
1659        }
1660    }
1661}
1662
1663fn pending_place_state_mut<'a>(
1664    place: ScopedPlaceId,
1665    symbol_states: &'a mut IndexVec<ScopedSymbolId, PendingPlaceState>,
1666    member_states: &'a mut IndexVec<ScopedMemberId, PendingPlaceState>,
1667) -> &'a mut PendingPlaceState {
1668    match place {
1669        ScopedPlaceId::Symbol(symbol) => &mut symbol_states[symbol],
1670        ScopedPlaceId::Member(member) => &mut member_states[member],
1671    }
1672}
1673
1674impl PendingReachability {
1675    /// Merges an alternative branch's place states into the current control-flow path.
1676    ///
1677    /// States shared by both branches only need their path constraints merged. States that differ
1678    /// are materialized before their bindings and declarations are merged, while places absent
1679    /// from the alternative branch are treated as undefined on that path.
1680    fn merge_place_states<I: Idx>(
1681        &self,
1682        current_states: &mut IndexVec<I, PendingPlaceState>,
1683        branch_states: IndexVec<I, PendingPlaceState>,
1684        branch: PendingReachabilityId,
1685        branch_reachability: ScopedReachabilityConstraintId,
1686        narrowing_constraints: &mut NarrowingConstraintsBuilder,
1687        reachability_constraints: &mut ReachabilityConstraintsBuilder,
1688    ) {
1689        let branch_ancestor = self.common_ancestor(self.current, branch);
1690        let current_narrowing =
1691            self.narrowing_constraint_between(branch_ancestor, self.current, narrowing_constraints);
1692        let branch_narrowing =
1693            self.narrowing_constraint_between(branch_ancestor, branch, narrowing_constraints);
1694        let merged_narrowing =
1695            narrowing_constraints.add_or_constraint(current_narrowing, branch_narrowing);
1696        let mut branch_states = branch_states.into_iter();
1697        for current in current_states {
1698            let Some(mut branch_state) = branch_states.next() else {
1699                let current = self.materialize(
1700                    current,
1701                    self.current,
1702                    narrowing_constraints,
1703                    reachability_constraints,
1704                );
1705                current.merge(
1706                    PlaceState::undefined(branch_reachability),
1707                    narrowing_constraints,
1708                    reachability_constraints,
1709                );
1710                continue;
1711            };
1712
1713            // If neither branch changed the place itself, merge just the path constraints. The
1714            // common case is a truthy/falsy pair whose constraints cancel to `ALWAYS_TRUE`, leaving
1715            // the shared state untouched.
1716            if current.reachability == branch_state.reachability
1717                && current.narrowing == branch_state.narrowing
1718                && Rc::ptr_eq(&current.state, &branch_state.state)
1719            {
1720                if self.current == branch {
1721                    continue;
1722                }
1723
1724                // Preserve gates that precede the branch, then merge gates introduced on the
1725                // individual branch paths. If either path has no gate, the merged gate simplifies
1726                // to `ALWAYS_TRUE` and can be discarded.
1727                self.materialize_narrowing(current, branch_ancestor, narrowing_constraints);
1728                if merged_narrowing != ScopedNarrowingConstraint::ALWAYS_TRUE {
1729                    Rc::make_mut(&mut current.state)
1730                        .record_narrowing_constraint(narrowing_constraints, merged_narrowing);
1731                }
1732
1733                let current_constraint = self.constraint_between(
1734                    current.reachability,
1735                    self.current,
1736                    reachability_constraints,
1737                );
1738                let branch_constraint = self.constraint_between(
1739                    branch_state.reachability,
1740                    branch,
1741                    reachability_constraints,
1742                );
1743                let merged_constraint = reachability_constraints
1744                    .add_or_constraint(current_constraint, branch_constraint);
1745                if merged_constraint != ScopedReachabilityConstraintId::ALWAYS_TRUE {
1746                    Rc::make_mut(&mut current.state).record_reachability_constraint(
1747                        reachability_constraints,
1748                        merged_constraint,
1749                    );
1750                }
1751                current.reachability = self.current;
1752                current.narrowing = self.current;
1753                continue;
1754            }
1755
1756            self.materialize(
1757                &mut branch_state,
1758                branch,
1759                narrowing_constraints,
1760                reachability_constraints,
1761            );
1762            let branch_state = Rc::unwrap_or_clone(branch_state.state);
1763            let current = self.materialize(
1764                current,
1765                self.current,
1766                narrowing_constraints,
1767                reachability_constraints,
1768            );
1769            current.merge(
1770                branch_state,
1771                narrowing_constraints,
1772                reachability_constraints,
1773            );
1774        }
1775    }
1776}
1777
1778/// A snapshot of the state of a single symbol (e.g. `obj`) and all of its associated members
1779/// (e.g. `obj.attr`, `obj["key"]`).
1780pub(super) struct SingleSymbolSnapshot {
1781    symbol_state: PlaceState,
1782    associated_member_states: FxHashMap<ScopedMemberId, PlaceState>,
1783}
1784
1785#[derive(Debug)]
1786pub(super) struct UseDefMapBuilder<'db> {
1787    /// Append-only history of declarations and bindings, including their usage state.
1788    all_definitions: IndexVec<ScopedDefinitionId, DefinitionEntry<'db>>,
1789
1790    /// Builder of predicates.
1791    predicates: PredicatesBuilder<'db>,
1792
1793    /// Predicate-place pairs for which a narrowing constraint was recorded.
1794    predicate_narrowing_targets: Vec<(ScopedPredicateId, ScopedPlaceId)>,
1795
1796    /// Builder of reachability constraints.
1797    pub(super) reachability_constraints: ReachabilityConstraintsBuilder,
1798
1799    /// Builder of narrowing constraints.
1800    pub(super) narrowing_constraints: NarrowingConstraintsBuilder,
1801
1802    /// Live bindings at each so-far-recorded use.
1803    bindings_by_use: IndexVec<ScopedUseId, Bindings>,
1804
1805    /// Live bindings associated with each so-far-recorded use.
1806    ///
1807    /// Unlike `bindings_by_use`, this field supports associating multiple bindings with a
1808    /// single use. This is only used for kwargs expressions, whose corresponding `bindings_by_use`
1809    /// entry is empty.
1810    multi_bindings_by_use: FxHashMap<ScopedUseId, Vec<Bindings>>,
1811
1812    /// Tracks whether or not the current point in control flow is reachable from the
1813    /// start of the scope.
1814    pub(super) reachability: ScopedReachabilityConstraintId,
1815
1816    /// Tracks the reachability constraint for statements and certain sub-expressions,
1817    /// keyed by their text range.
1818    range_reachability: Vec<(TextRange, RangeInfo)>,
1819
1820    /// Identifies the current control-flow path for exception checkpoints.
1821    ///
1822    /// Unlike `reachability`, this excludes per-call gates so repeated calls with unchanged
1823    /// bindings share a checkpoint.
1824    checkpoint_flow: ScopedReachabilityConstraintId,
1825
1826    /// Restorable identity of the bindings visible to exception handlers.
1827    checkpoint_state: ExceptionCheckpointState,
1828
1829    /// Live bindings for each so-far-recorded definition and, for binding-only definitions, the
1830    /// live declarations.
1831    definitions_by_definition:
1832        FxHashMap<Definition<'db>, DefinitionsAtDefinition<Bindings, Declarations>>,
1833
1834    /// Currently live bindings and declarations for each place.
1835    symbol_states: IndexVec<ScopedSymbolId, PendingPlaceState>,
1836
1837    member_states: IndexVec<ScopedMemberId, PendingPlaceState>,
1838
1839    /// Reachability constraints that apply to every currently live place are recorded here and
1840    /// folded into individual place states only when that place is observed or changed.
1841    pending_reachability: PendingReachability,
1842
1843    /// All potentially reachable bindings and declarations, for each place.
1844    reachable_symbol_definitions: IndexVec<ScopedSymbolId, ReachableDefinitions>,
1845
1846    reachable_member_definitions: IndexVec<ScopedMemberId, ReachableDefinitions>,
1847
1848    /// Snapshots of place states in this scope that can be used to resolve a reference in a
1849    /// nested scope.
1850    enclosing_snapshots: EnclosingSnapshots,
1851
1852    /// Loop headers reserved before walking a loop and populated afterward.
1853    loop_headers: IndexVec<LoopHeaderId, LoopHeader>,
1854
1855    /// Is this a class scope?
1856    is_class_scope: bool,
1857
1858    /// Whether reachability predicates should also preserve narrowing across branches.
1859    reachability_narrowing_enabled: bool,
1860}
1861
1862impl<'db> UseDefMapBuilder<'db> {
1863    pub(super) fn new(scope_kind: ScopeKind) -> Self {
1864        Self {
1865            all_definitions: IndexVec::from_iter([DefinitionEntry::Undefined]),
1866            predicates: PredicatesBuilder::default(),
1867            predicate_narrowing_targets: Vec::new(),
1868            reachability_constraints: ReachabilityConstraintsBuilder::default(),
1869            narrowing_constraints: NarrowingConstraintsBuilder::default(),
1870            bindings_by_use: IndexVec::new(),
1871            multi_bindings_by_use: FxHashMap::default(),
1872            reachability: ScopedReachabilityConstraintId::ALWAYS_TRUE,
1873            range_reachability: Vec::new(),
1874            checkpoint_flow: ScopedReachabilityConstraintId::ALWAYS_TRUE,
1875            checkpoint_state: ExceptionCheckpointState::default(),
1876            definitions_by_definition: FxHashMap::default(),
1877            symbol_states: IndexVec::new(),
1878            member_states: IndexVec::new(),
1879            pending_reachability: PendingReachability::default(),
1880            reachable_member_definitions: IndexVec::new(),
1881            reachable_symbol_definitions: IndexVec::new(),
1882            enclosing_snapshots: EnclosingSnapshots::default(),
1883            loop_headers: IndexVec::new(),
1884            is_class_scope: scope_kind.is_class(),
1885            reachability_narrowing_enabled: matches!(
1886                scope_kind,
1887                ScopeKind::Module | ScopeKind::Class | ScopeKind::Function | ScopeKind::Lambda
1888            ),
1889        }
1890    }
1891
1892    pub(super) fn reserve_loop_header(&mut self) -> LoopHeaderId {
1893        self.loop_headers.push(LoopHeader::new())
1894    }
1895
1896    pub(super) fn set_loop_header(&mut self, id: LoopHeaderId, header: LoopHeader) {
1897        self.loop_headers[id] = header;
1898    }
1899
1900    fn push_definition(&mut self, entry: DefinitionEntry<'db>) -> ScopedDefinitionId {
1901        // Declaration-only entries also change the type visible to an exception handler.
1902        self.checkpoint_state.record_binding_change();
1903        self.all_definitions.push(entry)
1904    }
1905
1906    pub(super) fn definition(&self, def_id: ScopedDefinitionId) -> DefinitionState<'db> {
1907        self.all_definitions[def_id].state()
1908    }
1909
1910    pub(super) fn mark_unreachable(&mut self) {
1911        self.record_reachability_constraint(ScopedReachabilityConstraintId::ALWAYS_FALSE);
1912    }
1913
1914    pub(super) fn add_place(&mut self, place: ScopedPlaceId) {
1915        self.checkpoint_state.record_binding_change();
1916        match place {
1917            ScopedPlaceId::Symbol(symbol) => {
1918                let new_place = self.symbol_states.push(PendingPlaceState::new(
1919                    PlaceState::undefined(self.reachability),
1920                    self.pending_reachability.current,
1921                ));
1922                debug_assert_eq!(symbol, new_place);
1923                let new_place = self
1924                    .reachable_symbol_definitions
1925                    .push(ReachableDefinitions {
1926                        bindings: Bindings::unbound(self.reachability),
1927                        declarations: Declarations::undeclared(self.reachability),
1928                    });
1929                debug_assert_eq!(symbol, new_place);
1930            }
1931            ScopedPlaceId::Member(member) => {
1932                let new_place = self.member_states.push(PendingPlaceState::new(
1933                    PlaceState::undefined(self.reachability),
1934                    self.pending_reachability.current,
1935                ));
1936                debug_assert_eq!(member, new_place);
1937                let new_place = self
1938                    .reachable_member_definitions
1939                    .push(ReachableDefinitions {
1940                        bindings: Bindings::unbound(self.reachability),
1941                        declarations: Declarations::undeclared(self.reachability),
1942                    });
1943                debug_assert_eq!(member, new_place);
1944            }
1945        }
1946    }
1947
1948    pub(super) fn next_definition_id(&self) -> ScopedDefinitionId {
1949        self.all_definitions.next_index()
1950    }
1951
1952    /// Identifies the visible bindings and control-flow path observed by an exception handler.
1953    pub(super) fn exception_checkpoint_key(&self) -> ExceptionCheckpointKey {
1954        self.checkpoint_state
1955            .key((!self.reachability_constraints.is_saturated()).then_some(self.checkpoint_flow))
1956    }
1957
1958    pub(super) fn record_binding(
1959        &mut self,
1960        place: ScopedPlaceId,
1961        binding: Definition<'db>,
1962        previous_definitions: PreviousDefinitions,
1963        can_be_shadowed: FutureDefinitions,
1964    ) {
1965        let pending = self.pending_reachability.current;
1966        let def_id = self.push_definition(DefinitionEntry::Unused(binding));
1967        let place_state =
1968            pending_place_state_mut(place, &mut self.symbol_states, &mut self.member_states);
1969        let place_state = self.pending_reachability.materialize(
1970            place_state,
1971            pending,
1972            &mut self.narrowing_constraints,
1973            &mut self.reachability_constraints,
1974        );
1975        let definitions_at_definition = DefinitionsAtDefinition {
1976            bindings: place_state.bindings().clone(),
1977            declarations: Some(place_state.declarations().clone()),
1978        };
1979
1980        place_state.record_binding(
1981            def_id,
1982            self.reachability,
1983            self.is_class_scope,
1984            place.is_symbol(),
1985            previous_definitions,
1986            can_be_shadowed,
1987        );
1988        self.definitions_by_definition
1989            .insert(binding, definitions_at_definition);
1990
1991        let bindings = match place {
1992            ScopedPlaceId::Symbol(symbol) => {
1993                &mut self.reachable_symbol_definitions[symbol].bindings
1994            }
1995            ScopedPlaceId::Member(member) => {
1996                &mut self.reachable_member_definitions[member].bindings
1997            }
1998        };
1999
2000        bindings.record_binding(
2001            def_id,
2002            self.reachability,
2003            self.is_class_scope,
2004            place.is_symbol(),
2005            PreviousDefinitions::AreKept,
2006            can_be_shadowed,
2007        );
2008    }
2009
2010    pub(crate) fn bindings_at_use(
2011        &self,
2012        use_id: ScopedUseId,
2013    ) -> impl Iterator<Item = &LiveBinding> {
2014        self.bindings_by_use[use_id].iter()
2015    }
2016
2017    pub(super) fn add_predicate(
2018        &mut self,
2019        predicate: PredicateOrLiteral<'db>,
2020    ) -> ScopedPredicateId {
2021        match predicate {
2022            PredicateOrLiteral::Predicate(predicate) => self.predicates.add_predicate(predicate),
2023            PredicateOrLiteral::Literal(true) => ScopedPredicateId::ALWAYS_TRUE,
2024            PredicateOrLiteral::Literal(false) => ScopedPredicateId::ALWAYS_FALSE,
2025        }
2026    }
2027
2028    /// Records a narrowing constraint for only the specified places.
2029    pub(super) fn record_narrowing_constraint_for_places(
2030        &mut self,
2031        predicate: ScopedPredicateId,
2032        places: &PossiblyNarrowedPlaces,
2033    ) {
2034        if predicate == ScopedPredicateId::ALWAYS_TRUE
2035            || predicate == ScopedPredicateId::ALWAYS_FALSE
2036        {
2037            // No need to record a narrowing constraint for `True` or `False`.
2038            return;
2039        }
2040
2041        self.predicate_narrowing_targets
2042            .extend(places.iter().map(|place| (predicate, *place)));
2043
2044        let atom = self.narrowing_constraints.add_atom(predicate);
2045        self.record_narrowing_constraint_node_for_places(atom, places);
2046    }
2047
2048    /// Records a narrowing constraint on the current live bindings that were read by the
2049    /// corresponding earlier uses.
2050    pub(super) fn record_narrowing_constraint_for_bindings_at_use(
2051        &mut self,
2052        predicate: ScopedPredicateId,
2053        place: ScopedPlaceId,
2054        use_id: ScopedUseId,
2055    ) {
2056        if predicate == ScopedPredicateId::ALWAYS_TRUE
2057            || predicate == ScopedPredicateId::ALWAYS_FALSE
2058        {
2059            return;
2060        }
2061
2062        self.predicate_narrowing_targets.push((predicate, place));
2063
2064        let constraint = self.narrowing_constraints.add_atom(predicate);
2065        let pending = self.pending_reachability.current;
2066        let state =
2067            pending_place_state_mut(place, &mut self.symbol_states, &mut self.member_states);
2068        let state = self.pending_reachability.materialize(
2069            state,
2070            pending,
2071            &mut self.narrowing_constraints,
2072            &mut self.reachability_constraints,
2073        );
2074        state.record_narrowing_constraint_for_bindings_at_use(
2075            &mut self.narrowing_constraints,
2076            constraint,
2077            &self.bindings_by_use[use_id],
2078        );
2079    }
2080
2081    /// Records a narrowing constraint on the current live bindings selected by definition ID.
2082    pub(super) fn record_narrowing_constraint_for_bindings(
2083        &mut self,
2084        predicate: ScopedPredicateId,
2085        place: ScopedPlaceId,
2086        bindings: &[ScopedDefinitionId],
2087    ) {
2088        if predicate == ScopedPredicateId::ALWAYS_TRUE
2089            || predicate == ScopedPredicateId::ALWAYS_FALSE
2090        {
2091            return;
2092        }
2093
2094        self.predicate_narrowing_targets.push((predicate, place));
2095
2096        let constraint = self.narrowing_constraints.add_atom(predicate);
2097        let pending = self.pending_reachability.current;
2098        let state =
2099            pending_place_state_mut(place, &mut self.symbol_states, &mut self.member_states);
2100        let state = self.pending_reachability.materialize(
2101            state,
2102            pending,
2103            &mut self.narrowing_constraints,
2104            &mut self.reachability_constraints,
2105        );
2106        state.record_narrowing_constraint_for_bindings(
2107            &mut self.narrowing_constraints,
2108            constraint,
2109            bindings,
2110        );
2111    }
2112
2113    /// Records a negated narrowing constraint for only the specified places.
2114    ///
2115    /// The positive and negative constraints use the same predicate ID. This lets `P or not P`
2116    /// simplify to `ALWAYS_TRUE`, so narrowing cancels out after a complete `if`/`else`. The
2117    /// predicate's possible targets are independent of its polarity and were already recorded
2118    /// with the positive constraint.
2119    pub(super) fn record_negated_narrowing_constraint_for_places(
2120        &mut self,
2121        predicate: ScopedPredicateId,
2122        places: &PossiblyNarrowedPlaces,
2123    ) {
2124        if predicate == ScopedPredicateId::ALWAYS_TRUE
2125            || predicate == ScopedPredicateId::ALWAYS_FALSE
2126        {
2127            return;
2128        }
2129
2130        let negated = self.narrowing_constraints.add_negated_atom(predicate);
2131        self.record_narrowing_constraint_node_for_places(negated, places);
2132    }
2133
2134    /// Records a narrowing constraint node for the specified places.
2135    fn record_narrowing_constraint_node_for_places(
2136        &mut self,
2137        constraint: ScopedNarrowingConstraint,
2138        places: &PossiblyNarrowedPlaces,
2139    ) {
2140        let pending = self.pending_reachability.current;
2141        #[expect(
2142            clippy::iter_over_hash_type,
2143            reason = "the same constraint is recorded independently for each place"
2144        )]
2145        for place in places {
2146            match place {
2147                ScopedPlaceId::Symbol(symbol_id) => {
2148                    if let Some(state) = self.symbol_states.get_mut(*symbol_id) {
2149                        let state = self.pending_reachability.materialize(
2150                            state,
2151                            pending,
2152                            &mut self.narrowing_constraints,
2153                            &mut self.reachability_constraints,
2154                        );
2155                        state.record_narrowing_constraint(
2156                            &mut self.narrowing_constraints,
2157                            constraint,
2158                        );
2159                    }
2160                }
2161                ScopedPlaceId::Member(member_id) => {
2162                    if let Some(state) = self.member_states.get_mut(*member_id) {
2163                        let state = self.pending_reachability.materialize(
2164                            state,
2165                            pending,
2166                            &mut self.narrowing_constraints,
2167                            &mut self.reachability_constraints,
2168                        );
2169                        state.record_narrowing_constraint(
2170                            &mut self.narrowing_constraints,
2171                            constraint,
2172                        );
2173                    }
2174                }
2175            }
2176        }
2177    }
2178
2179    /// Snapshot the state of a single symbol and all of its associated members, at the current
2180    /// point in control flow.
2181    ///
2182    /// This is only used for `*`-import reachability constraints, which are handled differently
2183    /// to most other reachability constraints. See the doc-comment for
2184    /// [`Self::record_and_negate_star_import_reachability_constraint`] for more details.
2185    pub(super) fn single_symbol_snapshot(
2186        &mut self,
2187        symbol: ScopedSymbolId,
2188        associated_member_ids: &[ScopedMemberId],
2189    ) -> SingleSymbolSnapshot {
2190        let pending = self.pending_reachability.current;
2191        let symbol_state = self
2192            .pending_reachability
2193            .materialize_ref(
2194                &mut self.symbol_states[symbol],
2195                pending,
2196                &mut self.narrowing_constraints,
2197                &mut self.reachability_constraints,
2198            )
2199            .clone();
2200        let mut associated_member_states = FxHashMap::default();
2201        for &member_id in associated_member_ids {
2202            let state = self.pending_reachability.materialize_ref(
2203                &mut self.member_states[member_id],
2204                pending,
2205                &mut self.narrowing_constraints,
2206                &mut self.reachability_constraints,
2207            );
2208            associated_member_states.insert(member_id, state.clone());
2209        }
2210        SingleSymbolSnapshot {
2211            symbol_state,
2212            associated_member_states,
2213        }
2214    }
2215
2216    /// This method exists solely for handling `*`-import reachability constraints.
2217    ///
2218    /// The reason why we add reachability constraints for [`Definition`]s created by `*` imports
2219    /// is laid out in the doc-comment for `StarImportPlaceholderPredicate`. But treating these
2220    /// reachability constraints in the use-def map the same way as all other reachability constraints
2221    /// was shown to lead to [significant regressions] for small codebases where typeshed
2222    /// dominates. (Although `*` imports are not common generally, they are used in several
2223    /// important places by typeshed.)
2224    ///
2225    /// To solve these regressions, it was observed that we could do significantly less work for
2226    /// `*`-import definitions. We do a number of things differently here to our normal handling of
2227    /// reachability constraints:
2228    ///
2229    /// - We only apply and negate the reachability constraints to a single symbol, rather than to
2230    ///   all symbols. This is possible here because, unlike most definitions, we know in advance that
2231    ///   exactly one definition occurs inside the "if-true" predicate branch, and we know exactly
2232    ///   which definition it is.
2233    ///
2234    /// - We only snapshot the state for a single place prior to the definition, rather than doing
2235    ///   expensive calls to [`Self::snapshot`]. Again, this is possible because we know
2236    ///   that only a single definition occurs inside the "if-predicate-true" predicate branch.
2237    ///
2238    /// - Normally we take care to check whether an "if-predicate-true" branch or an
2239    ///   "if-predicate-false" branch contains a terminal statement: these can affect the reachability
2240    ///   of symbols defined inside either branch. However, in the case of `*`-import definitions,
2241    ///   this is unnecessary (and therefore not done in this method), since we know that a `*`-import
2242    ///   predicate cannot create a terminal statement inside either branch.
2243    ///
2244    /// [significant regressions]: https://github.com/astral-sh/ruff/pull/17286#issuecomment-2786755746
2245    pub(super) fn record_and_negate_star_import_reachability_constraint(
2246        &mut self,
2247        reachability_id: ScopedReachabilityConstraintId,
2248        symbol: ScopedSymbolId,
2249        pre_definition: SingleSymbolSnapshot,
2250    ) {
2251        self.checkpoint_state.record_binding_change();
2252        let negated_reachability_id = self
2253            .reachability_constraints
2254            .add_not_constraint(reachability_id);
2255        let pending = self.pending_reachability.current;
2256
2257        let symbol_state = self.pending_reachability.materialize(
2258            &mut self.symbol_states[symbol],
2259            pending,
2260            &mut self.narrowing_constraints,
2261            &mut self.reachability_constraints,
2262        );
2263        let mut post_definition_state =
2264            std::mem::replace(symbol_state, pre_definition.symbol_state);
2265
2266        post_definition_state
2267            .record_reachability_constraint(&mut self.reachability_constraints, reachability_id);
2268
2269        symbol_state.record_reachability_constraint(
2270            &mut self.reachability_constraints,
2271            negated_reachability_id,
2272        );
2273
2274        symbol_state.merge(
2275            post_definition_state,
2276            &mut self.narrowing_constraints,
2277            &mut self.reachability_constraints,
2278        );
2279
2280        // And similarly for all associated members:
2281        #[expect(
2282            clippy::iter_over_hash_type,
2283            reason = "associated member states are merged independently"
2284        )]
2285        for (member_id, pre_definition_member_state) in pre_definition.associated_member_states {
2286            let member_state = self.pending_reachability.materialize(
2287                &mut self.member_states[member_id],
2288                pending,
2289                &mut self.narrowing_constraints,
2290                &mut self.reachability_constraints,
2291            );
2292            let mut post_definition_state =
2293                std::mem::replace(member_state, pre_definition_member_state);
2294
2295            post_definition_state.record_reachability_constraint(
2296                &mut self.reachability_constraints,
2297                reachability_id,
2298            );
2299
2300            member_state.record_reachability_constraint(
2301                &mut self.reachability_constraints,
2302                negated_reachability_id,
2303            );
2304
2305            member_state.merge(
2306                post_definition_state,
2307                &mut self.narrowing_constraints,
2308                &mut self.reachability_constraints,
2309            );
2310        }
2311    }
2312
2313    pub(super) fn record_reachability_constraint(
2314        &mut self,
2315        reachability_constraint: ScopedReachabilityConstraintId,
2316    ) {
2317        self.checkpoint_flow = self
2318            .reachability_constraints
2319            .add_and_constraint(self.checkpoint_flow, reachability_constraint);
2320        let narrowing_constraint = if self.reachability_narrowing_enabled {
2321            self.reachability_constraints
2322                .narrowing_gate(reachability_constraint, &mut self.narrowing_constraints)
2323        } else {
2324            ScopedNarrowingConstraint::ALWAYS_TRUE
2325        };
2326        self.record_reachability_constraint_impl(reachability_constraint, narrowing_constraint);
2327    }
2328
2329    /// Records a reachability predicate and its corresponding narrowing gate together.
2330    ///
2331    /// Reachability is materialized when a place is used, while the narrowing gate remains pending
2332    /// until that place is changed or merged.
2333    pub(super) fn record_non_terminal_call_constraints(
2334        &mut self,
2335        reachability_constraint: ScopedReachabilityConstraintId,
2336        narrowing_constraint: ScopedNarrowingConstraint,
2337    ) {
2338        self.checkpoint_state.record_call_gate();
2339        self.record_reachability_constraint_impl(reachability_constraint, narrowing_constraint);
2340    }
2341
2342    fn record_reachability_constraint_impl(
2343        &mut self,
2344        reachability_constraint: ScopedReachabilityConstraintId,
2345        narrowing_constraint: ScopedNarrowingConstraint,
2346    ) {
2347        self.reachability = self
2348            .reachability_constraints
2349            .add_and_constraint(self.reachability, reachability_constraint);
2350        self.pending_reachability
2351            .push(reachability_constraint, narrowing_constraint);
2352    }
2353
2354    pub(super) fn record_declaration(
2355        &mut self,
2356        place: ScopedPlaceId,
2357        declaration: Definition<'db>,
2358    ) {
2359        let def_id = self.push_definition(DefinitionEntry::Unused(declaration));
2360        let pending = self.pending_reachability.current;
2361        let place_state =
2362            pending_place_state_mut(place, &mut self.symbol_states, &mut self.member_states);
2363        let place_state = self.pending_reachability.materialize(
2364            place_state,
2365            pending,
2366            &mut self.narrowing_constraints,
2367            &mut self.reachability_constraints,
2368        );
2369
2370        self.definitions_by_definition.insert(
2371            declaration,
2372            DefinitionsAtDefinition {
2373                bindings: place_state.bindings().clone(),
2374                declarations: None,
2375            },
2376        );
2377        place_state.record_declaration(def_id, self.reachability);
2378
2379        let definitions = match place {
2380            ScopedPlaceId::Symbol(symbol) => &mut self.reachable_symbol_definitions[symbol],
2381            ScopedPlaceId::Member(member) => &mut self.reachable_member_definitions[member],
2382        };
2383
2384        definitions.declarations.record_declaration(
2385            def_id,
2386            self.reachability,
2387            PreviousDefinitions::AreKept,
2388        );
2389    }
2390
2391    /// Record some or all of a definition that both declares a type and binds a value.
2392    ///
2393    /// Annotated assignments can declare before their RHS and bind afterward. Each phase gets a
2394    /// fresh scoped ID, so definitions created by the RHS remain in execution order.
2395    pub(super) fn record_combined_definition(
2396        &mut self,
2397        place: ScopedPlaceId,
2398        definition: Definition<'db>,
2399        part: DefinitionCategory,
2400    ) {
2401        // We don't need to store prior state for a definition that is both a declaration and a
2402        // binding.
2403        let entry = if part.is_binding() {
2404            DefinitionEntry::Unused(definition)
2405        } else {
2406            DefinitionEntry::DeclarationPart(definition)
2407        };
2408        let def_id = self.push_definition(entry);
2409        let pending = self.pending_reachability.current;
2410        let place_state =
2411            pending_place_state_mut(place, &mut self.symbol_states, &mut self.member_states);
2412        let place_state = self.pending_reachability.materialize(
2413            place_state,
2414            pending,
2415            &mut self.narrowing_constraints,
2416            &mut self.reachability_constraints,
2417        );
2418        let reachable_definitions = match place {
2419            ScopedPlaceId::Symbol(symbol) => &mut self.reachable_symbol_definitions[symbol],
2420            ScopedPlaceId::Member(member) => &mut self.reachable_member_definitions[member],
2421        };
2422
2423        if part.is_declaration() {
2424            place_state.record_declaration(def_id, self.reachability);
2425            reachable_definitions.declarations.record_declaration(
2426                def_id,
2427                self.reachability,
2428                PreviousDefinitions::AreKept,
2429            );
2430        }
2431        if part.is_binding() {
2432            place_state.record_binding(
2433                def_id,
2434                self.reachability,
2435                self.is_class_scope,
2436                place.is_symbol(),
2437                PreviousDefinitions::AreShadowed,
2438                FutureDefinitions::ShadowThisOne,
2439            );
2440            reachable_definitions.bindings.record_binding(
2441                def_id,
2442                self.reachability,
2443                self.is_class_scope,
2444                place.is_symbol(),
2445                PreviousDefinitions::AreKept,
2446                FutureDefinitions::ShadowThisOne,
2447            );
2448        }
2449    }
2450
2451    pub(super) fn delete_binding(&mut self, place: ScopedPlaceId) {
2452        let def_id = self.push_definition(DefinitionEntry::Deleted);
2453        let pending = self.pending_reachability.current;
2454        let place_state =
2455            pending_place_state_mut(place, &mut self.symbol_states, &mut self.member_states);
2456        let place_state = self.pending_reachability.materialize(
2457            place_state,
2458            pending,
2459            &mut self.narrowing_constraints,
2460            &mut self.reachability_constraints,
2461        );
2462
2463        place_state.record_binding(
2464            def_id,
2465            self.reachability,
2466            self.is_class_scope,
2467            place.is_symbol(),
2468            PreviousDefinitions::AreShadowed,
2469            FutureDefinitions::ShadowThisOne,
2470        );
2471    }
2472
2473    pub(super) fn record_use(&mut self, place: ScopedPlaceId, use_id: ScopedUseId) {
2474        let pending = self.pending_reachability.current;
2475        let place_state =
2476            pending_place_state_mut(place, &mut self.symbol_states, &mut self.member_states);
2477        let place_state = self.pending_reachability.materialize_ref_at_use(
2478            place_state,
2479            pending,
2480            &mut self.reachability_constraints,
2481        );
2482        let bindings = place_state.bindings().clone();
2483
2484        self.record_use_bindings(bindings, use_id);
2485    }
2486
2487    pub(super) fn record_multi_use(
2488        &mut self,
2489        places: impl Iterator<Item = ScopedPlaceId>,
2490        use_id: ScopedUseId,
2491    ) {
2492        let pending = self.pending_reachability.current;
2493        for place in places {
2494            let place_state =
2495                pending_place_state_mut(place, &mut self.symbol_states, &mut self.member_states);
2496            let place_state = self.pending_reachability.materialize_ref_at_use(
2497                place_state,
2498                pending,
2499                &mut self.reachability_constraints,
2500            );
2501            let bindings = place_state.bindings().clone();
2502
2503            let binding_definition_ids = bindings.iter().map(LiveBinding::binding);
2504            self.mark_definition_ids_used(binding_definition_ids);
2505
2506            self.multi_bindings_by_use
2507                .entry(use_id)
2508                .or_default()
2509                .push(bindings);
2510        }
2511
2512        // Record a placeholder use of the parent expression to preserve the indices of `bindings_by_use`.
2513        self.record_use_bindings(Bindings::default(), use_id);
2514    }
2515
2516    fn record_use_bindings(&mut self, bindings: Bindings, use_id: ScopedUseId) {
2517        let binding_definition_ids = bindings.iter().map(LiveBinding::binding);
2518        self.mark_definition_ids_used(binding_definition_ids);
2519
2520        // We have a use of a place; clone the current bindings for that place, and record them
2521        // as the live bindings for this use.
2522        let new_use = self.bindings_by_use.push(bindings);
2523        debug_assert_eq!(use_id, new_use);
2524    }
2525
2526    pub(super) fn symbol_binding_definition_ids(
2527        &self,
2528        symbol: ScopedSymbolId,
2529    ) -> impl Iterator<Item = ScopedDefinitionId> + '_ {
2530        self.symbol_states[symbol]
2531            .state
2532            .bindings()
2533            .iter()
2534            .map(LiveBinding::binding)
2535    }
2536
2537    /// Returns the current boundness of `symbol` after applying pending reachability constraints.
2538    ///
2539    /// Bindings on statically unreachable paths do not contribute to the result. This is stricter
2540    /// than [`Symbol::is_bound`](crate::symbol::Symbol::is_bound), which records whether the symbol
2541    /// is bound anywhere in the scope without considering control flow.
2542    pub(super) fn symbol_live_binding_status(
2543        &mut self,
2544        symbol: ScopedSymbolId,
2545    ) -> LiveBindingStatus {
2546        let mut has_binding = false;
2547        let mut has_unbound = false;
2548
2549        for binding in self.current_bindings(symbol.into()) {
2550            if binding.reachability_constraint() == ScopedReachabilityConstraintId::ALWAYS_FALSE {
2551                continue;
2552            }
2553
2554            if binding.binding().is_unbound() {
2555                has_unbound = true;
2556            } else {
2557                has_binding = true;
2558            }
2559        }
2560
2561        match (has_binding, has_unbound) {
2562            (true, true) => LiveBindingStatus::PossiblyBound,
2563            (true, false) => LiveBindingStatus::Bound,
2564            (false, _) => LiveBindingStatus::Unbound,
2565        }
2566    }
2567
2568    pub(super) fn mark_binding_definitions_used(
2569        &mut self,
2570        binding_definition_ids: impl IntoIterator<Item = ScopedDefinitionId>,
2571    ) {
2572        self.mark_definition_ids_used(binding_definition_ids);
2573    }
2574
2575    pub(super) fn record_range_reachability(
2576        &mut self,
2577        range: TextRange,
2578        is_type_checking_block: bool,
2579    ) {
2580        let this_range_info = RangeInfo {
2581            reachability: self.reachability,
2582            in_type_checking_block: is_type_checking_block,
2583        };
2584
2585        // If the last entry has the same reachability constraint and the same
2586        // "in-TYPE_CHECKING" status, extend it to cover this range too, collapsing
2587        // consecutive statements in a contiguous range into a single entry.
2588        if let Some((last_range, last_range_info)) = self.range_reachability.last_mut()
2589            && *last_range_info == this_range_info
2590        {
2591            *last_range = last_range.cover(range);
2592            return;
2593        }
2594        self.range_reachability.push((range, this_range_info));
2595    }
2596
2597    pub(super) fn snapshot_enclosing_state(
2598        &mut self,
2599        enclosing_place: ScopedPlaceId,
2600        enclosing_scope: ScopeKind,
2601        enclosing_place_expr: PlaceExprRef,
2602        is_parent_of_annotation_scope: bool,
2603    ) -> ScopedEnclosingSnapshotId {
2604        let pending = self.pending_reachability.current;
2605        let place_state = pending_place_state_mut(
2606            enclosing_place,
2607            &mut self.symbol_states,
2608            &mut self.member_states,
2609        );
2610        let bindings = self
2611            .pending_reachability
2612            .materialize_ref(
2613                place_state,
2614                pending,
2615                &mut self.narrowing_constraints,
2616                &mut self.reachability_constraints,
2617            )
2618            .bindings();
2619
2620        let is_class_symbol = enclosing_scope.is_class() && enclosing_place.is_symbol();
2621        let is_forwarding_symbol = enclosing_place_expr
2622            .as_symbol()
2623            .is_some_and(|symbol| symbol.is_global() || symbol.is_nonlocal());
2624        let stores_visible_bindings = enclosing_place_expr.is_bound()
2625            && bindings
2626                .iter()
2627                .any(|binding| !binding.binding().is_unbound());
2628        // Names bound in class scopes are never visible to nested scopes (but
2629        // attributes/subscripts are visible), so we never need to save eager scope bindings in a
2630        // class scope. There is one exception to this rule: annotation scopes can see names
2631        // defined in an immediately-enclosing class scope. Likewise, unbound `global` and
2632        // `nonlocal` symbols in the enclosing scope are forwarding declarations, so nested scopes
2633        // should continue walking outward instead of treating any bindings here as owned by this
2634        // scope. However, if the enclosing scope actually rebound the forwarded name, that visible
2635        // state needs to be snapshotted so nested scopes can see the rebound type.
2636        if (is_class_symbol && !is_parent_of_annotation_scope)
2637            || !enclosing_place_expr.is_bound()
2638            || (is_forwarding_symbol && !stores_visible_bindings)
2639        {
2640            self.enclosing_snapshots.push(EnclosingSnapshot::Constraint(
2641                bindings.unbound_narrowing_constraint(),
2642            ))
2643        } else {
2644            self.enclosing_snapshots
2645                .push(EnclosingSnapshot::Bindings(bindings.clone()))
2646        }
2647    }
2648
2649    pub(super) fn update_enclosing_snapshot(
2650        &mut self,
2651        snapshot_id: ScopedEnclosingSnapshotId,
2652        enclosing_symbol: ScopedSymbolId,
2653    ) {
2654        let pending = self.pending_reachability.current;
2655        let new_bindings = self
2656            .pending_reachability
2657            .materialize_ref(
2658                &mut self.symbol_states[enclosing_symbol],
2659                pending,
2660                &mut self.narrowing_constraints,
2661                &mut self.reachability_constraints,
2662            )
2663            .bindings()
2664            .clone();
2665        match self.enclosing_snapshots.get_mut(snapshot_id) {
2666            Some(EnclosingSnapshot::Bindings(bindings)) => {
2667                bindings.merge(
2668                    new_bindings,
2669                    &mut self.narrowing_constraints,
2670                    &mut self.reachability_constraints,
2671                );
2672            }
2673            Some(EnclosingSnapshot::Constraint(constraint)) => {
2674                *constraint = ScopedNarrowingConstraint::ALWAYS_TRUE;
2675            }
2676            None => {}
2677        }
2678    }
2679
2680    fn mark_definition_ids_used(
2681        &mut self,
2682        definition_ids: impl IntoIterator<Item = ScopedDefinitionId>,
2683    ) {
2684        for definition_id in definition_ids {
2685            self.mark_definition_used(definition_id);
2686        }
2687    }
2688
2689    fn mark_definition_used(&mut self, definition_id: ScopedDefinitionId) {
2690        let entry = &mut self.all_definitions[definition_id];
2691        if let DefinitionEntry::Unused(definition) = *entry {
2692            *entry = DefinitionEntry::Used(definition);
2693        }
2694    }
2695
2696    /// Take a snapshot of the current visible-places state.
2697    pub(super) fn snapshot(&self) -> FlowSnapshot {
2698        FlowSnapshot {
2699            symbol_states: self.symbol_states.clone(),
2700            member_states: self.member_states.clone(),
2701            reachability: self.reachability,
2702            checkpoint_flow: self.checkpoint_flow,
2703            checkpoint_state: self.checkpoint_state.snapshot(),
2704            pending_reachability: self.pending_reachability.current,
2705        }
2706    }
2707
2708    /// Get the current live bindings for a place.
2709    pub(super) fn current_bindings(
2710        &mut self,
2711        place: ScopedPlaceId,
2712    ) -> impl Iterator<Item = LiveBinding> + '_ {
2713        let pending = self.pending_reachability.current;
2714        let place_state =
2715            pending_place_state_mut(place, &mut self.symbol_states, &mut self.member_states);
2716        let bindings = self
2717            .pending_reachability
2718            .materialize_ref(
2719                place_state,
2720                pending,
2721                &mut self.narrowing_constraints,
2722                &mut self.reachability_constraints,
2723            )
2724            .bindings();
2725
2726        bindings.iter().copied()
2727    }
2728
2729    /// Restore the current builder places state to the given snapshot.
2730    pub(super) fn restore(&mut self, snapshot: FlowSnapshot) {
2731        self.checkpoint_state.restore(snapshot.checkpoint_state);
2732        // We never remove places from `place_states` (it's an IndexVec, and the place
2733        // IDs must line up), so the current number of known places must always be equal to or
2734        // greater than the number of known places in a previously-taken snapshot.
2735        let num_symbols = self.symbol_states.len();
2736        let num_members = self.member_states.len();
2737        debug_assert!(num_symbols >= snapshot.symbol_states.len());
2738
2739        // Restore the current visible-definitions state to the given snapshot.
2740        self.symbol_states = snapshot.symbol_states;
2741        self.member_states = snapshot.member_states;
2742        self.reachability = snapshot.reachability;
2743        self.checkpoint_flow = snapshot.checkpoint_flow;
2744        self.pending_reachability.current = snapshot.pending_reachability;
2745
2746        // If the snapshot we are restoring is missing some places we've recorded since, we need
2747        // to fill them in so the place IDs continue to line up. Since they don't exist in the
2748        // snapshot, the correct state to fill them in with is "undefined".
2749        let undefined = PendingPlaceState::new(
2750            PlaceState::undefined(self.reachability),
2751            self.pending_reachability.current,
2752        );
2753        self.symbol_states.resize(num_symbols, undefined.clone());
2754        self.member_states.resize(num_members, undefined);
2755    }
2756
2757    /// Merge the given snapshot into the current state, reflecting that we might have taken either
2758    /// path to get here. The new state for each place should include definitions from both the
2759    /// prior state and the snapshot.
2760    pub(super) fn merge(&mut self, snapshot: FlowSnapshot) {
2761        // As an optimization, if we know statically that either of the snapshots is always
2762        // unreachable, we can leave it out of the merged result entirely. Note that we cannot
2763        // perform any type inference at this point, so this is largely limited to unreachability
2764        // via terminal statements. If a flow's reachability depends on an expression in the code,
2765        // we will include the flow in the merged result; the reachability constraints of its
2766        // bindings will include this reachability condition, so that later during type inference,
2767        // we can determine whether any particular binding is non-visible due to unreachability.
2768        if snapshot.reachability == ScopedReachabilityConstraintId::ALWAYS_FALSE {
2769            return;
2770        }
2771        if self.reachability == ScopedReachabilityConstraintId::ALWAYS_FALSE {
2772            self.restore(snapshot);
2773            return;
2774        }
2775
2776        self.checkpoint_state.merge(snapshot.checkpoint_state);
2777
2778        // We never remove places from `place_states` (it's an IndexVec, and the place
2779        // IDs must line up), so the current number of known places must always be equal to or
2780        // greater than the number of known places in a previously-taken snapshot.
2781        debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len());
2782        debug_assert!(self.member_states.len() >= snapshot.member_states.len());
2783
2784        let branch = snapshot.pending_reachability;
2785        self.pending_reachability.merge_place_states(
2786            &mut self.symbol_states,
2787            snapshot.symbol_states,
2788            branch,
2789            snapshot.reachability,
2790            &mut self.narrowing_constraints,
2791            &mut self.reachability_constraints,
2792        );
2793        self.pending_reachability.merge_place_states(
2794            &mut self.member_states,
2795            snapshot.member_states,
2796            branch,
2797            snapshot.reachability,
2798            &mut self.narrowing_constraints,
2799            &mut self.reachability_constraints,
2800        );
2801
2802        self.reachability = self
2803            .reachability_constraints
2804            .add_or_constraint(self.reachability, snapshot.reachability);
2805        self.checkpoint_flow = self
2806            .reachability_constraints
2807            .add_or_constraint(self.checkpoint_flow, snapshot.checkpoint_flow);
2808    }
2809
2810    pub(super) fn finish(mut self: Box<Self>) -> UseDefMap<'db> {
2811        let pending = self.pending_reachability.current;
2812        for state in self
2813            .symbol_states
2814            .iter_mut()
2815            .chain(self.member_states.iter_mut())
2816        {
2817            // No later place change or merge can require the path correlation represented by
2818            // pending narrowing gates, so only reachability needs to be finalized here.
2819            self.pending_reachability.materialize_reachability(
2820                state,
2821                pending,
2822                &mut self.reachability_constraints,
2823            );
2824        }
2825
2826        let place_state_count = self.symbol_states.len()
2827            + self.member_states.len()
2828            + self.reachable_symbol_definitions.len()
2829            + self.reachable_member_definitions.len();
2830        let definitions_with_declarations_count = self
2831            .definitions_by_definition
2832            .values()
2833            .filter(|definitions| definitions.declarations.is_some())
2834            .count();
2835        let interned_bindings_capacity = self.definitions_by_definition.len()
2836            + self.bindings_by_use.len()
2837            + self.enclosing_snapshots.len()
2838            + place_state_count;
2839        let interned_declarations_capacity =
2840            definitions_with_declarations_count + place_state_count;
2841        let interned_ids_by_declarations_capacity =
2842            definitions_with_declarations_count + self.member_states.len();
2843        let mut place_state_interner = PlaceStateInterner::with_capacity(
2844            interned_bindings_capacity,
2845            interned_ids_by_declarations_capacity,
2846            interned_declarations_capacity,
2847        );
2848        // These fields are manually interned because they have a statistically high duplication rate (>50%).
2849        let definitions_by_definition = Self::intern_definitions_by_definition(
2850            self.definitions_by_definition,
2851            &mut place_state_interner,
2852        );
2853        let bindings_by_use =
2854            Self::intern_bindings_by_use(self.bindings_by_use, &mut place_state_interner);
2855        let symbol_states = self
2856            .symbol_states
2857            .into_iter()
2858            .map(|state| Rc::unwrap_or_clone(state.state))
2859            .collect();
2860        let member_states = self
2861            .member_states
2862            .into_iter()
2863            .map(|state| Rc::unwrap_or_clone(state.state))
2864            .collect();
2865        let end_of_scope_symbols = Self::intern_place_states(
2866            symbol_states,
2867            PlaceState::into_parts,
2868            &mut place_state_interner,
2869        );
2870        let end_of_scope_members =
2871            Self::intern_end_of_scope_members(member_states, &mut place_state_interner);
2872        let reachable_definitions_by_symbol = Self::intern_place_states(
2873            self.reachable_symbol_definitions,
2874            |definitions| (definitions.bindings, definitions.declarations),
2875            &mut place_state_interner,
2876        );
2877        let reachable_definitions_by_member = Self::intern_place_states(
2878            self.reachable_member_definitions,
2879            |definitions| (definitions.bindings, definitions.declarations),
2880            &mut place_state_interner,
2881        );
2882        let enclosing_snapshots =
2883            Self::intern_enclosing_snapshots(self.enclosing_snapshots, &mut place_state_interner);
2884        let PlaceStateInterner {
2885            interned_bindings,
2886            interned_declarations,
2887            ..
2888        } = place_state_interner;
2889
2890        // We only walk the fields that are copied through to the UseDefMap when we finish building
2891        // it.
2892        let interned_bindings = interned_bindings.finish(
2893            &mut self.narrowing_constraints,
2894            &mut self.reachability_constraints,
2895        );
2896        let interned_declarations =
2897            interned_declarations.finish(&mut self.reachability_constraints);
2898        for bindings in self.multi_bindings_by_use.values_mut().flatten() {
2899            bindings.finish(
2900                &mut self.narrowing_constraints,
2901                &mut self.reachability_constraints,
2902            );
2903        }
2904        // Keep default entries while building so they remain barriers between non-contiguous
2905        // ranges with the same metadata. Once construction is complete, absence represents the
2906        // default of reachable code outside a `TYPE_CHECKING` block.
2907        self.range_reachability
2908            .retain(|(_, info)| *info != RangeInfo::default());
2909        for &(_, RangeInfo { reachability, .. }) in &self.range_reachability {
2910            self.reachability_constraints.mark_used(reachability);
2911        }
2912        for enclosing_snapshot in &enclosing_snapshots {
2913            // Bindings are already marked above.
2914            if let InternedEnclosingSnapshotId::Constraint(constraint) = enclosing_snapshot {
2915                self.narrowing_constraints.mark_used(*constraint);
2916            }
2917        }
2918        self.reachability_constraints.mark_used(self.reachability);
2919        let symbol_states =
2920            Self::zip_place_states(end_of_scope_symbols, reachable_definitions_by_symbol);
2921        let member_states =
2922            Self::zip_place_states(end_of_scope_members, reachable_definitions_by_member);
2923        let multi_bindings_by_use = MultiBindingsByUse::from_map(self.multi_bindings_by_use);
2924        let loop_headers = self.loop_headers;
2925        let extra = (!bindings_by_use.is_empty()
2926            || !member_states.is_empty()
2927            || !enclosing_snapshots.is_empty()
2928            || !loop_headers.is_empty())
2929        .then(|| {
2930            Box::new(UseDefMapExtra {
2931                bindings_by_use: bindings_by_use.into(),
2932                multi_bindings_by_use,
2933                member_states,
2934                enclosing_snapshots: enclosing_snapshots.into(),
2935                loop_headers: loop_headers.into(),
2936            })
2937        });
2938        let predicates = self.predicates.build();
2939        let predicate_narrowing_targets =
2940            PredicateNarrowingTargets::from_entries(self.predicate_narrowing_targets);
2941        let reachability_constraints = self.reachability_constraints.build();
2942        let narrowing_constraints = self.narrowing_constraints.build();
2943        let constraint_tables = (!reachability_constraints.used_interiors().is_empty()
2944            || !narrowing_constraints.is_empty())
2945        .then(|| {
2946            Box::new(ConstraintTables {
2947                predicates,
2948                predicate_narrowing_targets,
2949                reachability_constraints,
2950                narrowing_constraints,
2951            })
2952        });
2953        let all_definitions = RetainedDefinitions::new(self.all_definitions);
2954
2955        UseDefMap {
2956            all_definitions,
2957            constraint_tables,
2958            interned_bindings,
2959            interned_declarations,
2960            range_reachability: self.range_reachability.into_boxed_slice(),
2961            symbol_states,
2962            definitions_by_definition,
2963            extra,
2964            end_of_scope_reachability: self.reachability,
2965        }
2966    }
2967
2968    fn zip_place_states<I: Idx, T>(
2969        end_of_scope: IndexVec<I, T>,
2970        reachable: IndexVec<I, T>,
2971    ) -> FrozenIndexVec<I, RetainedPlaceStates<T>> {
2972        assert_eq!(end_of_scope.len(), reachable.len());
2973
2974        end_of_scope
2975            .into_iter()
2976            .zip(reachable)
2977            .map(|(end_of_scope, reachable)| RetainedPlaceStates {
2978                end_of_scope,
2979                reachable,
2980            })
2981            .collect()
2982    }
2983
2984    fn intern_definitions_by_definition(
2985        definitions_by_definition: FxHashMap<
2986            Definition<'db>,
2987            DefinitionsAtDefinition<Bindings, Declarations>,
2988        >,
2989        place_state_interner: &mut PlaceStateInterner,
2990    ) -> FrozenMap<
2991        Definition<'db>,
2992        DefinitionsAtDefinition<InternedBindingsId, InternedDeclarationsId>,
2993    > {
2994        let mut interned_ids_by_definition = Vec::with_capacity(definitions_by_definition.len());
2995
2996        // Keep the builder map hash-based because it is updated for every definition. We only need
2997        // stable iteration here, where insertion order determines the generated interned IDs.
2998        let mut definitions_by_definition =
2999            definitions_by_definition.into_iter().collect::<Vec<_>>();
3000        definitions_by_definition.sort_unstable_by_key(|(definition, _)| *definition);
3001
3002        for (
3003            definition,
3004            DefinitionsAtDefinition {
3005                bindings,
3006                declarations,
3007            },
3008        ) in definitions_by_definition
3009        {
3010            // Lookups use the shared start-of-scope defaults for these omitted entries.
3011            if bindings.is_always_unbound()
3012                && declarations
3013                    .as_ref()
3014                    .is_none_or(Declarations::is_always_undeclared)
3015            {
3016                continue;
3017            }
3018
3019            let bindings = place_state_interner.intern_bindings(&bindings);
3020            let declarations = declarations
3021                .map(|declarations| place_state_interner.intern_declarations(declarations));
3022            interned_ids_by_definition.push((
3023                definition,
3024                DefinitionsAtDefinition {
3025                    bindings,
3026                    declarations,
3027                },
3028            ));
3029        }
3030
3031        FrozenMap::from_entries(interned_ids_by_definition)
3032    }
3033
3034    fn intern_bindings_by_use(
3035        bindings_by_use: IndexVec<ScopedUseId, Bindings>,
3036        place_state_interner: &mut PlaceStateInterner,
3037    ) -> IndexVec<ScopedUseId, InternedBindingsId> {
3038        let mut interned_ids_by_use: IndexVec<ScopedUseId, InternedBindingsId> =
3039            IndexVec::with_capacity(bindings_by_use.len());
3040
3041        for bindings in bindings_by_use {
3042            let interned_id = place_state_interner.intern_bindings(&bindings);
3043            interned_ids_by_use.push(interned_id);
3044        }
3045
3046        interned_ids_by_use
3047    }
3048
3049    fn intern_place_states<I: Idx, T>(
3050        place_states: IndexVec<I, T>,
3051        get_parts: impl Fn(T) -> (Bindings, Declarations),
3052        place_state_interner: &mut PlaceStateInterner,
3053    ) -> IndexVec<I, InternedPlaceStateId> {
3054        let mut interned_ids_by_place = IndexVec::with_capacity(place_states.len());
3055
3056        for place_state in place_states {
3057            let (bindings, declarations) = get_parts(place_state);
3058            let interned_id = place_state_interner.retain_place_state(&bindings, declarations);
3059            interned_ids_by_place.push(interned_id);
3060        }
3061
3062        interned_ids_by_place
3063    }
3064
3065    fn intern_end_of_scope_members(
3066        end_of_scope_members: IndexVec<ScopedMemberId, PlaceState>,
3067        place_state_interner: &mut PlaceStateInterner,
3068    ) -> IndexVec<ScopedMemberId, InternedPlaceStateId> {
3069        let mut interned_ids_by_member = IndexVec::with_capacity(end_of_scope_members.len());
3070        let mut interned_ids_by_place_state =
3071            FxHashMap::with_capacity_and_hasher(end_of_scope_members.len(), FxBuildHasher);
3072
3073        for place_state in end_of_scope_members {
3074            let interned_id = match interned_ids_by_place_state.entry(place_state) {
3075                Entry::Occupied(entry) => *entry.get(),
3076                Entry::Vacant(entry) => {
3077                    let place_state = entry.key();
3078                    let interned_id = place_state_interner.intern_place_state(
3079                        place_state.bindings(),
3080                        place_state.declarations().clone(),
3081                    );
3082                    entry.insert(interned_id);
3083                    interned_id
3084                }
3085            };
3086            interned_ids_by_member.push(interned_id);
3087        }
3088
3089        interned_ids_by_member
3090    }
3091
3092    fn intern_enclosing_snapshots(
3093        enclosing_snapshots: EnclosingSnapshots,
3094        place_state_interner: &mut PlaceStateInterner,
3095    ) -> IndexVec<ScopedEnclosingSnapshotId, InternedEnclosingSnapshotId> {
3096        let mut interned_ids_by_snapshot: IndexVec<
3097            ScopedEnclosingSnapshotId,
3098            InternedEnclosingSnapshotId,
3099        > = IndexVec::with_capacity(enclosing_snapshots.len());
3100
3101        for snapshot in enclosing_snapshots {
3102            let interned_id = match snapshot {
3103                EnclosingSnapshot::Bindings(bindings) => {
3104                    let interned_bindings_id = place_state_interner.intern_bindings(&bindings);
3105                    InternedEnclosingSnapshotId::Bindings(interned_bindings_id)
3106                }
3107                EnclosingSnapshot::Constraint(constraint) => {
3108                    InternedEnclosingSnapshotId::Constraint(constraint)
3109                }
3110            };
3111            interned_ids_by_snapshot.push(interned_id);
3112        }
3113
3114        interned_ids_by_snapshot
3115    }
3116}