diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/callable.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/callable.md index 29cef6cae3..9bd7ae8742 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/callable.md @@ -300,12 +300,7 @@ from typing import Callable def _(c: Callable[[int], int]): reveal_type(c.__init__) # revealed: def __init__(self) -> None reveal_type(c.__class__) # revealed: type - - # TODO: The member lookup for `Callable` uses `object` which does not have a `__call__` - # attribute. We could special case `__call__` in this context. Refer to - # https://github.com/astral-sh/ruff/pull/16493#discussion_r1985098508 for more details. - # error: [unresolved-attribute] "Type `(int, /) -> int` has no attribute `__call__`" - reveal_type(c.__call__) # revealed: Unknown + reveal_type(c.__call__) # revealed: (int, /) -> int ``` [gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form diff --git a/crates/red_knot_python_semantic/resources/mdtest/protocols.md b/crates/red_knot_python_semantic/resources/mdtest/protocols.md index b55a41a17c..1b33205e47 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/protocols.md +++ b/crates/red_knot_python_semantic/resources/mdtest/protocols.md @@ -1476,26 +1476,32 @@ signature implied by the `Callable` type is assignable to the signature of the ` specified by the protocol: ```py +from knot_extensions import TypeOf + class Foo(Protocol): def __call__(self, x: int, /) -> str: ... -# TODO: these fail because we don't yet understand that all `Callable` types have a `__call__` method, -# and we therefore don't think that the `Callable` type is assignable to `Foo`. They should pass. -static_assert(is_subtype_of(Callable[[int], str], Foo)) # error: [static-assert-error] -static_assert(is_assignable_to(Callable[[int], str], Foo)) # error: [static-assert-error] +static_assert(is_subtype_of(Callable[[int], str], Foo)) +static_assert(is_assignable_to(Callable[[int], str], Foo)) -static_assert(not is_subtype_of(Callable[[str], str], Foo)) -static_assert(not is_assignable_to(Callable[[str], str], Foo)) -static_assert(not is_subtype_of(Callable[[CallMeMaybe, int], str], Foo)) -static_assert(not is_assignable_to(Callable[[CallMeMaybe, int], str], Foo)) +# TODO: these should pass +static_assert(not is_subtype_of(Callable[[str], str], Foo)) # error: [static-assert-error] +static_assert(not is_assignable_to(Callable[[str], str], Foo)) # error: [static-assert-error] +static_assert(not is_subtype_of(Callable[[CallMeMaybe, int], str], Foo)) # error: [static-assert-error] +static_assert(not is_assignable_to(Callable[[CallMeMaybe, int], str], Foo)) # error: [static-assert-error] def h(obj: Callable[[int], str], obj2: Foo, obj3: Callable[[str], str]): - # TODO: this fails because we don't yet understand that all `Callable` types have a `__call__` method, - # and we therefore don't think that the `Callable` type is assignable to `Foo`. It should pass. - obj2 = obj # error: [invalid-assignment] + obj2 = obj - # This diagnostic is correct, however. - obj2 = obj3 # error: [invalid-assignment] + # TODO: we should emit [invalid-assignment] here because the signature of `obj3` is not assignable + # to the declared type of `obj2` + obj2 = obj3 + +def satisfies_foo(x: int) -> str: + return "foo" + +static_assert(is_subtype_of(TypeOf[satisfies_foo], Foo)) +static_assert(is_assignable_to(TypeOf[satisfies_foo], Foo)) ``` ## Protocols are never singleton types, and are never single-valued types diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 404b59738e..049f2de3be 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2953,6 +2953,10 @@ impl<'db> Type<'db> { Type::DataclassDecorator(_) => KnownClass::FunctionType .to_instance(db) .member_lookup_with_policy(db, name, policy), + + Type::Callable(_) | Type::DataclassTransformer(_) if name_str == "__call__" => { + Symbol::bound(self).into() + } Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Object .to_instance(db) .member_lookup_with_policy(db, name, policy),