Skip to content

Commit b2cb8bf

Browse files
committed
[ty] Fall back to Divergent for deeply nested specializations
1 parent 0f21567 commit b2cb8bf

5 files changed

Lines changed: 165 additions & 3 deletions

File tree

crates/ty_python_semantic/resources/mdtest/attributes.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2457,6 +2457,31 @@ class Counter:
24572457
reveal_type(Counter().count) # revealed: Unknown | int
24582458
```
24592459

2460+
We also handle infinitely nested generics:
2461+
2462+
```py
2463+
class NestedLists:
2464+
def __init__(self: "NestedLists"):
2465+
self.x = 1
2466+
2467+
def f(self: "NestedLists"):
2468+
self.x = [self.x]
2469+
2470+
reveal_type(NestedLists().x) # revealed: Unknown | Literal[1] | list[Divergent]
2471+
2472+
class NestedMixed:
2473+
def f(self: "NestedMixed"):
2474+
self.x = [self.x]
2475+
2476+
def g(self: "NestedMixed"):
2477+
self.x = {self.x}
2478+
2479+
def h(self: "NestedMixed"):
2480+
self.x = {"a": self.x}
2481+
2482+
reveal_type(NestedMixed().x) # revealed: Unknown | list[Divergent] | set[Divergent] | dict[Unknown | str, Divergent]
2483+
```
2484+
24602485
### Builtin types attributes
24612486

24622487
This test can probably be removed eventually, but we currently include it because we do not yet

crates/ty_python_semantic/src/types.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ use crate::types::tuple::{TupleSpec, TupleSpecBuilder};
6969
pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type};
7070
pub use crate::types::variance::TypeVarVariance;
7171
use crate::types::variance::VarianceInferable;
72-
use crate::types::visitor::any_over_type;
72+
use crate::types::visitor::{any_over_type, specialization_depth};
7373
use crate::unpack::EvaluationMode;
7474
use crate::{Db, FxOrderSet, Module, Program};
7575
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
@@ -831,6 +831,10 @@ impl<'db> Type<'db> {
831831
Self::Dynamic(DynamicType::Divergent(DivergentType { scope }))
832832
}
833833

834+
pub(crate) const fn is_divergent(&self) -> bool {
835+
matches!(self, Type::Dynamic(DynamicType::Divergent(_)))
836+
}
837+
834838
pub const fn is_unknown(&self) -> bool {
835839
matches!(self, Type::Dynamic(DynamicType::Unknown))
836840
}

crates/ty_python_semantic/src/types/class.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use crate::types::{
3737
IsDisjointVisitor, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType,
3838
MaterializationKind, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
3939
TypeContext, TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable,
40-
declaration_type, determine_upper_bound, infer_definition_types,
40+
declaration_type, determine_upper_bound, infer_definition_types, specialization_depth,
4141
};
4242
use crate::{
4343
Db, FxIndexMap, FxIndexSet, FxOrderSet, Program,
@@ -1609,10 +1609,34 @@ impl<'db> ClassLiteral<'db> {
16091609
db: &'db dyn Db,
16101610
f: impl FnOnce(GenericContext<'db>) -> Specialization<'db>,
16111611
) -> ClassType<'db> {
1612+
// To prevent infinite recursion during type inference for infinite types, we fall back to
1613+
// `C[Divergent]` once a certain amount of levels of specialization have occurred. For
1614+
// example:
1615+
//
1616+
// ```py
1617+
// x = 1
1618+
// while random_bool():
1619+
// x = [x]
1620+
//
1621+
// reveal_type(x) # Unknown | Literal[1] | list[Divergent]
1622+
// ```
1623+
const MAX_SPECIALIZATION_DEPTH: usize = 10;
1624+
16121625
match self.generic_context(db) {
16131626
None => ClassType::NonGeneric(self),
16141627
Some(generic_context) => {
1615-
let specialization = f(generic_context);
1628+
let mut specialization = f(generic_context);
1629+
1630+
for (idx, ty) in specialization.types(db).iter().enumerate() {
1631+
if specialization_depth(db, *ty) > MAX_SPECIALIZATION_DEPTH {
1632+
specialization = specialization.with_replaced_type(
1633+
db,
1634+
idx,
1635+
Type::divergent(self.body_scope(db)),
1636+
);
1637+
}
1638+
}
1639+
16161640
ClassType::Generic(GenericAlias::new(db, self, specialization))
16171641
}
16181642
}

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,27 @@ impl<'db> Specialization<'db> {
12641264
// A tuple's specialization will include all of its element types, so we don't need to also
12651265
// look in `self.tuple`.
12661266
}
1267+
1268+
/// Returns a copy of this specialization with the type at a given index replaced.
1269+
pub(crate) fn with_replaced_type(
1270+
self,
1271+
db: &'db dyn Db,
1272+
index: usize,
1273+
new_type: Type<'db>,
1274+
) -> Self {
1275+
debug_assert!(index < self.types(db).len());
1276+
1277+
let mut new_types: Box<[_]> = self.types(db).to_vec().into_boxed_slice();
1278+
new_types[index] = new_type;
1279+
1280+
Self::new(
1281+
db,
1282+
self.generic_context(db),
1283+
new_types,
1284+
self.materialization_kind(db),
1285+
self.tuple_inner(db),
1286+
)
1287+
}
12671288
}
12681289

