Skip to content

Commit d2c93e3

Browse files
committed
First batch of review comments (minus call outcome feedback)
1 parent fd44af8 commit d2c93e3

12 files changed

Lines changed: 98 additions & 67 deletions

File tree

crates/red_knot_python_semantic/resources/mdtest/assignment/augmented.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class C:
4040
return 42
4141

4242
x = C()
43-
# error: [unsupported-operator]
43+
# error: [unsupported-operator] "Operator `-=` is unsupported between objects of type `C` and `Literal[1]`"
4444
x -= 1
4545

4646
reveal_type(x) # revealed: int

crates/red_knot_python_semantic/resources/mdtest/binary/instances.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ class B:
260260
__add__ = A()
261261

262262
# TODO: this could be `int` if we declare `B.__add__` using a `Callable` type
263-
# TODO: This should not be an error but we currently fail to bind `self` in `A.__call__` as `B`
264-
# and instead bind it to `A` which makes the `__add__` call fail.
263+
# TODO: Should not be an error: `A` instance is not a method descriptor, don't prepend `self` arg.
264+
# Revealed type should be `Unknown | int`.
265265
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `B` and `B`"
266266
reveal_type(B() + B()) # revealed: Unknown
267267
```

crates/red_knot_python_semantic/resources/mdtest/binary/integers.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ reveal_type(-3 // 3) # revealed: Literal[-1]
1010
reveal_type(-3 / 3) # revealed: float
1111
reveal_type(5 % 3) # revealed: Literal[2]
1212

13-
# TODO: We don't currently verify that the actual parameter to int.__add__ matches the declared
14-
# formal parameter type.
15-
# TODO: Should this emit an error?
13+
# TODO: This should emit an unsupported-operator error but we don't currently
14+
# verify that the actual parameter to `int.__add__` matches the declared
15+
# formal parameter type.
1616
reveal_type(2 + "f") # revealed: Unknown
1717

1818
def lhs(x: int):

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,27 @@ c = C()
9999
# error: 13 [invalid-argument-type] "Object of type `C` cannot be assigned to parameter 1 (`self`) of function `__call__`; expected type `int`"
100100
reveal_type(c()) # revealed: int
101101
```
102+
103+
## Union over callables
104+
105+
### Possibly unbound `__call__`
106+
107+
```py
108+
def outer(cond1: bool):
109+
class Test:
110+
if cond1:
111+
def __call__(self): ...
112+
113+
class Other:
114+
def __call__(self): ...
115+
116+
def inner(cond2: bool):
117+
if cond2:
118+
a = Test()
119+
else:
120+
a = Other()
121+
122+
# TODO: Improve the error message to a) be more specific what `__call__` is and b) mention that it is possibly unbound.
123+
# error: [call-non-callable] "Object of type `Test | Other` is not callable (due to union element `Literal[__call__]`)"
124+
a()
125+
```

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ def _(flag: bool):
8181
Calling a union where the arguments don't match the signature of all variants.
8282

8383
```py
84-
def f1(a: int): ...
85-
def f2(a: str): ...
84+
def f1(a: int) -> int: ...
85+
def f2(a: str) -> str: ...
8686
def _(flag: bool):
8787
if flag:
8888
f = f1
@@ -91,20 +91,20 @@ def _(flag: bool):
9191

9292
# error: [call-non-callable] "Object of type `Literal[f1, f2]` is not callable (due to union element `Literal[f2]`)"
9393
x = f(3)
94-
reveal_type(x) # revealed: Unknown
94+
reveal_type(x) # revealed: int | str
9595
```
9696

97-
## Any non callable variant
97+
## Any non-callable variant
9898

9999
```py
100100
def f1(a: int): ...
101101
def _(flag: bool):
102102
if flag:
103103
f = f1
104104
else:
105-
f = "str"
105+
f = "This is a string literal"
106106

107-
# error: [call-non-callable] "Object of type `Literal[f1] | Literal["str"]` is not callable (due to union element `Literal["str"]`)"
107+
# error: [call-non-callable] "Object of type `Literal[f1] | Literal["This is a string literal"]` is not callable (due to union element `Literal["This is a string literal"]`)"
108108
x = f(3)
109109
reveal_type(x) # revealed: Unknown
110110
```

