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

Code before:
    /// See [`Type::instance_member`] for more details.
    pub(super) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
        let (class_literal, specialization) = self.class_literal(db);

        if class_literal.is_typed_dict(db) {
            return Place::Undefined.into();
        }

        class_literal
            .instance_member(db, specialization, name)
            .map_type(|ty| ty.apply_optional_specialization(db, specialization))
    }

    /// A helper function for `instance_member` that looks up the `name` attribute only on
    /// this class, not on its superclasses.
    fn own_instance_member(self, db: &'db dyn Db, name: &str) -> Member<'db> {
        let (class_literal, specialization) = self.class_literal(db);
        class_literal
            .own_instance_member(db, name)
            .map_type(|ty| ty.apply_optional_specialization(db, specialization))
    }

    /// Return a callable type (or union of callable types) that represents the callable
    /// constructor signature of this class.
    #[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
    pub(super) fn into_callable(self, db: &'db dyn Db) -> Option<CallableTypes<'db>> {
        let self_ty = Type::from(self);
        let metaclass_dunder_call_function_symbol = self_ty
            .member_lookup_with_policy(
                db,
                "__call__".into(),
                MemberLookupPolicy::NO_INSTANCE_FALLBACK
                    | MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
            )
            .place;

        if let Place::Defined(Type::BoundMethod(metaclass_dunder_call_function), _, _) =
            metaclass_dunder_call_function_symbol
        {
            // TODO: this intentionally diverges from step 1 in
            // https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable
            // by always respecting the signature of the metaclass `__call__`, rather than
            // using a heuristic which makes unwarranted assumptions to sometimes ignore it.
            return Some(CallableTypes::one(
                metaclass_dunder_call_function.into_callable_type(db),
            ));
        }

        let dunder_new_function_symbol = self_ty.lookup_dunder_new(db);

        let dunder_new_signature = dunder_new_function_symbol

Review comment (refactor):
it looks like this method always returns `Some`. Can't we simplify this by having it return `CallableTypes` rather than `Option<CallableTypes>`? That would better reflect the invariant that a class literal should always be callable (as should a generic alias).

Well, I guess it isn't true that a class will _necessarily_ always be callable if the class's metaclass has `__call__ = None` or something strange. But I don't think we try to model that possibility at the moment.

Anyway, all tests pass if I apply this patch to your branch:

```diff
diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs
index 8d894b6c91..2ba63d8256 100644
--- a/crates/ty_python_semantic/src/types.rs
+++ b/crates/ty_python_semantic/src/types.rs
@@ -1565,10 +1565,10 @@ impl<'db> Type<'db> {
                 }
             }
             Type::ClassLiteral(class_literal) => {
-                ClassType::NonGeneric(class_literal).into_callable(db)
+                Some(ClassType::NonGeneric(class_literal).into_callable(db))
             }
 
-            Type::GenericAlias(alias) => ClassType::Generic(alias).into_callable(db),
+            Type::GenericAlias(alias) => Some(ClassType::Generic(alias).into_callable(db)),
 
             Type::NewTypeInstance(newtype) => {
                 Type::instance(db, newtype.base_class_type(db)).try_upcast_to_callable(db)
@@ -1576,7 +1576,7 @@ impl<'db> Type<'db> {
 
             // TODO: This is unsound so in future we can consider an opt-in option to disable it.
             Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
-                SubclassOfInner::Class(class) => class.into_callable(db),
+                SubclassOfInner::Class(class) => Some(class.into_callable(db)),
                 SubclassOfInner::Dynamic(dynamic) => {
                     Some(CallableTypes::one(CallableType::single(
                         db,
diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs
index bf97c60c93..7de3a86263 100644
--- a/crates/ty_python_semantic/src/types/class.rs
+++ b/crates/ty_python_semantic/src/types/class.rs
@@ -1052,7 +1052,7 @@ impl<'db> ClassType<'db> {
     /// Return a callable type (or union of callable types) that represents the callable
     /// constructor signature of this class.
     #[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
-    pub(super) fn into_callable(self, db: &'db dyn Db) -> Option<CallableTypes<'db>> {
+    pub(super) fn into_callable(self, db: &'db dyn Db) -> CallableTypes<'db> {
         let self_ty = Type::from(self);
         let metaclass_dunder_call_function_symbol = self_ty
             .member_lookup_with_policy(
@@ -1070,9 +1070,7 @@ impl<'db> ClassType<'db> {
             // https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable
             // by always respecting the signature of the metaclass `__call__`, rather than
             // using a heuristic which makes unwarranted assumptions to sometimes ignore it.
-            return Some(CallableTypes::one(
-                metaclass_dunder_call_function.into_callable_type(db),
-            ));
+            return CallableTypes::one(metaclass_dunder_call_function.into_callable_type(db));
         }
 
         let dunder_new_function_symbol = self_ty.lookup_dunder_new(db);
@@ -1107,7 +1105,7 @@ impl<'db> ClassType<'db> {
             );
 
             if returns_non_subclass {
-                return Some(CallableTypes::one(dunder_new_bound_method));
+                return CallableTypes::one(dunder_new_bound_method);
             }
             Some(dunder_new_bound_method)
         } else {
@@ -1164,13 +1162,13 @@ impl<'db> ClassType<'db> {
 
         match (dunder_new_function, synthesized_dunder_init_callable) {
             (Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => {
-                Some(CallableTypes::from_elements([
+                CallableTypes::from_elements([
                     dunder_new_function,
                     synthesized_dunder_init_callable,
-                ]))
+                ])
             }
             (Some(constructor), None) | (None, Some(constructor)) => {
-                Some(CallableTypes::one(constructor))
+                CallableTypes::one(constructor)
             }
             (None, None) => {
                 // If no `__new__` or `__init__` method is found, then we fall back to looking for
@@ -1186,17 +1184,17 @@ impl<'db> ClassType<'db> {
                 if let Place::Defined(Type::FunctionLiteral(new_function), _, _) =
                     new_function_symbol
                 {
-                    Some(CallableTypes::one(
+                    CallableTypes::one(
                         new_function
                             .into_bound_method_type(db, correct_return_type)
                             .into_callable_type(db),
-                    ))
+                    )
                 } else {
                     // Fallback if no `object.__new__` is found.
-                    Some(CallableTypes::one(CallableType::single(
+                    CallableTypes::one(CallableType::single(
                         db,
                         Signature::new(Parameters::empty(), Some(correct_return_type)),
-                    )))
+                    ))
                 }
             }
         }
@@ -1212,11 +1210,11 @@ impl<'db> ClassType<'db> {
 }
 
 fn into_callable_cycle_initial<'db>(
-    _db: &'db dyn Db,
+    db: &'db dyn Db,
     _id: salsa::Id,
     _self: ClassType<'db>,
-) -> Option<CallableTypes<'db>> {
-    None
+) -> CallableTypes<'db> {
+    CallableTypes::one(CallableType::bottom(db))
 }
 
 impl<'db> From<GenericAlias<'db>> for ClassType<'db> {
```

Code after:
    /// See [`Type::instance_member`] for more details.
    pub(super) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
        let (class_literal, specialization) = self.class_literal(db);

        if class_literal.is_typed_dict(db) {
            return Place::Undefined.into();
        }

        class_literal
            .instance_member(db, specialization, name)
            .map_type(|ty| ty.apply_optional_specialization(db, specialization))
    }

    /// A helper function for `instance_member` that looks up the `name` attribute only on
    /// this class, not on its superclasses.
    fn own_instance_member(self, db: &'db dyn Db, name: &str) -> Member<'db> {
        let (class_literal, specialization) = self.class_literal(db);
        class_literal
            .own_instance_member(db, name)
            .map_type(|ty| ty.apply_optional_specialization(db, specialization))
    }

    /// Return a callable type (or union of callable types) that represents the callable
    /// constructor signature of this class.
    #[salsa::tracked(cycle_initial=into_callable_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
    pub(super) fn into_callable(self, db: &'db dyn Db) -> CallableTypes<'db> {
        let self_ty = Type::from(self);
        let metaclass_dunder_call_function_symbol = self_ty
            .member_lookup_with_policy(
                db,
                "__call__".into(),
                MemberLookupPolicy::NO_INSTANCE_FALLBACK
                    | MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
            )
            .place;

        if let Place::Defined(Type::BoundMethod(metaclass_dunder_call_function), _, _) =
            metaclass_dunder_call_function_symbol
        {
            // TODO: this intentionally diverges from step 1 in
            // https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable
            // by always respecting the signature of the metaclass `__call__`, rather than
            // using a heuristic which makes unwarranted assumptions to sometimes ignore it.
            return CallableTypes::one(metaclass_dunder_call_function.into_callable_type(db));
        }

        let dunder_new_function_symbol = self_ty.lookup_dunder_new(db);

        let dunder_new_signature = dunder_new_function_symbol
            .and_then(|place_and_quals| place_and_quals.ignore_possibly_undefined())
            .and_then(|ty| match ty {