Repository navigation
Expand file tree
/
Copy pathlib.rs
More file actions
2032 lines (1743 loc) · 69.9 KB
/
Copy pathlib.rs
File metadata and controls
2032 lines (1743 loc) · 69.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#![warn(
clippy::disallowed_methods,
reason = "Prefer System trait methods over std methods in ty crates"
)]
use ruff_python_ast as ast;
use std::iter::{FusedIterator, once};
use std::sync::Arc;
use ruff_db::parsed::parsed_module;
use ruff_index::{FrozenIndexVec, IndexSlice};
use ruff_python_ast::NodeIndex;
use ruff_python_parser::semantic_errors::SemanticSyntaxError;
use ruff_text_size::TextRange;
use rustc_hash::{FxHashMap, FxHashSet};
use salsa::plumbing::AsId;
use smallvec::SmallVec;
use ty_module_resolver::ModuleName;
use crate::frozen::{FrozenMap, FrozenSet};
use crate::place::ScopedPlaceId;
pub use crate::statement::{Statement, StatementNodeKey};
use ast_ids::AstIds;
pub use ast_ids::ExpressionNodeKey;
use builder::SemanticIndexBuilder;
use definition::{Definition, DefinitionNodeKey, Definitions};
use expression::Expression;
use narrowing_constraints::ScopedNarrowingConstraint;
pub use place::{PlaceExprRef, PlaceTable};
pub use reachability_constraints::ReachabilityConstraintsBuilder;
pub use scope::FileScopeId;
use scope::{NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopeKind, ScopeLaziness};
use symbol::ScopedSymbolId;
pub use use_def::{
ApplicableConstraints, BindingWithConstraints, BindingWithConstraintsIterator,
DeclarationWithConstraint, DeclarationsIterator, LiveBinding, LoopHeaderId, NarrowingEvaluator,
PredicateNarrowingTargets, ScopedDefinitionId, UseDefMap,
};
use use_def::{EnclosingSnapshotKey, ScopedEnclosingSnapshotId};
pub mod ast_ids;
pub mod ast_node_ref;
mod builder;
mod db;
pub mod definition;
pub mod expression;
pub mod frozen;
pub(crate) mod member;
pub mod narrowing_constraints;
pub mod node_key;
pub mod place;
pub mod platform;
pub mod predicate;
pub mod rank;
mod re_exports;
pub mod reachability_constraints;
pub mod scope;
pub mod statement;
pub mod symbol;
pub mod unpack;
mod use_def;
pub use db::Db;
#[cfg(any(test, feature = "testing"))]
pub use db::TestProgramDb;
pub mod program;
pub mod program_file;
pub use program::Program;
pub use program_file::ProgramFile;
/// Returns the semantic index for `file`.
///
/// Prefer using [`symbol_table`] when working with symbols from a single scope.
#[salsa::tracked(returns(ref), no_eq, heap_size=ruff_memory_usage::heap_size)]
pub fn semantic_index<'db>(db: &'db dyn Db, file: ProgramFile<'db>) -> SemanticIndex<'db> {
let _span = tracing::trace_span!("semantic_index", ?file).entered();
let module = parsed_module(db, file.python_file(db)).load(db);
SemanticIndexBuilder::new(db, file, &module).build()
}
/// Returns the place table for a specific `scope`.
///
/// Using [`place_table`] over [`semantic_index`] has the advantage that
/// Salsa can avoid invalidating dependent queries if this scope's place table
/// is unchanged.
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
pub fn place_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<PlaceTable> {
let program_file = scope.program_file(db);
let _span = tracing::trace_span!("place_table", scope=?scope.as_id(), ?program_file).entered();
let index = semantic_index(db, program_file);
Arc::clone(&index.place_tables[scope.file_scope_id(db)])
}
/// Returns the use-def map for a specific `scope`.
///
/// Using [`use_def_map`] over [`semantic_index`] has the advantage that
/// Salsa can avoid invalidating dependent queries if this scope's use-def map
/// is unchanged.
#[salsa::tracked(returns(deref), heap_size=ruff_memory_usage::heap_size)]
pub fn use_def_map<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<UseDefMap<'db>> {
let program_file = scope.program_file(db);
let _span = tracing::trace_span!("use_def_map", scope=?scope.as_id(), ?program_file).entered();
let index = semantic_index(db, program_file);
Arc::clone(&index.use_def_maps[scope.file_scope_id(db)])
}
/// All the bindings made in a loop, which are visible to the entire loop via "loop header
/// definitions" (a.k.a. loop-back bindings)
///
/// Loop control flow analysis needs a way for uses early in the loop to see bindings that come
/// later, reflecting the fact that a previous iteration of the loop might've already executed
/// those bindings. For example:
///
/// ```py
/// x = "A"
/// while some_condition():
/// # The loop entry value (in the first iteration) and the following binding (in other
/// # iterations) are both visible at this use.
/// reveal_type(x) # revealed: Literal["A", "B"]
/// x = "B"
/// ```
///
/// As an important special case, these loop header definitions also combine with fixpoint
/// iteration to let us infer `int` for loop variables. For example:
///
/// ```py
/// i = 0 # revealed: Literal[0]
/// while i < 1_000_000:
/// i += 1 # revealed: int
/// ```
///
/// The add-assign statement `i += 1` is both a use and a binding. The use sees the `i = 0` binding
/// at the top, and it also sees *itself* via the loop header definition of `i`. When we infer the
/// type of `i` in or after the loop, fixpoint iteration produces an ever-expanding union of
/// literals (`Literal[0, 1, 2, ...]`) until we eventually reach the threshold for widening to
/// `int` and stop iterating. (See `should_widen` and `widen_literal_types`.)
///
/// Loop header definitions are synthesized before walking the loop body, but the bindings in the
/// header are only known after walking all loop-back edges. The builder reserves a [`LoopHeaderId`]
/// before the walk, fills the corresponding header afterward, and publishes the completed headers
/// in the scope's [`UseDefMap`].
#[derive(Debug, Clone, Default, PartialEq, Eq, get_size2::GetSize)]
pub struct LoopHeader {
bindings: FxHashMap<ScopedPlaceId, SmallVec<[LiveBinding; 1]>>,
}
impl LoopHeader {
fn new() -> Self {
Self {
bindings: FxHashMap::default(),
}
}
fn add_binding(&mut self, place: ScopedPlaceId, binding: LiveBinding) {
self.bindings.entry(place).or_default().push(binding);
}
pub fn bindings_for_place(
&self,
place: ScopedPlaceId,
) -> impl Iterator<Item = LiveBinding> + '_ {
self.bindings
.get(&place)
.map(|v: &SmallVec<[LiveBinding; 1]>| v.iter().copied())
.into_iter()
.flatten()
}
}
/// Returns all attribute assignments as scope IDs for a specific class body scope.
///
/// Only call this when doing type inference on the same file as `class_body_scope`, otherwise it
/// introduces a direct dependency on that file's AST.
pub fn attribute_scopes<'db>(
db: &'db dyn Db,
class_body_scope: ScopeId<'db>,
) -> impl Iterator<Item = FileScopeId> + 'db {
let index = semantic_index(db, class_body_scope.program_file(db));
let class_scope_id = class_body_scope.file_scope_id(db);
ChildrenIter::new(&index.scopes, class_scope_id)
.filter_map(move |(child_scope_id, scope)| {
let (function_scope_id, function_scope) =
if scope.node().scope_kind() == ScopeKind::TypeParams {
// This could be a generic method with a type-params scope.
// Go one level deeper to find the function scope. The first
// descendant is the (potential) function scope.
let function_scope_id = scope.descendants().start;
(function_scope_id, index.scope(function_scope_id))
} else {
(child_scope_id, scope)
};
function_scope.node().as_function()?;
Some(function_scope_id)
})
.flat_map(move |func_id| {
// Add any descendent scope that is eager and have eager scopes between the scope
// and the method scope. Since attributes can be defined in this scope.
let nested = index.descendent_scopes(func_id).filter_map(move |(id, s)| {
let is_eager = s.kind().is_eager();
let parents_are_eager = {
let mut all_parents_eager = true;
let mut current = Some(id);
while let Some(scope_id) = current {
if scope_id == func_id {
break;
}
let scope = index.scope(scope_id);
if !scope.is_eager() {
all_parents_eager = false;
break;
}
current = scope.parent();
}
all_parents_eager
};
(parents_are_eager && is_eager).then_some(id)
});
once(func_id).chain(nested)
})
}
/// Returns the module global scope of `file`.
#[salsa::tracked(returns(copy), heap_size=ruff_memory_usage::heap_size)]
pub fn global_scope<'db>(db: &'db dyn Db, file: ProgramFile<'db>) -> ScopeId<'db> {
let _span = tracing::trace_span!("global_scope", ?file).entered();
FileScopeId::global().to_scope_id(db, file)
}
pub enum EnclosingSnapshotResult<'map, 'db> {
FoundConstraint(ScopedNarrowingConstraint),
FoundBindings(BindingWithConstraintsIterator<'map, 'db>),
NotFound,
NoLongerInEagerContext,
}
#[derive(Debug, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
struct DefinitionsByNode<'db> {
single: FrozenMap<DefinitionNodeKey, Definition<'db>>,
non_single: FrozenMap<DefinitionNodeKey, Box<[Definition<'db>]>>,
}
impl<'db> DefinitionsByNode<'db> {
fn from_map(definitions_by_node: FxHashMap<DefinitionNodeKey, Definitions<'db>>) -> Self {
let single_count = definitions_by_node
.values()
.filter(|definitions| definitions.len() == 1)
.count();
let mut single = Vec::with_capacity(single_count);
let mut non_single = Vec::with_capacity(definitions_by_node.len() - single_count);
#[expect(
clippy::iter_over_hash_type,
reason = "each node is independently partitioned by definition count"
)]
for (key, definitions) in definitions_by_node {
if definitions.len() == 1 {
single.push((key, definitions[0]));
} else {
non_single.push((key, definitions.into_boxed_slice()));
}
}
Self {
single: FrozenMap::from_entries(single),
non_single: FrozenMap::from_entries(non_single),
}
}
fn get(&self, key: DefinitionNodeKey) -> Option<&[Definition<'db>]> {
self.single
.get(&key)
.map(std::slice::from_ref)
.or_else(|| self.non_single.get(&key).map(AsRef::as_ref))
}
}
/// The place tables and use-def maps for all scopes in a file.
#[derive(Debug, get_size2::GetSize, salsa::SalsaValue)]
pub struct SemanticIndex<'db> {
/// List of all place tables in this file, indexed by scope.
place_tables: FrozenIndexVec<FileScopeId, Arc<PlaceTable>>,
/// List of all scopes in this file.
scopes: FrozenIndexVec<FileScopeId, Scope>,
/// Map expressions to their corresponding scope.
scopes_by_expression: ExpressionsScopeMap,
/// Map from a node creating a definition to its definition.
definitions_by_node: DefinitionsByNode<'db>,
/// Map from a standalone expression to its [`Expression`] ingredient.
expressions_by_node: FxHashMap<ExpressionNodeKey, Expression<'db>>,
/// Map from an unpacking target to its [`unpack::Unpack`] ingredient.
unpacks_by_target: FrozenMap<ExpressionNodeKey, unpack::Unpack<'db>>,
/// Map from a standalone statement to its [`Statement`] ingredient.
statements_by_node: FxHashMap<StatementNodeKey, Statement<'db>>,
/// Map from nodes that create a scope to the scope they create.
scopes_by_node: FxHashMap<NodeWithScopeKey, FileScopeId>,
/// Map from a lambda expression to its containing statement.
enclosing_lambda_statements: FrozenMap<ExpressionNodeKey, Statement<'db>>,
// Map from a constraining use of a collection initializer to its definition.
collections_by_use: FrozenMap<ExpressionNodeKey, Definition<'db>>,
// Map from a collection initializer definition to statements containing a constraining use.
uses_by_collection: FrozenMap<Definition<'db>, Box<[(Statement<'db>, ExpressionNodeKey)]>>,
/// Map from the file-local [`FileScopeId`] to the salsa-ingredient [`ScopeId`].
scope_ids_by_scope: FrozenIndexVec<FileScopeId, ScopeId<'db>>,
/// Use-def map for each scope in this file.
use_def_maps: FrozenIndexVec<FileScopeId, Arc<UseDefMap<'db>>>,
/// Lookup table to map between node ids and ast nodes.
///
/// Note: We should not depend on this map when analysing other files or
/// changing a file invalidates all dependents.
ast_ids: AstIds,
/// The set of modules that are imported anywhere within this file.
imported_modules: FrozenSet<ModuleName>,
/// Flags about the global scope (code usage impacting inference)
has_future_annotations: bool,
/// Map of all of the enclosing snapshots that appear in this file.
enclosing_snapshots: FrozenMap<EnclosingSnapshotKey, ScopedEnclosingSnapshotId>,
/// List of all semantic syntax errors in this file.
semantic_syntax_errors: Vec<SemanticSyntaxError>,
/// Set of all generator functions in this file.
generator_functions: FrozenSet<FileScopeId>,
/// Set of all asynchronous comprehensions in this file.
async_comprehensions: FrozenSet<FileScopeId>,
/// Narrowing alias metadata for predicate leaf names.
/// When a predicate references an alias variable (e.g., `is_none` from `is_none = x is None`),
/// the alias Name node is mapped to its aliased expression for constraint-generation time.
narrowing_alias_predicates: FrozenMap<ExpressionNodeKey, NarrowingAliasPredicate<'db>>,
}
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize, salsa::SalsaValue)]
pub struct NarrowingAliasPredicate<'db> {
/// Aliased expression, e.g., `x is None` in `is_none = x is None`.
pub expression: Expression<'db>,
}
impl<'db> SemanticIndex<'db> {
/// Returns the place table for a specific scope.
///
/// Use the Salsa cached [`place_table()`] query if you only need the
/// place table for a single scope.
#[track_caller]
pub fn place_table(&self, scope_id: FileScopeId) -> &PlaceTable {
&self.place_tables[scope_id]
}
/// Returns alias metadata for an alias Name node in a predicate, if one exists.
pub fn narrowing_alias_predicate(
&self,
key: impl Into<ExpressionNodeKey>,
) -> Option<&NarrowingAliasPredicate<'db>> {
self.narrowing_alias_predicates.get(&key.into())
}
/// Returns the use-def map for a specific scope.
///
/// Use the Salsa cached [`use_def_map()`] query if you only need the
/// use-def map for a single scope.
#[track_caller]
pub fn use_def_map(&self, scope_id: FileScopeId) -> &UseDefMap<'db> {
&self.use_def_maps[scope_id]
}
/// Returns the set of modules that are imported anywhere in this file.
///
/// This set only considers `import` statements, not `from...import` statements.
/// See `ModuleLiteralType::available_submodule_attributes` for discussion
/// of why this analysis is intentionally limited.
pub fn imported_modules(&self) -> impl Iterator<Item = &ModuleName> {
self.imported_modules.iter()
}
#[track_caller]
fn ast_ids(&self) -> &AstIds {
&self.ast_ids
}
/// Returns the ID of the `expression`'s enclosing scope.
#[track_caller]
pub fn expression_scope_id<E>(&self, expression: &E) -> FileScopeId
where
E: HasTrackedScope,
{
self.try_expression_scope_id(expression)
.expect("Expression to be part of a scope if it is from the same module")
}
/// Returns the ID of the `expression`'s enclosing scope.
pub fn try_expression_scope_id<E>(&self, expression: &E) -> Option<FileScopeId>
where
E: HasTrackedScope,
{
self.scopes_by_expression.try_get(expression)
}
/// Returns the [`Scope`] of the `expression`'s enclosing scope.
#[allow(unused)]
#[track_caller]
pub fn expression_scope(&self, expression: &impl HasTrackedScope) -> &Scope {
&self.scopes[self.expression_scope_id(expression)]
}
/// Returns the [`Scope`] with the given id.
#[track_caller]
pub fn scope(&self, id: FileScopeId) -> &Scope {
&self.scopes[id]
}
pub fn scope_ids(&self) -> impl Iterator<Item = ScopeId<'db>> + '_ {
self.scope_ids_by_scope.iter().copied()
}
pub fn symbol_is_global_in_scope(&self, symbol: ScopedSymbolId, scope: FileScopeId) -> bool {
self.place_table(scope).symbol(symbol).is_global()
}
/// Returns `true` if the given symbol in the given scope resolves to the global scope, either
/// because:
///
/// 1. The given scope *is* the global scope.
/// 2. The symbol is explicitly declared `global` in the given scope.
/// 3. The symbol is a free variable in the given scope, and no enclosing function scope
/// defines it.
///
/// The third case requires walking ancestor scopes until we encounter either a binding or a
/// `nonlocal` declaration (those aren't allowed to resolve to the global scope, so we don't
/// need to chase them).
pub fn symbol_resolves_to_global_scope(
&self,
symbol: ScopedSymbolId,
scope: FileScopeId,
) -> bool {
let symbol = self.place_table(scope).symbol(symbol);
let name = symbol.name();
// Note that `visible_ancestor_scopes` includes the starting scope itself.
for (visible_scope_id, _) in self.visible_ancestor_scopes(scope) {
if visible_scope_id.is_global() {
// We return `true` here even if the global variable isn't actually defined. That
// case will be probably a diagnostic elsewhere.
return true;
}
let place_table = self.place_table(visible_scope_id);
let Some(visible_symbol_id) = place_table.symbol_id(name) else {
continue;
};
let visible_symbol = place_table.symbol(visible_symbol_id);
if visible_symbol.is_global() {
return true;
}
if visible_symbol.is_local() || visible_symbol.is_nonlocal() {
return false;
}
}
unreachable!("should return true at the global scope above");
}
/// Returns the id of the parent scope.
pub fn parent_scope_id(&self, scope_id: FileScopeId) -> Option<FileScopeId> {
let scope = self.scope(scope_id);
scope.parent()
}
/// Returns the parent scope of `scope_id`.
#[track_caller]
pub fn parent_scope(&self, scope_id: FileScopeId) -> Option<&Scope> {
Some(&self.scopes[self.parent_scope_id(scope_id)?])
}
/// Return the [`Definition`] of the class enclosing this method, given the
/// method's body scope, or `None` if it is not a method.
pub fn class_definition_of_method(
&self,
function_body_scope: FileScopeId,
) -> Option<Definition<'db>> {
let current_scope = self.scope(function_body_scope);
if current_scope.kind() != ScopeKind::Function {
return None;
}
let parent_scope_id = current_scope.parent()?;
let parent_scope = self.scope(parent_scope_id);
let class_scope = match parent_scope.kind() {
ScopeKind::Class => parent_scope,
ScopeKind::TypeParams => {
let class_scope_id = parent_scope.parent()?;
let potentially_class_scope = self.scope(class_scope_id);
match potentially_class_scope.kind() {
ScopeKind::Class => potentially_class_scope,
_ => return None,
}
}
_ => return None,
};
class_scope
.node()
.as_class()
.map(|node_ref| self.expect_single_definition(node_ref))
}
pub fn enclosing_lambda_statement(&self, lambda: ExpressionNodeKey) -> Option<Statement<'db>> {
self.enclosing_lambda_statements.get(&lambda).copied()
}
/// If this is a potentially constraining use of an unannotated collection initializer, returns
/// its definition.
pub fn unannotated_collection_initializer(
&self,
collection_use: &ast::Expr,
) -> Option<Definition<'db>> {
self.collections_by_use.get(&collection_use.into()).copied()
}
/// Returns all potentially constraining uses of the given unannotated collection initializer.
pub fn constraining_collection_uses(
&self,
collection_def: Definition<'db>,
) -> impl Iterator<Item = (Statement<'db>, ExpressionNodeKey)> {
self.uses_by_collection
.get(&collection_def)
.into_iter()
.flat_map(|uses| uses.iter().copied())
}
pub fn is_in_type_checking_block(&self, scope_id: FileScopeId, range: TextRange) -> bool {
self.ancestor_scopes(scope_id).any(|(scope_id, _)| {
self.use_def_map(scope_id)
.is_range_in_type_checking_block(range)
})
}
/// Returns an iterator over the descendent scopes of `scope`.
fn descendent_scopes(&self, scope: FileScopeId) -> DescendantsIter<'_> {
DescendantsIter::new(&self.scopes, scope)
}
/// Returns an iterator over the direct child scopes of `scope`.
pub fn child_scopes(&self, scope: FileScopeId) -> ChildrenIter<'_> {
ChildrenIter::new(&self.scopes, scope)
}
/// Returns an iterator over all ancestors of `scope`, starting with `scope` itself.
pub fn ancestor_scopes(&self, scope: FileScopeId) -> AncestorsIter<'_> {
AncestorsIter::new(&self.scopes, scope)
}
/// Returns an iterator over ancestors of `scope` that are visible for name resolution,
/// starting with `scope` itself. This follows Python's lexical scoping rules where
/// class scopes are skipped during name resolution (except for the starting scope
/// if it happens to be a class scope).
///
/// For example, in this code:
/// ```python
/// x = 1
/// class A:
/// x = 2
/// def method(self):
/// print(x) # Refers to global x=1, not class x=2
/// ```
/// The `method` function can see the global scope but not the class scope.
pub fn visible_ancestor_scopes(&self, scope: FileScopeId) -> VisibleAncestorsIter<'_> {
VisibleAncestorsIter::new(&self.scopes, scope)
}
/// Returns the [`definition::Definition`] salsa ingredient(s) for `definition_key`.
///
/// There will only ever be >1 `Definition` associated with a `definition_key`
/// if the definitions are created by a wildcard (`*`) import.
#[track_caller]
pub fn definitions(&self, definition_key: impl Into<DefinitionNodeKey>) -> &[Definition<'db>] {
self.definitions_by_node
.get(definition_key.into())
.expect("definition should be present in the semantic index")
}
/// Returns the [`definition::Definition`] salsa ingredient(s) for `definition_node`, if any.
pub fn try_definitions(
&self,
definition_node: ast::AnyNodeRef<'_>,
) -> Option<&[Definition<'db>]> {
let definition_key = DefinitionNodeKey::from_node_ref(definition_node);
self.definitions_by_node.get(definition_key)
}
/// Returns the [`definition::Definition`] salsa ingredient for `definition_key`.
///
/// ## Panics
///
/// If the number of definitions associated with the key is not exactly 1 and
/// the `debug_assertions` feature is enabled, this method will panic.
///
/// It is generally safe to use this method for any AST node that does not
/// correspond to a `*` (wildcard) import, since those are the only situations
/// that can result in multiple definitions being associated with a single AST
/// node.
#[track_caller]
pub fn expect_single_definition(
&self,
definition_key: impl Into<DefinitionNodeKey> + std::fmt::Debug + Copy,
) -> Definition<'db> {
let definitions = self.definitions(definition_key);
debug_assert_eq!(
definitions.len(),
1,
"Expected exactly one definition to be associated with AST node {definition_key:?} but found {}",
definitions.len()
);
definitions[0]
}
pub fn try_definition(
&self,
definition_key: impl Into<DefinitionNodeKey>,
) -> Option<Definition<'db>> {
self.definitions_by_node
.single
.get(&definition_key.into())
.copied()
}
/// Returns the [`Expression`] ingredient for an expression node.
/// Panics if we have no expression ingredient for that node. We can only call this method for
/// standalone-inferable expressions, which we call `add_standalone_expression` for in
/// `SemanticIndexBuilder`.
#[track_caller]
pub fn expression(&self, expression_key: impl Into<ExpressionNodeKey>) -> Expression<'db> {
self.expressions_by_node[&expression_key.into()]
}
pub fn try_expression(
&self,
expression_key: impl Into<ExpressionNodeKey>,
) -> Option<Expression<'db>> {
self.expressions_by_node
.get(&expression_key.into())
.copied()
}
/// Returns the [`unpack::Unpack`] ingredient for an unpacking target, if any.
pub fn try_unpack(&self, target: impl Into<ExpressionNodeKey>) -> Option<unpack::Unpack<'db>> {
self.unpacks_by_target.get(&target.into()).copied()
}
pub fn is_standalone_expression(&self, expression_key: impl Into<ExpressionNodeKey>) -> bool {
self.expressions_by_node
.contains_key(&expression_key.into())
}
pub fn try_statement(
&self,
statement_key: impl Into<StatementNodeKey>,
) -> Option<Statement<'db>> {
self.statements_by_node.get(&statement_key.into()).copied()
}
/// Returns the id of the scope that `node` creates.
/// This is different from [`definition::Definition::scope`] which
/// returns the scope in which that definition is defined in.
#[track_caller]
pub fn node_scope(&self, node: NodeWithScopeRef) -> FileScopeId {
self.scopes_by_node[&node.node_key()]
}
/// Returns the id of the scope that `node` creates, if it exists.
pub fn try_node_scope(&self, node: NodeWithScopeRef) -> Option<FileScopeId> {
self.scopes_by_node.get(&node.node_key()).copied()
}
/// Returns the id of the scope that the node identified by `key` creates.
///
/// This is useful when you have a [`NodeWithScopeKey`] constructed from an
/// [`AstNodeRef`](crate::ast_node_ref::AstNodeRef) and want to avoid loading
/// the parsed module just to look up the scope.
pub fn node_scope_by_key(&self, key: NodeWithScopeKey) -> FileScopeId {
self.scopes_by_node[&key]
}
/// Checks if there is an import of `__future__.annotations` in the global scope, which affects
/// the logic for type inference.
pub fn has_future_annotations(&self) -> bool {
self.has_future_annotations
}
/// Returns
/// * `NoLongerInEagerContext` if the nested scope is no longer in an eager context
/// (that is, not every scope that will be traversed is eager) and no lazy snapshots were found.
/// * an iterator of bindings for a particular nested scope reference if the bindings exist.
/// * a narrowing constraint if there are no bindings, but there is a narrowing constraint for an enclosing scope place.
/// * `NotFound` if the narrowing constraint / bindings do not exist in the nested scope.
pub fn enclosing_snapshot(
&self,
enclosing_scope: FileScopeId,
expr: PlaceExprRef,
nested_scope: FileScopeId,
) -> EnclosingSnapshotResult<'_, 'db> {
for (ancestor_scope_id, ancestor_scope) in self.ancestor_scopes(nested_scope) {
if ancestor_scope_id == enclosing_scope {
break;
}
if !ancestor_scope.is_eager() {
if let PlaceExprRef::Symbol(symbol) = expr
&& let Some(place_id) =
self.place_tables[enclosing_scope].symbol_id(symbol.name())
{
let key = EnclosingSnapshotKey {
enclosing_scope,
enclosing_place: place_id.into(),
nested_scope,
nested_laziness: ScopeLaziness::Lazy,
};
if let Some(id) = self.enclosing_snapshots.get(&key) {
return self.use_def_maps[enclosing_scope]
.enclosing_snapshot(*id, key.nested_laziness);
}
}
return EnclosingSnapshotResult::NoLongerInEagerContext;
}
}
let Some(place_id) = self.place_tables[enclosing_scope].place_id(expr) else {
return EnclosingSnapshotResult::NotFound;
};
let key = EnclosingSnapshotKey {
enclosing_scope,
enclosing_place: place_id,
nested_scope,
nested_laziness: ScopeLaziness::Eager,
};
let Some(id) = self.enclosing_snapshots.get(&key) else {
return EnclosingSnapshotResult::NotFound;
};
self.use_def_maps[enclosing_scope].enclosing_snapshot(*id, key.nested_laziness)
}
pub fn semantic_syntax_errors(&self) -> &[SemanticSyntaxError] {
&self.semantic_syntax_errors
}
}
pub struct AncestorsIter<'a> {
scopes: &'a IndexSlice<FileScopeId, Scope>,
next_id: Option<FileScopeId>,
}
impl<'a> AncestorsIter<'a> {
fn new(scopes: &'a IndexSlice<FileScopeId, Scope>, start: FileScopeId) -> Self {
Self {
scopes,
next_id: Some(start),
}
}
}
impl<'a> Iterator for AncestorsIter<'a> {
type Item = (FileScopeId, &'a Scope);
fn next(&mut self) -> Option<Self::Item> {
let current_id = self.next_id?;
let current = &self.scopes[current_id];
self.next_id = current.parent();
Some((current_id, current))
}
}
impl FusedIterator for AncestorsIter<'_> {}
pub struct VisibleAncestorsIter<'a> {
inner: AncestorsIter<'a>,
starting_scope_kind: ScopeKind,
yielded_count: usize,
}
impl<'a> VisibleAncestorsIter<'a> {
fn new(scopes: &'a IndexSlice<FileScopeId, Scope>, start: FileScopeId) -> Self {
let starting_scope = &scopes[start];
Self {
inner: AncestorsIter::new(scopes, start),
starting_scope_kind: starting_scope.kind(),
yielded_count: 0,
}
}
}
impl<'a> Iterator for VisibleAncestorsIter<'a> {
type Item = (FileScopeId, &'a Scope);
fn next(&mut self) -> Option<Self::Item> {
loop {
let (scope_id, scope) = self.inner.next()?;
self.yielded_count += 1;
// Always return the first scope (the starting scope)
if self.yielded_count == 1 {
return Some((scope_id, scope));
}
// Skip class scopes for subsequent scopes (following Python's lexical scoping rules)
// Exception: type parameter scopes can see names defined in an immediately-enclosing class scope
if scope.kind() == ScopeKind::Class {
// Allow annotation scopes to see their immediately-enclosing class scope exactly once
if self.starting_scope_kind.is_annotation() && self.yielded_count == 2 {
return Some((scope_id, scope));
}
continue;
}
return Some((scope_id, scope));
}
}
}
impl FusedIterator for VisibleAncestorsIter<'_> {}
pub(crate) struct DescendantsIter<'a> {
next_id: FileScopeId,
descendants: std::slice::Iter<'a, Scope>,
}
impl<'a> DescendantsIter<'a> {
fn new(scopes: &'a IndexSlice<FileScopeId, Scope>, scope_id: FileScopeId) -> Self {
let scope = &scopes[scope_id];
let scopes = &scopes[scope.descendants()];
Self {
next_id: scope_id + 1,
descendants: scopes.iter(),
}
}
}
impl<'a> Iterator for DescendantsIter<'a> {
type Item = (FileScopeId, &'a Scope);
fn next(&mut self) -> Option<Self::Item> {
let descendant = self.descendants.next()?;
let id = self.next_id;
self.next_id = self.next_id + 1;
Some((id, descendant))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.descendants.size_hint()
}
}
impl FusedIterator for DescendantsIter<'_> {}
impl ExactSizeIterator for DescendantsIter<'_> {}
pub struct ChildrenIter<'a> {
parent: FileScopeId,
descendants: DescendantsIter<'a>,
}
impl<'a> ChildrenIter<'a> {
fn new(scopes: &'a IndexSlice<FileScopeId, Scope>, parent: FileScopeId) -> Self {
let descendants = DescendantsIter::new(scopes, parent);
Self {
parent,
descendants,
}
}
}
impl<'a> Iterator for ChildrenIter<'a> {
type Item = (FileScopeId, &'a Scope);
fn next(&mut self) -> Option<Self::Item> {
self.descendants
.find(|(_, scope)| scope.parent() == Some(self.parent))
}
}
impl FusedIterator for ChildrenIter<'_> {}
/// Interval map that maps a range of expression node ids to their corresponding scopes.
///
/// Lookups require `O(log n)` time, where `n` is roughly the number of scopes (roughly
/// because sub-scopes can be interleaved with expressions in the outer scope, e.g. function, some statements, a function).
#[derive(Eq, PartialEq, Debug, get_size2::GetSize, Default)]
struct ExpressionsScopeMap(Box<[(std::ops::RangeInclusive<NodeIndex>, FileScopeId)]>);
impl ExpressionsScopeMap {
fn try_get<E>(&self, node: &E) -> Option<FileScopeId>
where
E: HasTrackedScope,
{
let node_index = node.node_index().load();
let entry = self
.0
.binary_search_by_key(&node_index, |(range, _)| *range.start());
let index = match entry {
Ok(index) => index,
Err(index) => index.checked_sub(1)?,
};
let (range, scope) = &self.0[index];
if range.contains(&node_index) {
Some(*scope)
} else {
None
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, get_size2::GetSize)]
pub enum Truthiness {
/// For an object `x`, `bool(x)` will always return `True`
AlwaysTrue,
/// For an object `x`, `bool(x)` will always return `False`
AlwaysFalse,
/// For an object `x`, `bool(x)` could return either `True` or `False`
Ambiguous,
}
impl Truthiness {
pub const fn is_ambiguous(self) -> bool {
matches!(self, Truthiness::Ambiguous)
}
pub const fn is_always_false(self) -> bool {
matches!(self, Truthiness::AlwaysFalse)
}
pub const fn may_be_true(self) -> bool {
!self.is_always_false()
}
pub const fn is_always_true(self) -> bool {
matches!(self, Truthiness::AlwaysTrue)
}
#[must_use]
pub const fn negate(self) -> Self {
match self {
Self::AlwaysTrue => Self::AlwaysFalse,
Self::AlwaysFalse => Self::AlwaysTrue,
Self::Ambiguous => Self::Ambiguous,
}
}
#[must_use]
pub const fn negate_if(self, condition: bool) -> Self {
if condition { self.negate() } else { self }
}
#[must_use]
pub fn or(self, other: Self) -> Self {
match self {
Truthiness::AlwaysTrue => self,
Truthiness::AlwaysFalse => other,
Truthiness::Ambiguous => match other {
Truthiness::AlwaysTrue => Truthiness::AlwaysTrue,
Truthiness::AlwaysFalse | Truthiness::Ambiguous => Truthiness::Ambiguous,
},
}
}
#[must_use]
pub fn or_else(self, other: impl Fn() -> Self) -> Self {
match self {
Truthiness::AlwaysTrue => self,
Truthiness::AlwaysFalse => other(),
Truthiness::Ambiguous => match other() {
Truthiness::AlwaysTrue => Truthiness::AlwaysTrue,
Truthiness::AlwaysFalse | Truthiness::Ambiguous => Truthiness::Ambiguous,
},
}
}
}
impl From<bool> for Truthiness {