Skip to content

Commit 28c6893

Browse files
authored
[red-knot] Fix inference for pow between two literal integers (#17161)
## Summary Python `**` works differently to Rust `**`! ## Test Plan Added an mdtest for various edge cases, and checked in the Python REPL that we infer the correct type in all the new cases tested.
1 parent 195bb43 commit 28c6893

File tree

2 files changed

+22
-9
lines changed

2 files changed

+22
-9
lines changed

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

+12
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ def variable(x: int):
5555
reveal_type(x**x) # revealed: @Todo(return type of overloaded function)
5656
```
5757

58+
If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but
59+
the second argument is >=0, an `int` is still returned:
60+
61+
```py
62+
reveal_type(1**0) # revealed: Literal[1]
63+
reveal_type(0**1) # revealed: Literal[0]
64+
reveal_type(0**0) # revealed: Literal[1]
65+
reveal_type((-1) ** 2) # revealed: Literal[1]
66+
reveal_type(2 ** (-1)) # revealed: float
67+
reveal_type((-1) ** (-1)) # revealed: float
68+
```
69+
5870
## Division by Zero
5971

6072
This error is really outside the current Python type system, because e.g. `int.__truediv__` and

crates/red_knot_python_semantic/src/types/infer.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -4615,16 +4615,17 @@ impl<'db> TypeInferenceBuilder<'db> {
46154615
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
46164616
),
46174617

4618-
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Pow) => {
4619-
let m = u32::try_from(m);
4620-
Some(match m {
4621-
Ok(m) => n
4622-
.checked_pow(m)
4618+
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Pow) => Some({
4619+
if m < 0 {
4620+
KnownClass::Float.to_instance(self.db())
4621+
} else {
4622+
u32::try_from(m)
4623+
.ok()
4624+
.and_then(|m| n.checked_pow(m))
46234625
.map(Type::IntLiteral)
4624-
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
4625-
Err(_) => KnownClass::Int.to_instance(self.db()),
4626-
})
4627-
}
4626+
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db()))
4627+
}
4628+
}),
46284629

46294630
(Type::BytesLiteral(lhs), Type::BytesLiteral(rhs), ast::Operator::Add) => {
46304631
let bytes = [&**lhs.value(self.db()), &**rhs.value(self.db())].concat();

0 commit comments

Comments
 (0)