[ty] Avoid stack overflow when calculating inferable typevars (#21971)

When we calculate which typevars are inferable in a generic context, the
result might include more than the typevars bound by the generic
context. The canonical example is a generic method of a generic class:

```py
class C[A]:
    def method[T](self, t: T): ...
```

Here, the inferable typevar set of `method` contains `Self` and `T`, as
you'd expect. (Those are the typevars bound by the method.) But it also
contains `A@C`, since the implicit `Self` typevar is defined as `Self:
C[A]`. That means when we call `method`, we need to mark `A@C` as
inferable, so that we can determine the correct mapping for `A@C` at the
call site.

Fixes https://github.com/astral-sh/ty/issues/1874
This commit is contained in:
Douglas Creager
2025-12-15 10:25:33 -05:00
committed by GitHub
parent 8f530a7ab0
commit cbfecfaf41
2 changed files with 41 additions and 3 deletions

View File

@@ -23,7 +23,7 @@ use crate::types::{
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Signature, Type,
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity,
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
walk_bound_type_var_type,
walk_type_var_bounds,
};
use crate::{Db, FxOrderMap, FxOrderSet};
@@ -290,6 +290,18 @@ impl<'db> GenericContext<'db> {
)
}
/// Returns the typevars that are inferable in this generic context. This set might include
/// more typevars than the ones directly bound by the generic context. For instance, consider a
/// method of a generic class:
///
/// ```py
/// class C[A]:
/// def method[T](self, t: T):
/// ```
///
/// In this example, `method`'s generic context binds `Self` and `T`, but its inferable set
/// also includes `A@C`. This is needed because at each call site, we need to infer the
/// specialized class instance type whose method is being invoked.
pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
#[derive(Default)]
struct CollectTypeVars<'db> {
@@ -299,7 +311,7 @@ impl<'db> GenericContext<'db> {
impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> {
fn should_visit_lazy_type_attributes(&self) -> bool {
true
false
}
fn visit_bound_type_var_type(
@@ -310,7 +322,10 @@ impl<'db> GenericContext<'db> {
self.typevars
.borrow_mut()
.insert(bound_typevar.identity(db));
walk_bound_type_var_type(db, bound_typevar, self);
let typevar = bound_typevar.typevar(db);
if let Some(bound_or_constraints) = typevar.bound_or_constraints(db) {
walk_type_var_bounds(db, bound_or_constraints, self);
}
}
fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) {