Skip to content

Commit 92e5ed6

Browse files
committed
Only use all bindings for within-module lookups
1 parent e101400 commit 92e5ed6

File tree

13 files changed

+248
-102
lines changed

13 files changed

+248
-102
lines changed

crates/ty_python_semantic/resources/mdtest/public_types.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@ def outer(flag: bool) -> None:
8989
x = A()
9090

9191
def inner() -> None:
92-
# TODO: this should not be an error
93-
# error: [possibly-unresolved-reference]
9492
reveal_type(x) # revealed: Unknown | A
9593
inner()
9694
```
@@ -103,9 +101,7 @@ def outer(flag: bool) -> None:
103101
x = A()
104102

105103
def inner() -> None:
106-
# TODO: currently an error (good), but this diagnostic might go away if
107-
# we try to silence the one above.
108-
# error: [possibly-unresolved-reference]
104+
# TODO: Ideally, we would emit a possibly-unresolved-reference error here.
109105
reveal_type(x) # revealed: Unknown | A
110106
inner()
111107
```
@@ -165,7 +161,6 @@ if flag():
165161
x = 1
166162

167163
def f() -> None:
168-
# error: [possibly-unresolved-reference]
169164
reveal_type(x) # revealed: Unknown | Literal[1, 2]
170165
# Function only used inside this branch
171166
f()

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ if returns_bool():
1313
chr: int = 1
1414

1515
def f():
16-
reveal_type(chr) # revealed: int | (def chr(i: SupportsIndex, /) -> str)
16+
# TODO: should be `int | (def chr(i: SupportsIndex, /) -> str)`
17+
reveal_type(chr) # revealed: int
1718
```
1819

1920
## Conditionally global or builtin, with annotation
@@ -28,5 +29,6 @@ if returns_bool():
2829
chr: int = 1
2930

3031
def f():
31-
reveal_type(chr) # revealed: int | (def chr(i: SupportsIndex, /) -> str)
32+
# TODO: should be `int | (def chr(i: SupportsIndex, /) -> str)`
33+
reveal_type(chr) # revealed: int
3234
```

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,8 @@ x = int
389389
class C:
390390
var: ClassVar[x]
391391

392-
reveal_type(C.var) # revealed: Unknown | str
392+
# TODO: should be `Unknown | str`
393+
reveal_type(C.var) # revealed: Unknown | int | str
393394

394395
x = str
395396
```
@@ -404,7 +405,8 @@ x = int
404405
class C:
405406
var: ClassVar[x]
406407

407-
reveal_type(C.var) # revealed: str
408+
# TODO: should be `str`
409+
reveal_type(C.var) # revealed: int | str
408410

409411
x = str
410412
```

crates/ty_python_semantic/resources/mdtest/statically_known_branches.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,8 @@ if False:
14781478
```py
14791479
# error: [unresolved-import]
14801480
from module import symbol
1481+
1482+
reveal_type(symbol) # revealed: Unknown
14811483
```
14821484

14831485
#### Always true, bound

crates/ty_python_semantic/src/place.rs

Lines changed: 120 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use ruff_db::files::File;
2+
use salsa::Update;
23

34
use crate::dunder_all::dunder_all_names;
45
use crate::module_resolver::file_to_module;
@@ -202,8 +203,15 @@ pub(crate) fn symbol<'db>(
202203
db: &'db dyn Db,
203204
scope: ScopeId<'db>,
204205
name: &str,
206+
considered_bindings: ConsideredBindings,
205207
) -> PlaceAndQualifiers<'db> {
206-
symbol_impl(db, scope, name, RequiresExplicitReExport::No)
208+
symbol_impl(
209+
db,
210+
scope,
211+
name,
212+
RequiresExplicitReExport::No,
213+
considered_bindings,
214+
)
207215
}
208216

209217
/// Infer the public type of a place (its type as seen from outside its scope) in the given
@@ -212,8 +220,15 @@ pub(crate) fn place<'db>(
212220
db: &'db dyn Db,
213221
scope: ScopeId<'db>,
214222
expr: &PlaceExpr,
223+
considered_bindings: ConsideredBindings,
215224
) -> PlaceAndQualifiers<'db> {
216-
place_impl(db, scope, expr, RequiresExplicitReExport::No)
225+
place_impl(
226+
db,
227+
scope,
228+
expr,
229+
RequiresExplicitReExport::No,
230+
considered_bindings,
231+
)
217232
}
218233

