red_knot_python_semantic: improve diagnostics for unsupported boolean conversions

This mostly only improves things for incorrect arguments and for an
incorrect return type. It doesn't do much to improve the case where
`__bool__` isn't callable and leaves the union/other cases untouched
completely.

I picked this one because, at first glance, this _looked_ like a lower
hanging fruit. The conceptual improvement here is pretty
straight-forward: add annotations for relevant data. But it took me a
bit to figure out how to connect all of the pieces.
This commit is contained in:
Andrew Gallant
2025-04-23 12:28:49 -04:00
committed by Andrew Gallant
parent eb1d2518c1
commit 0f47810768
19 changed files with 107 additions and 35 deletions

View File

@@ -42,6 +42,6 @@ def _(flag: bool):
class NotBoolable:
__bool__: int = 3
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
3 if NotBoolable() else 4
```

View File

@@ -154,10 +154,10 @@ def _(flag: bool):
class NotBoolable:
__bool__: int = 3
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
if NotBoolable():
...
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
elif NotBoolable():
...
```

View File

@@ -292,7 +292,7 @@ class NotBoolable:
def _(target: int, flag: NotBoolable):
y = 1
match target:
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
case 1 if flag:
y = 2
case 2:

View File

@@ -4,6 +4,6 @@
class NotBoolable:
__bool__: int = 3
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
assert NotBoolable()
```

View File

@@ -123,7 +123,7 @@ if NotBoolable():
class NotBoolable:
__bool__: None = None
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
if NotBoolable():
...
```
@@ -135,7 +135,7 @@ def test(cond: bool):
class NotBoolable:
__bool__: int | None = None if cond else 3
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
if NotBoolable():
...
```
@@ -149,7 +149,7 @@ def test(cond: bool):
a = 10 if cond else NotBoolable()
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `Literal[10] | NotBoolable`; its `__bool__` method isn't callable"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `Literal[10] | NotBoolable`"
if a:
...
```

View File

@@ -123,7 +123,7 @@ def _(flag: bool, flag2: bool):
class NotBoolable:
__bool__: int = 3
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
while NotBoolable():
...
```

View File

@@ -270,7 +270,7 @@ def _(
if af:
reveal_type(af) # revealed: type[AmbiguousClass] & ~AlwaysFalsy
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `MetaDeferred`; the return type of its bool method (`MetaAmbiguous`) isn't assignable to `bool"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `MetaDeferred`"
if d:
# TODO: Should be `Unknown`
reveal_type(d) # revealed: type[DeferredClass] & ~AlwaysFalsy

View File

@@ -24,12 +24,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/binary/instances.m
# Diagnostics
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:7:8
|
6 | # error: [unsupported-bool-conversion]
7 | 10 and a and True
| ^
|
info: `__bool__` on `NotBoolable` must be callable
```

View File

@@ -28,7 +28,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/instanc
# Diagnostics
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:9:1
|
8 | # error: [unsupported-bool-conversion]
@@ -37,11 +37,12 @@ error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for t
10 | # error: [unsupported-bool-conversion]
11 | 10 not in WithContains()
|
info: `__bool__` on `NotBoolable` must be callable
```
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:11:1
|
9 | 10 in WithContains()
@@ -49,5 +50,6 @@ error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for t
11 | 10 not in WithContains()
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
info: `__bool__` on `NotBoolable` must be callable
```

View File

@@ -22,12 +22,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/unary/not.md
# Diagnostics
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:5:1
|
4 | # error: [unsupported-bool-conversion]
5 | not NotBoolable()
| ^^^^^^^^^^^^^^^^^
|
info: `__bool__` on `NotBoolable` must be callable
```

View File

@@ -33,7 +33,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/instanc
# Diagnostics
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:12:1
|
11 | # error: [unsupported-bool-conversion]
@@ -42,11 +42,12 @@ error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for t
13 | # error: [unsupported-bool-conversion]
14 | 10 < Comparable() < Comparable()
|
info: `__bool__` on `NotBoolable` must be callable
```
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:14:1
|
12 | 10 < Comparable() < 20
@@ -56,5 +57,6 @@ error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for t
15 |
16 | Comparable() < Comparable() # fine
|
info: `__bool__` on `NotBoolable` must be callable
```

View File

@@ -34,7 +34,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.
# Diagnostics
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable | Literal[False]`; its `__bool__` method isn't callable
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable | Literal[False]`
--> /src/mdtest_snippet.py:15:1
|
14 | # error: [unsupported-bool-conversion]
@@ -43,5 +43,6 @@ error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for t
16 |
17 | a < b # fine
|
info: `__bool__` on `NotBoolable | Literal[False]` must be callable
```

View File

@@ -26,12 +26,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.
# Diagnostics
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:9:1
|
8 | # error: [unsupported-bool-conversion]
9 | (A(),) == (A(),)
| ^^^^^^^^^^^^^^^^
|
info: `__bool__` on `NotBoolable` must be callable
```

View File

@@ -24,12 +24,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unsupp
# Diagnostics
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:7:8
|
6 | # error: [unsupported-bool-conversion]
7 | 10 and a and True
| ^
|
info: `__bool__` must be callable
```

View File

@@ -25,12 +25,22 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unsupp
# Diagnostics
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; the return type of its bool method (`str`) isn't assignable to `bool
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:8:8
|
7 | # error: [unsupported-bool-conversion]
8 | 10 and a and True
| ^
|
info: `str` is not assignable to `bool`
--> /src/mdtest_snippet.py:2:9
|
1 | class NotBoolable:
2 | def __bool__(self) -> str:
| -------- ^^^ Incorrect return type
| |
| Method defined here
3 | return "wat"
|
```

View File

@@ -25,12 +25,22 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unsupp
# Diagnostics
```
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; it incorrectly implements `__bool__`
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
--> /src/mdtest_snippet.py:8:8
|
7 | # error: [unsupported-bool-conversion]
8 | 10 and a and True
| ^
|
info: `__bool__` methods must only have a `self` parameter
--> /src/mdtest_snippet.py:2:9
|
1 | class NotBoolable:
2 | def __bool__(self, foo):
| --------^^^^^^^^^^^ Incorrect parameters
| |
| Method defined here
3 | return False
|
```

View File

@@ -235,7 +235,7 @@ class InvalidBoolDunder:
def __bool__(self) -> int:
return 1
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `InvalidBoolDunder`; the return type of its bool method (`int`) isn't assignable to `bool"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `InvalidBoolDunder`"
static_assert(InvalidBoolDunder())
```

View File

@@ -187,7 +187,7 @@ class MethodBoolInvalid:
def __bool__(self) -> int:
return 0
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `MethodBoolInvalid`; the return type of its bool method (`int`) isn't assignable to `bool"
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `MethodBoolInvalid`"
# revealed: bool
reveal_type(not MethodBoolInvalid())