Skip to content

[red-knot] Fix inference for pow between two literal integers #17161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ def variable(x: int):
reveal_type(x**x) # revealed: @Todo(return type of overloaded function)
```

If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but
the second argument is >=0, an `int` is still returned:

```py
reveal_type(1**0) # revealed: Literal[1]
reveal_type(0**1) # revealed: Literal[0]
reveal_type(0**0) # revealed: Literal[1]
reveal_type((-1) ** 2) # revealed: Literal[1]
reveal_type(2 ** (-1)) # revealed: float
reveal_type((-1) ** (-1)) # revealed: float
```

## Division by Zero

This error is really outside the current Python type system, because e.g. `int.__truediv__` and
Expand Down
19 changes: 10 additions & 9 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4615,16 +4615,17 @@ impl<'db> TypeInferenceBuilder<'db> {
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
),

(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Pow) => {
let m = u32::try_from(m);
Some(match m {
Ok(m) => n
.checked_pow(m)
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Pow) => Some({
if m < 0 {
KnownClass::Float.to_instance(self.db())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we fall back to float anyway via typeshed stubs if we guard the whole match pattern with m < 0? Or do we not understand the typeshed stubs yet? Either way, I'm also fine with hardcoding float here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, merged before I saw this. Yes, that would also work! But, eh, this is probably a tiny bit more performant than looking up the stub in typeshed, and it's unlikely to ever change at runtime ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt there are many code bases that have so many pow operations that the performance difference is noticeable 😉

} else {
u32::try_from(m)
.ok()
.and_then(|m| n.checked_pow(m))
.map(Type::IntLiteral)
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
Err(_) => KnownClass::Int.to_instance(self.db()),
})
}
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db()))
}
}),

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