219234
/// Infer the public type of a class symbol (its type as seen from outside its scope) in the given
@@ -226,7 +241,13 @@ pub(crate) fn class_symbol<'db>(
226241
place_table(db, scope)
227242
.place_id_by_name(name)
228243
.map(|symbol| {
229-
let symbol_and_quals = place_by_id(db, scope, symbol, RequiresExplicitReExport::No);
244+
let symbol_and_quals = place_by_id(
245+
db,
246+
scope,
247+
symbol,
248+
RequiresExplicitReExport::No,
249+
ConsideredBindings::LiveBindingsAtUse,
250+
);
230251

231252
if symbol_and_quals.is_class_var() {
232253
// For declared class vars we do not need to check if they have bindings,
@@ -241,8 +262,13 @@ pub(crate) fn class_symbol<'db>(
241262
{
242263
// Otherwise, we need to check if the symbol has bindings
243264
let use_def = use_def_map(db, scope);
244-
let bindings = use_def.public_bindings(symbol);
245-
let inferred = place_from_bindings_impl(db, bindings, RequiresExplicitReExport::No);
265+
let bindings = use_def.end_of_scope_bindings(symbol);
266+
let inferred = place_from_bindings_impl(
267+
db,
268+
bindings,
269+
RequiresExplicitReExport::No,
270+
ConsideredBindings::LiveBindingsAtUse,
271+
);
246272

247273
// TODO: we should not need to calculate inferred type second time. This is a temporary
248274
// solution until the notion of Boundness and Declaredness is split. See #16036, #16264
@@ -277,6 +303,7 @@ pub(crate) fn explicit_global_symbol<'db>(
277303
global_scope(db, file),
278304
name,
279305
RequiresExplicitReExport::No,
306+
ConsideredBindings::AllReachable,
280307
)
281308
}
282309

@@ -330,18 +357,22 @@ pub(crate) fn imported_symbol<'db>(
330357
// ignore `__getattr__`. Typeshed has a fake `__getattr__` on `types.ModuleType` to help out with
331358
// dynamic imports; we shouldn't use it for `ModuleLiteral` types where we know exactly which
332359
// module we're dealing with.
333-
symbol_impl(db, global_scope(db, file), name, requires_explicit_reexport).or_fall_back_to(
360+
symbol_impl(
334361
db,
335-
|| {
336-
if name == "__getattr__" {
337-
Place::Unbound.into()
338-
} else if name == "__builtins__" {
339-
Place::bound(Type::any()).into()
340-
} else {
341-
KnownClass::ModuleType.to_instance(db).member(db, name)
342-
}
343-
},
362+
global_scope(db, file),
363+
name,
364+
requires_explicit_reexport,
365+
ConsideredBindings::LiveBindingsAtUse,
344366
)
367+
.or_fall_back_to(db, || {
368+
if name == "__getattr__" {
369+
Place::Unbound.into()
370+
} else if name == "__builtins__" {
371+
Place::bound(Type::any()).into()
372+
} else {
373+
KnownClass::ModuleType.to_instance(db).member(db, name)
374+
}
375+
})
345376
}
346377

347378
/// Lookup the type of `symbol` in the builtins namespace.
@@ -361,6 +392,7 @@ pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQua
361392
global_scope(db, file),
362393
symbol,
363394
RequiresExplicitReExport::Yes,
395+
ConsideredBindings::LiveBindingsAtUse,
364396
)
365397
.or_fall_back_to(db, || {
366398
// We're looking up in the builtins namespace and not the module, so we should
@@ -431,8 +463,14 @@ fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_
431463
pub(super) fn place_from_bindings<'db>(
432464
db: &'db dyn Db,
433465
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
466+
considered_bindings: ConsideredBindings, // TODO: remove this and always use `LiveBindingsAtUse` here?
434467
) -> Place<'db> {
435-
place_from_bindings_impl(db, bindings_with_constraints, RequiresExplicitReExport::No)
468+
place_from_bindings_impl(
469+
db,
470+
bindings_with_constraints,
471+
RequiresExplicitReExport::No,
472+
considered_bindings,
473+
)
436474
}
437475

438476
/// Build a declared type from a [`DeclarationsIterator`].
@@ -581,6 +619,7 @@ fn place_cycle_recover<'db>(
581619
_scope: ScopeId<'db>,
582620
_place_id: ScopedPlaceId,
583621
_requires_explicit_reexport: RequiresExplicitReExport,
622+
_considered_bindings: ConsideredBindings,
584623
) -> salsa::CycleRecoveryAction<PlaceAndQualifiers<'db>> {
585624
salsa::CycleRecoveryAction::Iterate
586625
}
@@ -590,6 +629,7 @@ fn place_cycle_initial<'db>(
590629
_scope: ScopeId<'db>,
591630
_place_id: ScopedPlaceId,
592631
_requires_explicit_reexport: RequiresExplicitReExport,
632+
_considered_bindings: ConsideredBindings,
593633
) -> PlaceAndQualifiers<'db> {
594634
Place::bound(Type::Never).into()
595635
}
@@ -600,6 +640,7 @@ fn place_by_id<'db>(
600640
scope: ScopeId<'db>,
601641
place_id: ScopedPlaceId,
602642
requires_explicit_reexport: RequiresExplicitReExport,
643+
considered_bindings: ConsideredBindings,
603644
) -> PlaceAndQualifiers<'db> {
604645
let use_def = use_def_map(db, scope);
605646

