From f82b3f1eff42338b06d192cfcdab28ff60775ff0 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 9 Dec 2025 08:29:30 -0500 Subject: [PATCH] abstract over any mention of a typevar --- .../mdtest/generics/specialize_constrained.md | 3 +- .../src/types/constraints.rs | 29 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md b/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md index 4e04b91944..da4c5e7c49 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md @@ -348,7 +348,8 @@ from ty_extensions import ConstraintSet, generic_context def mentions[T, U](): # (T@mentions ≤ int) ∧ (U@mentions = list[T@mentions]) constraints = ConstraintSet.range(Never, T, int) & ConstraintSet.range(list[T], U, list[T]) - # revealed: ty_extensions.Specialization[T@mentions = int, U@mentions = list[int]] + # TODO: revealed: ty_extensions.Specialization[T@mentions = int, U@mentions = list[int]] + # revealed: ty_extensions.Specialization[T@mentions = int, U@mentions = Unknown] reveal_type(generic_context(mentions).specialize_constrained(constraints)) ``` diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index bbec6c7f3d..21ae3f0384 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -76,7 +76,9 @@ use rustc_hash::{FxHashMap, FxHashSet}; use salsa::plumbing::AsId; use crate::types::generics::{GenericContext, InferableTypeVars, Specialization}; -use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard}; +use crate::types::visitor::{ + TypeCollector, TypeVisitor, any_over_type, walk_type_with_recursion_guard, +}; use crate::types::{ BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeRelation, TypeVarBoundOrConstraints, UnionType, walk_bound_type_var_type, @@ -1730,22 +1732,27 @@ impl<'db> InteriorNode<'db> { fn exists_one(self, db: &'db dyn Db, bound_typevar: BoundTypeVarIdentity<'db>) -> Node<'db> { let map = self.sequent_map(db); let mut path = PathAssignments::default(); + let mentions_typevar = |ty: Type<'db>| match ty { + Type::TypeVar(haystack) => haystack.identity(db) == bound_typevar, + _ => false, + }; self.abstract_one_inner( db, - // Remove any node that constrains `bound_typevar`, or that has a lower/upper bound of - // `bound_typevar`. + // Remove any node that constrains `bound_typevar`, or that has a lower/upper bound + // that mentions `bound_typevar`. + // TODO: This will currently remove constraints that mention a typevar, but the sequent + // map is not yet propagating all derived facts about those constraints. For instance, + // removing `T` from `T ≤ int ∧ U ≤ Sequence[T]` should produce `U ≤ Sequence[int]`. + // But that requires `T ≤ int ∧ U ≤ Sequence[T] → U ≤ Sequence[int]` to exist in the + // sequent map. It doesn't, and so we currently produce `U ≤ Unknown` in this case. &mut |constraint| { if constraint.typevar(db).identity(db) == bound_typevar { return true; } - if let Type::TypeVar(lower_bound_typevar) = constraint.lower(db) - && lower_bound_typevar.identity(db) == bound_typevar - { + if any_over_type(db, constraint.lower(db), &mentions_typevar, false) { return true; } - if let Type::TypeVar(upper_bound_typevar) = constraint.upper(db) - && upper_bound_typevar.identity(db) == bound_typevar - { + if any_over_type(db, constraint.upper(db), &mentions_typevar, false) { return true; } false @@ -1769,9 +1776,7 @@ impl<'db> InteriorNode<'db> { if constraint.typevar(db).identity(db) != bound_typevar { return true; } - if matches!(constraint.lower(db), Type::TypeVar(_)) - || matches!(constraint.upper(db), Type::TypeVar(_)) - { + if constraint.lower(db).has_typevar(db) || constraint.upper(db).has_typevar(db) { return true; } false