Skip to content

Commit a2a7b1e

Browse files
[red-knot] Do not assume that x != 0 if x inhabits ~Literal[0] (#17370)
## Summary Fixes incorrect negated type eq and ne assertions in infer_binary_intersection_type_comparison fixes #17360 ## Test Plan Remove and update some now incorrect tests
1 parent 1dedcb9 commit a2a7b1e

File tree

3 files changed

+32
-22
lines changed

3 files changed

+32
-22
lines changed

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,17 @@ reveal_type(x) # revealed: LiteralString
5050
if x != "abc":
5151
reveal_type(x) # revealed: LiteralString & ~Literal["abc"]
5252

53-
reveal_type(x == "abc") # revealed: Literal[False]
54-
reveal_type("abc" == x) # revealed: Literal[False]
53+
# TODO: This should be `Literal[False]`
54+
reveal_type(x == "abc") # revealed: bool
55+
# TODO: This should be `Literal[False]`
56+
reveal_type("abc" == x) # revealed: bool
5557
reveal_type(x == "something else") # revealed: bool
5658
reveal_type("something else" == x) # revealed: bool
5759

58-
reveal_type(x != "abc") # revealed: Literal[True]
59-
reveal_type("abc" != x) # revealed: Literal[True]
60+
# TODO: This should be `Literal[True]`
61+
reveal_type(x != "abc") # revealed: bool
62+
# TODO: This should be `Literal[True]`
63+
reveal_type("abc" != x) # revealed: bool
6064
reveal_type(x != "something else") # revealed: bool
6165
reveal_type("something else" != x) # revealed: bool
6266

@@ -79,10 +83,10 @@ def _(x: int):
7983
if x != 1:
8084
reveal_type(x) # revealed: int & ~Literal[1]
8185

82-
reveal_type(x != 1) # revealed: Literal[True]
86+
reveal_type(x != 1) # revealed: bool
8387
reveal_type(x != 2) # revealed: bool
8488

85-
reveal_type(x == 1) # revealed: Literal[False]
89+
reveal_type(x == 1) # revealed: bool
8690
reveal_type(x == 2) # revealed: bool
8791
```
8892

crates/red_knot_python_semantic/resources/mdtest/type_api.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,21 @@ def negate(n1: Not[int], n2: Not[Not[int]], n3: Not[Not[Not[int]]]) -> None:
2323
reveal_type(n2) # revealed: int
2424
reveal_type(n3) # revealed: ~int
2525

26-
def static_truthiness(not_one: Not[Literal[1]]) -> None:
27-
static_assert(not_one != 1)
28-
static_assert(not (not_one == 1))
29-
3026
# error: "Special form `knot_extensions.Not` expected exactly one type parameter"
3127
n: Not[int, str]
28+
29+
def static_truthiness(not_one: Not[Literal[1]]) -> None:
30+
# these are both boolean-literal types,
31+
# since all possible runtime objects that are created by the literal syntax `1`
32+
# are members of the type `Literal[1]`
33+
reveal_type(not_one is not 1) # revealed: bool
34+
reveal_type(not_one is 1) # revealed: bool
35+
36+
# But these are both `bool`, rather than `Literal[True]` or `Literal[False]`
37+
# as there are many runtime objects that inhabit the type `~Literal[1]`
38+
# but still compare equal to `1`. Two examples are `1.0` and `True`.
39+
reveal_type(not_one != 1) # revealed: bool
40+
reveal_type(not_one == 1) # revealed: bool
3241
```
3342

3443
### Intersection
@@ -170,13 +179,11 @@ Static assertions can be used to enforce narrowing constraints:
170179
```py
171180
from knot_extensions import static_assert
172181

173-
def f(x: int) -> None:
174-
if x != 0:
175-
static_assert(x != 0)
182+
def f(x: int | None) -> None:
183+
if x is not None:
184+
static_assert(x is not None)
176185
else:
177-
# `int` can be subclassed, so we cannot assert that `x == 0` here:
178-
# error: "Static assertion error: argument of type `bool` has an ambiguous static truthiness"
179-
static_assert(x == 0)
186+
static_assert(x is None)
180187
```
181188

182189
### Truthy expressions

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5283,12 +5283,6 @@ impl<'db> TypeInferenceBuilder<'db> {
52835283
};
52845284

52855285
match (op, result) {
5286-
(ast::CmpOp::Eq, Some(Type::BooleanLiteral(true))) => {
5287-
return Ok(Type::BooleanLiteral(false));
5288-
}
5289-
(ast::CmpOp::NotEq, Some(Type::BooleanLiteral(false))) => {
5290-
return Ok(Type::BooleanLiteral(true));
5291-
}
52925286
(ast::CmpOp::Is, Some(Type::BooleanLiteral(true))) => {
52935287
return Ok(Type::BooleanLiteral(false));
52945288
}
@@ -5338,6 +5332,9 @@ impl<'db> TypeInferenceBuilder<'db> {
53385332
// we would get a result type `Literal[True]` which is too narrow.
53395333
//
53405334
let mut builder = IntersectionBuilder::new(self.db());
5335+
5336+
builder = builder.add_positive(KnownClass::Bool.to_instance(self.db()));
5337+
53415338
for pos in intersection.positive(self.db()) {
53425339
let result = match intersection_on {
53435340
IntersectionOn::Left => {
@@ -5412,6 +5409,8 @@ impl<'db> TypeInferenceBuilder<'db> {
54125409
ast::CmpOp::LtE => Ok(Type::BooleanLiteral(n <= m)),
54135410
ast::CmpOp::Gt => Ok(Type::BooleanLiteral(n > m)),
54145411
ast::CmpOp::GtE => Ok(Type::BooleanLiteral(n >= m)),
5412+
// We cannot say that two equal int Literals will return True from an `is` or `is not` comparison.
5413+
// Even if they are the same value, they may not be the same object.
54155414
ast::CmpOp::Is => {
54165415
if n == m {
54175416
Ok(KnownClass::Bool.to_instance(self.db()))

0 commit comments

Comments
 (0)