Skip to main content

ty_python_core/
lib.rs

1#![warn(
2    clippy::disallowed_methods,
3    reason = "Prefer System trait methods over std methods in ty crates"
4)]
5use ruff_python_ast as ast;
6use std::iter::{FusedIterator, once};
7use std::sync::Arc;
8
9use ruff_db::parsed::parsed_module;
10
11use ruff_index::{FrozenIndexVec, IndexSlice};
12use ruff_python_ast::NodeIndex;
13use ruff_python_parser::semantic_errors::SemanticSyntaxError;
14use ruff_text_size::TextRange;
15use rustc_hash::{FxHashMap, FxHashSet};
16use salsa::plumbing::AsId;
17use smallvec::SmallVec;
18use ty_module_resolver::ModuleName;
19
20use crate::frozen::{FrozenMap, FrozenSet};
21use crate::place::ScopedPlaceId;
22pub use crate::statement::{Statement, StatementNodeKey};
23use ast_ids::AstIds;
24pub use ast_ids::ExpressionNodeKey;
25use builder::SemanticIndexBuilder;
26use definition::{Definition, DefinitionNodeKey, Definitions};
27use expression::Expression;
28use narrowing_constraints::ScopedNarrowingConstraint;
29pub use place::{PlaceExprRef, PlaceTable};
30pub use reachability_constraints::ReachabilityConstraintsBuilder;
31pub use scope::FileScopeId;
32use scope::{NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopeKind, ScopeLaziness};
33use symbol::ScopedSymbolId;
34pub use use_def::{
35    ApplicableConstraints, BindingWithConstraints, BindingWithConstraintsIterator,
36    DeclarationWithConstraint, DeclarationsIterator, LiveBinding, LoopHeaderId, NarrowingEvaluator,
37    PredicateNarrowingTargets, ScopedDefinitionId, UseDefMap,
38};
39use use_def::{EnclosingSnapshotKey, ScopedEnclosingSnapshotId};
40
41pub mod ast_ids;
42pub mod ast_node_ref;
43mod builder;
44mod db;
45pub mod definition;
46pub mod expression;
47pub mod frozen;
48pub(crate) mod member;
49pub mod narrowing_constraints;
50pub mod node_key;
51pub mod place;
52pub mod platform;
53pub mod predicate;
54pub mod rank;
55mod re_exports;
56pub mod reachability_constraints;
57pub mod scope;
58pub mod statement;
59pub mod symbol;
60pub mod unpack;
61mod use_def;
62pub use db::Db;
63#[cfg(any(test, feature = "testing"))]
64pub use db::TestProgramDb;
65pub mod program;
66pub mod program_file;
67pub use program::Program;
68pub use program_file::ProgramFile;
69
70/// Returns the semantic index for `file`.
71///
72/// Prefer using [`symbol_table`] when working with symbols from a single scope.
73#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size)]
74pub fn semantic_index<'db>(db: &'db dyn Db, file: ProgramFile<'db>) -> SemanticIndex<'db> {
75    let _span = tracing::trace_span!("semantic_index", ?file).entered();
76
77    let module = parsed_module(db, file.python_file(db)).load(db);
78
79    SemanticIndexBuilder::new(db, file, &module).build()
80}
81
82/// Returns the place table for a specific `scope`.
83///
84/// Using [`place_table`] over [`semantic_index`] has the advantage that
85/// Salsa can avoid invalidating dependent queries if this scope's place table
86/// is unchanged.
87#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
88pub fn place_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<PlaceTable> {
89    let program_file = scope.program_file(db);
90    let _span = tracing::trace_span!("place_table", scope=?scope.as_id(), ?program_file).entered();
91    let index = semantic_index(db, program_file);
92    Arc::clone(&index.place_tables[scope.file_scope_id(db)])
93}
94
95/// Returns the use-def map for a specific `scope`.
96///
97/// Using [`use_def_map`] over [`semantic_index`] has the advantage that
98/// Salsa can avoid invalidating dependent queries if this scope's use-def map
99/// is unchanged.
100#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
101pub fn use_def_map<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<UseDefMap<'db>> {
102    let program_file = scope.program_file(db);
103    let _span = tracing::trace_span!("use_def_map", scope=?scope.as_id(), ?program_file).entered();
104    let index = semantic_index(db, program_file);
105    Arc::clone(&index.use_def_maps[scope.file_scope_id(db)])
106}
107
108/// All the bindings made in a loop, which are visible to the entire loop via "loop header
109/// definitions" (a.k.a. loop-back bindings)
110///
111/// Loop control flow analysis needs a way for uses early in the loop to see bindings that come
112/// later, reflecting the fact that a previous iteration of the loop might've already executed
113/// those bindings. For example:
114///
115/// ```py
116/// x = "A"
117/// while some_condition():
118///     # The loop entry value (in the first iteration) and the following binding (in other
119///     # iterations) are both visible at this use.
120///     reveal_type(x)  # revealed: Literal["A", "B"]
121///     x = "B"
122/// ```
123///
124/// As an important special case, these loop header definitions also combine with fixpoint
125/// iteration to let us infer `int` for loop variables. For example:
126///
127/// ```py
128/// i = 0  # revealed: Literal[0]
129/// while i < 1_000_000:
130///     i += 1  # revealed: int
131/// ```
132///
133/// The add-assign statement `i += 1` is both a use and a binding. The use sees the `i = 0` binding
134/// at the top, and it also sees *itself* via the loop header definition of `i`. When we infer the
135/// type of `i` in or after the loop, fixpoint iteration produces an ever-expanding union of
136/// literals (`Literal[0, 1, 2, ...]`) until we eventually reach the threshold for widening to
137/// `int` and stop iterating. (See `should_widen` and `widen_literal_types`.)
138///
139/// Loop header definitions are synthesized before walking the loop body, but the bindings in the
140/// header are only known after walking all loop-back edges. The builder reserves a [`LoopHeaderId`]
141/// before the walk, fills the corresponding header afterward, and publishes the completed headers
142/// in the scope's [`UseDefMap`].
143#[derive(Debug, Clone, Default, PartialEq, Eq, get_size2::GetSize)]
144pub struct LoopHeader {
145    bindings: FxHashMap<ScopedPlaceId, SmallVec<[LiveBinding; 1]>>,
146}
147
148impl LoopHeader {
149    fn new() -> Self {
150        Self {
151            bindings: FxHashMap::default(),
152        }
153    }
154
155    fn add_binding(&mut self, place: ScopedPlaceId, binding: LiveBinding) {
156        self.bindings.entry(place).or_default().push(binding);
157    }
158
159    pub fn bindings_for_place(
160        &self,
161        place: ScopedPlaceId,
162    ) -> impl Iterator<Item = LiveBinding> + '_ {
163        self.bindings
164            .get(&place)
165            .map(|v: &SmallVec<[LiveBinding; 1]>| v.iter().copied())
166            .into_iter()
167            .flatten()
168    }
169}
170
171/// Returns all attribute assignments as scope IDs for a specific class body scope.
172///
173/// Only call this when doing type inference on the same file as `class_body_scope`, otherwise it
174/// introduces a direct dependency on that file's AST.
175pub fn attribute_scopes<'db>(
176    db: &'db dyn Db,
177    class_body_scope: ScopeId<'db>,
178) -> impl Iterator<Item = FileScopeId> + 'db {
179    let index = semantic_index(db, class_body_scope.program_file(db));
180    let class_scope_id = class_body_scope.file_scope_id(db);
181    ChildrenIter::new(&index.scopes, class_scope_id)
182        .filter_map(move |(child_scope_id, scope)| {
183            let (function_scope_id, function_scope) =
184                if scope.node().scope_kind() == ScopeKind::TypeParams {
185                    // This could be a generic method with a type-params scope.
186                    // Go one level deeper to find the function scope. The first
187                    // descendant is the (potential) function scope.
188                    let function_scope_id = scope.descendants().start;
189                    (function_scope_id, index.scope(function_scope_id))
190                } else {
191                    (child_scope_id, scope)
192                };
193            function_scope.node().as_function()?;
194            Some(function_scope_id)
195        })
196        .flat_map(move |func_id| {
197            // Add any descendent scope that is eager and have eager scopes between the scope
198            // and the method scope. Since attributes can be defined in this scope.
199            let nested = index.descendent_scopes(func_id).filter_map(move |(id, s)| {
200                let is_eager = s.kind().is_eager();
201                let parents_are_eager = {
202                    let mut all_parents_eager = true;
203                    let mut current = Some(id);
204
205                    while let Some(scope_id) = current {
206                        if scope_id == func_id {
207                            break;
208                        }
209                        let scope = index.scope(scope_id);
210                        if !scope.is_eager() {
211                            all_parents_eager = false;
212                            break;
213                        }
214                        current = scope.parent();
215                    }
216
217                    all_parents_eager
218                };
219
220                (parents_are_eager && is_eager).then_some(id)
221            });
222            once(func_id).chain(nested)
223        })
224}
225
226/// Returns the module global scope of `file`.
227#[salsa::tracked(returns(copy), heap_size=ruff_memory_usage::heap_size)]
228pub fn global_scope<'db>(db: &'db dyn Db, file: ProgramFile<'db>) -> ScopeId<'db> {
229    let _span = tracing::trace_span!("global_scope", ?file).entered();
230
231    FileScopeId::global().to_scope_id(db, file)
232}
233
234pub enum EnclosingSnapshotResult<'map, 'db> {
235    FoundConstraint(ScopedNarrowingConstraint),
236    FoundBindings(BindingWithConstraintsIterator<'map, 'db>),
237    NotFound,
238    NoLongerInEagerContext,
239}
240
241#[derive(Debug, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
242struct DefinitionsByNode<'db> {
243    single: FrozenMap<DefinitionNodeKey, Definition<'db>>,
244    non_single: FrozenMap<DefinitionNodeKey, Box<[Definition<'db>]>>,
245}
246
247impl<'db> DefinitionsByNode<'db> {
248    fn from_map(definitions_by_node: FxHashMap<DefinitionNodeKey, Definitions<'db>>) -> Self {
249        let single_count = definitions_by_node
250            .values()
251            .filter(|definitions| definitions.len() == 1)
252            .count();
253        let mut single = Vec::with_capacity(single_count);
254        let mut non_single = Vec::with_capacity(definitions_by_node.len() - single_count);
255
256        #[expect(
257            clippy::iter_over_hash_type,
258            reason = "each node is independently partitioned by definition count"
259        )]
260        for (key, definitions) in definitions_by_node {
261            if definitions.len() == 1 {
262                single.push((key, definitions[0]));
263            } else {
264                non_single.push((key, definitions.into_boxed_slice()));
265            }
266        }
267
268        Self {
269            single: FrozenMap::from_entries(single),
270            non_single: FrozenMap::from_entries(non_single),
271        }
272    }
273
274    fn get(&self, key: DefinitionNodeKey) -> Option<&[Definition<'db>]> {
275        self.single
276            .get(&key)
277            .map(std::slice::from_ref)
278            .or_else(|| self.non_single.get(&key).map(AsRef::as_ref))
279    }
280}
281
282/// The place tables and use-def maps for all scopes in a file.
283#[derive(Debug, get_size2::GetSize, salsa::SalsaValue)]
284pub struct SemanticIndex<'db> {
285    /// List of all place tables in this file, indexed by scope.
286    place_tables: FrozenIndexVec<FileScopeId, Arc<PlaceTable>>,
287
288    /// List of all scopes in this file.
289    scopes: FrozenIndexVec<FileScopeId, Scope>,
290
291    /// Map expressions to their corresponding scope.
292    scopes_by_expression: ExpressionsScopeMap,
293
294    /// Map from a node creating a definition to its definition.
295    definitions_by_node: DefinitionsByNode<'db>,
296
297    /// Map from a standalone expression to its [`Expression`] ingredient.
298    expressions_by_node: FxHashMap<ExpressionNodeKey, Expression<'db>>,
299
300    /// Map from an unpacking target to its [`unpack::Unpack`] ingredient.
301    unpacks_by_target: FrozenMap<ExpressionNodeKey, unpack::Unpack<'db>>,
302
303    /// Map from a standalone statement to its [`Statement`] ingredient.
304    statements_by_node: FxHashMap<StatementNodeKey, Statement<'db>>,
305
306    /// Map from nodes that create a scope to the scope they create.
307    scopes_by_node: FxHashMap<NodeWithScopeKey, FileScopeId>,
308
309    /// Map from a lambda expression to its containing statement.
310    enclosing_lambda_statements: FrozenMap<ExpressionNodeKey, Statement<'db>>,
311
312    // Map from a constraining use of a collection initializer to its definition.
313    collections_by_use: FrozenMap<ExpressionNodeKey, Definition<'db>>,
314
315    // Map from a collection initializer definition to statements containing a constraining use.
316    uses_by_collection: FrozenMap<Definition<'db>, Box<[(Statement<'db>, ExpressionNodeKey)]>>,
317
318    /// Map from the file-local [`FileScopeId`] to the salsa-ingredient [`ScopeId`].
319    scope_ids_by_scope: FrozenIndexVec<FileScopeId, ScopeId<'db>>,
320
321    /// Use-def map for each scope in this file.
322    use_def_maps: FrozenIndexVec<FileScopeId, Arc<UseDefMap<'db>>>,
323
324    /// Lookup table to map between node ids and ast nodes.
325    ///
326    /// Note: We should not depend on this map when analysing other files or
327    /// changing a file invalidates all dependents.
328    ast_ids: AstIds,
329
330    /// The set of modules that are imported anywhere within this file.
331    imported_modules: FrozenSet<ModuleName>,
332
333    /// Flags about the global scope (code usage impacting inference)
334    has_future_annotations: bool,
335
336    /// Map of all of the enclosing snapshots that appear in this file.
337    enclosing_snapshots: FrozenMap<EnclosingSnapshotKey, ScopedEnclosingSnapshotId>,
338
339    /// List of all semantic syntax errors in this file.
340    semantic_syntax_errors: Vec<SemanticSyntaxError>,
341
342    /// Set of all generator functions in this file.
343    generator_functions: FrozenSet<FileScopeId>,
344
345    /// Set of all asynchronous comprehensions in this file.
346    async_comprehensions: FrozenSet<FileScopeId>,
347
348    /// Narrowing alias metadata for predicate leaf names.
349    /// When a predicate references an alias variable (e.g., `is_none` from `is_none = x is None`),
350    /// the alias Name node is mapped to its aliased expression for constraint-generation time.
351    narrowing_alias_predicates: FrozenMap<ExpressionNodeKey, NarrowingAliasPredicate<'db>>,
352}
353
354#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
355pub struct NarrowingAliasPredicate<'db> {
356    /// Aliased expression, e.g., `x is None` in `is_none = x is None`.
357    pub expression: Expression<'db>,
358}
359
360impl<'db> SemanticIndex<'db> {
361    /// Returns the place table for a specific scope.
362    ///
363    /// Use the Salsa cached [`place_table()`] query if you only need the
364    /// place table for a single scope.
365    #[track_caller]
366    pub fn place_table(&self, scope_id: FileScopeId) -> &PlaceTable {
367        &self.place_tables[scope_id]
368    }
369
370    /// Returns alias metadata for an alias Name node in a predicate, if one exists.
371    pub fn narrowing_alias_predicate(
372        &self,
373        key: impl Into<ExpressionNodeKey>,
374    ) -> Option<&NarrowingAliasPredicate<'db>> {
375        self.narrowing_alias_predicates.get(&key.into())
376    }
377
378    /// Returns the use-def map for a specific scope.
379    ///
380    /// Use the Salsa cached [`use_def_map()`] query if you only need the
381    /// use-def map for a single scope.
382    #[track_caller]
383    pub fn use_def_map(&self, scope_id: FileScopeId) -> &UseDefMap<'db> {
384        &self.use_def_maps[scope_id]
385    }
386
387    /// Returns the set of modules that are imported anywhere in this file.
388    ///
389    /// This set only considers `import` statements, not `from...import` statements.
390    /// See `ModuleLiteralType::available_submodule_attributes` for discussion
391    /// of why this analysis is intentionally limited.
392    pub fn imported_modules(&self) -> impl Iterator<Item = &ModuleName> {
393        self.imported_modules.iter()
394    }
395
396    #[track_caller]
397    fn ast_ids(&self) -> &AstIds {
398        &self.ast_ids
399    }
400
401    /// Returns the ID of the `expression`'s enclosing scope.
402    #[track_caller]
403    pub fn expression_scope_id<E>(&self, expression: &E) -> FileScopeId
404    where
405        E: HasTrackedScope,
406    {
407        self.try_expression_scope_id(expression)
408            .expect("Expression to be part of a scope if it is from the same module")
409    }
410
411    /// Returns the ID of the `expression`'s enclosing scope.
412    pub fn try_expression_scope_id<E>(&self, expression: &E) -> Option<FileScopeId>
413    where
414        E: HasTrackedScope,
415    {
416        self.scopes_by_expression.try_get(expression)
417    }
418
419    /// Returns the [`Scope`] of the `expression`'s enclosing scope.
420    #[allow(unused)]
421    #[track_caller]
422    pub fn expression_scope(&self, expression: &impl HasTrackedScope) -> &Scope {
423        &self.scopes[self.expression_scope_id(expression)]
424    }
425
426    /// Returns the [`Scope`] with the given id.
427    #[track_caller]
428    pub fn scope(&self, id: FileScopeId) -> &Scope {
429        &self.scopes[id]
430    }
431
432    pub fn scope_ids(&self) -> impl Iterator<Item = ScopeId<'db>> + '_ {
433        self.scope_ids_by_scope.iter().copied()
434    }
435
436    pub fn symbol_is_global_in_scope(&self, symbol: ScopedSymbolId, scope: FileScopeId) -> bool {
437        self.place_table(scope).symbol(symbol).is_global()
438    }
439
440    /// Returns `true` if the given symbol in the given scope resolves to the global scope, either
441    /// because:
442    ///
443    /// 1. The given scope *is* the global scope.
444    /// 2. The symbol is explicitly declared `global` in the given scope.
445    /// 3. The symbol is a free variable in the given scope, and no enclosing function scope
446    ///    defines it.
447    ///
448    /// The third case requires walking ancestor scopes until we encounter either a binding or a
449    /// `nonlocal` declaration (those aren't allowed to resolve to the global scope, so we don't
450    /// need to chase them).
451    pub fn symbol_resolves_to_global_scope(
452        &self,
453        symbol: ScopedSymbolId,
454        scope: FileScopeId,
455    ) -> bool {
456        let symbol = self.place_table(scope).symbol(symbol);
457        let name = symbol.name();
458        // Note that `visible_ancestor_scopes` includes the starting scope itself.
459        for (visible_scope_id, _) in self.visible_ancestor_scopes(scope) {
460            if visible_scope_id.is_global() {
461                // We return `true` here even if the global variable isn't actually defined. That
462                // case will be probably a diagnostic elsewhere.
463                return true;
464            }
465            let place_table = self.place_table(visible_scope_id);
466            let Some(visible_symbol_id) = place_table.symbol_id(name) else {
467                continue;
468            };
469            let visible_symbol = place_table.symbol(visible_symbol_id);
470            if visible_symbol.is_global() {
471                return true;
472            }
473            if visible_symbol.is_local() || visible_symbol.is_nonlocal() {
474                return false;
475            }
476        }
477        unreachable!("should return true at the global scope above");
478    }
479
480    /// Returns the id of the parent scope.
481    pub fn parent_scope_id(&self, scope_id: FileScopeId) -> Option<FileScopeId> {
482        let scope = self.scope(scope_id);
483        scope.parent()
484    }
485
486    /// Returns the parent scope of `scope_id`.
487    #[track_caller]
488    pub fn parent_scope(&self, scope_id: FileScopeId) -> Option<&Scope> {
489        Some(&self.scopes[self.parent_scope_id(scope_id)?])
490    }
491
492    /// Return the [`Definition`] of the class enclosing this method, given the
493    /// method's body scope, or `None` if it is not a method.
494    pub fn class_definition_of_method(
495        &self,
496        function_body_scope: FileScopeId,
497    ) -> Option<Definition<'db>> {
498        let current_scope = self.scope(function_body_scope);
499        if current_scope.kind() != ScopeKind::Function {
500            return None;
501        }
502        let parent_scope_id = current_scope.parent()?;
503        let parent_scope = self.scope(parent_scope_id);
504
505        let class_scope = match parent_scope.kind() {
506            ScopeKind::Class => parent_scope,
507            ScopeKind::TypeParams => {
508                let class_scope_id = parent_scope.parent()?;
509                let potentially_class_scope = self.scope(class_scope_id);
510
511                match potentially_class_scope.kind() {
512                    ScopeKind::Class => potentially_class_scope,
513                    _ => return None,
514                }
515            }
516            _ => return None,
517        };
518
519        class_scope
520            .node()
521            .as_class()
522            .map(|node_ref| self.expect_single_definition(node_ref))
523    }
524
525    pub fn enclosing_lambda_statement(&self, lambda: ExpressionNodeKey) -> Option<Statement<'db>> {
526        self.enclosing_lambda_statements.get(&lambda).copied()
527    }
528
529    /// If this is a potentially constraining use of an unannotated collection initializer, returns
530    /// its definition.
531    pub fn unannotated_collection_initializer(
532        &self,
533        collection_use: &ast::Expr,
534    ) -> Option<Definition<'db>> {
535        self.collections_by_use.get(&collection_use.into()).copied()
536    }
537
538    /// Returns all potentially constraining uses of the given unannotated collection initializer.
539    pub fn constraining_collection_uses(
540        &self,
541        collection_def: Definition<'db>,
542    ) -> impl Iterator<Item = (Statement<'db>, ExpressionNodeKey)> {
543        self.uses_by_collection
544            .get(&collection_def)
545            .into_iter()
546            .flat_map(|uses| uses.iter().copied())
547    }
548
549    pub fn is_in_type_checking_block(&self, scope_id: FileScopeId, range: TextRange) -> bool {
550        self.ancestor_scopes(scope_id).any(|(scope_id, _)| {
551            self.use_def_map(scope_id)
552                .is_range_in_type_checking_block(range)
553        })
554    }
555
556    /// Returns an iterator over the descendent scopes of `scope`.
557    fn descendent_scopes(&self, scope: FileScopeId) -> DescendantsIter<'_> {
558        DescendantsIter::new(&self.scopes, scope)
559    }
560
561    /// Returns an iterator over the direct child scopes of `scope`.
562    pub fn child_scopes(&self, scope: FileScopeId) -> ChildrenIter<'_> {
563        ChildrenIter::new(&self.scopes, scope)
564    }
565
566    /// Returns an iterator over all ancestors of `scope`, starting with `scope` itself.
567    pub fn ancestor_scopes(&self, scope: FileScopeId) -> AncestorsIter<'_> {
568        AncestorsIter::new(&self.scopes, scope)
569    }
570
571    /// Returns an iterator over ancestors of `scope` that are visible for name resolution,
572    /// starting with `scope` itself. This follows Python's lexical scoping rules where
573    /// class scopes are skipped during name resolution (except for the starting scope
574    /// if it happens to be a class scope).
575    ///
576    /// For example, in this code:
577    /// ```python
578    /// x = 1
579    /// class A:
580    ///     x = 2
581    ///     def method(self):
582    ///         print(x)  # Refers to global x=1, not class x=2
583    /// ```
584    /// The `method` function can see the global scope but not the class scope.
585    pub fn visible_ancestor_scopes(&self, scope: FileScopeId) -> VisibleAncestorsIter<'_> {
586        VisibleAncestorsIter::new(&self.scopes, scope)
587    }
588
589    /// Returns the [`definition::Definition`] salsa ingredient(s) for `definition_key`.
590    ///
591    /// There will only ever be >1 `Definition` associated with a `definition_key`
592    /// if the definitions are created by a wildcard (`*`) import.
593    #[track_caller]
594    pub fn definitions(&self, definition_key: impl Into<DefinitionNodeKey>) -> &[Definition<'db>] {
595        self.definitions_by_node
596            .get(definition_key.into())
597            .expect("definition should be present in the semantic index")
598    }
599
600    /// Returns the [`definition::Definition`] salsa ingredient(s) for `definition_node`, if any.
601    pub fn try_definitions(
602        &self,
603        definition_node: ast::AnyNodeRef<'_>,
604    ) -> Option<&[Definition<'db>]> {
605        let definition_key = DefinitionNodeKey::from_node_ref(definition_node);
606        self.definitions_by_node.get(definition_key)
607    }
608
609    /// Returns the [`definition::Definition`] salsa ingredient for `definition_key`.
610    ///
611    /// ## Panics
612    ///
613    /// If the number of definitions associated with the key is not exactly 1 and
614    /// the `debug_assertions` feature is enabled, this method will panic.
615    ///
616    /// It is generally safe to use this method for any AST node that does not
617    /// correspond to a `*` (wildcard) import, since those are the only situations
618    /// that can result in multiple definitions being associated with a single AST
619    /// node.
620    #[track_caller]
621    pub fn expect_single_definition(
622        &self,
623        definition_key: impl Into<DefinitionNodeKey> + std::fmt::Debug + Copy,
624    ) -> Definition<'db> {
625        let definitions = self.definitions(definition_key);
626        debug_assert_eq!(
627            definitions.len(),
628            1,
629            "Expected exactly one definition to be associated with AST node {definition_key:?} but found {}",
630            definitions.len()
631        );
632        definitions[0]
633    }
634
635    pub fn try_definition(
636        &self,
637        definition_key: impl Into<DefinitionNodeKey>,
638    ) -> Option<Definition<'db>> {
639        self.definitions_by_node
640            .single
641            .get(&definition_key.into())
642            .copied()
643    }
644
645    /// Returns the [`Expression`] ingredient for an expression node.
646    /// Panics if we have no expression ingredient for that node. We can only call this method for
647    /// standalone-inferable expressions, which we call `add_standalone_expression` for in
648    /// `SemanticIndexBuilder`.
649    #[track_caller]
650    pub fn expression(&self, expression_key: impl Into<ExpressionNodeKey>) -> Expression<'db> {
651        self.expressions_by_node[&expression_key.into()]
652    }
653
654    pub fn try_expression(
655        &self,
656        expression_key: impl Into<ExpressionNodeKey>,
657    ) -> Option<Expression<'db>> {
658        self.expressions_by_node
659            .get(&expression_key.into())
660            .copied()
661    }
662
663    /// Returns the [`unpack::Unpack`] ingredient for an unpacking target, if any.
664    pub fn try_unpack(&self, target: impl Into<ExpressionNodeKey>) -> Option<unpack::Unpack<'db>> {
665        self.unpacks_by_target.get(&target.into()).copied()
666    }
667
668    pub fn is_standalone_expression(&self, expression_key: impl Into<ExpressionNodeKey>) -> bool {
669        self.expressions_by_node
670            .contains_key(&expression_key.into())
671    }
672
673    pub fn try_statement(
674        &self,
675        statement_key: impl Into<StatementNodeKey>,
676    ) -> Option<Statement<'db>> {
677        self.statements_by_node.get(&statement_key.into()).copied()
678    }
679
680    /// Returns the id of the scope that `node` creates.
681    /// This is different from [`definition::Definition::scope`] which
682    /// returns the scope in which that definition is defined in.
683    #[track_caller]
684    pub fn node_scope(&self, node: NodeWithScopeRef) -> FileScopeId {
685        self.scopes_by_node[&node.node_key()]
686    }
687
688    /// Returns the id of the scope that `node` creates, if it exists.
689    pub fn try_node_scope(&self, node: NodeWithScopeRef) -> Option<FileScopeId> {
690        self.scopes_by_node.get(&node.node_key()).copied()
691    }
692
693    /// Returns the id of the scope that the node identified by `key` creates.
694    ///
695    /// This is useful when you have a [`NodeWithScopeKey`] constructed from an
696    /// [`AstNodeRef`](crate::ast_node_ref::AstNodeRef) and want to avoid loading
697    /// the parsed module just to look up the scope.
698    pub fn node_scope_by_key(&self, key: NodeWithScopeKey) -> FileScopeId {
699        self.scopes_by_node[&key]
700    }
701
702    /// Checks if there is an import of `__future__.annotations` in the global scope, which affects
703    /// the logic for type inference.
704    pub fn has_future_annotations(&self) -> bool {
705        self.has_future_annotations
706    }
707
708    /// Returns
709    /// * `NoLongerInEagerContext` if the nested scope is no longer in an eager context
710    ///   (that is, not every scope that will be traversed is eager) and no lazy snapshots were found.
711    /// *  an iterator of bindings for a particular nested scope reference if the bindings exist.
712    /// *  a narrowing constraint if there are no bindings, but there is a narrowing constraint for an enclosing scope place.
713    /// * `NotFound` if the narrowing constraint / bindings do not exist in the nested scope.
714    pub fn enclosing_snapshot(
715        &self,
716        enclosing_scope: FileScopeId,
717        expr: PlaceExprRef,
718        nested_scope: FileScopeId,
719    ) -> EnclosingSnapshotResult<'_, 'db> {
720        for (ancestor_scope_id, ancestor_scope) in self.ancestor_scopes(nested_scope) {
721            if ancestor_scope_id == enclosing_scope {
722                break;
723            }
724            if !ancestor_scope.is_eager() {
725                if let PlaceExprRef::Symbol(symbol) = expr
726                    && let Some(place_id) =
727                        self.place_tables[enclosing_scope].symbol_id(symbol.name())
728                {
729                    let key = EnclosingSnapshotKey {
730                        enclosing_scope,
731                        enclosing_place: place_id.into(),
732                        nested_scope,
733                        nested_laziness: ScopeLaziness::Lazy,
734                    };
735                    if let Some(id) = self.enclosing_snapshots.get(&key) {
736                        return self.use_def_maps[enclosing_scope]
737                            .enclosing_snapshot(*id, key.nested_laziness);
738                    }
739                }
740                return EnclosingSnapshotResult::NoLongerInEagerContext;
741            }
742        }
743        let Some(place_id) = self.place_tables[enclosing_scope].place_id(expr) else {
744            return EnclosingSnapshotResult::NotFound;
745        };
746        let key = EnclosingSnapshotKey {
747            enclosing_scope,
748            enclosing_place: place_id,
749            nested_scope,
750            nested_laziness: ScopeLaziness::Eager,
751        };
752        let Some(id) = self.enclosing_snapshots.get(&key) else {
753            return EnclosingSnapshotResult::NotFound;
754        };
755        self.use_def_maps[enclosing_scope].enclosing_snapshot(*id, key.nested_laziness)
756    }
757
758    pub fn semantic_syntax_errors(&self) -> &[SemanticSyntaxError] {
759        &self.semantic_syntax_errors
760    }
761}
762
763pub struct AncestorsIter<'a> {
764    scopes: &'a IndexSlice<FileScopeId, Scope>,
765    next_id: Option<FileScopeId>,
766}
767
768impl<'a> AncestorsIter<'a> {
769    fn new(scopes: &'a IndexSlice<FileScopeId, Scope>, start: FileScopeId) -> Self {
770        Self {
771            scopes,
772            next_id: Some(start),
773        }
774    }
775}
776
777impl<'a> Iterator for AncestorsIter<'a> {
778    type Item = (FileScopeId, &'a Scope);
779
780    fn next(&mut self) -> Option<Self::Item> {
781        let current_id = self.next_id?;
782        let current = &self.scopes[current_id];
783        self.next_id = current.parent();
784
785        Some((current_id, current))
786    }
787}
788
789impl FusedIterator for AncestorsIter<'_> {}
790
791pub struct VisibleAncestorsIter<'a> {
792    inner: AncestorsIter<'a>,
793    starting_scope_kind: ScopeKind,
794    yielded_count: usize,
795}
796
797impl<'a> VisibleAncestorsIter<'a> {
798    fn new(scopes: &'a IndexSlice<FileScopeId, Scope>, start: FileScopeId) -> Self {
799        let starting_scope = &scopes[start];
800        Self {
801            inner: AncestorsIter::new(scopes, start),
802            starting_scope_kind: starting_scope.kind(),
803            yielded_count: 0,
804        }
805    }
806}
807
808impl<'a> Iterator for VisibleAncestorsIter<'a> {
809    type Item = (FileScopeId, &'a Scope);
810
811    fn next(&mut self) -> Option<Self::Item> {
812        loop {
813            let (scope_id, scope) = self.inner.next()?;
814            self.yielded_count += 1;
815
816            // Always return the first scope (the starting scope)
817            if self.yielded_count == 1 {
818                return Some((scope_id, scope));
819            }
820
821            // Skip class scopes for subsequent scopes (following Python's lexical scoping rules)
822            // Exception: type parameter scopes can see names defined in an immediately-enclosing class scope
823            if scope.kind() == ScopeKind::Class {
824                // Allow annotation scopes to see their immediately-enclosing class scope exactly once
825                if self.starting_scope_kind.is_annotation() && self.yielded_count == 2 {
826                    return Some((scope_id, scope));
827                }
828                continue;
829            }
830
831            return Some((scope_id, scope));
832        }
833    }
834}
835
836impl FusedIterator for VisibleAncestorsIter<'_> {}
837
838pub(crate) struct DescendantsIter<'a> {
839    next_id: FileScopeId,
840    descendants: std::slice::Iter<'a, Scope>,
841}
842
843impl<'a> DescendantsIter<'a> {
844    fn new(scopes: &'a IndexSlice<FileScopeId, Scope>, scope_id: FileScopeId) -> Self {
845        let scope = &scopes[scope_id];
846        let scopes = &scopes[scope.descendants()];
847
848        Self {
849            next_id: scope_id + 1,
850            descendants: scopes.iter(),
851        }
852    }
853}
854
855impl<'a> Iterator for DescendantsIter<'a> {
856    type Item = (FileScopeId, &'a Scope);
857
858    fn next(&mut self) -> Option<Self::Item> {
859        let descendant = self.descendants.next()?;
860        let id = self.next_id;
861        self.next_id = self.next_id + 1;
862
863        Some((id, descendant))
864    }
865
866    fn size_hint(&self) -> (usize, Option<usize>) {
867        self.descendants.size_hint()
868    }
869}
870
871impl FusedIterator for DescendantsIter<'_> {}
872
873impl ExactSizeIterator for DescendantsIter<'_> {}
874
875pub struct ChildrenIter<'a> {
876    parent: FileScopeId,
877    descendants: DescendantsIter<'a>,
878}
879
880impl<'a> ChildrenIter<'a> {
881    fn new(scopes: &'a IndexSlice<FileScopeId, Scope>, parent: FileScopeId) -> Self {
882        let descendants = DescendantsIter::new(scopes, parent);
883
884        Self {
885            parent,
886            descendants,
887        }
888    }
889}
890
891impl<'a> Iterator for ChildrenIter<'a> {
892    type Item = (FileScopeId, &'a Scope);
893
894    fn next(&mut self) -> Option<Self::Item> {
895        self.descendants
896            .find(|(_, scope)| scope.parent() == Some(self.parent))
897    }
898}
899
900impl FusedIterator for ChildrenIter<'_> {}
901
902/// Interval map that maps a range of expression node ids to their corresponding scopes.
903///
904/// Lookups require `O(log n)` time, where `n` is roughly the number of scopes (roughly
905/// because sub-scopes can be interleaved with expressions in the outer scope, e.g. function, some statements, a function).
906#[derive(Eq, PartialEq, Debug, get_size2::GetSize, Default)]
907struct ExpressionsScopeMap(Box<[(std::ops::RangeInclusive<NodeIndex>, FileScopeId)]>);
908
909impl ExpressionsScopeMap {
910    fn try_get<E>(&self, node: &E) -> Option<FileScopeId>
911    where
912        E: HasTrackedScope,
913    {
914        let node_index = node.node_index().load();
915
916        let entry = self
917            .0
918            .binary_search_by_key(&node_index, |(range, _)| *range.start());
919
920        let index = match entry {
921            Ok(index) => index,
922            Err(index) => index.checked_sub(1)?,
923        };
924
925        let (range, scope) = &self.0[index];
926        if range.contains(&node_index) {
927            Some(*scope)
928        } else {
929            None
930        }
931    }
932}
933
934#[derive(Debug, Copy, Clone, PartialEq, Eq, get_size2::GetSize)]
935pub enum Truthiness {
936    /// For an object `x`, `bool(x)` will always return `True`
937    AlwaysTrue,
938    /// For an object `x`, `bool(x)` will always return `False`
939    AlwaysFalse,
940    /// For an object `x`, `bool(x)` could return either `True` or `False`
941    Ambiguous,
942}
943
944impl Truthiness {
945    pub const fn is_ambiguous(self) -> bool {
946        matches!(self, Truthiness::Ambiguous)
947    }
948
949    pub const fn is_always_false(self) -> bool {
950        matches!(self, Truthiness::AlwaysFalse)
951    }
952
953    pub const fn may_be_true(self) -> bool {
954        !self.is_always_false()
955    }
956
957    pub const fn is_always_true(self) -> bool {
958        matches!(self, Truthiness::AlwaysTrue)
959    }
960
961    #[must_use]
962    pub const fn negate(self) -> Self {
963        match self {
964            Self::AlwaysTrue => Self::AlwaysFalse,
965            Self::AlwaysFalse => Self::AlwaysTrue,
966            Self::Ambiguous => Self::Ambiguous,
967        }
968    }
969
970    #[must_use]
971    pub const fn negate_if(self, condition: bool) -> Self {
972        if condition { self.negate() } else { self }
973    }
974
975    #[must_use]
976    pub fn or(self, other: Self) -> Self {
977        match self {
978            Truthiness::AlwaysTrue => self,
979            Truthiness::AlwaysFalse => other,
980            Truthiness::Ambiguous => match other {
981                Truthiness::AlwaysTrue => Truthiness::AlwaysTrue,
982                Truthiness::AlwaysFalse | Truthiness::Ambiguous => Truthiness::Ambiguous,
983            },
984        }
985    }
986
987    #[must_use]
988    pub fn or_else(self, other: impl Fn() -> Self) -> Self {
989        match self {
990            Truthiness::AlwaysTrue => self,
991            Truthiness::AlwaysFalse => other(),
992            Truthiness::Ambiguous => match other() {
993                Truthiness::AlwaysTrue => Truthiness::AlwaysTrue,
994                Truthiness::AlwaysFalse | Truthiness::Ambiguous => Truthiness::Ambiguous,
995            },
996        }
997    }
998}
999
1000impl From<bool> for Truthiness {
1001    fn from(value: bool) -> Self {
1002        if value {
1003            Truthiness::AlwaysTrue
1004        } else {
1005            Truthiness::AlwaysFalse
1006        }
1007    }
1008}
1009
1010#[derive(Clone, Copy, Debug, Hash, PartialEq, get_size2::GetSize)]
1011pub enum EvaluationMode {
1012    Sync,
1013    Async,
1014}
1015
1016impl EvaluationMode {
1017    pub const fn from_is_async(is_async: bool) -> Self {
1018        if is_async {
1019            EvaluationMode::Async
1020        } else {
1021            EvaluationMode::Sync
1022        }
1023    }
1024
1025    pub const fn is_async(self) -> bool {
1026        matches!(self, EvaluationMode::Async)
1027    }
1028}
1029
1030/// Specifies how the boundness of a place should be determined.
1031#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1032pub enum BoundnessAnalysis {
1033    /// The place is always considered bound.
1034    AssumeBound,
1035    /// The boundness of the place is determined based on the visibility of the implicit
1036    /// `unbound` binding. In the example below, when analyzing the visibility of the
1037    /// `x = <unbound>` binding from the position of the end of the scope, it would be
1038    /// `Truthiness::Ambiguous`, because it could either be visible or not, depending on the
1039    /// `flag()` return value. This would result in a `Definedness::PossiblyUndefined` for `x`.
1040    ///
1041    /// ```py
1042    /// x = <unbound>
1043    ///
1044    /// if flag():
1045    ///     x = 1
1046    /// ```
1047    BasedOnUnboundVisibility,
1048}
1049
1050/// A set of places that could possibly be narrowed by a predicate.
1051///
1052/// This is a conservative upper bound - all places that actually get narrowed
1053/// will be in this set, but there may be additional places that end up not
1054/// being narrowed after full analysis.
1055pub type PossiblyNarrowedPlaces = FxHashSet<ScopedPlaceId>;
1056
1057/// Implemented by types for which the semantic index tracks their scope.
1058pub trait HasTrackedScope: ast::HasNodeIndex {}
1059
1060impl HasTrackedScope for ast::Expr {}
1061impl HasTrackedScope for ast::ExprRef<'_> {}
1062impl HasTrackedScope for &ast::ExprRef<'_> {}
1063
1064// We never explicitly register the scope of an `Identifier`.
1065// However, `ExpressionsScopeMap` stores the text ranges of each scope.
1066// That allows us to look up the identifier's scope for as long as it's
1067// inside an expression (because the ranges overlap).
1068impl HasTrackedScope for ast::Identifier {}
1069
1070#[cfg(test)]
1071mod tests {
1072    use ruff_db::{
1073        files::{File, system_path_to_file},
1074        parsed::ParsedModuleRef,
1075    };
1076    use ruff_python_ast as ast;
1077    use ruff_text_size::{Ranged, TextRange};
1078
1079    use super::*;
1080
1081    use crate::{
1082        ast_ids::{HasScopedUseId, ScopedUseId},
1083        db::tests::{TestDb, TestDbBuilder},
1084        definition::{
1085            DefinitionKind, LambdaParameterDefinitionNodeKind, ParameterDefinitionNodeKind,
1086        },
1087        program::Program,
1088    };
1089
1090    impl UseDefMap<'_> {
1091        fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
1092            self.end_of_scope_symbol_bindings(symbol)
1093                .find_map(|constrained_binding| constrained_binding.binding.definition())
1094        }
1095
1096        fn first_public_declaration(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
1097            self.end_of_scope_symbol_declarations(symbol)
1098                .find_map(|declaration_with_constraint| {
1099                    declaration_with_constraint.declaration.definition()
1100                })
1101        }
1102
1103        fn first_binding_at_use(&self, use_id: ScopedUseId) -> Option<Definition<'_>> {
1104            self.bindings_at_use(use_id)
1105                .find_map(|constrained_binding| constrained_binding.binding.definition())
1106        }
1107    }
1108
1109    struct TestCase {
1110        db: TestDb,
1111        file: File,
1112    }
1113
1114    fn test_case(content: &str) -> TestCase {
1115        const FILENAME: &str = "test.py";
1116
1117        let db = TestDbBuilder::new()
1118            .with_file(FILENAME, content)
1119            .build()
1120            .unwrap();
1121
1122        let file = system_path_to_file(&db, FILENAME).unwrap();
1123
1124        TestCase { db, file }
1125    }
1126
1127    fn program_file(db: &TestDb, file: File) -> ProgramFile<'_> {
1128        db.program().program_file(db, file)
1129    }
1130
1131    fn names(table: &PlaceTable) -> Vec<String> {
1132        table
1133            .symbols()
1134            .map(|expr| expr.name().to_string())
1135            .collect()
1136    }
1137
1138    #[test]
1139    fn empty() {
1140        let TestCase { db, file } = test_case("");
1141        let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
1142
1143        let global_names = names(global_table);
1144
1145        assert_eq!(global_names, Vec::<&str>::new());
1146    }
1147
1148    #[test]
1149    fn simple() {
1150        let TestCase { db, file } = test_case("x");
1151        let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
1152
1153        assert_eq!(names(global_table), vec!["x"]);
1154    }
1155
1156    #[test]
1157    fn annotation_only() {
1158        let TestCase { db, file } = test_case("x: int");
1159        let scope = global_scope(&db, program_file(&db, file));
1160        let global_table = place_table(&db, scope);
1161
1162        assert_eq!(names(global_table), vec!["int", "x"]);
1163
1164        let use_def = use_def_map(&db, scope);
1165        let declaration = use_def
1166            .first_public_declaration(global_table.symbol_id("x").expect("symbol to exist"))
1167            .unwrap();
1168        assert!(matches!(
1169            declaration.kind(&db),
1170            DefinitionKind::AnnotatedAssignment(_)
1171        ));
1172    }
1173
1174    #[test]
1175    fn import() {
1176        let TestCase { db, file } = test_case("import foo");
1177        let scope = global_scope(&db, program_file(&db, file));
1178        let global_table = place_table(&db, scope);
1179
1180        assert_eq!(names(global_table), vec!["foo"]);
1181        let foo = global_table.symbol_id("foo").unwrap();
1182
1183        let use_def = use_def_map(&db, scope);
1184        let binding = use_def.first_public_binding(foo).unwrap();
1185        assert!(matches!(binding.kind(&db), DefinitionKind::Import(_)));
1186    }
1187
1188    #[test]
1189    fn import_sub() {
1190        let TestCase { db, file } = test_case("import foo.bar");
1191        let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
1192
1193        assert_eq!(names(global_table), vec!["foo"]);
1194    }
1195
1196    #[test]
1197    fn import_as() {
1198        let TestCase { db, file } = test_case("import foo.bar as baz");
1199        let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
1200
1201        assert_eq!(names(global_table), vec!["baz"]);
1202    }
1203
1204    #[test]
1205    fn import_from() {
1206        let TestCase { db, file } = test_case("from bar import foo");
1207        let scope = global_scope(&db, program_file(&db, file));
1208        let global_table = place_table(&db, scope);
1209
1210        assert_eq!(names(global_table), vec!["foo"]);
1211        assert!(
1212            global_table
1213                .symbol_by_name("foo")
1214                .is_some_and(|symbol| { symbol.is_bound() && !symbol.is_used() }),
1215            "symbols that are defined get the defined flag"
1216        );
1217
1218        let use_def = use_def_map(&db, scope);
1219        let binding = use_def
1220            .first_public_binding(global_table.symbol_id("foo").expect("symbol to exist"))
1221            .unwrap();
1222        assert!(matches!(binding.kind(&db), DefinitionKind::ImportFrom(_)));
1223    }
1224
1225    #[test]
1226    fn assign() {
1227        let TestCase { db, file } = test_case("x = foo");
1228        let scope = global_scope(&db, program_file(&db, file));
1229        let global_table = place_table(&db, scope);
1230
1231        assert_eq!(names(global_table), vec!["foo", "x"]);
1232        assert!(
1233            global_table
1234                .symbol_by_name("foo")
1235                .is_some_and(|symbol| { !symbol.is_bound() && symbol.is_used() }),
1236            "a symbol used but not bound in a scope should have only the used flag"
1237        );
1238        let use_def = use_def_map(&db, scope);
1239        let binding = use_def
1240            .first_public_binding(global_table.symbol_id("x").expect("symbol exists"))
1241            .unwrap();
1242        assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
1243    }
1244
1245    #[test]
1246    fn augmented_assignment() {
1247        let TestCase { db, file } = test_case("x += 1");
1248        let scope = global_scope(&db, program_file(&db, file));
1249        let global_table = place_table(&db, scope);
1250
1251        assert_eq!(names(global_table), vec!["x"]);
1252
1253        let use_def = use_def_map(&db, scope);
1254        let binding = use_def
1255            .first_public_binding(global_table.symbol_id("x").unwrap())
1256            .unwrap();
1257
1258        assert!(matches!(
1259            binding.kind(&db),
1260            DefinitionKind::AugmentedAssignment(_)
1261        ));
1262    }
1263
1264    #[test]
1265    fn class_scope() {
1266        let TestCase { db, file } = test_case(
1267            "
1268class C:
1269    x = 1
1270y = 2
1271",
1272        );
1273        let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
1274
1275        assert_eq!(names(global_table), vec!["C", "y"]);
1276
1277        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1278        let index = semantic_index(&db, program_file(&db, file));
1279
1280        let [(class_scope_id, class_scope)] = index
1281            .child_scopes(FileScopeId::global())
1282            .collect::<Vec<_>>()[..]
1283        else {
1284            panic!("expected one child scope")
1285        };
1286        assert_eq!(class_scope.kind(), ScopeKind::Class);
1287        assert_eq!(
1288            class_scope_id
1289                .to_scope_id(&db, program_file(&db, file))
1290                .name(&db, &module),
1291            "C"
1292        );
1293
1294        let class_table = index.place_table(class_scope_id);
1295        assert_eq!(names(class_table), vec!["x"]);
1296
1297        let use_def = index.use_def_map(class_scope_id);
1298        let binding = use_def
1299            .first_public_binding(class_table.symbol_id("x").expect("symbol exists"))
1300            .unwrap();
1301        assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
1302    }
1303
1304    #[test]
1305    fn function_scope() {
1306        let TestCase { db, file } = test_case(
1307            "
1308def func():
1309    x = 1
1310y = 2
1311",
1312        );
1313        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1314        let index = semantic_index(&db, program_file(&db, file));
1315        let global_table = index.place_table(FileScopeId::global());
1316
1317        assert_eq!(names(global_table), vec!["func", "y"]);
1318
1319        let [(function_scope_id, function_scope)] = index
1320            .child_scopes(FileScopeId::global())
1321            .collect::<Vec<_>>()[..]
1322        else {
1323            panic!("expected one child scope")
1324        };
1325        assert_eq!(function_scope.kind(), ScopeKind::Function);
1326        assert_eq!(
1327            function_scope_id
1328                .to_scope_id(&db, program_file(&db, file))
1329                .name(&db, &module),
1330            "func"
1331        );
1332
1333        let function_table = index.place_table(function_scope_id);
1334        assert_eq!(names(function_table), vec!["x"]);
1335
1336        let use_def = index.use_def_map(function_scope_id);
1337        let binding = use_def
1338            .first_public_binding(function_table.symbol_id("x").expect("symbol exists"))
1339            .unwrap();
1340        assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
1341    }
1342
1343    #[test]
1344    fn function_parameter_symbols() {
1345        let TestCase { db, file } = test_case(
1346            "
1347def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
1348    pass
1349",
1350        );
1351
1352        let index = semantic_index(&db, program_file(&db, file));
1353        let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
1354
1355        assert_eq!(names(global_table), vec!["str", "int", "f"]);
1356
1357        let [(function_scope_id, _function_scope)] = index
1358            .child_scopes(FileScopeId::global())
1359            .collect::<Vec<_>>()[..]
1360        else {
1361            panic!("Expected a function scope")
1362        };
1363
1364        let function_table = index.place_table(function_scope_id);
1365        assert_eq!(
1366            names(function_table),
1367            vec!["a", "b", "c", "d", "args", "kwargs"],
1368        );
1369
1370        let use_def = index.use_def_map(function_scope_id);
1371        for name in ["a", "b", "c", "d"] {
1372            let binding = use_def
1373                .first_public_binding(function_table.symbol_id(name).expect("symbol exists"))
1374                .unwrap();
1375            assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_)));
1376        }
1377        let args_binding = use_def
1378            .first_public_binding(function_table.symbol_id("args").expect("symbol exists"))
1379            .unwrap();
1380        assert!(matches!(
1381            args_binding.kind(&db),
1382            DefinitionKind::Parameter(ParameterDefinitionNodeKind::VariadicPositionalParameter(_))
1383        ));
1384        let kwargs_binding = use_def
1385            .first_public_binding(function_table.symbol_id("kwargs").expect("symbol exists"))
1386            .unwrap();
1387        assert!(matches!(
1388            kwargs_binding.kind(&db),
1389            DefinitionKind::Parameter(ParameterDefinitionNodeKind::VariadicKeywordParameter(_))
1390        ));
1391    }
1392
1393    #[test]
1394    fn lambda_parameter_symbols() {
1395        let TestCase { db, file } = test_case("lambda a, b, c=1, *args, d=2, **kwargs: None");
1396
1397        let index = semantic_index(&db, program_file(&db, file));
1398        let global_table = place_table(&db, global_scope(&db, program_file(&db, file)));
1399
1400        assert!(names(global_table).is_empty());
1401
1402        let [(lambda_scope_id, _lambda_scope)] = index
1403            .child_scopes(FileScopeId::global())
1404            .collect::<Vec<_>>()[..]
1405        else {
1406            panic!("Expected a lambda scope")
1407        };
1408
1409        let lambda_table = index.place_table(lambda_scope_id);
1410        assert_eq!(
1411            names(lambda_table),
1412            vec!["a", "b", "c", "args", "d", "kwargs"],
1413        );
1414
1415        let use_def = index.use_def_map(lambda_scope_id);
1416        for name in ["a", "b", "c", "d"] {
1417            let binding = use_def
1418                .first_public_binding(lambda_table.symbol_id(name).expect("symbol exists"))
1419                .unwrap();
1420            assert!(matches!(
1421                binding.kind(&db),
1422                DefinitionKind::LambdaParameter(LambdaParameterDefinitionNodeKind {
1423                    index: _,
1424                    lambda: _,
1425                    parameter: ParameterDefinitionNodeKind::Parameter(_)
1426                })
1427            ));
1428        }
1429        let args_binding = use_def
1430            .first_public_binding(lambda_table.symbol_id("args").expect("symbol exists"))
1431            .unwrap();
1432        assert!(matches!(
1433            args_binding.kind(&db),
1434            DefinitionKind::LambdaParameter(LambdaParameterDefinitionNodeKind {
1435                index: 3,
1436                lambda: _,
1437                parameter: ParameterDefinitionNodeKind::VariadicPositionalParameter(_)
1438            })
1439        ));
1440        let kwargs_binding = use_def
1441            .first_public_binding(lambda_table.symbol_id("kwargs").expect("symbol exists"))
1442            .unwrap();
1443        assert!(matches!(
1444            kwargs_binding.kind(&db),
1445            DefinitionKind::LambdaParameter(LambdaParameterDefinitionNodeKind {
1446                index: 5,
1447                lambda: _,
1448                parameter: ParameterDefinitionNodeKind::VariadicKeywordParameter(_)
1449            })
1450        ));
1451    }
1452
1453    /// Test case to validate that the comprehension scope is correctly identified and that the target
1454    /// variable is defined only in the comprehension scope and not in the global scope.
1455    #[test]
1456    fn comprehension_scope() {
1457        let TestCase { db, file } = test_case(
1458            "
1459[x for x, y in iter1]
1460",
1461        );
1462
1463        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1464        let index = semantic_index(&db, program_file(&db, file));
1465        let global_table = index.place_table(FileScopeId::global());
1466
1467        assert_eq!(names(global_table), vec!["iter1"]);
1468
1469        let [(comprehension_scope_id, comprehension_scope)] = index
1470            .child_scopes(FileScopeId::global())
1471            .collect::<Vec<_>>()[..]
1472        else {
1473            panic!("expected one child scope")
1474        };
1475
1476        assert_eq!(comprehension_scope.kind(), ScopeKind::Comprehension);
1477        assert_eq!(
1478            comprehension_scope_id
1479                .to_scope_id(&db, program_file(&db, file))
1480                .name(&db, &module),
1481            "<listcomp>"
1482        );
1483
1484        let comprehension_symbol_table = index.place_table(comprehension_scope_id);
1485
1486        assert_eq!(names(comprehension_symbol_table), vec!["x", "y"]);
1487
1488        let use_def = index.use_def_map(comprehension_scope_id);
1489        for name in ["x", "y"] {
1490            let binding = use_def
1491                .first_public_binding(
1492                    comprehension_symbol_table
1493                        .symbol_id(name)
1494                        .expect("symbol exists"),
1495                )
1496                .unwrap();
1497            assert!(matches!(
1498                binding.kind(&db),
1499                DefinitionKind::Comprehension(_)
1500            ));
1501        }
1502    }
1503
1504    /// Test case to validate that the `x` variable used in the comprehension is referencing the
1505    /// `x` variable defined by the inner generator (`for x in iter2`) and not the outer one.
1506    #[test]
1507    fn multiple_generators() {
1508        let TestCase { db, file } = test_case(
1509            "
1510[x for x in iter1 for x in iter2]
1511",
1512        );
1513
1514        let index = semantic_index(&db, program_file(&db, file));
1515        let [(comprehension_scope_id, _)] = index
1516            .child_scopes(FileScopeId::global())
1517            .collect::<Vec<_>>()[..]
1518        else {
1519            panic!("expected one child scope")
1520        };
1521
1522        let use_def = index.use_def_map(comprehension_scope_id);
1523
1524        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1525        let syntax = module.syntax();
1526        let element = syntax.body[0]
1527            .as_expr_stmt()
1528            .unwrap()
1529            .value
1530            .as_list_comp_expr()
1531            .unwrap()
1532            .elt
1533            .as_name_expr()
1534            .unwrap();
1535        let element_use_id = element.scoped_use_id(&db, program_file(&db, file));
1536
1537        let binding = use_def.first_binding_at_use(element_use_id).unwrap();
1538        let DefinitionKind::Comprehension(comprehension) = binding.kind(&db) else {
1539            panic!("expected generator definition")
1540        };
1541        let target = comprehension.target(&module);
1542        let name = target.as_name_expr().unwrap().id().as_str();
1543
1544        assert_eq!(name, "x");
1545        assert_eq!(target.range(), TextRange::new(23.into(), 24.into()));
1546    }
1547
1548    /// Test case to validate that the nested comprehension creates a new scope which is a child of
1549    /// the outer comprehension scope and the variables are correctly defined in the respective
1550    /// scopes.
1551    #[test]
1552    fn nested_generators() {
1553        let TestCase { db, file } = test_case(
1554            "
1555[{x for x in iter2} for y in iter1]
1556",
1557        );
1558
1559        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1560        let index = semantic_index(&db, program_file(&db, file));
1561        let global_table = index.place_table(FileScopeId::global());
1562
1563        assert_eq!(names(global_table), vec!["iter1"]);
1564
1565        let [(comprehension_scope_id, comprehension_scope)] = index
1566            .child_scopes(FileScopeId::global())
1567            .collect::<Vec<_>>()[..]
1568        else {
1569            panic!("expected one child scope")
1570        };
1571
1572        assert_eq!(comprehension_scope.kind(), ScopeKind::Comprehension);
1573        assert_eq!(
1574            comprehension_scope_id
1575                .to_scope_id(&db, program_file(&db, file))
1576                .name(&db, &module),
1577            "<listcomp>"
1578        );
1579
1580        let comprehension_symbol_table = index.place_table(comprehension_scope_id);
1581
1582        assert_eq!(names(comprehension_symbol_table), vec!["y", "iter2"]);
1583
1584        let [(inner_comprehension_scope_id, inner_comprehension_scope)] = index
1585            .child_scopes(comprehension_scope_id)
1586            .collect::<Vec<_>>()[..]
1587        else {
1588            panic!("expected one inner generator scope")
1589        };
1590
1591        assert_eq!(inner_comprehension_scope.kind(), ScopeKind::Comprehension);
1592        assert_eq!(
1593            inner_comprehension_scope_id
1594                .to_scope_id(&db, program_file(&db, file))
1595                .name(&db, &module),
1596            "<setcomp>"
1597        );
1598
1599        let inner_comprehension_symbol_table = index.place_table(inner_comprehension_scope_id);
1600
1601        assert_eq!(names(inner_comprehension_symbol_table), vec!["x"]);
1602    }
1603
1604    #[test]
1605    fn with_item_definition() {
1606        let TestCase { db, file } = test_case(
1607            "
1608with item1 as x, item2 as y:
1609    pass
1610",
1611        );
1612
1613        let index = semantic_index(&db, program_file(&db, file));
1614        let global_table = index.place_table(FileScopeId::global());
1615
1616        assert_eq!(names(global_table), vec!["item1", "x", "item2", "y"]);
1617
1618        let use_def = index.use_def_map(FileScopeId::global());
1619        for name in ["x", "y"] {
1620            let binding = use_def
1621                .first_public_binding(global_table.symbol_id(name).expect("symbol exists"))
1622                .expect("Expected with item definition for {name}");
1623            assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
1624        }
1625    }
1626
1627    #[test]
1628    fn with_item_unpacked_definition() {
1629        let TestCase { db, file } = test_case(
1630            "
1631with context() as (x, y):
1632    pass
1633",
1634        );
1635
1636        let index = semantic_index(&db, program_file(&db, file));
1637        let global_table = index.place_table(FileScopeId::global());
1638
1639        assert_eq!(names(global_table), vec!["context", "x", "y"]);
1640
1641        let use_def = index.use_def_map(FileScopeId::global());
1642        for name in ["x", "y"] {
1643            let binding = use_def
1644                .first_public_binding(global_table.symbol_id(name).expect("symbol exists"))
1645                .expect("Expected with item definition for {name}");
1646            assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
1647        }
1648    }
1649
1650    #[test]
1651    fn dupes() {
1652        let TestCase { db, file } = test_case(
1653            "
1654def func():
1655    x = 1
1656def func():
1657    y = 2
1658",
1659        );
1660        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1661        let index = semantic_index(&db, program_file(&db, file));
1662        let global_table = index.place_table(FileScopeId::global());
1663
1664        assert_eq!(names(global_table), vec!["func"]);
1665        let [
1666            (func_scope1_id, func_scope_1),
1667            (func_scope2_id, func_scope_2),
1668        ] = index
1669            .child_scopes(FileScopeId::global())
1670            .collect::<Vec<_>>()[..]
1671        else {
1672            panic!("expected two child scopes");
1673        };
1674
1675        assert_eq!(func_scope_1.kind(), ScopeKind::Function);
1676
1677        assert_eq!(
1678            func_scope1_id
1679                .to_scope_id(&db, program_file(&db, file))
1680                .name(&db, &module),
1681            "func"
1682        );
1683        assert_eq!(func_scope_2.kind(), ScopeKind::Function);
1684        assert_eq!(
1685            func_scope2_id
1686                .to_scope_id(&db, program_file(&db, file))
1687                .name(&db, &module),
1688            "func"
1689        );
1690
1691        let func1_table = index.place_table(func_scope1_id);
1692        let func2_table = index.place_table(func_scope2_id);
1693        assert_eq!(names(func1_table), vec!["x"]);
1694        assert_eq!(names(func2_table), vec!["y"]);
1695
1696        let use_def = index.use_def_map(FileScopeId::global());
1697        let binding = use_def
1698            .first_public_binding(global_table.symbol_id("func").expect("symbol exists"))
1699            .unwrap();
1700        assert!(matches!(binding.kind(&db), DefinitionKind::Function(_)));
1701    }
1702
1703    #[test]
1704    fn generic_function() {
1705        let TestCase { db, file } = test_case(
1706            "
1707def func[T]():
1708    x = 1
1709",
1710        );
1711
1712        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1713        let index = semantic_index(&db, program_file(&db, file));
1714        let global_table = index.place_table(FileScopeId::global());
1715
1716        assert_eq!(names(global_table), vec!["func"]);
1717
1718        let [(ann_scope_id, ann_scope)] = index
1719            .child_scopes(FileScopeId::global())
1720            .collect::<Vec<_>>()[..]
1721        else {
1722            panic!("expected one child scope");
1723        };
1724
1725        assert_eq!(ann_scope.kind(), ScopeKind::TypeParams);
1726        assert_eq!(
1727            ann_scope_id
1728                .to_scope_id(&db, program_file(&db, file))
1729                .name(&db, &module),
1730            "func"
1731        );
1732        let ann_table = index.place_table(ann_scope_id);
1733        assert_eq!(names(ann_table), vec!["T"]);
1734
1735        let [(func_scope_id, func_scope)] =
1736            index.child_scopes(ann_scope_id).collect::<Vec<_>>()[..]
1737        else {
1738            panic!("expected one child scope");
1739        };
1740        assert_eq!(func_scope.kind(), ScopeKind::Function);
1741        assert_eq!(
1742            func_scope_id
1743                .to_scope_id(&db, program_file(&db, file))
1744                .name(&db, &module),
1745            "func"
1746        );
1747        let func_table = index.place_table(func_scope_id);
1748        assert_eq!(names(func_table), vec!["x"]);
1749    }
1750
1751    #[test]
1752    fn generic_class() {
1753        let TestCase { db, file } = test_case(
1754            "
1755class C[T]:
1756    x = 1
1757",
1758        );
1759
1760        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1761        let index = semantic_index(&db, program_file(&db, file));
1762        let global_table = index.place_table(FileScopeId::global());
1763
1764        assert_eq!(names(global_table), vec!["C"]);
1765
1766        let [(ann_scope_id, ann_scope)] = index
1767            .child_scopes(FileScopeId::global())
1768            .collect::<Vec<_>>()[..]
1769        else {
1770            panic!("expected one child scope");
1771        };
1772
1773        assert_eq!(ann_scope.kind(), ScopeKind::TypeParams);
1774        assert_eq!(
1775            ann_scope_id
1776                .to_scope_id(&db, program_file(&db, file))
1777                .name(&db, &module),
1778            "C"
1779        );
1780        let ann_table = index.place_table(ann_scope_id);
1781        assert_eq!(names(ann_table), vec!["T"]);
1782        assert!(
1783            ann_table
1784                .symbol_by_name("T")
1785                .is_some_and(|s| s.is_bound() && !s.is_used()),
1786            "type parameters are defined by the scope that introduces them"
1787        );
1788
1789        let [(class_scope_id, class_scope)] =
1790            index.child_scopes(ann_scope_id).collect::<Vec<_>>()[..]
1791        else {
1792            panic!("expected one child scope");
1793        };
1794
1795        assert_eq!(class_scope.kind(), ScopeKind::Class);
1796        assert_eq!(
1797            class_scope_id
1798                .to_scope_id(&db, program_file(&db, file))
1799                .name(&db, &module),
1800            "C"
1801        );
1802        assert_eq!(names(index.place_table(class_scope_id)), vec!["x"]);
1803    }
1804
1805    #[test]
1806    fn reachability_trivial() {
1807        let TestCase { db, file } = test_case("x = 1; x");
1808        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1809        let scope = global_scope(&db, program_file(&db, file));
1810        let ast = module.syntax();
1811        let ast::Stmt::Expr(ast::StmtExpr {
1812            value: x_use_expr, ..
1813        }) = &ast.body[1]
1814        else {
1815            panic!("should be an expr")
1816        };
1817        let ast::Expr::Name(x_use_expr_name) = x_use_expr.as_ref() else {
1818            panic!("expected a Name");
1819        };
1820        let x_use_id = x_use_expr_name.scoped_use_id(&db, program_file(&db, file));
1821        let use_def = use_def_map(&db, scope);
1822        let binding = use_def.first_binding_at_use(x_use_id).unwrap();
1823        let DefinitionKind::Assignment(assignment) = binding.kind(&db) else {
1824            panic!("should be an assignment definition")
1825        };
1826        let ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
1827            value: ast::Number::Int(num),
1828            ..
1829        }) = assignment.value(&module)
1830        else {
1831            panic!("should be a number literal")
1832        };
1833        assert_eq!(*num, 1);
1834    }
1835
1836    #[test]
1837    fn expression_scope() {
1838        let TestCase { db, file } = test_case("x = 1;\ndef test():\n  y = 4");
1839
1840        let index = semantic_index(&db, program_file(&db, file));
1841        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1842        let ast = module.syntax();
1843
1844        let x_stmt = ast.body[0].as_assign_stmt().unwrap();
1845        let x = &x_stmt.targets[0];
1846
1847        assert_eq!(index.expression_scope(x).kind(), ScopeKind::Module);
1848        assert_eq!(index.expression_scope_id(x), FileScopeId::global());
1849
1850        let def = ast.body[1].as_function_def_stmt().unwrap();
1851        let y_stmt = def.body[0].as_assign_stmt().unwrap();
1852        let y = &y_stmt.targets[0];
1853
1854        assert_eq!(index.expression_scope(y).kind(), ScopeKind::Function);
1855    }
1856
1857    #[test]
1858    fn scope_iterators() {
1859        fn scope_names<'a, 'db>(
1860            scopes: impl Iterator<Item = (FileScopeId, &'db Scope)>,
1861            db: &'db dyn Db,
1862            file: File,
1863            program: Program<'db>,
1864            module: &'a ParsedModuleRef,
1865        ) -> Vec<&'a str> {
1866            scopes
1867                .into_iter()
1868                .map(|(scope_id, _)| {
1869                    scope_id
1870                        .to_scope_id(db, program.program_file(db, file))
1871                        .name(db, module)
1872                })
1873                .collect()
1874        }
1875
1876        let TestCase { db, file } = test_case(
1877            r"
1878class Test:
1879    def foo():
1880        def bar():
1881            ...
1882    def baz():
1883        pass
1884
1885def x():
1886    pass",
1887        );
1888
1889        let module = parsed_module(&db, program_file(&db, file).python_file(&db)).load(&db);
1890        let index = semantic_index(&db, program_file(&db, file));
1891
1892        let descendants = index.descendent_scopes(FileScopeId::global());
1893        assert_eq!(
1894            scope_names(descendants, &db, file, db.program(), &module),
1895            vec!["Test", "foo", "bar", "baz", "x"]
1896        );
1897
1898        let children = index.child_scopes(FileScopeId::global());
1899        assert_eq!(
1900            scope_names(children, &db, file, db.program(), &module),
1901            vec!["Test", "x"]
1902        );
1903
1904        let test_class = index.child_scopes(FileScopeId::global()).next().unwrap().0;
1905        let test_child_scopes = index.child_scopes(test_class);
1906        assert_eq!(
1907            scope_names(test_child_scopes, &db, file, db.program(), &module),
1908            vec!["foo", "baz"]
1909        );
1910
1911        let bar_scope = index
1912            .descendent_scopes(FileScopeId::global())
1913            .nth(2)
1914            .unwrap()
1915            .0;
1916        let ancestors = index.ancestor_scopes(bar_scope);
1917
1918        assert_eq!(
1919            scope_names(ancestors, &db, file, db.program(), &module),
1920            vec!["bar", "foo", "Test", "<module>"]
1921        );
1922    }
1923
1924    #[test]
1925    fn match_stmt() {
1926        let TestCase { db, file } = test_case(
1927            "
1928match subject:
1929    case a: ...
1930    case [b, c, *d]: ...
1931    case e as f: ...
1932    case {'x': g, **h}: ...
1933    case Foo(i, z=j): ...
1934    case k | l: ...
1935    case _: ...
1936",
1937        );
1938
1939        let global_scope_id = global_scope(&db, program_file(&db, file));
1940        let global_table = place_table(&db, global_scope_id);
1941
1942        assert!(global_table.symbol_by_name("Foo").unwrap().is_used());
1943        assert_eq!(
1944            names(global_table),
1945            vec![
1946                "subject", "a", "b", "c", "d", "e", "f", "g", "h", "Foo", "i", "j", "k", "l"
1947            ]
1948        );
1949
1950        let use_def = use_def_map(&db, global_scope_id);
1951        for name in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"] {
1952            let binding = use_def
1953                .first_public_binding(global_table.symbol_id(name).expect("symbol exists"))
1954                .expect("Expected with item definition for {name}");
1955            assert!(matches!(binding.kind(&db), DefinitionKind::MatchPattern(_)));
1956        }
1957    }
1958
1959    #[test]
1960    fn nested_match_case() {
1961        let TestCase { db, file } = test_case(
1962            "
1963match 1:
1964    case first:
1965        match 2:
1966            case second:
1967                pass
1968",
1969        );
1970
1971        let global_scope_id = global_scope(&db, program_file(&db, file));
1972        let global_table = place_table(&db, global_scope_id);
1973
1974        assert_eq!(names(global_table), vec!["first", "second"]);
1975
1976        let use_def = use_def_map(&db, global_scope_id);
1977        for name in ["first", "second"] {
1978            let binding = use_def
1979                .first_public_binding(global_table.symbol_id(name).expect("symbol exists"))
1980                .expect("Expected with item definition for {name}");
1981            assert!(matches!(binding.kind(&db), DefinitionKind::MatchPattern(_)));
1982        }
1983    }
1984
1985    #[test]
1986    fn for_loops_single_assignment() {
1987        let TestCase { db, file } = test_case("for x in a: pass");
1988        let scope = global_scope(&db, program_file(&db, file));
1989        let global_table = place_table(&db, scope);
1990
1991        assert_eq!(&names(global_table), &["a", "x"]);
1992
1993        let use_def = use_def_map(&db, scope);
1994        let binding = use_def
1995            .first_public_binding(global_table.symbol_id("x").unwrap())
1996            .unwrap();
1997
1998        assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
1999    }
2000
2001    #[test]
2002    fn for_loops_simple_unpacking() {
2003        let TestCase { db, file } = test_case("for (x, y) in a: pass");
2004        let scope = global_scope(&db, program_file(&db, file));
2005        let global_table = place_table(&db, scope);
2006
2007        assert_eq!(&names(global_table), &["a", "x", "y"]);
2008
2009        let use_def = use_def_map(&db, scope);
2010        let x_binding = use_def
2011            .first_public_binding(global_table.symbol_id("x").unwrap())
2012            .unwrap();
2013        let y_binding = use_def
2014            .first_public_binding(global_table.symbol_id("y").unwrap())
2015            .unwrap();
2016
2017        assert!(matches!(x_binding.kind(&db), DefinitionKind::For(_)));
2018        assert!(matches!(y_binding.kind(&db), DefinitionKind::For(_)));
2019    }
2020
2021    #[test]
2022    fn for_loops_complex_unpacking() {
2023        let TestCase { db, file } = test_case("for [((a,) b), (c, d)] in e: pass");
2024        let scope = global_scope(&db, program_file(&db, file));
2025        let global_table = place_table(&db, scope);
2026
2027        assert_eq!(&names(global_table), &["e", "a", "b", "c", "d"]);
2028
2029        let use_def = use_def_map(&db, scope);
2030        let binding = use_def
2031            .first_public_binding(global_table.symbol_id("a").unwrap())
2032            .unwrap();
2033
2034        assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
2035    }
2036}