@@ -2,6 +2,7 @@ use std::cell::{OnceCell, RefCell};
22use std:: sync:: Arc ;
33
44use except_handlers:: TryNodeContextStackManager ;
5+ use itertools:: Itertools ;
56use rustc_hash:: { FxHashMap , FxHashSet } ;
67
78use ruff_db:: files:: File ;
@@ -10,7 +11,7 @@ use ruff_db::source::{SourceText, source_text};
1011use ruff_index:: IndexVec ;
1112use ruff_python_ast:: name:: Name ;
1213use ruff_python_ast:: visitor:: { Visitor , walk_expr, walk_pattern, walk_stmt} ;
13- use ruff_python_ast:: { self as ast, PySourceType , PythonVersion } ;
14+ use ruff_python_ast:: { self as ast, NodeIndex , PySourceType , PythonVersion } ;
1415use ruff_python_parser:: semantic_errors:: {
1516 SemanticSyntaxChecker , SemanticSyntaxContext , SemanticSyntaxError , SemanticSyntaxErrorKind ,
1617} ;
@@ -45,7 +46,8 @@ use crate::semantic_index::reachability_constraints::{
4546use crate :: semantic_index:: use_def:: {
4647 EagerSnapshotKey , FlowSnapshot , ScopedEagerSnapshotId , UseDefMapBuilder ,
4748} ;
48- use crate :: semantic_index:: { ArcUseDefMap , SemanticIndex } ;
49+ use crate :: semantic_index:: { ArcUseDefMap , ExpressionsScopeMap , SemanticIndex } ;
50+ use crate :: semantic_model:: HasTrackedScope ;
4951use crate :: unpack:: { Unpack , UnpackKind , UnpackPosition , UnpackValue } ;
5052use crate :: { Db , Program } ;
5153
@@ -102,7 +104,7 @@ pub(super) struct SemanticIndexBuilder<'db, 'ast> {
102104 ast_ids : IndexVec < FileScopeId , AstIdsBuilder > ,
103105 use_def_maps : IndexVec < FileScopeId , UseDefMapBuilder < ' db > > ,
104106 scopes_by_node : FxHashMap < NodeWithScopeKey , FileScopeId > ,
105- scopes_by_expression : FxHashMap < ExpressionNodeKey , FileScopeId > ,
107+ scopes_by_expression : ExpressionsScopeMapBuilder ,
106108 globals_by_scope : FxHashMap < FileScopeId , FxHashSet < ScopedPlaceId > > ,
107109 definitions_by_node : FxHashMap < DefinitionNodeKey , Definitions < ' db > > ,
108110 expressions_by_node : FxHashMap < ExpressionNodeKey , Expression < ' db > > ,
@@ -137,7 +139,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
137139 scope_ids_by_scope : IndexVec :: new ( ) ,
138140 use_def_maps : IndexVec :: new ( ) ,
139141
140- scopes_by_expression : FxHashMap :: default ( ) ,
142+ scopes_by_expression : ExpressionsScopeMapBuilder :: new ( ) ,
141143 scopes_by_node : FxHashMap :: default ( ) ,
142144 definitions_by_node : FxHashMap :: default ( ) ,
143145 expressions_by_node : FxHashMap :: default ( ) ,
@@ -1011,7 +1013,6 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
10111013 place_tables. shrink_to_fit ( ) ;
10121014 use_def_maps. shrink_to_fit ( ) ;
10131015 ast_ids. shrink_to_fit ( ) ;
1014- self . scopes_by_expression . shrink_to_fit ( ) ;
10151016 self . definitions_by_node . shrink_to_fit ( ) ;
10161017
10171018 self . scope_ids_by_scope . shrink_to_fit ( ) ;
@@ -1028,7 +1029,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
10281029 scope_ids_by_scope : self . scope_ids_by_scope ,
10291030 globals_by_scope : self . globals_by_scope ,
10301031 ast_ids,
1031- scopes_by_expression : self . scopes_by_expression ,
1032+ scopes_by_expression : self . scopes_by_expression . build ( ) ,
10321033 scopes_by_node : self . scopes_by_node ,
10331034 use_def_maps,
10341035 imported_modules : Arc :: new ( self . imported_modules ) ,
@@ -1901,7 +1902,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
19011902 self . with_semantic_checker ( |semantic, context| semantic. visit_expr ( expr, context) ) ;
19021903
19031904 self . scopes_by_expression
1904- . insert ( expr. into ( ) , self . current_scope ( ) ) ;
1905+ . record_expression ( expr, self . current_scope ( ) ) ;
19051906
19061907 let node_key = NodeKey :: from_node ( expr) ;
19071908
@@ -2539,3 +2540,79 @@ fn dunder_all_extend_argument(value: &ast::Expr) -> Option<&ast::Expr> {
25392540
25402541 ( attr == "__all__" ) . then_some ( value)
25412542}
2543+
2544+ /// Builds a map of expressions to their enclosing scopes.
2545+ ///
2546+ /// Ideally, we would condense the expression node ids to scoope index directly when
2547+ /// registering the expression. However, there are a few things that make this hard (but maybe still possible?)
2548+ ///
2549+ /// * The expression ids are assigned in source order, but we visit the expressions in semantic order.
2550+ /// There's no difference for most expressions but some expressions will be registered out of order.
2551+ /// * The expression ids aren't always consecutive elements (in fact, they normally aren't). That makes it
2552+ /// harder to detect out of order expressions.
2553+ struct ExpressionsScopeMapBuilder {
2554+ expression_and_scope : Vec < ( NodeIndex , FileScopeId ) > ,
2555+ out_of_order : Vec < ( NodeIndex , FileScopeId ) > ,
2556+ }
2557+
2558+ impl ExpressionsScopeMapBuilder {
2559+ fn new ( ) -> Self {
2560+ Self {
2561+ expression_and_scope : vec ! [ ] ,
2562+ out_of_order : vec ! [ ] ,
2563+ }
2564+ }
2565+
2566+ // Ugh, the expression ranges can be off because they are in source order but we visit the
2567+ // expressions in semantic order.
2568+ fn record_expression ( & mut self , expression : & impl HasTrackedScope , scope : FileScopeId ) {
2569+ let expression_index = expression. node_index ( ) . load ( ) ;
2570+
2571+ if self
2572+ . expression_and_scope
2573+ . last ( )
2574+ . is_some_and ( |last| last. 0 > expression_index)
2575+ {
2576+ self . out_of_order . push ( ( expression_index, scope) ) ;
2577+ } else {
2578+ self . expression_and_scope . push ( ( expression_index, scope) ) ;
2579+ }
2580+ }
2581+
2582+ fn build ( mut self ) -> ExpressionsScopeMap {
2583+ self . out_of_order . sort_by_key ( |( index, _) | * index) ;
2584+
2585+ let mut condensed = Vec :: new ( ) ;
2586+
2587+ let mut iter = self
2588+ . expression_and_scope
2589+ . into_iter ( )
2590+ . merge_by ( self . out_of_order , |left, right| left. 0 <= right. 0 ) ;
2591+
2592+ let Some ( first) = iter. next ( ) else {
2593+ return ExpressionsScopeMap {
2594+ scopes_by_expression : Box :: default ( ) ,
2595+ } ;
2596+ } ;
2597+
2598+ let mut current_scope = first. 1 ;
2599+ let mut range = first. 0 ..NodeIndex :: from ( first. 0 . as_u32 ( ) + 1 ) ;
2600+
2601+ for ( index, scope) in iter {
2602+ if scope == current_scope {
2603+ range. end = NodeIndex :: from ( index. as_u32 ( ) + 1 ) ;
2604+ continue ;
2605+ }
2606+
2607+ condensed. push ( ( range, current_scope) ) ;
2608+ current_scope = scope;
2609+ range = index..NodeIndex :: from ( index. as_u32 ( ) + 1 ) ;
2610+ }
2611+
2612+ condensed. push ( ( range, current_scope) ) ;
2613+
2614+ ExpressionsScopeMap {
2615+ scopes_by_expression : condensed. into_boxed_slice ( ) ,
2616+ }
2617+ }
2618+ }
0 commit comments