Skip to content

Commit f785733

Browse files
committed
Introduce SnapshotCompleteness to retain and utilize incomplete snapshots
1 parent 6898535 commit f785733

File tree

7 files changed

+260
-123
lines changed

7 files changed

+260
-123
lines changed

crates/ty_python_semantic/resources/mdtest/public_types.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,7 @@ def outer() -> None:
214214
x = 1
215215

216216
def inner() -> None:
217-
# TODO: ideally, this should be `Literal[1, 2]`
218-
reveal_type(x) # revealed: None | Literal[1, 2]
217+
reveal_type(x) # revealed: Literal[1, 2]
219218
inner()
220219

221220
x = 2

crates/ty_python_semantic/src/place.rs

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ use crate::dunder_all::dunder_all_names;
44
use crate::module_resolver::{KnownModule, file_to_module};
55
use crate::semantic_index::definition::{Definition, DefinitionState};
66
use crate::semantic_index::place::{PlaceExprRef, ScopedPlaceId};
7-
use crate::semantic_index::scope::ScopeId;
7+
use crate::semantic_index::scope::{ScopeId, ScopeLaziness};
88
use crate::semantic_index::{
9-
BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, place_table,
9+
BindingWithConstraints, BindingWithConstraintsIterator, BoundnessAnalysis,
10+
ConsideredDefinitions, DeclarationsIterator, EnclosingSnapshotResult, place_table,
1011
};
1112
use crate::semantic_index::{DeclarationWithConstraint, global_scope, use_def_map};
1213
use crate::types::{
@@ -293,7 +294,7 @@ pub(crate) fn explicit_global_symbol<'db>(
293294
global_scope(db, file),
294295
name,
295296
RequiresExplicitReExport::No,
296-
ConsideredDefinitions::AllReachable,
297+
ConsideredDefinitions::AllReachable(None),
297298
)
298299
}
299300

@@ -700,15 +701,24 @@ fn place_by_id<'db>(
700701

701702
let declarations = match considered_definitions {
702703
ConsideredDefinitions::EndOfScope => use_def.end_of_scope_declarations(place_id),
703-
ConsideredDefinitions::AllReachable => use_def.all_reachable_declarations(place_id),
704+
ConsideredDefinitions::AllReachable(_) => use_def.all_reachable_declarations(place_id),
704705
};
705706

706707
let declared = place_from_declarations_impl(db, declarations, requires_explicit_reexport)
707708
.ignore_conflicting_declarations();
708709

709710
let all_considered_bindings = || match considered_definitions {
710711
ConsideredDefinitions::EndOfScope => use_def.end_of_scope_bindings(place_id),
711-
ConsideredDefinitions::AllReachable => use_def.all_reachable_bindings(place_id),
712+
ConsideredDefinitions::AllReachable(None) => use_def.all_reachable_bindings(place_id),
713+
ConsideredDefinitions::AllReachable(Some(snapshot)) => {
714+
if let EnclosingSnapshotResult::FoundBindings(bindings, _) =
715+
use_def.enclosing_snapshot(snapshot, ScopeLaziness::Lazy)
716+
{
717+
bindings
718+
} else {
719+
use_def.all_reachable_bindings(place_id)
720+
}
721+
}
712722
};
713723

