[ty] Optimize protocol subtyping by removing expensive and unnecessary equivalence check from the top of Type::has_relation_to() (#19230)

This commit is contained in:
Alex Waygood 2025-07-10 09:42:27 +01:00 committed by GitHub
parent edaffa6c4f
commit 59aa869724
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 22 additions and 15 deletions

View file

@ -1215,10 +1215,11 @@ impl<'db> Type<'db> {
fn has_relation_to(self, db: &'db dyn Db, target: Type<'db>, relation: TypeRelation) -> bool { fn has_relation_to(self, db: &'db dyn Db, target: Type<'db>, relation: TypeRelation) -> bool {
// Subtyping implies assignability, so if subtyping is reflexive and the two types are // Subtyping implies assignability, so if subtyping is reflexive and the two types are
// equivalent, it is both a subtype and assignable. Assignability is always reflexive. // equal, it is both a subtype and assignable. Assignability is always reflexive.
if (relation.is_assignability() || self.subtyping_is_always_reflexive()) //
&& self.is_equivalent_to(db, target) // Note that we could do a full equivalence check here, but that would be both expensive
{ // and unnecessary. This early return is only an optimisation.
if (relation.is_assignability() || self.subtyping_is_always_reflexive()) && self == target {
return true; return true;
} }
@ -1256,6 +1257,9 @@ impl<'db> Type<'db> {
// Two identical typevars must always solve to the same type, so they are always // Two identical typevars must always solve to the same type, so they are always
// subtypes of each other and assignable to each other. // subtypes of each other and assignable to each other.
//
// Note that this is not handled by the early return at the beginning of this method,
// since subtyping between a TypeVar and an arbitrary other type cannot be guaranteed to be reflexive.
(Type::TypeVar(lhs_typevar), Type::TypeVar(rhs_typevar)) (Type::TypeVar(lhs_typevar), Type::TypeVar(rhs_typevar))
if lhs_typevar == rhs_typevar => if lhs_typevar == rhs_typevar =>
{ {
@ -7906,6 +7910,10 @@ impl<'db> UnionType<'db> {
/// Return `true` if `self` represents the exact same sets of possible runtime objects as `other` /// Return `true` if `self` represents the exact same sets of possible runtime objects as `other`
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
if self == other {
return true;
}
let self_elements = self.elements(db); let self_elements = self.elements(db);
let other_elements = other.elements(db); let other_elements = other.elements(db);
@ -7913,10 +7921,6 @@ impl<'db> UnionType<'db> {
return false; return false;
} }
if self == other {
return true;
}
let sorted_self = self.normalized(db); let sorted_self = self.normalized(db);
if sorted_self == other { if sorted_self == other {
@ -7997,8 +8001,11 @@ impl<'db> IntersectionType<'db> {
/// Return `true` if `self` represents exactly the same set of possible runtime objects as `other` /// Return `true` if `self` represents exactly the same set of possible runtime objects as `other`
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
let self_positive = self.positive(db); if self == other {
return true;
}
let self_positive = self.positive(db);
let other_positive = other.positive(db); let other_positive = other.positive(db);
if self_positive.len() != other_positive.len() { if self_positive.len() != other_positive.len() {
@ -8006,17 +8013,12 @@ impl<'db> IntersectionType<'db> {
} }
let self_negative = self.negative(db); let self_negative = self.negative(db);
let other_negative = other.negative(db); let other_negative = other.negative(db);
if self_negative.len() != other_negative.len() { if self_negative.len() != other_negative.len() {
return false; return false;
} }
if self == other {
return true;
}
let sorted_self = self.normalized(db); let sorted_self = self.normalized(db);
if sorted_self == other { if sorted_self == other {

View file

@ -443,8 +443,13 @@ impl<'db> ClassType<'db> {
} }
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool { pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
if self == other {
return true;
}
match (self, other) { match (self, other) {
(ClassType::NonGeneric(this), ClassType::NonGeneric(other)) => this == other, // A non-generic class is never equivalent to a generic class.
// Two non-generic classes are only equivalent if they are equal (handled above).
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => false, (ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => false,
(ClassType::Generic(this), ClassType::Generic(other)) => { (ClassType::Generic(this), ClassType::Generic(other)) => {