@@ -651,32 +651,6 @@ fn place_by_id<'db>(
651651) -> PlaceAndQualifiers < ' db > {
652652 let use_def = use_def_map ( db, scope) ;
653653
654- // If there are any nested bindings (via `global` or `nonlocal` variables) for this symbol,
655- // infer them and union the results. Nested bindings aren't allowed to have declarations or
656- // qualifiers, and we can just union their inferred types.
657- let mut nested_bindings_union = UnionBuilder :: new ( db) ;
658- if let Some ( symbol_id) = place_id. as_symbol ( ) {
659- let current_place_table = place_table ( db, scope) ;
660- let symbol = current_place_table. symbol ( symbol_id) ;
661- for nested_file_scope_id in place_table ( db, scope) . nested_scopes_with_bindings ( symbol_id) {
662- let nested_scope_id = nested_file_scope_id. to_scope_id ( db, scope. file ( db) ) ;
663- let nested_place_table = place_table ( db, nested_scope_id) ;
664- let nested_symbol_id = nested_place_table
665- . symbol_id ( symbol. name ( ) )
666- . expect ( "nested_scopes_with_bindings says this reference exists" ) ;
667- let nested_place = place_by_id (
668- db,
669- nested_scope_id,
670- ScopedPlaceId :: Symbol ( nested_symbol_id) ,
671- RequiresExplicitReExport :: No ,
672- ConsideredDefinitions :: AllReachable ,
673- ) ;
674- if let Place :: Type ( nested_type, _) = nested_place. place {
675- nested_bindings_union. add_in_place ( nested_type) ;
676- }
677- }
678- }
679-
680654 // If the place is declared, the public type is based on declarations; otherwise, it's based
681655 // on inference from bindings.
682656
@@ -692,6 +666,47 @@ fn place_by_id<'db>(
692666 ConsideredDefinitions :: AllReachable => use_def. all_reachable_bindings ( place_id) ,
693667 } ;
694668
669+ // If there are any nested bindings (via `global` or `nonlocal` variables) for this symbol,
670+ // infer them and union the results. Note that this is potentially recursive, and we can
671+ // trigger fixed-point iteration here in cases like this:
672+ // ```
673+ // def f():
674+ // x = 1
675+ // def g():
676+ // nonlocal x
677+ // x += 1
678+ // ```
679+ let nested_bindings = || {
680+ // For performance reasons, avoid creating a union unless we have more one binding.
681+ let mut union = UnionBuilder :: new ( db) ;
682+ if let Some ( symbol_id) = place_id. as_symbol ( ) {
683+ let current_place_table = place_table ( db, scope) ;
684+ let symbol = current_place_table. symbol ( symbol_id) ;
685+ for & nested_file_scope_id in
686+ place_table ( db, scope) . nested_scopes_with_bindings ( symbol_id)
687+ {
688+ let nested_scope_id = nested_file_scope_id. to_scope_id ( db, scope. file ( db) ) ;
689+ let nested_place_table = place_table ( db, nested_scope_id) ;
690+ let nested_symbol_id = nested_place_table
691+ . symbol_id ( symbol. name ( ) )
692+ . expect ( "nested_scopes_with_bindings says this reference exists" ) ;
693+ let place = place_by_id (
694+ db,
695+ nested_scope_id,
696+ ScopedPlaceId :: Symbol ( nested_symbol_id) ,
697+ RequiresExplicitReExport :: No ,
698+ ConsideredDefinitions :: AllReachable ,
699+ ) ;
700+ // Nested bindings aren't allowed to have declarations or qualifiers, so we can
701+ // just extract their inferred types.
702+ if let Place :: Type ( nested_type, _) = place. place {
703+ union. add_in_place ( nested_type) ;
704+ }
705+ }
706+ }
707+ union
708+ } ;
709+
695710 // If a symbol is undeclared, but qualified with `typing.Final`, we use the right-hand side
696711 // inferred type, without unioning with `Unknown`, because it can not be modified.
697712 if let Some ( qualifiers) = declared
@@ -751,17 +766,11 @@ fn place_by_id<'db>(
751766 // TODO: We probably don't want to report `Bound` here. This requires a bit of
752767 // design work though as we might want a different behavior for stubs and for
753768 // normal modules.
754- Place :: Type (
755- nested_bindings_union. add ( declared_ty) . build ( ) ,
756- Boundness :: Bound ,
757- )
769+ Place :: Type ( nested_bindings ( ) . add ( declared_ty) . build ( ) , Boundness :: Bound )
758770 }
759771 // Place is possibly undeclared and (possibly) bound
760772 Place :: Type ( inferred_ty, boundness) => Place :: Type (
761- nested_bindings_union
762- . add ( inferred_ty)
763- . add ( declared_ty)
764- . build ( ) ,
773+ nested_bindings ( ) . add ( inferred_ty) . add ( declared_ty) . build ( ) ,
765774 if boundness_analysis == BoundnessAnalysis :: AssumeBound {
766775 Boundness :: Bound
767776 } else {
@@ -784,13 +793,12 @@ fn place_by_id<'db>(
784793
785794 // If there are nested bindings, union whatever we inferred from those into what we've
786795 // inferred here.
787- if let Some ( nested_bindings_type) = nested_bindings_union. try_build ( ) {
788- match & mut inferred {
789- Place :: Type ( inferred_type, _) => {
790- * inferred_type =
791- UnionType :: from_elements ( db, [ * inferred_type, nested_bindings_type] ) ;
792- }
793- Place :: Unbound => {
796+ match & mut inferred {
797+ Place :: Type ( inferred_type, _) => {
798+ * inferred_type = nested_bindings ( ) . add ( * inferred_type) . build ( ) ;
799+ }
800+ Place :: Unbound => {
801+ if let Some ( nested_bindings_type) = nested_bindings ( ) . try_build ( ) {
794802 inferred = Place :: Type ( nested_bindings_type, Boundness :: PossiblyUnbound ) ;
795803 }
796804 }
0 commit comments