[ty] Avoid stack overflow with recursive typevar#23652
Conversation
Typing conformance resultsNo changes detected ✅ |
d971798 to
1c93d1b
Compare
|
Memory usage reportSummary
Significant changesClick to expand detailed breakdownprefect
sphinx
trio
flake8
|
AlexWaygood
left a comment
There was a problem hiding this comment.
This fixes the issue, but it feels like it's patching a symptom rather than addressing the underlying cause. TypeVarInstance::default_type is already a wrapper around the low-level field _default_type that's stored directly on the Salsa struct. It feels like we should be adding cycle handling to TypeVarInstance::default_type, since apparently any access of t.default_type(db) is enough to trigger infinite recursion currently, given a sufficiently pathological typevar?
You can take a look at the BinaryComparisonVisitor, and the way we thread that through infer_binary_type_comparison, for an example of how we might add cycle detection to lazy_default
|
It's proving to be harder to do that here because |
|
can you add an inner function to |
e04f35d to
96e2835
Compare
| { | ||
| let mut seen_typevars = seen_typevars.borrow_mut(); | ||
| if !seen_typevars.insert(typevar) { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
can be a bit simpler
| { | |
| let mut seen_typevars = seen_typevars.borrow_mut(); | |
| if !seen_typevars.insert(typevar) { | |
| return false; | |
| } | |
| } | |
| if !seen_typevars.borrow_mut().insert(typevar) { | |
| return false; | |
| } |
(I'm still working through the rest of the PR, just posting this now so I don't forget about it...)
| { | ||
| let mut seen_type_aliases = seen_type_aliases.borrow_mut(); | ||
| if !seen_type_aliases.insert(type_alias) { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
| { | |
| let mut seen_type_aliases = seen_type_aliases.borrow_mut(); | |
| if !seen_type_aliases.insert(type_alias) { | |
| return false; | |
| } | |
| } | |
| if !seen_type_aliases.borrow_mut().insert(type_alias) { | |
| return false; | |
| } |
|
I think you can improve readability for the fn type_is_self_referential(
self,
db: &'db dyn Db,
ty: Type<'db>,
visitor: &TypeVarDefaultVisitor<'db>,
) -> bool {
#[derive(Copy, Clone)]
struct State<'db, 'a> {
db: &'db dyn Db,
visitor: &'a TypeVarDefaultVisitor<'db>,
seen_typevars: &'a RefCell<FxHashSet<TypeVarInstance<'db>>>,
seen_type_aliases: &'a RefCell<FxHashSet<TypeAliasType<'db>>>,
}
fn typevar_default_is_self_referential<'db>(
state: State<'db, '_>,
typevar: TypeVarInstance<'db>,
self_identity: TypeVarIdentity<'db>,
) -> bool {
if typevar.identity(state.db) == self_identity {
return true;
}
if !state.seen_typevars.borrow_mut().insert(typevar) {
return false;
}
typevar
.default_type_impl(state.db, state.visitor)
.is_some_and(|default_ty| {
type_is_self_referential_impl(state, default_ty, self_identity)
})
}
fn type_alias_is_self_referential<'db>(
state: State<'db, '_>,
type_alias: TypeAliasType<'db>,
self_identity: TypeVarIdentity<'db>,
) -> bool {
if !state.seen_type_aliases.borrow_mut().insert(type_alias) {
return false;
}
type_is_self_referential_impl(state, type_alias.raw_value_type(state.db), self_identity)
}
fn type_is_self_referential_impl<'db>(
state: State<'db, '_>,
ty: Type<'db>,
self_identity: TypeVarIdentity<'db>,
) -> bool {
any_over_type(state.db, ty, false, |inner_ty| match inner_ty {
Type::TypeVar(bound_typevar) => typevar_default_is_self_referential(
state,
bound_typevar.typevar(state.db),
self_identity,
),
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => {
typevar_default_is_self_referential(state, typevar, self_identity)
}
Type::TypeAlias(alias) => {
type_alias_is_self_referential(state, alias, self_identity)
}
Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => {
type_alias_is_self_referential(state, alias, self_identity)
}
_ => false,
})
}
let seen_typevars = RefCell::new(FxHashSet::default());
let seen_type_aliases = RefCell::new(FxHashSet::default());
let state = State {
db,
visitor,
seen_typevars: &seen_typevars,
seen_type_aliases: &seen_type_aliases,
};
type_is_self_referential_impl(state, ty, self.identity(db))
} |
AlexWaygood
left a comment
There was a problem hiding this comment.
Modulo my suggestions regarding readability, this LGTM, thanks!
| if typevar.default_type(db).is_some() { | ||
| typevar_with_defaults += 1; | ||
| } | ||
|
|
There was a problem hiding this comment.
What's the motivation for this change?
There was a problem hiding this comment.
I think this was a mistake, but re-adding it surfaced another panic, and I had to add cycle handling for bound_typevar_default_type...
There was a problem hiding this comment.
(I think this specific one at least could be avoided if I just did a syntactic query for the default type presence.)
9469e1f to
b4b57eb
Compare
Summary
Use a syntactic check rather than eagerly resolving the type.
Closes astral-sh/ty#2889