Skip to content

Commit a0e9746

Browse files
committed
The same exercise for declarations
1 parent 92e5ed6 commit a0e9746

File tree

10 files changed

+239
-155
lines changed

10 files changed

+239
-155
lines changed

crates/ty_python_semantic/resources/mdtest/public_types.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Public types
22

3+
## Basic
4+
35
The "public type" of a symbol refers to the type that is inferred for a symbol from another scope.
46
Since it is not generally possible to analyze the full control flow of a program, we currently make
57
the assumption that the inner scope (such as the inner function below) could be executed at any
@@ -133,9 +135,33 @@ def outer(flag: bool) -> None:
133135
inner()
134136
```
135137

136-
Interplay with type narrowing:
138+
This works at arbitrary levels of nesting:
137139

138140
```py
141+
def outer() -> None:
142+
x = A()
143+
144+
def intermediate() -> None:
145+
def inner() -> None:
146+
reveal_type(x) # revealed: Unknown | A | B
147+
inner()
148+
intermediate()
149+
150+
x = B()
151+
152+
intermediate()
153+
154+
def outer(x: A) -> None:
155+
def inner() -> None:
156+
reveal_type(x) # revealed: A
157+
raise
158+
```
159+
160+
## Interplay with type narrowing
161+
162+
```py
163+
class A: ...
164+
139165
def outer(x: A | None):
140166
def inner() -> None:
141167
reveal_type(x) # revealed: A | None
@@ -151,7 +177,7 @@ def outer(x: A | None):
151177
inner()
152178
```
153179

154-
The same set of problems can appear at module scope:
180+
## At module level
155181

156182
```py
157183
def flag() -> bool:

crates/ty_python_semantic/resources/mdtest/statically_known_branches.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,7 +1249,7 @@ if True:
12491249
x: int
12501250

12511251
def f() -> None:
1252-
reveal_type(x) # revealed: int
1252+
reveal_type(x) # revealed: str | int
12531253
```
12541254

12551255
#### `if False … else`
@@ -1263,7 +1263,7 @@ else:
12631263
x: int
12641264

12651265
def f() -> None:
1266-
reveal_type(x) # revealed: int
1266+
reveal_type(x) # revealed: str | int
12671267
```
12681268

12691269
### Ambiguous

crates/ty_python_semantic/src/place.rs

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -203,14 +203,14 @@ pub(crate) fn symbol<'db>(
203203
db: &'db dyn Db,
204204
scope: ScopeId<'db>,
205205
name: &str,
206-
considered_bindings: ConsideredBindings,
206+
considered_definitions: ConsideredDefinitions,
207207
) -> PlaceAndQualifiers<'db> {
208208
symbol_impl(
209209
db,
210210
scope,
211211
name,
212212
RequiresExplicitReExport::No,
213-
considered_bindings,
213+
considered_definitions,
214214
)
215215
}
216216

@@ -220,14 +220,14 @@ pub(crate) fn place<'db>(
220220
db: &'db dyn Db,
221221
scope: ScopeId<'db>,
222222
expr: &PlaceExpr,
223-
considered_bindings: ConsideredBindings,
223+
considered_definitions: ConsideredDefinitions,
224224
) -> PlaceAndQualifiers<'db> {
225225
place_impl(
226226
db,
227227
scope,
228228
expr,
229229
RequiresExplicitReExport::No,
230-
considered_bindings,
230+
considered_definitions,
231231
)
232232
}
233233

@@ -246,7 +246,7 @@ pub(crate) fn class_symbol<'db>(
246246
scope,
247247
symbol,
248248
RequiresExplicitReExport::No,
249-
ConsideredBindings::LiveBindingsAtUse,
249+
ConsideredDefinitions::AllLiveAtUse,
250250
);
251251