crates/red_knot_python_semantic/resources/mdtest/comparison/intersections.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,15 @@ types, we can infer that the result for the intersection type is also true/false
88
```py
99
from typing import Literal
1010

11-
class Base: ...
11+
class Base:
12+
def __gt__(self, other) -> bool:
13+
return False
1214

1315
class Child1(Base):
1416
def __eq__(self, other) -> Literal[True]:
1517
return True
1618

17-
def __gt__(self, other) -> bool:
18-
return False
19-
20-
class Child2(Base):
21-
def __gt__(self, other) -> bool:
22-
return False
19+
class Child2(Base): ...
2320

2421
def _(x: Base):
2522
c1 = Child1()

crates/red_knot_python_semantic/resources/mdtest/comparison/non_bool_returns.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ from __future__ import annotations
2323

2424
class A:
2525
def __lt__(self, other) -> A: ...
26-
def __gt__(self, other) -> A: ...
26+
def __gt__(self, other) -> bool: ...
2727

2828
class B:
2929
def __lt__(self, other) -> B: ...
@@ -35,7 +35,7 @@ x = A() < B() < C()
3535
reveal_type(x) # revealed: A & ~AlwaysTruthy | B
3636

3737
y = 0 < 1 < A() < 3
38-
reveal_type(y) # revealed: A
38+
reveal_type(y) # revealed: Literal[False] | A
3939

4040
z = 10 < 0 < A() < B() < C()
4141
reveal_type(z) # revealed: Literal[False]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ class Test2:
245245
return 42
246246

247247
def _(flag: bool):
248+
# TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
248249
# error: "Object of type `Test | Test2` is not iterable"
249250
for x in Test() if flag else Test2():
250251
reveal_type(x) # revealed: int

crates/red_knot_python_semantic/resources/mdtest/with/sync.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class Manager:
8080

8181
def __exit__(self, exc_tpe, exc_value, traceback): ...
8282

83-
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` of type `int` is not callable"
83+
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not correctly implement `__enter__`"
8484
with Manager():
8585
...
8686
```
@@ -95,7 +95,7 @@ class Manager:
9595

9696
__exit__: int = 32
9797

98-
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__exit__` of type `int` is not callable"
98+
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not correctly implement `__exit__`"
9999
with Manager():
100100
...
101101
```
@@ -146,8 +146,7 @@ class Manager:
146146

147147
context_expr = Manager()
148148

149-
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` of type `Literal[__enter__]` is not callable"
149+
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not correctly implement `__enter__`"
150150
with context_expr as f:
151-
# TODO: Should this be Unknown?
152151
reveal_type(f) # revealed: str
153152
```

crates/red_knot_python_semantic/src/types.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,6 +1658,8 @@ impl<'db> Type<'db> {
16581658
.map(|arg| arg.bool(db).into_type(db))
16591659
.unwrap_or(Type::BooleanLiteral(false)),
16601660

1661+
// TODO: Don't ignore the second and third arguments to `str`
1662+
// https://github.com/astral-sh/ruff/pull/16161#discussion_r1958425568
16611663
Some(KnownClass::Str) => arguments
16621664
.first_argument()
16631665
.map(|arg| arg.str(db))
@@ -1701,7 +1703,7 @@ impl<'db> Type<'db> {
17011703
Type::Dynamic(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(self))),
17021704

17031705
Type::Union(union) => {
1704-
CallOutcome::try_call(db, union, |element| element.call(db, arguments))
1706+
CallOutcome::try_call_union(db, union, |element| element.call(db, arguments))
17051707
}
17061708

17071709
Type::Intersection(_) => Ok(CallOutcome::Single(CallBinding::from_return_type(
@@ -1740,7 +1742,7 @@ impl<'db> Type<'db> {
17401742
self.call(db, arguments)
17411743
}
17421744

1743-
Type::Union(union) => CallOutcome::try_call(db, union, |element| {
1745+
Type::Union(union) => CallOutcome::try_call_union(db, union, |element| {
17441746
element.call_bound(db, receiver_ty, arguments)
17451747
}),
17461748

@@ -1826,7 +1828,8 @@ impl<'db> Type<'db> {
18261828
},
18271829
};
18281830
}
1829-
// If the attribute exists but can't be called, return not iterable over falling back to `__get__item`.
1831+
// If `__iter__` exists but can't be called or doesn't have the expected signature,
1832+
// return not iterable over falling back to `__getitem__`.
18301833
Err(CallDunderError::Call(_)) => {
18311834
return IterationOutcome::NotIterable {
18321835
not_iterable_ty: self,
@@ -3669,7 +3672,7 @@ impl<'db> Class<'db> {
36693672

36703673
Err(CallError::NotCallableVariants {
36713674
called_ty,
3672-
not_callable,
3675+
call_errors: not_callable,
36733676
callable,
36743677
}) => {
36753678
let mut partly_not_callable = false;

0 commit comments

Comments
 (0)