[refurb] Further special cases added to verbose-decimal-constructor (FURB157) (#14216)

This PR accounts for further subtleties in `Decimal` parsing:

- Strings which are empty modulo underscores and surrounding whitespace
are skipped
- `Decimal("-0")` is skipped
- `Decimal("{integer literal that is longer than 640 digits}")` are
skipped (see linked issue for explanation)

NB: The snapshot did not need to be updated since the new test cases are
"Ok" instances and added below the diff.

Closes #14204
This commit is contained in:
Dylan 2024-11-08 20:08:22 -06:00 committed by GitHub
parent 93fdf7ed36
commit b8dc780bdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 24 deletions

View file

@ -34,4 +34,11 @@ Decimal("1.2")
# Ok: even though this is equal to `Decimal(123)`,
# we assume that a developer would
# only write it this way if they meant to.
Decimal("١٢٣")
Decimal("١٢٣")
# Further subtleties
# https://github.com/astral-sh/ruff/issues/14204
Decimal("-0") # Ok
Decimal("_") # Ok
Decimal(" ") # Ok
Decimal("10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") # Ok

View file

@ -94,6 +94,12 @@ pub(crate) fn verbose_decimal_constructor(checker: &mut Checker, call: &ast::Exp
("", trimmed)
};
// Early return if we now have an empty string
// or a very long string:
if (rest.len() > PYTHONINTMAXSTRDIGITS) || (rest.len() == 0) {
return;
}
// Skip leading zeros.
let rest = rest.trim_start_matches('0');
@ -103,7 +109,15 @@ pub(crate) fn verbose_decimal_constructor(checker: &mut Checker, call: &ast::Exp
};
// If all the characters are zeros, then the value is zero.
let rest = if rest.is_empty() { "0" } else { rest };
let rest = match (unary, rest.is_empty()) {
// `Decimal("-0")` is not the same as `Decimal("0")`
// so we return early.
("-", true) => {
return;
}
(_, true) => "0",
_ => rest,
};
let replacement = format!("{unary}{rest}");
let mut diagnostic = Diagnostic::new(
@ -168,25 +182,10 @@ pub(crate) fn verbose_decimal_constructor(checker: &mut Checker, call: &ast::Exp
checker.diagnostics.push(diagnostic);
}
// // Slightly modified from [CPython regex] to ignore https://github.com/python/cpython/blob/ac556a2ad1213b8bb81372fe6fb762f5fcb076de/Lib/_pydecimal.py#L6060-L6077
// static DECIMAL_PARSER_REGEX: LazyLock<Regex> = LazyLock::new(|| {
// Regex::new(
// r"(?x) # Verbose mode for comments
// ^ # Start of string
// (?P<sign>[-+])? # Optional sign
// (?:
// (?P<int>\d*) # Integer part (can be empty)
// (\.(?P<frac>\d+))? # Optional fractional part
// (E(?P<exp>[-+]?\d+))? # Optional exponent
// |
// Inf(inity)? # Infinity
// |
// (?P<signal>s)? # Optional signal
// NaN # NaN
// (?P<diag>\d*) # Optional diagnostic info
// )
// $ # End of string
// ",
// )
// .unwrap()
// });
// ```console
// $ python
// >>> import sys
// >>> sys.int_info.str_digits_check_threshold
// 640
// ```
const PYTHONINTMAXSTRDIGITS: usize = 640;