252252
if symbol_and_quals.is_class_var() {
@@ -267,7 +267,7 @@ pub(crate) fn class_symbol<'db>(
267267
db,
268268
bindings,
269269
RequiresExplicitReExport::No,
270-
ConsideredBindings::LiveBindingsAtUse,
270+
ConsideredDefinitions::AllLiveAtUse,
271271
);
272272

273273
// TODO: we should not need to calculate inferred type second time. This is a temporary
@@ -303,7 +303,7 @@ pub(crate) fn explicit_global_symbol<'db>(
303303
global_scope(db, file),
304304
name,
305305
RequiresExplicitReExport::No,
306-
ConsideredBindings::AllReachable,
306+
ConsideredDefinitions::AllReachable,
307307
)
308308
}
309309

@@ -362,7 +362,7 @@ pub(crate) fn imported_symbol<'db>(
362362
global_scope(db, file),
363363
name,
364364
requires_explicit_reexport,
365-
ConsideredBindings::LiveBindingsAtUse,
365+
ConsideredDefinitions::AllLiveAtUse,
366366
)
367367
.or_fall_back_to(db, || {
368368
if name == "__getattr__" {
@@ -392,7 +392,7 @@ pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> PlaceAndQua
392392
global_scope(db, file),
393393
symbol,
394394
RequiresExplicitReExport::Yes,
395-
ConsideredBindings::LiveBindingsAtUse,
395+
ConsideredDefinitions::AllLiveAtUse,
396396
)
397397
.or_fall_back_to(db, || {
398398
// We're looking up in the builtins namespace and not the module, so we should
@@ -463,13 +463,13 @@ fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_
463463
pub(super) fn place_from_bindings<'db>(
464464
db: &'db dyn Db,
465465
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
466-
considered_bindings: ConsideredBindings, // TODO: remove this and always use `LiveBindingsAtUse` here?
466+
considered_definitions: ConsideredDefinitions, // TODO: remove this and always use `LiveBindingsAtUse` here?
467467
) -> Place<'db> {
468468
place_from_bindings_impl(
469469
db,
470470
bindings_with_constraints,
471471
RequiresExplicitReExport::No,
472-
considered_bindings,
472+
considered_definitions,
473473
)
474474
}
475475

@@ -484,8 +484,14 @@ pub(super) fn place_from_bindings<'db>(
484484
pub(crate) fn place_from_declarations<'db>(
485485
db: &'db dyn Db,
486486
declarations: DeclarationsIterator<'_, 'db>,
487+
considered_definitions: ConsideredDefinitions,
487488
) -> PlaceFromDeclarationsResult<'db> {
488-
place_from_declarations_impl(db, declarations, RequiresExplicitReExport::No)
489+
place_from_declarations_impl(
490+
db,
491+
declarations,
492+
RequiresExplicitReExport::No,
493+
considered_definitions,
494+
)
489495
}
490496

491497
/// The result of looking up a declared type from declarations; see [`place_from_declarations`].
@@ -619,7 +625,7 @@ fn place_cycle_recover<'db>(
619625
_scope: ScopeId<'db>,
620626
_place_id: ScopedPlaceId,
621627
_requires_explicit_reexport: RequiresExplicitReExport,
622-
_considered_bindings: ConsideredBindings,
628+
_considered_definitions: ConsideredDefinitions,
623629
) -> salsa::CycleRecoveryAction<PlaceAndQualifiers<'db>> {
624630
salsa::CycleRecoveryAction::Iterate
625631
}
@@ -629,7 +635,7 @@ fn place_cycle_initial<'db>(
629635
_scope: ScopeId<'db>,
630636
_place_id: ScopedPlaceId,
631637
_requires_explicit_reexport: RequiresExplicitReExport,
632-
_considered_bindings: ConsideredBindings,
638+
_considered_definitions: ConsideredDefinitions,
633639
) -> PlaceAndQualifiers<'db> {
634640
Place::bound(Type::Never).into()
635641
}
@@ -640,19 +646,28 @@ fn place_by_id<'db>(
640646
scope: ScopeId<'db>,
641647
place_id: ScopedPlaceId,
642648
requires_explicit_reexport: RequiresExplicitReExport,
643-
considered_bindings: ConsideredBindings,
649+
considered_definitions: ConsideredDefinitions,
644650
) -> PlaceAndQualifiers<'db> {
645651
let use_def = use_def_map(db, scope);
646652

