Skip to content

Commit f2e2b2f

Browse files
committed
More explanation
1 parent a142ae3 commit f2e2b2f

File tree

2 files changed

+40
-0
lines changed

2 files changed

+40
-0
lines changed

crates/ty_python_semantic/resources/mdtest/call/callables_as_descriptors.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,40 @@ class C2:
146146

147147
C2().method_decorated(1)
148148
```
149+
150+
Note that we currently only apply this heuristic when calling a function such as `memoize` via the
151+
decorator syntax. This is inconsistent, of course, because the above *should* be equivalent to the
152+
following, but here we emit errors:
153+
154+
```py
155+
def memoize3(f: Callable[[C3, int], str]) -> Callable[[C3, int], str]:
156+
raise NotImplementedError
157+
158+
class C3:
159+
def method(self, x: int) -> str:
160+
return str(x)
161+
162+
method_decorated = memoize3(method)
163+
164+
# error: [missing-argument]
165+
# error: [invalid-argument-type]
166+
C3().method_decorated(1)
167+
```
168+
169+
The reason for this is that the heuristic is problematic. We don't *know* that the `Callable` in the
170+
return type of `memoize` is actually related to the method that we pass in. But when `memoize` is
171+
applied as a decorator, it is reasonable to assume so. In general, a function call might however
172+
return a `Callable` that is unrelated to the argument passed in. And here, it seems more
173+
reasonable/safe to treat the `Callable` as a non-descriptor:
174+
175+
```py
176+
def convert_int_function(c: Callable[[int], int]) -> Callable[[int], str]:
177+
return lambda x: str(c(x))
178+
179+
class C4:
180+
abs = convert_int_function(abs)
181+
round = convert_int_function(round)
182+
183+
reveal_type(C4().abs) # revealed: Unknown | ((int, /) -> str)
184+
reveal_type(C4().round) # revealed: Unknown | ((int, /) -> str)
185+
```

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,6 +2182,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
21822182
if is_input_function_like
21832183
&& let Some(callable_type) = return_ty.unwrap_as_callable_type()
21842184
{
2185+
// When a method on a class is decorated with a function that returns a `Callable`, assume that
2186+
// the returned callable is also function-like. See "Decorating a method with a `Callable`-typed
2187+
// decorator" in `callables_as_descriptors.md` for the extended explanation.
21852188
Type::Callable(CallableType::new(
21862189
self.db(),
21872190
callable_type.signatures(self.db()),

0 commit comments

Comments
 (0)