@@ -609,6 +650,11 @@ fn place_by_id<'db>(
609650
let declarations = use_def.public_declarations(place_id);
610651
let declared = place_from_declarations_impl(db, declarations, requires_explicit_reexport);
611652

653+
let all_considered_bindings = || match considered_bindings {
654+
ConsideredBindings::LiveBindingsAtUse => use_def.end_of_scope_bindings(place_id),
655+
ConsideredBindings::AllReachable => use_def.potentially_reachable_bindings(place_id),
656+
};
657+
612658
match declared {
613659
// Place is declared, trust the declared type
614660
Ok(
@@ -622,8 +668,13 @@ fn place_by_id<'db>(
622668
place: Place::Type(declared_ty, Boundness::PossiblyUnbound),
623669
qualifiers,
624670
}) => {
625-
let bindings = use_def.all_bindings(place_id);
626-
let inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport);
671+
let bindings = all_considered_bindings();
672+
let inferred = place_from_bindings_impl(
673+
db,
674+
bindings,
675+
requires_explicit_reexport,
676+
considered_bindings,
677+
);
627678

628679
let place = match inferred {
629680
// Place is possibly undeclared and definitely unbound
@@ -647,8 +698,13 @@ fn place_by_id<'db>(
647698
place: Place::Unbound,
648699
qualifiers: _,
649700
}) => {
650-
let bindings = use_def.all_bindings(place_id);
651-
let inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport);
701+
let bindings = all_considered_bindings();
702+
let inferred = place_from_bindings_impl(
703+
db,
704+
bindings,
705+
requires_explicit_reexport,
706+
considered_bindings,
707+
);
652708

653709
// `__slots__` is a symbol with special behavior in Python's runtime. It can be
654710
// modified externally, but those changes do not take effect. We therefore issue
@@ -707,6 +763,7 @@ fn symbol_impl<'db>(
707763
scope: ScopeId<'db>,
708764
name: &str,
709765
requires_explicit_reexport: RequiresExplicitReExport,
766+
considered_bindings: ConsideredBindings,
710767
) -> PlaceAndQualifiers<'db> {
711768
let _span = tracing::trace_span!("symbol", ?name).entered();
712769

@@ -726,7 +783,15 @@ fn symbol_impl<'db>(
726783

727784
place_table(db, scope)
728785
.place_id_by_name(name)
729-
.map(|symbol| place_by_id(db, scope, symbol, requires_explicit_reexport))
786+
.map(|symbol| {
787+
place_by_id(
788+
db,
789+
scope,
790+
symbol,
791+
requires_explicit_reexport,
792+
considered_bindings,
793+
)
794+
})
730795
.unwrap_or_default()
731796
}
732797

