Skip to content

Commit 4f74db5

Browse files
authored
[red-knot] Improve Symbol API for callable types (#14137)
## Summary - Get rid of `Symbol::unwrap_or` (unclear semantics, not needed anymore) - Introduce `Type::call_dunder` - Emit new diagnostic for possibly-unbound `__iter__` methods - Better diagnostics for callables with possibly-unbound / possibly-non-callable `__call__` methods part of: #14022 closes #14016 ## Test Plan - Updated test for iterables with possibly-unbound `__iter__` methods. - New tests for callables
1 parent adc4216 commit 4f74db5

File tree

7 files changed

+241
-63
lines changed

7 files changed

+241
-63
lines changed

crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,58 @@ class Unit: ...
1818
b = Unit()(3.0) # error: "Object of type `Unit` is not callable"
1919
reveal_type(b) # revealed: Unknown
2020
```
21+
22+
## Possibly unbound `__call__` method
23+
24+
```py
25+
def flag() -> bool: ...
26+
27+
class PossiblyNotCallable:
28+
if flag():
29+
def __call__(self) -> int: ...
30+
31+
a = PossiblyNotCallable()
32+
result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
33+
reveal_type(result) # revealed: int
34+
```
35+
36+
## Possibly unbound callable
37+
38+
```py
39+
def flag() -> bool: ...
40+
41+
if flag():
42+
class PossiblyUnbound:
43+
def __call__(self) -> int: ...
44+
45+
# error: [possibly-unresolved-reference]
46+
a = PossiblyUnbound()
47+
reveal_type(a()) # revealed: int
48+
```
49+
50+
## Non-callable `__call__`
51+
52+
```py
53+
class NonCallable:
54+
__call__ = 1
55+
56+
a = NonCallable()
57+
# error: "Object of type `NonCallable` is not callable"
58+
reveal_type(a()) # revealed: Unknown
59+
```
60+
61+
## Possibly non-callable `__call__`
62+
63+
```py
64+
def flag() -> bool: ...
65+
66+
class NonCallable:
67+
if flag():
68+
__call__ = 1
69+
else:
70+
def __call__(self) -> int: ...
71+
72+
a = NonCallable()
73+
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
74+
reveal_type(a()) # revealed: Unknown | int
75+
```

crates/red_knot_python_semantic/resources/mdtest/call/function.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,16 @@ reveal_type(bar()) # revealed: @Todo
4444
nonsense = 123
4545
x = nonsense() # error: "Object of type `Literal[123]` is not callable"
4646
```
47+
48+
## Potentially unbound function
49+
50+
```py
51+
def flag() -> bool: ...
52+
53+
if flag():
54+
def foo() -> int:
55+
return 42
56+
57+
# error: [possibly-unresolved-reference]
58+
reveal_type(foo()) # revealed: int
59+
```

crates/red_knot_python_semantic/resources/mdtest/loops/for_loop.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ class Test:
238238
def coinflip() -> bool:
239239
return True
240240

241-
# TODO: we should emit a diagnostic here (it might not be iterable)
241+
# error: [not-iterable] "Object of type `Test | Literal[42]` is not iterable because its `__iter__` method is possibly unbound"
242242
for x in Test() if coinflip() else 42:
243243
reveal_type(x) # revealed: int
244244
```

crates/red_knot_python_semantic/src/symbol.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
Db,
44
};
55

6-
#[derive(Debug, Clone, Copy, PartialEq)]
6+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77
pub(crate) enum Boundness {
88
Bound,
99
MayBeUnbound,
@@ -44,17 +44,13 @@ impl<'db> Symbol<'db> {
4444
}
4545
}
4646

47-
pub(crate) fn unwrap_or(&self, other: Type<'db>) -> Type<'db> {
47+
pub(crate) fn unwrap_or_unknown(&self) -> Type<'db> {
4848
match self {
4949
Symbol::Type(ty, _) => *ty,
50-
Symbol::Unbound => other,
50+
Symbol::Unbound => Type::Unknown,
5151
}
5252
}
5353

54-
pub(crate) fn unwrap_or_unknown(&self) -> Type<'db> {
55-
self.unwrap_or(Type::Unknown)
56-
}
57-
5854
pub(crate) fn as_type(&self) -> Option<Type<'db>> {
5955
match self {
6056
Symbol::Type(ty, _) => Some(*ty),

0 commit comments

Comments
 (0)