647653
// If the place is declared, the public type is based on declarations; otherwise, it's based
648654
// on inference from bindings.
649655

650-
let declarations = use_def.public_declarations(place_id);
651-
let declared = place_from_declarations_impl(db, declarations, requires_explicit_reexport);
656+
let declarations = match considered_definitions {
657+
ConsideredDefinitions::AllLiveAtUse => use_def.end_of_scope_declarations(place_id),
658+
ConsideredDefinitions::AllReachable => use_def.reachable_declarations(place_id),
659+
};
660+
661+
let declared = place_from_declarations_impl(
662+
db,
663+
declarations,
664+
requires_explicit_reexport,
665+
considered_definitions,
666+
);
652667

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),
668+
let all_considered_bindings = || match considered_definitions {
669+
ConsideredDefinitions::AllLiveAtUse => use_def.end_of_scope_bindings(place_id),
670+
ConsideredDefinitions::AllReachable => use_def.reachable_bindings(place_id),
656671
};
657672

658673
match declared {
@@ -673,7 +688,7 @@ fn place_by_id<'db>(
673688
db,
674689
bindings,
675690
requires_explicit_reexport,
676-
considered_bindings,
691+
considered_definitions,
677692
);
678693

679694
let place = match inferred {
@@ -703,7 +718,7 @@ fn place_by_id<'db>(
703718
db,
704719
bindings,
705720
requires_explicit_reexport,
706-
considered_bindings,
721+
considered_definitions,
707722
);
708723

709724
// `__slots__` is a symbol with special behavior in Python's runtime. It can be
@@ -763,7 +778,7 @@ fn symbol_impl<'db>(
763778
scope: ScopeId<'db>,
764779
name: &str,
765780
requires_explicit_reexport: RequiresExplicitReExport,
766-
considered_bindings: ConsideredBindings,
781+
considered_definitions: ConsideredDefinitions,
767782
) -> PlaceAndQualifiers<'db> {
768783
let _span = tracing::trace_span!("symbol", ?name).entered();
769784

@@ -789,7 +804,7 @@ fn symbol_impl<'db>(
789804
scope,
790805
symbol,
791806
requires_explicit_reexport,
792-
considered_bindings,
807+
considered_definitions,
793808
)
794809
})
795810
.unwrap_or_default()
@@ -801,7 +816,7 @@ fn place_impl<'db>(
801816
scope: ScopeId<'db>,
802817
expr: &PlaceExpr,
803818
requires_explicit_reexport: RequiresExplicitReExport,
804-
considered_bindings: ConsideredBindings,
819+
considered_definitions: ConsideredDefinitions,
805820
) -> PlaceAndQualifiers<'db> {
806821
let _span = tracing::trace_span!("place", ?expr).entered();
807822

@@ -813,7 +828,7 @@ fn place_impl<'db>(
813828
scope,
814829
place,
815830
requires_explicit_reexport,
816-
considered_bindings,
831+
considered_definitions,
817832
)
818833
})
819834
.unwrap_or_default()
@@ -828,7 +843,7 @@ fn place_from_bindings_impl<'db>(
828843
db: &'db dyn Db,
829844
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
830845
requires_explicit_reexport: RequiresExplicitReExport,
831-
considered_bindings: ConsideredBindings,
846+
considered_definitions: ConsideredDefinitions,
832847
) -> Place<'db> {
833848
let predicates = bindings_with_constraints.predicates;
834849
let reachability_constraints = bindings_with_constraints.reachability_constraints;
@@ -943,12 +958,12 @@ fn place_from_bindings_impl<'db>(
943958
);
944959

