mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
[pylint
] Detect more exotic NaN literals in PLW0177
(#18630)
Some checks are pending
CI / cargo test (linux) (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / cargo fuzz build (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
Some checks are pending
CI / cargo test (linux) (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / cargo fuzz build (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
136443b71b
commit
4e83db4d40
7 changed files with 72 additions and 30 deletions
|
@ -92,3 +92,8 @@ if y == np.inf:
|
|||
# OK
|
||||
if x == "nan":
|
||||
pass
|
||||
|
||||
# PLW0117
|
||||
# https://github.com/astral-sh/ruff/issues/18596
|
||||
assert x == float("-NaN ")
|
||||
assert x == float(" \n+nan \t")
|
||||
|
|
|
@ -39,6 +39,8 @@ use crate::settings::{LinterSettings, TargetVersion, flags};
|
|||
use crate::source_kind::SourceKind;
|
||||
use crate::{Locator, directives, fs, warn_user_once};
|
||||
|
||||
pub(crate) mod float;
|
||||
|
||||
pub struct LinterResult {
|
||||
/// A collection of diagnostic messages generated by the linter.
|
||||
pub messages: Vec<Message>,
|
||||
|
|
39
crates/ruff_linter/src/linter/float.rs
Normal file
39
crates/ruff_linter/src/linter/float.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use ruff_python_ast as ast;
|
||||
|
||||
/// Checks if `expr` is a string literal that represents NaN.
|
||||
/// E.g., `"NaN"`, `"-nAn"`, `"+nan"`, or even `" -NaN \n \t"`
|
||||
/// Returns `None` if it's not. Else `Some("nan")`, `Some("-nan")`, or `Some("+nan")`.
|
||||
pub(crate) fn as_nan_float_string_literal(expr: &ast::Expr) -> Option<&'static str> {
|
||||
find_any_ignore_ascii_case(expr, &["nan", "+nan", "-nan"])
|
||||
}
|
||||
|
||||
/// Returns `true` if `expr` is a string literal that represents a non-finite float.
|
||||
/// E.g., `"NaN"`, "-inf", `"Infinity"`, or even `" +Inf \n \t"`.
|
||||
/// Return `None` if it's not. Else the lowercased, trimmed string literal,
|
||||
/// e.g., `Some("nan")`, `Some("-inf")`, or `Some("+infinity")`.
|
||||
pub(crate) fn as_non_finite_float_string_literal(expr: &ast::Expr) -> Option<&'static str> {
|
||||
find_any_ignore_ascii_case(
|
||||
expr,
|
||||
&[
|
||||
"nan",
|
||||
"+nan",
|
||||
"-nan",
|
||||
"inf",
|
||||
"+inf",
|
||||
"-inf",
|
||||
"infinity",
|
||||
"+infinity",
|
||||
"-infinity",
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn find_any_ignore_ascii_case(expr: &ast::Expr, patterns: &[&'static str]) -> Option<&'static str> {
|
||||
let value = &expr.as_string_literal_expr()?.value;
|
||||
|
||||
let value = value.to_str().trim();
|
||||
patterns
|
||||
.iter()
|
||||
.find(|other| value.eq_ignore_ascii_case(other))
|
||||
.copied()
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::linter::float::as_nan_float_string_literal;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for comparisons against NaN values.
|
||||
|
@ -113,14 +115,10 @@ fn is_nan_float(expr: &Expr, semantic: &SemanticModel) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
let [Expr::StringLiteral(ast::ExprStringLiteral { value, .. })] = &**args else {
|
||||
let [expr] = &**args else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if !matches!(
|
||||
value.to_str(),
|
||||
"nan" | "NaN" | "NAN" | "Nan" | "nAn" | "naN" | "nAN" | "NAn"
|
||||
) {
|
||||
if as_nan_float_string_literal(expr).is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -107,3 +107,20 @@ nan_comparison.py:60:10: PLW0177 Comparing against a NaN value; use `math.isnan`
|
|||
61 |
|
||||
62 | # No errors
|
||||
|
|
||||
|
||||
nan_comparison.py:98:13: PLW0177 Comparing against a NaN value; use `math.isnan` instead
|
||||
|
|
||||
96 | # PLW0117
|
||||
97 | # https://github.com/astral-sh/ruff/issues/18596
|
||||
98 | assert x == float("-NaN ")
|
||||
| ^^^^^^^^^^^^^^ PLW0177
|
||||
99 | assert x == float(" \n+nan \t")
|
||||
|
|
||||
|
||||
nan_comparison.py:99:13: PLW0177 Comparing against a NaN value; use `math.isnan` instead
|
||||
|
|
||||
97 | # https://github.com/astral-sh/ruff/issues/18596
|
||||
98 | assert x == float("-NaN ")
|
||||
99 | assert x == float(" \n+nan \t")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ PLW0177
|
||||
|
|
||||
|
|
|
@ -140,6 +140,7 @@ pub(crate) fn unnecessary_from_float(checker: &Checker, call: &ExprCall) {
|
|||
let Some(float) = float.as_string_literal_expr() else {
|
||||
break 'short_circuit;
|
||||
};
|
||||
// FIXME: use `as_non_finite_float_string_literal` instead.
|
||||
if !matches!(
|
||||
float.value.to_str().to_lowercase().as_str(),
|
||||
"inf" | "-inf" | "infinity" | "-infinity" | "nan"
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_trivia::PythonWhitespace;
|
||||
use ruff_text_size::Ranged;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::linter::float::as_non_finite_float_string_literal;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
|
@ -150,35 +152,13 @@ pub(crate) fn verbose_decimal_constructor(checker: &Checker, call: &ast::ExprCal
|
|||
let [float] = arguments.args.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Some(float) = float.as_string_literal_expr() else {
|
||||
let Some(float_str) = as_non_finite_float_string_literal(float) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let trimmed = float.value.to_str().trim();
|
||||
let mut matches_non_finite_keyword = false;
|
||||
for non_finite_keyword in [
|
||||
"inf",
|
||||
"+inf",
|
||||
"-inf",
|
||||
"infinity",
|
||||
"+infinity",
|
||||
"-infinity",
|
||||
"nan",
|
||||
"+nan",
|
||||
"-nan",
|
||||
] {
|
||||
if trimmed.eq_ignore_ascii_case(non_finite_keyword) {
|
||||
matches_non_finite_keyword = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !matches_non_finite_keyword {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut replacement = checker.locator().slice(float).to_string();
|
||||
// `Decimal(float("-nan")) == Decimal("nan")`
|
||||
if trimmed.eq_ignore_ascii_case("-nan") {
|
||||
if float_str == "-nan" {
|
||||
// Here we do not attempt to remove just the '-' character.
|
||||
// It may have been encoded (e.g. as '\N{hyphen-minus}')
|
||||
// in the original source slice, and the added complexity
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue