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#[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#[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#[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#[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
171pub 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 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 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#[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#[derive(Debug, get_size2::GetSize, salsa::SalsaValue)]
284pub struct SemanticIndex<'db> {
285 place_tables: FrozenIndexVec<FileScopeId, Arc<PlaceTable>>,
287
288 scopes: FrozenIndexVec<FileScopeId, Scope>,
290
291 scopes_by_expression: ExpressionsScopeMap,
293
294 definitions_by_node: DefinitionsByNode<'db>,
296
297 expressions_by_node: FxHashMap<ExpressionNodeKey, Expression<'db>>,
299
300 unpacks_by_target: FrozenMap<ExpressionNodeKey, unpack::Unpack<'db>>,
302
303 statements_by_node: FxHashMap<StatementNodeKey, Statement<'db>>,
305
306 scopes_by_node: FxHashMap<NodeWithScopeKey, FileScopeId>,
308
309 enclosing_lambda_statements: FrozenMap<ExpressionNodeKey, Statement<'db>>,
311
312 collections_by_use: FrozenMap<ExpressionNodeKey, Definition<'db>>,
314
315 uses_by_collection: FrozenMap<Definition<'db>, Box<[(Statement<'db>, ExpressionNodeKey)]>>,
317
318 scope_ids_by_scope: FrozenIndexVec<FileScopeId, ScopeId<'db>>,
320
321 use_def_maps: FrozenIndexVec<FileScopeId, Arc<UseDefMap<'db>>>,
323
324 ast_ids: AstIds,
329
330 imported_modules: FrozenSet<ModuleName>,
332
333 has_future_annotations: bool,
335
336 enclosing_snapshots: FrozenMap<EnclosingSnapshotKey, ScopedEnclosingSnapshotId>,
338
339 semantic_syntax_errors: Vec<SemanticSyntaxError>,
341
342 generator_functions: FrozenSet<FileScopeId>,
344
345 async_comprehensions: FrozenSet<FileScopeId>,
347
348 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 pub expression: Expression<'db>,
358}
359
360impl<'db> SemanticIndex<'db> {
361 #[track_caller]
366 pub fn place_table(&self, scope_id: FileScopeId) -> &PlaceTable {
367 &self.place_tables[scope_id]
368 }
369
370 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 #[track_caller]
383 pub fn use_def_map(&self, scope_id: FileScopeId) -> &UseDefMap<'db> {
384 &self.use_def_maps[scope_id]
385 }
386
387 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 #[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 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 #[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 #[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 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 for (visible_scope_id, _) in self.visible_ancestor_scopes(scope) {
460 if visible_scope_id.is_global() {
461 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 pub fn parent_scope_id(&self, scope_id: FileScopeId) -> Option<FileScopeId> {
482 let scope = self.scope(scope_id);
483 scope.parent()
484 }
485
486 #[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 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 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 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 fn descendent_scopes(&self, scope: FileScopeId) -> DescendantsIter<'_> {
558 DescendantsIter::new(&self.scopes, scope)
559 }
560
561 pub fn child_scopes(&self, scope: FileScopeId) -> ChildrenIter<'_> {
563 ChildrenIter::new(&self.scopes, scope)
564 }
565
566 pub fn ancestor_scopes(&self, scope: FileScopeId) -> AncestorsIter<'_> {
568 AncestorsIter::new(&self.scopes, scope)
569 }
570
571 pub fn visible_ancestor_scopes(&self, scope: FileScopeId) -> VisibleAncestorsIter<'_> {
586 VisibleAncestorsIter::new(&self.scopes, scope)
587 }
588
589 #[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 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 #[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 #[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 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 #[track_caller]
684 pub fn node_scope(&self, node: NodeWithScopeRef) -> FileScopeId {
685 self.scopes_by_node[&node.node_key()]
686 }
687
688 pub fn try_node_scope(&self, node: NodeWithScopeRef) -> Option<FileScopeId> {
690 self.scopes_by_node.get(&node.node_key()).copied()
691 }
692
693 pub fn node_scope_by_key(&self, key: NodeWithScopeKey) -> FileScopeId {
699 self.scopes_by_node[&key]
700 }
701
702 pub fn has_future_annotations(&self) -> bool {
705 self.has_future_annotations
706 }
707
708 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 if self.yielded_count == 1 {
818 return Some((scope_id, scope));
819 }
820
821 if scope.kind() == ScopeKind::Class {
824 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#[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 AlwaysTrue,
938 AlwaysFalse,
940 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#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1032pub enum BoundnessAnalysis {
1033 AssumeBound,
1035 BasedOnUnboundVisibility,
1048}
1049
1050pub type PossiblyNarrowedPlaces = FxHashSet<ScopedPlaceId>;
1056
1057pub trait HasTrackedScope: ast::HasNodeIndex {}
1059
1060impl HasTrackedScope for ast::Expr {}
1061impl HasTrackedScope for ast::ExprRef<'_> {}
1062impl HasTrackedScope for &ast::ExprRef<'_> {}
1063
1064impl 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]
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]
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]
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}