diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md index bb2092aa5c..7eb2ff9d67 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md @@ -1204,9 +1204,9 @@ python-version = "3.12" from dataclasses import dataclass from typing import Callable from types import FunctionType -from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to +from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to, is_equivalent_to -@dataclass +@dataclass(order=True) class C: x: int @@ -1233,8 +1233,20 @@ static_assert(not is_assignable_to(EquivalentPureCallableType, DunderInitType)) static_assert(is_subtype_of(DunderInitType, EquivalentFunctionLikeCallableType)) static_assert(is_assignable_to(DunderInitType, EquivalentFunctionLikeCallableType)) -static_assert(not is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType)) -static_assert(not is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType)) +static_assert(is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType)) +static_assert(is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType)) + +static_assert(is_equivalent_to(EquivalentFunctionLikeCallableType, DunderInitType)) static_assert(is_subtype_of(DunderInitType, FunctionType)) ``` + +It should be possible to mock out synthesized methods: + +```py +from unittest.mock import Mock + +def test_c(): + c = C(1) + c.__lt__ = Mock() +``` diff --git a/crates/ty_python_semantic/resources/mdtest/literal/collections/list.md b/crates/ty_python_semantic/resources/mdtest/literal/collections/list.md index 13c02c15b8..15f385fa88 100644 --- a/crates/ty_python_semantic/resources/mdtest/literal/collections/list.md +++ b/crates/ty_python_semantic/resources/mdtest/literal/collections/list.md @@ -25,6 +25,12 @@ x = [a, b] reveal_type(x) # revealed: list[Unknown | ((_: int) -> int)] ``` +The inferred `Callable` type is function-like, i.e. we can still access attributes like `__name__`: + +```py +reveal_type(x[0].__name__) # revealed: Unknown | str +``` + ## Mixed list ```py diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index aa1f4d26cf..17da33f1d3 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -963,6 +963,40 @@ c: Callable[[Any], str] = f c: Callable[[Any], str] = g ``` +A function with no explicit return type should be assignable to a callable with a return type of +`Any`. + +```py +def h(): + return + +c: Callable[[], Any] = h +``` + +And, similarly for parameters with no annotations: + +```py +def i(a, b, /) -> None: + return + +c: Callable[[Any, Any], None] = i +``` + +Additionally, a function definition that includes both `*args` and `**kwargs` parameters that are +annotated as `Any` or kept unannotated should be assignable to a callable with `...` as the +parameter type. + +```py +def variadic_without_annotation(*args, **kwargs): + return + +def variadic_with_annotation(*args: Any, **kwargs: Any) -> Any: + return + +c: Callable[..., Any] = variadic_without_annotation +c: Callable[..., Any] = variadic_with_annotation +``` + ### Method types ```py diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md index 225a24ecda..624c5884c8 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md @@ -464,7 +464,7 @@ gradual types. The cases with fully static types and using different combination are covered above. ```py -from ty_extensions import Unknown, CallableTypeOf, is_equivalent_to, static_assert +from ty_extensions import Unknown, CallableTypeOf, TypeOf, is_equivalent_to, static_assert from typing import Any, Callable static_assert(is_equivalent_to(Callable[..., int], Callable[..., int])) @@ -476,14 +476,17 @@ static_assert(not is_equivalent_to(Callable[[int, str], None], Callable[[int, st static_assert(not is_equivalent_to(Callable[..., None], Callable[[], None])) ``` -A function with no explicit return type should be gradual equivalent to a callable with a return -type of `Any`. +A function with no explicit return type should be gradually equivalent to a function-like callable +with a return type of `Any`. ```py def f1(): return -static_assert(is_equivalent_to(CallableTypeOf[f1], Callable[[], Any])) +def f1_equivalent() -> Any: + return + +static_assert(is_equivalent_to(CallableTypeOf[f1], CallableTypeOf[f1_equivalent])) ``` And, similarly for parameters with no annotations. @@ -492,12 +495,15 @@ And, similarly for parameters with no annotations. def f2(a, b, /) -> None: return -static_assert(is_equivalent_to(CallableTypeOf[f2], Callable[[Any, Any], None])) +def f2_equivalent(a: Any, b: Any, /) -> None: + return + +static_assert(is_equivalent_to(CallableTypeOf[f2], CallableTypeOf[f2_equivalent])) ``` -Additionally, as per the spec, a function definition that includes both `*args` and `**kwargs` -parameter that are annotated as `Any` or kept unannotated should be gradual equivalent to a callable -with `...` as the parameter type. +A function definition that includes both `*args` and `**kwargs` parameter that are annotated as +`Any` or kept unannotated should be gradual equivalent to a callable with `...` as the parameter +type. ```py def variadic_without_annotation(*args, **kwargs): @@ -506,12 +512,27 @@ def variadic_without_annotation(*args, **kwargs): def variadic_with_annotation(*args: Any, **kwargs: Any) -> Any: return -static_assert(is_equivalent_to(CallableTypeOf[variadic_without_annotation], Callable[..., Any])) -static_assert(is_equivalent_to(CallableTypeOf[variadic_with_annotation], Callable[..., Any])) +def _( + signature_variadic_without_annotation: CallableTypeOf[variadic_without_annotation], + signature_variadic_with_annotation: CallableTypeOf[variadic_with_annotation], +) -> None: + # revealed: (...) -> Unknown + reveal_type(signature_variadic_without_annotation) + # revealed: (...) -> Any + reveal_type(signature_variadic_with_annotation) ``` -But, a function with either `*args` or `**kwargs` (and not both) is not gradual equivalent to a -callable with `...` as the parameter type. +Note that `variadic_without_annotation` and `variadic_with_annotation` are *not* considered +gradually equivalent to `Callable[..., Any]`, because the latter is not a function-like callable +type: + +```py +static_assert(not is_equivalent_to(CallableTypeOf[variadic_without_annotation], Callable[..., Any])) +static_assert(not is_equivalent_to(CallableTypeOf[variadic_with_annotation], Callable[..., Any])) +``` + +A function with either `*args` or `**kwargs` (and not both) is is not equivalent to a callable with +`...` as the parameter type. ```py def variadic_args(*args): @@ -520,6 +541,15 @@ def variadic_args(*args): def variadic_kwargs(**kwargs): return +def _( + signature_variadic_args: CallableTypeOf[variadic_args], + signature_variadic_kwargs: CallableTypeOf[variadic_kwargs], +) -> None: + # revealed: (*args) -> Unknown + reveal_type(signature_variadic_args) + # revealed: (**kwargs) -> Unknown + reveal_type(signature_variadic_kwargs) + static_assert(not is_equivalent_to(CallableTypeOf[variadic_args], Callable[..., Any])) static_assert(not is_equivalent_to(CallableTypeOf[variadic_kwargs], Callable[..., Any])) ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index e6c8b2e99d..62e3145ca1 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1401,7 +1401,7 @@ impl<'db> Type<'db> { match self { Type::Callable(_) => Some(self), - Type::Dynamic(_) => Some(CallableType::single(db, Signature::dynamic(self))), + Type::Dynamic(_) => Some(CallableType::function_like(db, Signature::dynamic(self))), Type::FunctionLiteral(function_literal) => { Some(Type::Callable(function_literal.into_callable_type(db))) @@ -9770,7 +9770,7 @@ impl<'db> BoundMethodType<'db> { .iter() .map(|signature| signature.bind_self(db, Some(self_instance))), ), - false, + true, ) } diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 89ebac378f..027642f542 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -943,7 +943,7 @@ impl<'db> FunctionType<'db> { /// Convert the `FunctionType` into a [`CallableType`]. pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> CallableType<'db> { - CallableType::new(db, self.signature(db), false) + CallableType::new(db, self.signature(db), true) } /// Convert the `FunctionType` into a [`BoundMethodType`].