Skip to content

Commit b62ff10

Browse files
mtshibacarljm
authored andcommitted
fix snapshot behavior in annotation scopes
annotation scopes can see names defined in an immediately-enclosing class scope
1 parent 41ba670 commit b62ff10

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

crates/ty_python_semantic/resources/mdtest/annotations/deferred.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,23 @@ class Foo:
8787
class Baz[T: Foo]:
8888
pass
8989

90+
# error: [unresolved-reference] "Name `Foo` used when not defined"
91+
# error: [unresolved-reference] "Name `Bar` used when not defined"
92+
class Qux(Foo, Bar, Baz):
93+
pass
94+
95+
# error: [unresolved-reference] "Name `Foo` used when not defined"
96+
# error: [unresolved-reference] "Name `Bar` used when not defined"
97+
class Quux[_T](Foo, Bar, Baz):
98+
pass
99+
90100
# error: [unresolved-reference]
91101
type S = a
92102
type T = b
103+
type U = Foo
104+
# error: [unresolved-reference]
105+
type V = Bar
106+
type W = Baz
93107

94108
def h[T: Bar]():
95109
# error: [unresolved-reference]
@@ -141,9 +155,23 @@ class Foo:
141155
class Baz[T: Foo]:
142156
pass
143157

158+
# error: [unresolved-reference] "Name `Foo` used when not defined"
159+
# error: [unresolved-reference] "Name `Bar` used when not defined"
160+
class Qux(Foo, Bar, Baz):
161+
pass
162+
163+
# error: [unresolved-reference] "Name `Foo` used when not defined"
164+
# error: [unresolved-reference] "Name `Bar` used when not defined"
165+
class Quux[_T](Foo, Bar, Baz):
166+
pass
167+
144168
# error: [unresolved-reference]
145169
type S = a
146170
type T = b
171+
type U = Foo
172+
# error: [unresolved-reference]
173+
type V = Bar
174+
type W = Baz
147175

148176
def h[T: Bar]():
149177
# error: [unresolved-reference]

crates/ty_python_semantic/src/semantic_index/builder.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
314314

315315
// Records snapshots of the place states visible from the current eager scope.
316316
fn record_eager_snapshots(&mut self, popped_scope_id: FileScopeId) {
317+
let popped_scope = &self.scopes[popped_scope_id];
318+
317319
// If the scope that we just popped off is an eager scope, we need to "lock" our view of
318320
// which bindings reach each of the uses in the scope. Loop through each enclosing scope,
319321
// looking for any that bind each place.
@@ -350,11 +352,14 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
350352
nested_scope: popped_scope_id,
351353
nested_laziness: ScopeLaziness::Eager,
352354
};
355+
let is_immediately_enclosing_scope = popped_scope.kind().is_annotation()
356+
&& popped_scope.parent() == Some(enclosing_scope_id);
353357
let eager_snapshot = self.use_def_maps[enclosing_scope_id]
354358
.snapshot_enclosing_state(
355359
enclosing_place_id,
356360
enclosing_scope_kind,
357361
enclosing_place,
362+
is_immediately_enclosing_scope,
358363
);
359364
self.enclosing_snapshots.insert(key, eager_snapshot);
360365
}
@@ -429,6 +434,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
429434
enclosed_symbol_id.into(),
430435
enclosing_scope_kind,
431436
enclosing_place.into(),
437+
false,
432438
);
433439
self.enclosing_snapshots.insert(key, lazy_snapshot);
434440
}

crates/ty_python_semantic/src/semantic_index/use_def.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,17 +1186,22 @@ impl<'db> UseDefMapBuilder<'db> {
11861186
pub(super) fn snapshot_enclosing_state(
11871187
&mut self,
11881188
enclosing_place: ScopedPlaceId,
1189-
scope: ScopeKind,
1189+
enclosing_scope: ScopeKind,
11901190
enclosing_place_expr: PlaceExprRef,
1191+
is_immediately_enclosing_scope: bool,
11911192
) -> ScopedEnclosingSnapshotId {
11921193
let bindings = match enclosing_place {
11931194
ScopedPlaceId::Symbol(symbol) => self.symbol_states[symbol].bindings(),
11941195
ScopedPlaceId::Member(member) => self.member_states[member].bindings(),
11951196
};
11961197

1198+
let is_class_symbol = enclosing_scope.is_class() && enclosing_place.is_symbol();
11971199
// Names bound in class scopes are never visible to nested scopes (but attributes/subscripts are visible),
11981200
// so we never need to save eager scope bindings in a class scope.
1199-
if (scope.is_class() && enclosing_place.is_symbol()) || !enclosing_place_expr.is_bound() {
1201+
// There is one exception to this rule: annotation scopes can see
1202+
// names defined in an immediately-enclosing class scope.
1203+
if (is_class_symbol && !is_immediately_enclosing_scope) || !enclosing_place_expr.is_bound()
1204+
{
12001205
self.enclosing_snapshots.push(EnclosingSnapshot::Constraint(
12011206
bindings.unbound_narrowing_constraint(),
12021207
))

0 commit comments

Comments
 (0)