12691290
/// A mapping between type variables and types.

crates/ty_python_semantic/src/types/visitor.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,91 @@ pub(super) fn any_over_type<'db>(
295295
visitor.visit_type(db, ty);
296296
visitor.found_matching_type.get()
297297
}
298+
299+
/// Returns the maximum number of layers of generic specializations for a given type.
300+
///
301+
/// For example, `int` has a depth of `0`, `list[int]` has a depth of `1`, and `list[set[int]]`
302+
/// has a depth of `2`. A set-theoretic type like `list[int] | list[list[int]]` has a maximum
303+
/// depth of `2`.
304+
pub(super) fn specialization_depth(db: &dyn Db, ty: Type<'_>) -> usize {
305+
struct SpecializationDepthVisitor<'db> {
306+
seen_types: RefCell<FxIndexSet<NonAtomicType<'db>>>,
307+
max_depth: Cell<usize>,
308+
}
309+
310+
impl<'db> TypeVisitor<'db> for SpecializationDepthVisitor<'db> {
311+
fn should_visit_lazy_type_attributes(&self) -> bool {
312+
false
313+
}
314+
315+
fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) {
316+
match TypeKind::from(ty) {
317+
TypeKind::Atomic => {
318+
if ty.is_divergent() {
319+
self.max_depth.set(usize::MAX);
320+
}
321+
}
322+
TypeKind::NonAtomic(non_atomic_type) => {
323+
if !self.seen_types.borrow_mut().insert(non_atomic_type) {
324+
return;
325+
}
326+
327+
walk_non_atomic_type(db, non_atomic_type, self);
328+
let child_max_depth = self.max_depth.get();
329+
330+
let self_depth: usize =
331+
matches!(non_atomic_type, NonAtomicType::GenericAlias(_)).into();
332+
333+
self.max_depth.set(
334+
self.max_depth
335+
.get()
336+
.max(child_max_depth.saturating_add(self_depth)),
337+
);
338+
}
339+
}
340+
}
341+
}
342+
343+
let visitor = SpecializationDepthVisitor {
344+
seen_types: RefCell::new(FxIndexSet::default()),
345+
max_depth: Cell::new(0),
346+
};
347+
visitor.visit_type(db, ty);
348+
visitor.max_depth.get()
349+
}
350+
351+
#[cfg(test)]
352+
mod tests {
353+
use super::*;
354+
use crate::{db::tests::setup_db, types::KnownClass};
355+
356+
#[test]
357+
fn test_generics_layering_depth() {
358+
let db = setup_db();
359+
360+
let list_of_int =
361+
KnownClass::List.to_specialized_instance(&db, [KnownClass::Int.to_instance(&db)]);
362+
assert_eq!(specialization_depth(&db, list_of_int), 1);
363+
364+
let list_of_list_of_int = KnownClass::List.to_specialized_instance(&db, [list_of_int]);
365+
assert_eq!(specialization_depth(&db, list_of_list_of_int), 2);
366+
367+
let list_of_list_of_list_of_int =
368+
KnownClass::List.to_specialized_instance(&db, [list_of_list_of_int]);
369+
assert_eq!(specialization_depth(&db, list_of_list_of_list_of_int), 3);
370+
371+
let set_of_dict_of_str_and_list_of_int = KnownClass::Set.to_specialized_instance(
372+
&db,
373+
[KnownClass::Dict
374+
.to_specialized_instance(&db, [KnownClass::Str.to_instance(&db), list_of_int])],
375+
);
376+
assert_eq!(
377+
specialization_depth(&db, set_of_dict_of_str_and_list_of_int),
378+
3
379+
);
380+
381+
let union_type =
382+
UnionType::from_elements(&db, [list_of_list_of_int, list_of_list_of_list_of_int]);
383+
assert_eq!(specialization_depth(&db, union_type), 3);
384+
}
385+
}

0 commit comments

Comments
 (0)