Skip to content

Commit edba601

Browse files
Support classes that implement __call__ (#13580)
## Summary This looked straightforward and removes some TODOs.
1 parent 043fba7 commit edba601

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed

crates/red_knot_python_semantic/src/types.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -609,8 +609,20 @@ impl<'db> Type<'db> {
609609
})
610610
}
611611

612-
// TODO: handle classes which implement the `__call__` protocol
613-
Type::Instance(_instance_ty) => CallOutcome::callable(Type::Todo),
612+
Type::Instance(class) => {
613+
// Since `__call__` is a dunder, we need to access it as an attribute on the class
614+
// rather than the instance (matching runtime semantics).
615+
let meta_ty = Type::Class(class);
616+
let dunder_call_method = meta_ty.member(db, "__call__");
617+
if dunder_call_method.is_unbound() {
618+
CallOutcome::not_callable(self)
619+
} else {
620+
let args = std::iter::once(self)
621+
.chain(arg_types.iter().copied())
622+
.collect::<Vec<_>>();
623+
dunder_call_method.call(db, &args)
624+
}
625+
}
614626

615627
// `Any` is callable, and its return type is also `Any`.
616628
Type::Any => CallOutcome::callable(Type::Any),

crates/red_knot_python_semantic/src/types/infer.rs

+31
Original file line numberDiff line numberDiff line change
@@ -6723,6 +6723,37 @@ mod tests {
67236723
Ok(())
67246724
}
67256725

6726+
#[test]
6727+
fn dunder_call() -> anyhow::Result<()> {
6728+
let mut db = setup_db();
6729+
6730+
db.write_dedented(
6731+
"/src/a.py",
6732+
"
6733+
class Multiplier:
6734+
def __init__(self, factor: float):
6735+
self.factor = factor
6736+
6737+
def __call__(self, number: float) -> float:
6738+
return number * self.factor
6739+
6740+
a = Multiplier(2.0)(3.0)
6741+
6742+
class Unit:
6743+
...
6744+
6745+
b = Unit()(3.0)
6746+
",
6747+
)?;
6748+
6749+
assert_public_ty(&db, "/src/a.py", "a", "float");
6750+
assert_public_ty(&db, "/src/a.py", "b", "Unknown");
6751+
6752+
assert_file_diagnostics(&db, "src/a.py", &["Object of type 'Unit' is not callable."]);
6753+
6754+
Ok(())
6755+
}
6756+
67266757
#[test]
67276758
fn boolean_or_expression() -> anyhow::Result<()> {
67286759
let mut db = setup_db();

0 commit comments

Comments
 (0)