mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-30 23:27:27 +00:00
Implement our own small-integer optimization (#7584)
## Summary This is a follow-up to #7469 that attempts to achieve similar gains, but without introducing malachite. Instead, this PR removes the `BigInt` type altogether, instead opting for a simple enum that allows us to store small integers directly and only allocate for values greater than `i64`: ```rust /// A Python integer literal. Represents both small (fits in an `i64`) and large integers. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Int(Number); #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Number { /// A "small" number that can be represented as an `i64`. Small(i64), /// A "large" number that cannot be represented as an `i64`. Big(Box<str>), } impl std::fmt::Display for Number { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Number::Small(value) => write!(f, "{value}"), Number::Big(value) => write!(f, "{value}"), } } } ``` We typically don't care about numbers greater than `isize` -- our only uses are comparisons against small constants (like `1`, `2`, `3`, etc.), so there's no real loss of information, except in one or two rules where we're now a little more conservative (with the worst-case being that we don't flag, e.g., an `itertools.pairwise` that uses an extremely large value for the slice start constant). For simplicity, a few diagnostics now show a dedicated message when they see integers that are out of the supported range (e.g., `outdated-version-block`). An additional benefit here is that we get to remove a few dependencies, especially `num-bigint`. ## Test Plan `cargo test`
This commit is contained in:
parent
65aebf127a
commit
93b5d8a0fb
40 changed files with 707 additions and 385 deletions
|
@ -15,8 +15,6 @@
|
|||
//! an implicit concatenation of string literals, as these expressions are considered to
|
||||
//! have the same shape in that they evaluate to the same value.
|
||||
|
||||
use num_bigint::BigInt;
|
||||
|
||||
use crate as ast;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
|
@ -334,7 +332,7 @@ pub enum ComparableConstant<'a> {
|
|||
Bool(&'a bool),
|
||||
Str { value: &'a str, unicode: bool },
|
||||
Bytes(&'a [u8]),
|
||||
Int(&'a BigInt),
|
||||
Int(&'a ast::Int),
|
||||
Tuple(Vec<ComparableConstant<'a>>),
|
||||
Float(u64),
|
||||
Complex { real: u64, imag: u64 },
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use num_traits::Zero;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
@ -1073,7 +1072,7 @@ impl Truthiness {
|
|||
Constant::None => Some(false),
|
||||
Constant::Str(ast::StringConstant { value, .. }) => Some(!value.is_empty()),
|
||||
Constant::Bytes(bytes) => Some(!bytes.is_empty()),
|
||||
Constant::Int(int) => Some(!int.is_zero()),
|
||||
Constant::Int(int) => Some(*int != 0),
|
||||
Constant::Float(float) => Some(*float != 0.0),
|
||||
Constant::Complex { real, imag } => Some(*real != 0.0 || *imag != 0.0),
|
||||
Constant::Ellipsis => Some(true),
|
||||
|
@ -1140,7 +1139,7 @@ mod tests {
|
|||
|
||||
use crate::helpers::{any_over_stmt, any_over_type_param, resolve_imported_module_path};
|
||||
use crate::{
|
||||
Constant, Expr, ExprConstant, ExprContext, ExprName, Identifier, Stmt, StmtTypeAlias,
|
||||
Constant, Expr, ExprConstant, ExprContext, ExprName, Identifier, Int, Stmt, StmtTypeAlias,
|
||||
TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple, TypeParams,
|
||||
};
|
||||
|
||||
|
@ -1240,7 +1239,7 @@ mod tests {
|
|||
assert!(!any_over_type_param(&type_var_no_bound, &|_expr| true));
|
||||
|
||||
let bound = Expr::Constant(ExprConstant {
|
||||
value: Constant::Int(1.into()),
|
||||
value: Constant::Int(Int::ONE),
|
||||
range: TextRange::default(),
|
||||
});
|
||||
|
||||
|
|
228
crates/ruff_python_ast/src/int.rs
Normal file
228
crates/ruff_python_ast/src/int.rs
Normal file
|
@ -0,0 +1,228 @@
|
|||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// A Python integer literal. Represents both small (fits in an `i64`) and large integers.
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Int(Number);
|
||||
|
||||
impl FromStr for Int {
|
||||
type Err = std::num::ParseIntError;
|
||||
|
||||
/// Parse an [`Int`] from a string.
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.parse::<i64>() {
|
||||
Ok(value) => Ok(Int::small(value)),
|
||||
Err(err) => {
|
||||
if matches!(
|
||||
err.kind(),
|
||||
std::num::IntErrorKind::PosOverflow | std::num::IntErrorKind::NegOverflow
|
||||
) {
|
||||
Ok(Int::big(s))
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Int {
|
||||
pub const ZERO: Int = Int(Number::Small(0));
|
||||
pub const ONE: Int = Int(Number::Small(1));
|
||||
|
||||
/// Create an [`Int`] to represent a value that can be represented as an `i64`.
|
||||
fn small(value: i64) -> Self {
|
||||
Self(Number::Small(value))
|
||||
}
|
||||
|
||||
/// Create an [`Int`] to represent a value that cannot be represented as an `i64`.
|
||||
fn big(value: impl Into<Box<str>>) -> Self {
|
||||
Self(Number::Big(value.into()))
|
||||
}
|
||||
|
||||
/// Parse an [`Int`] from a string with a given radix.
|
||||
pub fn from_str_radix(s: &str, radix: u32) -> Result<Self, std::num::ParseIntError> {
|
||||
match i64::from_str_radix(s, radix) {
|
||||
Ok(value) => Ok(Int::small(value)),
|
||||
Err(err) => {
|
||||
if matches!(
|
||||
err.kind(),
|
||||
std::num::IntErrorKind::PosOverflow | std::num::IntErrorKind::NegOverflow
|
||||
) {
|
||||
Ok(Int::big(s))
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Int`] as an u8, if it can be represented as that data type.
|
||||
pub fn as_u8(&self) -> Option<u8> {
|
||||
match &self.0 {
|
||||
Number::Small(small) => u8::try_from(*small).ok(),
|
||||
Number::Big(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Int`] as an u16, if it can be represented as that data type.
|
||||
pub fn as_u16(&self) -> Option<u16> {
|
||||
match &self.0 {
|
||||
Number::Small(small) => u16::try_from(*small).ok(),
|
||||
Number::Big(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Int`] as an u32, if it can be represented as that data type.
|
||||
pub fn as_u32(&self) -> Option<u32> {
|
||||
match &self.0 {
|
||||
Number::Small(small) => u32::try_from(*small).ok(),
|
||||
Number::Big(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Int`] as an i8, if it can be represented as that data type.
|
||||
pub fn as_i8(&self) -> Option<i8> {
|
||||
match &self.0 {
|
||||
Number::Small(small) => i8::try_from(*small).ok(),
|
||||
Number::Big(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Int`] as an i16, if it can be represented as that data type.
|
||||
pub fn as_i16(&self) -> Option<i16> {
|
||||
match &self.0 {
|
||||
Number::Small(small) => i16::try_from(*small).ok(),
|
||||
Number::Big(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Int`] as an i32, if it can be represented as that data type.
|
||||
pub fn as_i32(&self) -> Option<i32> {
|
||||
match &self.0 {
|
||||
Number::Small(small) => i32::try_from(*small).ok(),
|
||||
Number::Big(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Int`] as an i64, if it can be represented as that data type.
|
||||
pub const fn as_i64(&self) -> Option<i64> {
|
||||
match &self.0 {
|
||||
Number::Small(small) => Some(*small),
|
||||
Number::Big(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Int {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Int {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u8> for Int {
|
||||
fn eq(&self, other: &u8) -> bool {
|
||||
self.as_u8() == Some(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u16> for Int {
|
||||
fn eq(&self, other: &u16) -> bool {
|
||||
self.as_u16() == Some(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u32> for Int {
|
||||
fn eq(&self, other: &u32) -> bool {
|
||||
self.as_u32() == Some(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i8> for Int {
|
||||
fn eq(&self, other: &i8) -> bool {
|
||||
self.as_i8() == Some(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i16> for Int {
|
||||
fn eq(&self, other: &i16) -> bool {
|
||||
self.as_i16() == Some(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i32> for Int {
|
||||
fn eq(&self, other: &i32) -> bool {
|
||||
self.as_i32() == Some(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i64> for Int {
|
||||
fn eq(&self, other: &i64) -> bool {
|
||||
self.as_i64() == Some(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Int {
|
||||
fn from(value: u8) -> Self {
|
||||
Self::small(i64::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Int {
|
||||
fn from(value: u16) -> Self {
|
||||
Self::small(i64::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Int {
|
||||
fn from(value: u32) -> Self {
|
||||
Self::small(i64::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for Int {
|
||||
fn from(value: i8) -> Self {
|
||||
Self::small(i64::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for Int {
|
||||
fn from(value: i16) -> Self {
|
||||
Self::small(i64::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Int {
|
||||
fn from(value: i32) -> Self {
|
||||
Self::small(i64::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Int {
|
||||
fn from(value: i64) -> Self {
|
||||
Self::small(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
enum Number {
|
||||
/// A "small" number that can be represented as an `i64`.
|
||||
Small(i64),
|
||||
/// A "large" number that cannot be represented as an `i64`.
|
||||
Big(Box<str>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Number {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Number::Small(value) => write!(f, "{value}"),
|
||||
Number::Big(value) => write!(f, "{value}"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use std::path::Path;
|
||||
|
||||
pub use expression::*;
|
||||
pub use int::*;
|
||||
pub use nodes::*;
|
||||
|
||||
pub mod all;
|
||||
|
@ -12,6 +13,7 @@ pub mod hashable;
|
|||
pub mod helpers;
|
||||
pub mod identifier;
|
||||
pub mod imports;
|
||||
mod int;
|
||||
pub mod node;
|
||||
mod nodes;
|
||||
pub mod parenthesize;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#![allow(clippy::derive_partial_eq_without_eq)]
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
|
||||
use crate::int;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
/// See also [mod](https://docs.python.org/3/library/ast.html#ast.mod)
|
||||
|
@ -2584,7 +2584,7 @@ pub enum Constant {
|
|||
Bool(bool),
|
||||
Str(StringConstant),
|
||||
Bytes(BytesConstant),
|
||||
Int(BigInt),
|
||||
Int(int::Int),
|
||||
Float(f64),
|
||||
Complex { real: f64, imag: f64 },
|
||||
Ellipsis,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue