Repository: astral-sh/ruff
File: crates/ty_python_semantic/src/types/class.rs (Rust)

Code before:
        specialization: Option<Specialization<'db>>,
    ) -> Option<Type<'db>> {
        const ORDERING_METHODS: [&str; 4] = ["__lt__", "__le__", "__gt__", "__ge__"];

        for name in ORDERING_METHODS {
            for base in self.iter_mro(db, specialization) {
                let Some(base_class) = base.into_class() else {
                    continue;
                };
                let (base_literal, base_specialization) = base_class.class_literal(db);
                if base_literal.is_known(db, KnownClass::Object) {
                    continue;
                }
                let member = class_member(db, base_literal.body_scope(db), name);
                if let Some(ty) = member.ignore_possibly_undefined() {
                    return Some(ty.apply_optional_specialization(db, base_specialization));
                }
            }
        }

        None
    }

    /// Returns `true` if a method with the given name is defined in this class's MRO
    /// (excluding `object`).
    fn has_method_in_mro(
        self,
        db: &'db dyn Db,
        specialization: Option<Specialization<'db>>,
        name: &str,
    ) -> bool {
        self.iter_mro(db, specialization)
            .filter_map(ClassBase::into_class)
            .filter(|class| !class.class_literal(db).0.is_known(db, KnownClass::Object))
            .any(|class| {
                class_member(db, class.class_literal(db).0.body_scope(db), name)
                    .ignore_possibly_undefined()
                    .is_some()
            })
    }

    pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
        // Several typeshed definitions examine `sys.version_info`. To break cycles, we hard-code
        // the knowledge that this class is not generic.
        if self.is_known(db, KnownClass::VersionInfo) {
            return None;
        }

        // We've already verified that the class literal does not contain both a PEP-695 generic
        // scope and a `typing.Generic` base class.
        //

Review comment (suggestion):
I would be tempted to inline this method, since I don't think it should be used outside of the `total_ordering` logic (and I think it can be simplified slightly):

```diff
diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs
index 2102467619..6aaa22a60b 100644
--- a/crates/ty_python_semantic/src/types/class.rs
+++ b/crates/ty_python_semantic/src/types/class.rs
@@ -1626,24 +1626,6 @@ impl<'db> ClassLiteral<'db> {
         None
     }
 
-    /// Returns `true` if a method with the given name is defined in this class's MRO
-    /// (excluding `object`).
-    fn has_method_in_mro(
-        self,
-        db: &'db dyn Db,
-        specialization: Option<Specialization<'db>>,
-        name: &str,
-    ) -> bool {
-        self.iter_mro(db, specialization)
-            .filter_map(ClassBase::into_class)
-            .filter(|class| !class.class_literal(db).0.is_known(db, KnownClass::Object))
-            .any(|class| {
-                class_member(db, class.class_literal(db).0.body_scope(db), name)
-                    .ignore_possibly_undefined()
-                    .is_some()
-            })
-    }
-
     pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
         // Several typeshed definitions examine `sys.version_info`. To break cycles, we hard-code
         // the knowledge that this class is not generic.
@@ -2497,7 +2479,15 @@ impl<'db> ClassLiteral<'db> {
         // Only synthesize methods that are not already defined in the MRO.
         if self.total_ordering(db)
             && matches!(name, "__lt__" | "__le__" | "__gt__" | "__ge__")
-            && !self.has_method_in_mro(db, specialization, name)
+            && class_member(db, self.body_scope(db), name).is_undefined()
+            && self
+                .class_member_from_mro(
+                    db,
+                    name,
+                    MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK,
+                    self.iter_mro(db, None).skip(1),
+                )
+                .is_undefined()
             && self.has_ordering_method_in_mro(db, specialization)
             && let Some(root_method_ty) = self.total_ordering_root_method(db, specialization)
             && let Some(callables) = root_method_ty.try_upcast_to_callable(db)
```

Code after:
        specialization: Option<Specialization<'db>>,
    ) -> Option<Type<'db>> {
        const ORDERING_METHODS: [&str; 4] = ["__lt__", "__le__", "__gt__", "__ge__"];

        for name in ORDERING_METHODS {
            for base in self.iter_mro(db, specialization) {
                let Some(base_class) = base.into_class() else {
                    continue;
                };
                let (base_literal, base_specialization) = base_class.class_literal(db);
                if base_literal.is_known(db, KnownClass::Object) {
                    continue;
                }
                let member = class_member(db, base_literal.body_scope(db), name);
                if let Some(ty) = member.ignore_possibly_undefined() {
                    return Some(ty.apply_optional_specialization(db, base_specialization));
                }
            }
        }

        None
    }

    pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
        // Several typeshed definitions examine `sys.version_info`. To break cycles, we hard-code
        // the knowledge that this class is not generic.
        if self.is_known(db, KnownClass::VersionInfo) {
            return None;
        }

        // We've already verified that the class literal does not contain both a PEP-695 generic
        // scope and a `typing.Generic` base class.
        //
        // Note that if a class has an explicit legacy generic context (by inheriting from
        // `typing.Generic`), and also an implicit one (by inheriting from other generic classes,
        // specialized by typevars), the explicit one takes precedence.
        self.pep695_generic_context(db)
            .or_else(|| self.legacy_generic_context(db))
            .or_else(|| self.inherited_legacy_generic_context(db))
    }

    pub(crate) fn has_pep_695_type_params(self, db: &'db dyn Db) -> bool {
        self.pep695_generic_context(db).is_some()
    }

    #[salsa::tracked(cycle_initial=generic_context_cycle_initial,
        heap_size=ruff_memory_usage::heap_size,
    )]
    pub(crate) fn pep695_generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
        let scope = self.body_scope(db);
        let file = scope.file(db);