Skip to content

Commit cae39bb

Browse files
committed
fix stack overflow on protocols that use Self
1 parent 7964380 commit cae39bb

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

crates/ty_python_semantic/resources/mdtest/protocols.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,6 +1753,28 @@ if isinstance(obj, (B, A)):
17531753
reveal_type(obj) # revealed: (Unknown & B) | (Unknown & A)
17541754
```
17551755

1756+
### Protocols that use `Self`
1757+
1758+
`Self` is a `TypeVar` with an upper bound of the class in which it is defined. This means that
1759+
`Self` annotations in protocols can also be tricky to handle without infinite recursion and stack
1760+
overflows.
1761+
1762+
```py
1763+
from typing_extensions import Protocol, Self
1764+
from ty_extensions import static_assert, is_fully_static
1765+
1766+
class _HashObject(Protocol):
1767+
def copy(self) -> Self: ...
1768+
1769+
class Foo: ...
1770+
1771+
# Attempting to build this union caused us to overflow on an early version of
1772+
# <https://github.com/astral-sh/ruff/pull/18659>
1773+
x: Foo | _HashObject
1774+
1775+
static_assert(is_fully_static(_HashObject))
1776+
```
1777+
17561778
## TODO
17571779

17581780
Add tests for:

crates/ty_python_semantic/src/types.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -771,9 +771,10 @@ impl<'db> Type<'db> {
771771
),
772772

773773
Self::Callable(callable) => Self::Callable(callable.replace_self_reference(db, class)),
774+
Self::TypeVar(type_var) => Self::TypeVar(type_var.replace_self_reference(db, class)),
774775

775-
Self::GenericAlias(_) | Self::TypeVar(_) => {
776-
// TODO: replace self-references in generic aliases and typevars
776+
Self::GenericAlias(_) => {
777+
// TODO: replace self-references in generic aliases
777778
*self
778779
}
779780

@@ -6126,6 +6127,20 @@ impl<'db> TypeVarInstance<'db> {
61266127
self.kind(db),
61276128
)
61286129
}
6130+
6131+
fn replace_self_reference(self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self {
6132+
Self::new(
6133+
db,
6134+
self.name(db),
6135+
self.definition(db),
6136+
self.bound_or_constraints(db)
6137+
.map(|b| b.replace_self_reference(db, class)),
6138+
self.variance(db),
6139+
self.default_ty(db)
6140+
.map(|d| d.replace_self_reference(db, class)),
6141+
self.kind(db),
6142+
)
6143+
}
61296144
}
61306145

61316146
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)]
@@ -6186,6 +6201,24 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
61866201
}
61876202
}
61886203
}
6204+
6205+
fn replace_self_reference(self, db: &'db dyn Db, class: ClassLiteral<'db>) -> Self {
6206+
match self {
6207+
TypeVarBoundOrConstraints::UpperBound(bound) => {
6208+
TypeVarBoundOrConstraints::UpperBound(bound.replace_self_reference(db, class))
6209+
}
6210+
TypeVarBoundOrConstraints::Constraints(constraints) => {
6211+
let constraints = constraints.map(db, |ty| ty.replace_self_reference(db, class));
6212+
match constraints {
6213+
Type::Union(constraints) => TypeVarBoundOrConstraints::Constraints(constraints),
6214+
_ => TypeVarBoundOrConstraints::Constraints(UnionType::new(
6215+
db,
6216+
Box::from([constraints]),
6217+
)),
6218+
}
6219+
}
6220+
}
6221+
}
61896222
}
61906223

61916224
/// Error returned if a type is not (or may not be) a context manager.

0 commit comments

Comments
 (0)