714724
// If a symbol is undeclared, but qualified with `typing.Final`, we use the right-hand side
@@ -763,7 +773,7 @@ fn place_by_id<'db>(
763773
// Place is possibly undeclared and (possibly) bound
764774
Place::Type(inferred_ty, boundness) => Place::Type(
765775
UnionType::from_elements(db, [inferred_ty, declared_ty]),
766-
if boundness_analysis == BoundnessAnalysis::AssumeBound {
776+
if boundness_analysis.is_assume_bound() {
767777
Boundness::Bound
768778
} else {
769779
boundness
@@ -782,7 +792,7 @@ fn place_by_id<'db>(
782792
let boundness_analysis = bindings.boundness_analysis;
783793
let mut inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport);
784794

785-
if boundness_analysis == BoundnessAnalysis::AssumeBound {
795+
if boundness_analysis.is_assume_bound() {
786796
if let Place::Type(ty, Boundness::PossiblyUnbound) = inferred {
787797
inferred = Place::Type(ty, Boundness::Bound);
788798
}
@@ -1037,7 +1047,7 @@ fn place_from_bindings_impl<'db>(
10371047
};
10381048

10391049
let boundness = match boundness_analysis {
1040-
BoundnessAnalysis::AssumeBound => Boundness::Bound,
1050+
BoundnessAnalysis::AssumeBound(_) => Boundness::Bound,
10411051
BoundnessAnalysis::BasedOnUnboundVisibility => match unbound_visibility() {
10421052
Some(Truthiness::AlwaysTrue) => {
10431053
unreachable!(
@@ -1245,7 +1255,7 @@ fn place_from_declarations_impl<'db>(
12451255
};
12461256

12471257
let boundness = match boundness_analysis {
1248-
BoundnessAnalysis::AssumeBound => {
1258+
BoundnessAnalysis::AssumeBound(_) => {
12491259
if all_declarations_definitely_reachable {
12501260
Boundness::Bound
12511261
} else {
@@ -1458,48 +1468,6 @@ impl RequiresExplicitReExport {
14581468
}
14591469
}
14601470

1461-
/// Specifies which definitions should be considered when looking up a place.
1462-
///
1463-
/// In the example below, the `EndOfScope` variant would consider the `x = 2` and `x = 3` definitions,
1464-
/// while the `AllReachable` variant would also consider the `x = 1` definition.
1465-
/// ```py
1466-
/// def _():
1467-
/// x = 1
1468-
///
1469-
/// x = 2
1470-
///
1471-
/// if flag():
1472-
/// x = 3
1473-
/// ```
1474-
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
1475-
pub(crate) enum ConsideredDefinitions {
1476-
/// Consider only the definitions that are "live" at the end of the scope, i.e. those
1477-
/// that have not been shadowed or deleted.
1478-
EndOfScope,
1479-
/// Consider all definitions that are reachable from the start of the scope.
1480-
AllReachable,
1481-
}
1482-
1483-
/// Specifies how the boundness of a place should be determined.
1484-
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
1485-
pub(crate) enum BoundnessAnalysis {
1486-
/// The place is always considered bound.
1487-
AssumeBound,
1488-
/// The boundness of the place is determined based on the visibility of the implicit
1489-
/// `unbound` binding. In the example below, when analyzing the visibility of the
1490-
/// `x = <unbound>` binding from the position of the end of the scope, it would be
1491-
/// `Truthiness::Ambiguous`, because it could either be visible or not, depending on the
1492-
/// `flag()` return value. This would result in a `Boundness::PossiblyUnbound` for `x`.
1493-
///
1494-
/// ```py
1495-
/// x = <unbound>
1496-
///
1497-
/// if flag():
1498-
/// x = 1
1499-
/// ```
1500-
BasedOnUnboundVisibility,
1501-
}
1502-
15031471
/// Computes a possibly-widened type `Unknown | T_inferred` from the inferred type `T_inferred`
15041472
/// of a symbol, unless the type is a known-instance type (e.g. `typing.Any`) or the symbol is
15051473
/// 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: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ use crate::semantic_index::scope::{
2626
NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopeKind, ScopeLaziness,
2727
};
2828
use crate::semantic_index::symbol::ScopedSymbolId;
29+
pub(crate) use crate::semantic_index::use_def::{
30+
BoundnessAnalysis, ConsideredDefinitions, SnapshotCompleteness,
31+
};
2932
use crate::semantic_index::use_def::{EnclosingSnapshotKey, ScopedEnclosingSnapshotId, UseDefMap};
3033
use crate::semantic_model::HasTrackedScope;
3134

@@ -190,7 +193,10 @@ pub(crate) fn global_scope(db: &dyn Db, file: File) -> ScopeId<'_> {
190193

191194
pub(crate) enum EnclosingSnapshotResult<'map, 'db> {
192195
FoundConstraint(ScopedNarrowingConstraint),
193-
FoundBindings(BindingWithConstraintsIterator<'map, 'db>),
196+
FoundBindings(
197+
BindingWithConstraintsIterator<'map, 'db>,
198+
SnapshotCompleteness,
199+
),
194200
NotFound,
195201
NoLongerInEagerContext,
196202
}

crates/ty_python_semantic/src/semantic_index/builder.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -440,19 +440,28 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
440440
|| (key.enclosing_scope != popped_scope_id
441441
&& VisibleAncestorsIter::new(&self.scopes, key.enclosing_scope)
442442
.all(|(ancestor, _)| ancestor != popped_scope_id))
443-
|| !key.enclosing_place.as_symbol().is_some_and(|symbol_id| {
444-
let name = &self.place_tables[key.enclosing_scope]
445-
.symbol(symbol_id)
446-
.name();
447-
popped_place_table.symbol_id(name).is_some_and(|symbol_id| {
448-
popped_place_table.symbol(symbol_id).is_reassigned()
449-
&& !self.use_def_maps[popped_scope_id].bindings_are_unchanged(
450-
&self.use_def_maps[key.enclosing_scope],
451-
*snapshot_id,
452-
symbol_id,
453-
)
443+
|| !key
444+
.enclosing_place
445+
.as_symbol()
446+
.is_some_and(|enclosing_symbol| {
447+
let name = &self.place_tables[key.enclosing_scope]
448+
.symbol(enclosing_symbol)
449+
.name();
450+
popped_place_table.symbol_id(name).is_some_and(|symbol_id| {
451+
if popped_place_table.symbol(symbol_id).is_reassigned()
452+
&& !self.use_def_maps[popped_scope_id].bindings_are_unchanged(
453+
&self.use_def_maps[key.enclosing_scope],
454+
*snapshot_id,
455+
symbol_id,
456+
)
457+
{
458+
self.use_def_maps[key.enclosing_scope]
459+
.mark_snapshot_incomplete(*snapshot_id, enclosing_symbol);
460+
}
461+
// TODO:
462+
false
463+
})
454464
})
455-
})
456465
});
457466
}
458467

0 commit comments

Comments
 (0)