945960
if let Some(first) = types.next() {
946-
let boundness = match considered_bindings {
947-
ConsideredBindings::AllReachable => {
961+
let boundness = match considered_definitions {
962+
ConsideredDefinitions::AllReachable => {
948963
// TODO: explain why we do this
949964
Boundness::Bound
950965
}
951-
ConsideredBindings::LiveBindingsAtUse => {
966+
ConsideredDefinitions::AllLiveAtUse => {
952967
// If we have at least one binding, the implicit `unbound` binding should not be
953968
// definitely visible.
954969
match unbound_reachability() {
@@ -987,6 +1002,7 @@ fn place_from_declarations_impl<'db>(
9871002
db: &'db dyn Db,
9881003
declarations: DeclarationsIterator<'_, 'db>,
9891004
requires_explicit_reexport: RequiresExplicitReExport,
1005+
considered_definitions: ConsideredDefinitions,
9901006
) -> PlaceFromDeclarationsResult<'db> {
9911007
let predicates = declarations.predicates;
9921008
let reachability_constraints = declarations.reachability_constraints;
@@ -1050,14 +1066,17 @@ fn place_from_declarations_impl<'db>(
10501066
first
10511067
};
10521068
if conflicting.is_empty() {
1053-
let boundness = match undeclared_reachability {
1054-
Truthiness::AlwaysTrue => {
1055-
unreachable!(
1056-
"If we have at least one declaration, the implicit `unbound` binding should not be definitely visible"
1057-
)
1058-
}
1059-
Truthiness::AlwaysFalse => Boundness::Bound,
1060-
Truthiness::Ambiguous => Boundness::PossiblyUnbound,
1069+
let boundness = match considered_definitions {
1070+
ConsideredDefinitions::AllReachable => Boundness::Bound,
1071+
ConsideredDefinitions::AllLiveAtUse => match undeclared_reachability {
1072+
Truthiness::AlwaysTrue => {
1073+
unreachable!(
1074+
"If we have at least one declaration, the implicit `unbound` binding should not be definitely visible"
1075+
)
1076+
}
1077+
Truthiness::AlwaysFalse => Boundness::Bound,
1078+
Truthiness::Ambiguous => Boundness::PossiblyUnbound,
1079+
},
10611080
};
10621081

10631082
Ok(
@@ -1102,7 +1121,7 @@ mod implicit_globals {
11021121
use ruff_python_ast as ast;
11031122

11041123
use crate::db::Db;
1105-
use crate::place::PlaceAndQualifiers;
1124+
use crate::place::{ConsideredDefinitions, PlaceAndQualifiers};
11061125
use crate::semantic_index::place::PlaceExpr;
11071126
use crate::semantic_index::{self, place_table, use_def_map};
11081127
use crate::types::{KnownClass, Type};
@@ -1130,7 +1149,8 @@ mod implicit_globals {
11301149
};
11311150
place_from_declarations(
11321151
db,
1133-
use_def_map(db, module_type_scope).public_declarations(place_id),
1152+
use_def_map(db, module_type_scope).end_of_scope_declarations(place_id),
1153+
ConsideredDefinitions::AllLiveAtUse,
11341154
)
11351155
}
11361156

@@ -1251,9 +1271,9 @@ impl RequiresExplicitReExport {
12511271
}
12521272

12531273
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Update)]
1254-
pub(crate) enum ConsideredBindings {
1274+
pub(crate) enum ConsideredDefinitions {
12551275
AllReachable,
1256-
LiveBindingsAtUse,
1276+
AllLiveAtUse,
12571277
}
12581278

12591279
/// Computes a possibly-widened type `Unknown | T_inferred` from the inferred type `T_inferred`

0 commit comments

Comments
 (0)