Skip to content

Commit abe6a36

Browse files
committed
don't do nested bindings inference if we have a declared type
1 parent b82bbcd commit abe6a36

File tree

4 files changed

+54
-46
lines changed

4 files changed

+54
-46
lines changed

crates/ty_python_semantic/resources/mdtest/public_types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def outer() -> None:
312312
set_x()
313313

314314
def inner() -> None:
315-
reveal_type(x) # revealed: None | Literal[1]
315+
reveal_type(x) # revealed: Literal[1] | None
316316
inner()
317317
```
318318

crates/ty_python_semantic/resources/mdtest/scopes/global.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ x = 42
201201

202202
def f():
203203
global x
204-
reveal_type(x) # revealed: Unknown | Literal[42, "56"]
204+
reveal_type(x) # revealed: Unknown | Literal["56", 42]
205205
x = "56"
206206
reveal_type(x) # revealed: Literal["56"]
207207
```

crates/ty_python_semantic/resources/mdtest/scopes/nonlocal.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ def a():
106106
nonlocal x
107107
# It's counterintuitive that 4 gets included here, since we haven't reached the
108108
# binding in this scope, but this function might get called more than once.
109-
reveal_type(x) # revealed: Literal[2, 3, 4]
109+
reveal_type(x) # revealed: Literal[3, 4, 2]
110110
x = 4
111111
reveal_type(x) # revealed: Literal[4]
112112

113113
def e():
114-
reveal_type(x) # revealed: Literal[2, 3, 4]
114+
reveal_type(x) # revealed: Literal[3, 4, 2]
115115
```
116116

117117
In addition to parent scopes, we also consider sibling scopes, child scopes,
@@ -131,7 +131,7 @@ def a():
131131
def d():
132132
nonlocal x
133133
x = 3
134-
reveal_type(x) # revealed: Literal[1, 2, 3]
134+
reveal_type(x) # revealed: Literal[2, 3, 1]
135135
# `x` is local here, so we don't look at nested scopes.
136136
reveal_type(x) # revealed: Literal[1]
137137
```

crates/ty_python_semantic/src/place.rs

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)