From b314119835e89fc5d0d9aac694e3a79017843c0a Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 3 Dec 2025 20:00:06 -0500 Subject: [PATCH] catch self-referential typevars --- .../mdtest/generics/legacy/variables.md | 3 +- .../mdtest/generics/pep695/variables.md | 2 +- crates/ty_python_semantic/src/types.rs | 54 ++++++++++++++----- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md index 5917e340ab..c7db62281d 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md @@ -490,8 +490,7 @@ V = TypeVar("V", default="V") class D(Generic[V]): x: V -# TODO: we shouldn't leak a typevar like this in type inference -reveal_type(D().x) # revealed: V@D +reveal_type(D().x) # revealed: Unknown ``` ## Regression diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md index eaa4b8923e..cd8343772c 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md @@ -863,7 +863,7 @@ reveal_type(C[int]().y) # revealed: int class D[T = T]: x: T -reveal_type(D().x) # revealed: T@D +reveal_type(D().x) # revealed: Unknown ``` [pep 695]: https://peps.python.org/pep-0695/ diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 5366cf270d..96699f4b2e 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -9662,6 +9662,22 @@ impl<'db> TypeVarInstance<'db> { )) } + fn type_is_self_referential(self, db: &'db dyn Db, ty: Type<'db>) -> bool { + let identity = self.identity(db); + any_over_type( + db, + ty, + &|ty| match ty { + Type::TypeVar(bound_typevar) => identity == bound_typevar.typevar(db).identity(db), + Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => { + identity == typevar.identity(db) + } + _ => false, + }, + false, + ) + } + #[salsa::tracked( cycle_initial=lazy_bound_or_constraints_cycle_initial, heap_size=ruff_memory_usage::heap_size @@ -9683,6 +9699,11 @@ impl<'db> TypeVarInstance<'db> { } _ => return None, }; + + if self.type_is_self_referential(db, ty) { + return None; + } + Some(TypeVarBoundOrConstraints::UpperBound(ty)) } @@ -9721,6 +9742,15 @@ impl<'db> TypeVarInstance<'db> { } _ => return None, }; + + if ty + .elements(db) + .iter() + .any(|ty| self.type_is_self_referential(db, *ty)) + { + return None; + } + Some(TypeVarBoundOrConstraints::Constraints(ty)) } @@ -9728,33 +9758,31 @@ impl<'db> TypeVarInstance<'db> { fn lazy_default(self, db: &'db dyn Db) -> Option> { let definition = self.definition(db)?; let module = parsed_module(db, definition.file(db)).load(db); - match definition.kind(db) { + let ty = match definition.kind(db) { // PEP 695 typevar DefinitionKind::TypeVar(typevar) => { let typevar_node = typevar.node(&module); - Some(definition_expression_type( - db, - definition, - typevar_node.default.as_ref()?, - )) + definition_expression_type(db, definition, typevar_node.default.as_ref()?) } // legacy typevar DefinitionKind::Assignment(assignment) => { let call_expr = assignment.value(&module).as_call_expr()?; let expr = &call_expr.arguments.find_keyword("default")?.value; - Some(definition_expression_type(db, definition, expr)) + definition_expression_type(db, definition, expr) } // PEP 695 ParamSpec DefinitionKind::ParamSpec(paramspec) => { let paramspec_node = paramspec.node(&module); - Some(definition_expression_type( - db, - definition, - paramspec_node.default.as_ref()?, - )) + definition_expression_type(db, definition, paramspec_node.default.as_ref()?) } - _ => None, + _ => return None, + }; + + if self.type_is_self_referential(db, ty) { + return None; } + + Some(ty) } pub fn bind_pep695(self, db: &'db dyn Db) -> Option> {