Skip to content

Commit 0138cd2

Browse files
authored
[ty] avoid fixpoint unioning of types containing current-cycle Divergent (#21910)
Partially addresses astral-sh/ty#1732 ## Summary Don't union the previous type in fixpoint iteration if the previous type contains a `Divergent` from the current cycle and the latest type does not. The theory here, as outlined by @mtshiba at astral-sh/ty#1732 (comment), is that oscillation can't occur by removing and then reintroducing a `Divergent` type repeatedly, since `Divergent` types are only introduced at the start of fixpoint iteration. ## Test Plan Removes a `Divergent` type from the added mdtest, doesn't otherwise regress any tests.
1 parent 5e42926 commit 0138cd2

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

crates/ty_python_semantic/resources/mdtest/cycle.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,18 @@ class C:
141141
# revealed: (*, kw_only=Unknown | ((*, kw_only=Unknown) -> Unknown)) -> Unknown
142142
reveal_type(self.d)
143143
```
144+
145+
## Self-referential implicit attributes
146+
147+
```py
148+
class Cyclic:
149+
def __init__(self, data: str | dict):
150+
self.data = data
151+
152+
def update(self):
153+
if isinstance(self.data, str):
154+
self.data = {"url": self.data}
155+
156+
# revealed: Unknown | str | dict[Unknown, Unknown] | dict[Unknown | str, Unknown | str]
157+
reveal_type(Cyclic("").data)
158+
```

crates/ty_python_semantic/src/types.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,30 @@ impl<'db> Type<'db> {
922922
{
923923
self
924924
}
925-
_ => UnionType::from_elements_cycle_recovery(db, [self, previous]),
925+
_ => {
926+
// Also avoid unioning in a previous type which contains a Divergent from the
927+
// current cycle, if the most-recent type does not. This cannot cause an
928+
// oscillation, since Divergent is only introduced at the start of fixpoint
929+
// iteration.
930+
let has_divergent_type_in_cycle = |ty| {
931+
any_over_type(
932+
db,
933+
ty,
934+
&|nested_ty| {
935+
matches!(
936+
nested_ty,
937+
Type::Dynamic(DynamicType::Divergent(DivergentType { id }))
938+
if cycle.head_ids().contains(&id))
939+
},
940+
false,
941+
)
942+
};
943+
if has_divergent_type_in_cycle(previous) && !has_divergent_type_in_cycle(self) {
944+
self
945+
} else {
946+
UnionType::from_elements_cycle_recovery(db, [self, previous])
947+
}
948+
}
926949
}
927950
.recursive_type_normalized(db, cycle)
928951
}

0 commit comments

Comments
 (0)