From 949a4f1c42b0aed0695266e68570e94e227d7f74 Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 10 Oct 2025 12:04:37 +0200 Subject: [PATCH] [ty] Simplify and fix `CallableTypeOf[..]` implementation (#20797) ## Summary Simplify and fix the implementation of `ty_extensions.CallableTypeOf[..]`. closes https://github.com/astral-sh/ty/issues/1331 ## Test Plan Added regression test. --- .../resources/mdtest/call/methods.md | 2 +- .../resources/mdtest/ty_extensions.md | 8 ++--- crates/ty_python_semantic/src/types.rs | 7 ----- .../types/infer/builder/type_expression.rs | 30 +++---------------- 4 files changed, 8 insertions(+), 39 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/call/methods.md b/crates/ty_python_semantic/resources/mdtest/call/methods.md index 7db0259951..f11adb34e4 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/methods.md +++ b/crates/ty_python_semantic/resources/mdtest/call/methods.md @@ -689,7 +689,7 @@ def _( # revealed: (obj: type) -> None reveal_type(e) - # revealed: (fget: ((Any, /) -> Any) | None = None, fset: ((Any, Any, /) -> None) | None = None, fdel: ((Any, /) -> Any) | None = None, doc: str | None = None) -> Unknown + # revealed: (fget: ((Any, /) -> Any) | None = EllipsisType, fset: ((Any, Any, /) -> None) | None = EllipsisType, fdel: ((Any, /) -> None) | None = EllipsisType, doc: str | None = EllipsisType) -> property reveal_type(f) # revealed: Overload[(self: property, instance: None, owner: type, /) -> Unknown, (self: property, instance: object, owner: type | None = None, /) -> Unknown] diff --git a/crates/ty_python_semantic/resources/mdtest/ty_extensions.md b/crates/ty_python_semantic/resources/mdtest/ty_extensions.md index c6cec38db4..37a76f24e2 100644 --- a/crates/ty_python_semantic/resources/mdtest/ty_extensions.md +++ b/crates/ty_python_semantic/resources/mdtest/ty_extensions.md @@ -493,16 +493,14 @@ def _( c5: CallableTypeOf[Foo(42).__call__], c6: CallableTypeOf[Foo(42).returns_self], c7: CallableTypeOf[Foo.class_method], + c8: CallableTypeOf[Foo(42)], ) -> None: reveal_type(c1) # revealed: () -> Unknown reveal_type(c2) # revealed: () -> int reveal_type(c3) # revealed: (x: int, y: str) -> None - - # TODO: should be `(x: int) -> Foo` - reveal_type(c4) # revealed: (...) -> Foo - + reveal_type(c4) # revealed: (x: int) -> Foo reveal_type(c5) # revealed: (x: int) -> str - reveal_type(c6) # revealed: (x: int) -> Foo reveal_type(c7) # revealed: (x: int) -> Foo + reveal_type(c8) # revealed: (x: int) -> str ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index a8d05b4b3a..00f149bfe4 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1108,13 +1108,6 @@ impl<'db> Type<'db> { matches!(self, Type::FunctionLiteral(..)) } - pub(crate) const fn into_bound_method(self) -> Option> { - match self { - Type::BoundMethod(bound_method) => Some(bound_method), - _ => None, - } - } - pub(crate) fn is_union_of_single_valued(&self, db: &'db dyn Db) -> bool { self.into_union().is_some_and(|union| { union.elements(db).iter().all(|ty| { diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index 3bca7f1eca..a04f690338 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -7,7 +7,7 @@ use crate::types::diagnostic::{ report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, }; use crate::types::enums::is_enum_class; -use crate::types::signatures::{CallableSignature, Signature}; +use crate::types::signatures::Signature; use crate::types::string_annotation::parse_string_annotation; use crate::types::tuple::{TupleSpecBuilder, TupleType}; use crate::types::visitor::any_over_type; @@ -1156,26 +1156,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } let argument_type = self.infer_expression(&arguments[0], TypeContext::default()); - let bindings = argument_type.bindings(db); - // SAFETY: This is enforced by the constructor methods on `Bindings` even in - // the case of a non-callable union. - let callable_binding = bindings - .into_iter() - .next() - .expect("`Bindings` should have at least one `CallableBinding`"); - - let mut signature_iter = callable_binding.into_iter().map(|binding| { - if let Some(bound_method) = argument_type.into_bound_method() { - binding - .signature - .bind_self(self.db(), Some(bound_method.typing_self_type(db))) - } else { - binding.signature.clone() - } - }); - - let Some(signature) = signature_iter.next() else { + let Some(callable_type) = argument_type.into_callable(db) else { if let Some(builder) = self .context .report_lint(&INVALID_TYPE_FORM, arguments_slice) @@ -1193,14 +1175,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { return Type::unknown(); }; - let signature = CallableSignature::from_overloads( - std::iter::once(signature).chain(signature_iter), - ); - let callable_type_of = Type::Callable(CallableType::new(db, signature, false)); if arguments_slice.is_tuple_expr() { - self.store_expression_type(arguments_slice, callable_type_of); + self.store_expression_type(arguments_slice, callable_type); } - callable_type_of + callable_type } SpecialFormType::ChainMap => self.infer_parameterized_legacy_typing_alias(