@@ -251,6 +251,23 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
251251 false
252252 }
253253
254+ /// Returns the enclosing non-comprehension scope for walrus operator targets,
255+ /// per [PEP 572]. Named expressions in comprehensions bind in the first
256+ /// enclosing scope that is *not* a comprehension.
257+ ///
258+ /// Returns `None` if the current scope is not a comprehension.
259+ ///
260+ /// [PEP 572]: https://peps.python.org/pep-0572/#scope-of-the-target
261+ fn enclosing_scope_for_walrus ( & self ) -> Option < FileScopeId > {
262+ if self . scopes [ self . current_scope ( ) ] . kind ( ) != ScopeKind :: Comprehension {
263+ return None ;
264+ }
265+ self . scope_stack . iter ( ) . rev ( ) . skip ( 1 ) . find_map ( |info| {
266+ ( self . scopes [ info. file_scope_id ] . kind ( ) != ScopeKind :: Comprehension )
267+ . then_some ( info. file_scope_id )
268+ } )
269+ }
270+
254271 /// Push a new loop, returning the outer loop, if any.
255272 fn push_loop ( & mut self ) -> Option < Loop > {
256273 self . current_scope_info_mut ( )
@@ -668,8 +685,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
668685 definition
669686 }
670687
671- fn delete_associated_bindings ( & mut self , place : ScopedPlaceId ) {
672- let scope = self . current_scope ( ) ;
688+ fn delete_associated_bindings_in_scope ( & mut self , scope : FileScopeId , place : ScopedPlaceId ) {
673689 // Don't delete associated bindings if the scope is a class scope & place is a name (it's never visible to nested scopes)
674690 if self . scopes [ scope] . kind ( ) == ScopeKind :: Class && place. is_symbol ( ) {
675691 return ;
@@ -687,19 +703,18 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
687703 self . current_use_def_map_mut ( ) . delete_binding ( place) ;
688704 }
689705
690- /// Push a new [`Definition`] onto the list of definitions
691- /// associated with the `definition_node` AST node .
706+ /// Create a [`Definition`] for `definition_node` in the given `scope`, recording it in
707+ /// that scope's place table and use-def map .
692708 ///
693- /// Returns a 2-element tuple, where the first element is the newly created [`Definition`]
694- /// and the second element is the number of definitions that are now associated with
695- /// `definition_node`.
709+ /// Returns a 2-element tuple: the newly created [`Definition`] and the total number of
710+ /// definitions now associated with the AST node.
696711 ///
697- /// This method should only be used when adding a definition associated with a `*` import.
698- /// All other nodes can only ever be associated with exactly 1 or 0 [`Definition`]s.
699- /// For any node other than an [`ast::Alias`] representing a `*` import,
700- /// prefer to use `self.add_definition()`, which ensures that this invariant is maintained.
701- fn push_additional_definition (
712+ /// This is the low-level helper; callers that add definitions to the **current** scope
713+ /// should normally use [`Self::push_additional_definition`] or [`Self::add_definition`],
714+ /// which also update lazy snapshots and the try-node context stack.
715+ fn add_definition_in_scope (
702716 & mut self ,
717+ scope : FileScopeId ,
703718 place : ScopedPlaceId ,
704719 definition_node : impl Into < DefinitionNodeRef < ' ast , ' db > > ,
705720 ) -> ( Definition < ' db > , usize ) {
@@ -711,14 +726,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
711726 let category = kind. category ( self . source_type . is_stub ( ) , self . module ) ;
712727 let is_reexported = kind. is_reexported ( ) ;
713728
714- let definition: Definition < ' db > = Definition :: new (
715- self . db ,
716- self . file ,
717- self . current_scope ( ) ,
718- place,
719- kind,
720- is_reexported,
721- ) ;
729+ let definition: Definition < ' db > =
730+ Definition :: new ( self . db , self . file , scope, place, kind, is_reexported) ;
722731
723732 let num_definitions = {
724733 let definitions = self . add_entry_for_definition_key ( definition_node. key ( ) ) ;
@@ -727,25 +736,53 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
727736 } ;
728737
729738 if category. is_binding ( ) {
730- self . mark_place_bound ( place) ;
739+ self . place_tables [ scope ] . mark_bound ( place) ;
731740 }
732741 if category. is_declaration ( ) {
733- self . mark_place_declared ( place) ;
742+ self . place_tables [ scope ] . mark_declared ( place) ;
734743 }
735744
736- let use_def = self . current_use_def_map_mut ( ) ;
737745 match category {
738746 DefinitionCategory :: DeclarationAndBinding => {
739- use_def. record_declaration_and_binding ( place, definition) ;
740- self . delete_associated_bindings ( place) ;
747+ self . use_def_maps [ scope] . record_declaration_and_binding ( place, definition) ;
748+ self . delete_associated_bindings_in_scope ( scope, place) ;
749+ }
750+ DefinitionCategory :: Declaration => {
751+ self . use_def_maps [ scope] . record_declaration ( place, definition) ;
741752 }
742- DefinitionCategory :: Declaration => use_def. record_declaration ( place, definition) ,
743753 DefinitionCategory :: Binding => {
744- use_def . record_binding ( place, definition) ;
745- self . delete_associated_bindings ( place) ;
754+ self . use_def_maps [ scope ] . record_binding ( place, definition) ;
755+ self . delete_associated_bindings_in_scope ( scope , place) ;
746756 }
747757 }
748758
759+ ( definition, num_definitions)
760+ }
761+
762+ /// Push a new [`Definition`] onto the list of definitions
763+ /// associated with the `definition_node` AST node in the **current** scope.
764+ ///
765+ /// Returns a 2-element tuple, where the first element is the newly created [`Definition`]
766+ /// and the second element is the number of definitions that are now associated with
767+ /// `definition_node`.
768+ ///
769+ /// This method should only be used when adding a definition associated with a `*` import.
770+ /// All other nodes can only ever be associated with exactly 1 or 0 [`Definition`]s.
771+ /// For any node other than an [`ast::Alias`] representing a `*` import,
772+ /// prefer to use `self.add_definition()`, which ensures that this invariant is maintained.
773+ fn push_additional_definition (
774+ & mut self ,
775+ place : ScopedPlaceId ,
776+ definition_node : impl Into < DefinitionNodeRef < ' ast , ' db > > ,
777+ ) -> ( Definition < ' db > , usize ) {
778+ let scope = self . current_scope ( ) ;
779+ let ( definition, num_definitions) =
780+ self . add_definition_in_scope ( scope, place, definition_node) ;
781+
782+ let category = definition
783+ . kind ( self . db )
784+ . category ( self . source_type . is_stub ( ) , self . module ) ;
785+
749786 if category. is_binding ( ) {
750787 if let Some ( id) = place. as_symbol ( ) {
751788 self . update_lazy_snapshots ( id) ;
@@ -2632,10 +2669,28 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
26322669 ) ;
26332670 }
26342671 Some ( CurrentAssignment :: Named ( named) ) => {
2635- // TODO(dhruvmanila): If the current scope is a comprehension, then the
2636- // named expression is implicitly nonlocal. This is yet to be
2637- // implemented.
2638- self . add_definition ( place_id, named) ;
2672+ if let Some ( enclosing_scope) = self . enclosing_scope_for_walrus ( ) {
2673+ // PEP 572: walrus in comprehension binds in enclosing scope.
2674+ let target_name = named
2675+ . target
2676+ . as_name_expr ( )
2677+ . expect ( "target should be a Name expression" )
2678+ . id
2679+ . clone ( ) ;
2680+ let ( symbol_id, added) = self . place_tables [ enclosing_scope]
2681+ . add_symbol ( Symbol :: new ( target_name) ) ;
2682+ if added {
2683+ self . use_def_maps [ enclosing_scope]
2684+ . add_place ( symbol_id. into ( ) ) ;
2685+ }
2686+ self . add_definition_in_scope (
2687+ enclosing_scope,
2688+ symbol_id. into ( ) ,
2689+ named,
2690+ ) ;
2691+ } else {
2692+ self . add_definition ( place_id, named) ;
2693+ }
26392694 }
26402695 Some ( CurrentAssignment :: Comprehension {
26412696 unpack,
@@ -2690,11 +2745,16 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
26902745 walk_expr ( self , expr) ;
26912746 }
26922747 ast:: Expr :: Named ( node) => {
2693- // TODO walrus in comprehensions is implicitly nonlocal
26942748 self . visit_expr ( & node. value ) ;
26952749
26962750 // See https://peps.python.org/pep-0572/#differences-between-assignment-expressions-and-assignment-statements
26972751 if node. target . is_name_expr ( ) {
2752+ // PEP 572: walrus in comprehension binds in the enclosing scope.
2753+ // Make the value a standalone expression so inference can evaluate
2754+ // it in the comprehension scope where the iteration variables are visible.
2755+ if self . enclosing_scope_for_walrus ( ) . is_some ( ) {
2756+ self . add_standalone_expression ( & node. value ) ;
2757+ }
26982758 self . push_assignment ( node. into ( ) ) ;
26992759 self . visit_expr ( & node. target ) ;
27002760 self . pop_assignment ( ) ;
0 commit comments