@@ -736,12 +801,21 @@ fn place_impl<'db>(
736801
scope: ScopeId<'db>,
737802
expr: &PlaceExpr,
738803
requires_explicit_reexport: RequiresExplicitReExport,
804+
considered_bindings: ConsideredBindings,
739805
) -> PlaceAndQualifiers<'db> {
740806
let _span = tracing::trace_span!("place", ?expr).entered();
741807

742808
place_table(db, scope)
743809
.place_id_by_expr(expr)
744-
.map(|place| place_by_id(db, scope, place, requires_explicit_reexport))
810+
.map(|place| {
811+
place_by_id(
812+
db,
813+
scope,
814+
place,
815+
requires_explicit_reexport,
816+
considered_bindings,
817+
)
818+
})
745819
.unwrap_or_default()
746820
}
747821

@@ -754,6 +828,7 @@ fn place_from_bindings_impl<'db>(
754828
db: &'db dyn Db,
755829
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
756830
requires_explicit_reexport: RequiresExplicitReExport,
831+
considered_bindings: ConsideredBindings,
757832
) -> Place<'db> {
758833
let predicates = bindings_with_constraints.predicates;
759834
let reachability_constraints = bindings_with_constraints.reachability_constraints;
@@ -868,15 +943,24 @@ fn place_from_bindings_impl<'db>(
868943
);
869944

870945
if let Some(first) = types.next() {
871-
let boundness = match unbound_reachability() {
872-
Some(Truthiness::AlwaysTrue) => {
873-
// unreachable!(
874-
// "If we have at least one binding, the implicit `unbound` binding should not be definitely visible"
875-
// )
876-
Boundness::Bound // TODO
946+
let boundness = match considered_bindings {
947+
ConsideredBindings::AllReachable => {
948+
// TODO: explain why we do this
949+
Boundness::Bound
950+
}
951+
ConsideredBindings::LiveBindingsAtUse => {
952+
// If we have at least one binding, the implicit `unbound` binding should not be
953+
// definitely visible.
954+
match unbound_reachability() {
955+
Some(Truthiness::AlwaysTrue) => {
956+
unreachable!(
957+
"If we have at least one binding, the implicit `unbound` binding should not be definitely visible"
958+
)
959+
}
960+
Some(Truthiness::AlwaysFalse) | None => Boundness::Bound,
961+
Some(Truthiness::Ambiguous) => Boundness::PossiblyUnbound,
962+
}
877963
}
878-
Some(Truthiness::AlwaysFalse) | None => Boundness::Bound,
879-
Some(Truthiness::Ambiguous) => Boundness::PossiblyUnbound,
880964
};
881965

882966
let ty = if let Some(second) = types.next() {
@@ -1166,6 +1250,12 @@ impl RequiresExplicitReExport {
11661250
}
11671251
}
11681252

1253+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Update)]
1254+
pub(crate) enum ConsideredBindings {
1255+
AllReachable,
1256+
LiveBindingsAtUse,
1257+
}
1258+
11691259
/// Computes a possibly-widened type `Unknown | T_inferred` from the inferred type `T_inferred`
11701260
/// of a symbol, unless the type is a known-instance type (e.g. `typing.Any`) or the symbol is
11711261
/// considered non-modifiable (e.g. when the symbol is `@Final`). We need this for public uses

crates/ty_python_semantic/src/semantic_index.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub(crate) fn attribute_assignments<'db, 's>(
116116
let place_table = index.place_table(function_scope_id);
117117
let place = place_table.place_id_by_instance_attribute_name(name)?;
118118
let use_def = &index.use_def_maps[function_scope_id];
119-
Some((use_def.public_bindings(place), function_scope_id))
119+
Some((use_def.end_of_scope_bindings(place), function_scope_id))
120120
})
121121
}
122122

@@ -574,7 +574,7 @@ mod tests {
574574

575575
impl UseDefMap<'_> {
576576
fn first_public_binding(&self, symbol: ScopedPlaceId) -> Option<Definition<'_>> {
577-
self.public_bindings(symbol)
577+
self.end_of_scope_bindings(symbol)
578578
.find_map(|constrained_binding| constrained_binding.binding.definition())
579579
}
580580

0 commit comments

Comments
 (0)