mirror of https://github.com/astral-sh/ruff
[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:
parent
8f530a7ab0
commit
cbfecfaf41
|
|
@ -800,6 +800,29 @@ def func(x: D): ...
|
||||||
func(G()) # error: [invalid-argument-type]
|
func(G()) # error: [invalid-argument-type]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Self-referential protocol with different specialization
|
||||||
|
|
||||||
|
This is a minimal reproduction for [ty#1874](https://github.com/astral-sh/ty/issues/1874).
|
||||||
|
|
||||||
|
```py
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import Protocol
|
||||||
|
from ty_extensions import generic_context
|
||||||
|
|
||||||
|
class A[S, R](Protocol):
|
||||||
|
def get(self, s: S) -> R: ...
|
||||||
|
def set(self, s: S, r: R) -> S: ...
|
||||||
|
def merge[R2](self, other: A[S, R2]) -> A[S, tuple[R, R2]]: ...
|
||||||
|
|
||||||
|
class Impl[S, R](A[S, R]):
|
||||||
|
def foo(self, s: S) -> S:
|
||||||
|
return self.set(s, self.get(s))
|
||||||
|
|
||||||
|
reveal_type(generic_context(A.get)) # revealed: ty_extensions.GenericContext[Self@get]
|
||||||
|
reveal_type(generic_context(A.merge)) # revealed: ty_extensions.GenericContext[Self@merge, R2@merge]
|
||||||
|
reveal_type(generic_context(Impl.foo)) # revealed: ty_extensions.GenericContext[Self@foo]
|
||||||
|
```
|
||||||
|
|
||||||
## Tuple as a PEP-695 generic class
|
## Tuple as a PEP-695 generic class
|
||||||
|
|
||||||
Our special handling for `tuple` does not break if `tuple` is defined as a PEP-695 generic class in
|
Our special handling for `tuple` does not break if `tuple` is defined as a PEP-695 generic class in
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ use crate::types::{
|
||||||
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Signature, Type,
|
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Signature, Type,
|
||||||
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity,
|
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity,
|
||||||
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
|
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, declaration_type,
|
||||||
walk_bound_type_var_type,
|
walk_type_var_bounds,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderMap, FxOrderSet};
|
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> {
|
pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct CollectTypeVars<'db> {
|
struct CollectTypeVars<'db> {
|
||||||
|
|
@ -299,7 +311,7 @@ impl<'db> GenericContext<'db> {
|
||||||
|
|
||||||
impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> {
|
impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> {
|
||||||
fn should_visit_lazy_type_attributes(&self) -> bool {
|
fn should_visit_lazy_type_attributes(&self) -> bool {
|
||||||
true
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_bound_type_var_type(
|
fn visit_bound_type_var_type(
|
||||||
|
|
@ -310,7 +322,10 @@ impl<'db> GenericContext<'db> {
|
||||||
self.typevars
|
self.typevars
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(bound_typevar.identity(db));
|
.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>) {
|
fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue