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

Code before:
        inherited_generic_context: Option<GenericContext<'db>>,
        name: &str,
    ) -> Option<Type<'db>> {
        let dataclass_params = self.dataclass_params(db);

        // Handle `@functools.total_ordering`: synthesize comparison methods
        // for classes that have `@total_ordering` and define at least one
        // ordering method. The decorator requires at least one of __lt__,
        // __le__, __gt__, or __ge__ to be defined (either in this class or
        // inherited from a superclass, excluding `object`).
        //
        // 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)
            && 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)
        {
            let bool_ty = KnownClass::Bool.to_instance(db);
            let synthesized_callables = callables.map(|callable| {
                let signatures = CallableSignature::from_overloads(
                    callable.signatures(db).iter().map(|signature| {
                        Signature::new_generic(
                            signature.generic_context,
                            signature.parameters().clone(),
                            bool_ty,
                        )
                    }),
                );
                CallableType::new(db, signatures, CallableTypeKind::FunctionLike)
            });

            return Some(synthesized_callables.into_type(db));
        }

        let field_policy = CodeGeneratorKind::from_class(db, self, specialization)?;

        let mut transformer_params =
            if let CodeGeneratorKind::DataclassLike(Some(transformer_params)) = field_policy {
                Some(DataclassParams::from_transformer_params(
                    db,
                    transformer_params,
                ))
            } else {
                None
            };

        // Dataclass transformer flags can be overwritten using class arguments.
        // TODO this should be done more generally, not just in `own_synthesized_member`, so that
        // `dataclass_params` always reflects the transformer params.

Review comment (bug):
I think we want to union together the return type of the root method and `bool` -- the generated methods won't necessarily return `bool` if the root method doesn't:

```pycon
>>> from functools import total_ordering
>>> @total_ordering
... class F:
...     def __init__(self, x: int):
...         self.x: int = x
...         
...     def __lt__(self, other) -> int:
...         return self.x
...         
...     def __eq__(self, other) -> bool:
...         return self.x == other.x
...         
>>> F(42) < F(42)
42
>>> F(42) > F(42)
False
>>> F(42) >= F(42)
False
>>> F(42) <= F(42)
42
```

Interestingly, three different type checkers give three different answers for what those comparisons evaluate to (and I'm proposing to give a fourth answer!). Pyright says they all evaluate to `bool`, pyrefly says they all evaluate to `int`, and mypy says they all evaluate to `Any`. Mypy seems the most accurate there (`Any` is at least not _wrong_ if the root method returns a non-`bool`), but I think we can be more precise. (Mypy only infers `Any` if the root method returns a non-`bool`; if it returns a `bool`, it infers a `bool`.)

Code after:
                .any(|class| {
                    class_member(db, class.class_literal(db).0.body_scope(db), name)
                        .ignore_possibly_undefined()
                        .is_some()
                })
            && 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)
        {
            let bool_ty = KnownClass::Bool.to_instance(db);
            let synthesized_callables = callables.map(|callable| {
                let signatures = CallableSignature::from_overloads(
                    callable.signatures(db).iter().map(|signature| {
                        // The generated methods return a union of the root method's return type
                        // and `bool`. This is because `@total_ordering` synthesizes methods like:
                        //     def __gt__(self, other): return not (self == other or self < other)
                        // If `__lt__` returns `int`, then `__gt__` could return `int | bool`.
                        let return_ty =
                            UnionType::from_elements(db, [signature.return_ty, bool_ty]);
                        Signature::new_generic(
                            signature.generic_context,
                            signature.parameters().clone(),
                            return_ty,
                        )
                    }),
                );
                CallableType::new(db, signatures, CallableTypeKind::FunctionLike)
            });

            return Some(synthesized_callables.into_type(db));
        }

        let field_policy = CodeGeneratorKind::from_class(db, self, specialization)?;

        let mut transformer_params =
            if let CodeGeneratorKind::DataclassLike(Some(transformer_params)) = field_policy {
                Some(DataclassParams::from_transformer_params(
                    db,
                    transformer_params,
                ))
            } else {
                None
            };

        // Dataclass transformer flags can be overwritten using class arguments.
        // TODO this should be done more generally, not just in `own_synthesized_member`, so that
        // `dataclass_params` always reflects the transformer params.
        if let Some(transformer_params) = transformer_params.as_mut() {
            if let Some(class_def) = self.definition(db).kind(db).as_class() {
                let module = parsed_module(db, self.file(db)).load(db);