Skip to content

Commit 4fdb4e8

Browse files
authored
[ty] avoid unions of generic aliases of the same class in fixpoint (#21909)
Partially addresses astral-sh/ty#1732 Fixes astral-sh/ty#1800 ## Summary At each fixpoint iteration, we union the "previous" and "current" iteration types, to ensure that the type can only widen at each iteration. This prevents oscillation and ensures convergence. But some unions triggered by this behavior (in particular, unions of differently-specialized generic-aliases of the same class) never simplify, and cause spurious errors. Since we haven't seen examples of oscillating types involving class-literal or generic-alias types, just don't union those. There may be more thorough/principled ways to avoid undesirable unions in fixpoint iteration, but this narrow change seems like it results in strict improvement. ## Test Plan Removes two false positive `unsupported-class-base` in mdtests, and several in the ecosystem, without causing other regression.
1 parent c548ef2 commit 4fdb4e8

File tree

2 files changed

+13
-7
lines changed

2 files changed

+13
-7
lines changed

crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -860,9 +860,6 @@ reveal_type(Sub) # revealed: <class 'Sub'>
860860
U = TypeVar("U")
861861

862862
class Base2(Generic[T, U]): ...
863-
864-
# TODO: no error
865-
# error: [unsupported-base] "Unsupported class base with type `<class 'Base2[Sub2, U@Sub2]'> | <class 'Base2[Sub2[Unknown], U@Sub2]'>`"
866863
class Sub2(Base2["Sub2", U]): ...
867864
```
868865

@@ -888,8 +885,6 @@ from typing_extensions import Generic, TypeVar
888885

889886
T = TypeVar("T")
890887

891-
# TODO: no error "Unsupported class base with type `<class 'list[Derived[T@Derived]]'> | <class 'list[@Todo]'>`"
892-
# error: [unsupported-base]
893888
class Derived(list[Derived[T]], Generic[T]): ...
894889
```
895890

crates/ty_python_semantic/src/types.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -912,8 +912,19 @@ impl<'db> Type<'db> {
912912
previous: Self,
913913
cycle: &salsa::Cycle,
914914
) -> Self {
915-
UnionType::from_elements_cycle_recovery(db, [self, previous])
916-
.recursive_type_normalized(db, cycle)
915+
// Avoid unioning two generic aliases of the same class together; this union will never
916+
// simplify and is likely to cause downstream problems. This introduces the theoretical
917+
// possibility of cycle oscillation involving such types (because we are not strictly
918+
// widening the type on each iteration), but so far we have not seen an example of that.
919+
match (previous, self) {
920+
(Type::GenericAlias(prev_alias), Type::GenericAlias(curr_alias))
921+
if prev_alias.origin(db) == curr_alias.origin(db) =>
922+
{
923+
self
924+
}
925+
_ => UnionType::from_elements_cycle_recovery(db, [self, previous]),
926+
}
927+
.recursive_type_normalized(db, cycle)
917928
}
918929

919930
fn is_none(&self, db: &'db dyn Db) -> bool {

0 commit comments

Comments
 (0)