[red-knot] Support calling a typing.Callable (#16888)

## Summary

Part of #15382, this PR adds support for calling a variable that's
annotated with `typing.Callable`.

## Test Plan

Add test cases in a new `call/annotation.md` file.
This commit is contained in:
Dhruv Manilawala 2025-03-23 02:39:33 +05:30 committed by GitHub
parent 1cffb323bc
commit 0360c6b219
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 2 deletions

View file

@ -29,8 +29,6 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.
# TODO: should understand the annotation
reveal_type(kwargs) # revealed: dict
# TODO: not an error; remove once `call` is implemented for `Callable`
# error: [call-non-callable]
return callback(42, *args, **kwargs)
class Foo:

View file

@ -0,0 +1,43 @@
# `typing.Callable`
```py
from typing import Callable
def _(c: Callable[[], int]):
reveal_type(c()) # revealed: int
def _(c: Callable[[int, str], int]):
reveal_type(c(1, "a")) # revealed: int
# error: [invalid-argument-type] "Object of type `Literal["a"]` cannot be assigned to parameter 1; expected type `int`"
# error: [invalid-argument-type] "Object of type `Literal[1]` cannot be assigned to parameter 2; expected type `str`"
reveal_type(c("a", 1)) # revealed: int
```
The `Callable` annotation can only be used to describe positional-only parameters.
```py
def _(c: Callable[[int, str], None]):
# error: [unknown-argument] "Argument `a` does not match any known parameter"
# error: [unknown-argument] "Argument `b` does not match any known parameter"
# error: [missing-argument] "No arguments provided for required parameters 1, 2"
reveal_type(c(a=1, b="b")) # revealed: None
```
If the annotation uses a gradual form (`...`) for the parameter list, then it can accept any kind of
parameter with any type.
```py
def _(c: Callable[..., int]):
reveal_type(c()) # revealed: int
reveal_type(c(1)) # revealed: int
reveal_type(c(1, "str", False, a=[1, 2], b=(3, 4))) # revealed: int
```
An invalid `Callable` form can accept any parameters and will return `Unknown`.
```py
# error: [invalid-type-form]
def _(c: Callable[42, str]):
reveal_type(c()) # revealed: Unknown
```

View file

@ -2340,6 +2340,10 @@ impl<'db> Type<'db> {
/// [`CallErrorKind::NotCallable`].
fn signatures(self, db: &'db dyn Db) -> Signatures<'db> {
match self {
Type::Callable(CallableType::General(callable)) => Signatures::single(
CallableSignature::single(self, callable.signature(db).clone()),
),
Type::Callable(CallableType::BoundMethod(bound_method)) => {
let signature = bound_method.function(db).signature(db);
let signature = CallableSignature::single(self, signature.clone())