mirror of https://github.com/astral-sh/ruff
[`pylint`] Detect more exotic NaN literals in `PLW0177` (#18630)
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
136443b71b
commit
4e83db4d40
|
|
@ -92,3 +92,8 @@ if y == np.inf:
|
||||||
# OK
|
# OK
|
||||||
if x == "nan":
|
if x == "nan":
|
||||||
pass
|
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::source_kind::SourceKind;
|
||||||
use crate::{Locator, directives, fs, warn_user_once};
|
use crate::{Locator, directives, fs, warn_user_once};
|
||||||
|
|
||||||
|
pub(crate) mod float;
|
||||||
|
|
||||||
pub struct LinterResult {
|
pub struct LinterResult {
|
||||||
/// A collection of diagnostic messages generated by the linter.
|
/// A collection of diagnostic messages generated by the linter.
|
||||||
pub messages: Vec<Message>,
|
pub messages: Vec<Message>,
|
||||||
|
|
|
||||||
|
|
@ -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_macros::{ViolationMetadata, derive_message_formats};
|
||||||
|
|
||||||
use ruff_python_ast::{self as ast, Expr};
|
use ruff_python_ast::{self as ast, Expr};
|
||||||
use ruff_python_semantic::SemanticModel;
|
use ruff_python_semantic::SemanticModel;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::Violation;
|
use crate::Violation;
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::linter::float::as_nan_float_string_literal;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for comparisons against NaN values.
|
/// Checks for comparisons against NaN values.
|
||||||
|
|
@ -113,14 +115,10 @@ fn is_nan_float(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let [Expr::StringLiteral(ast::ExprStringLiteral { value, .. })] = &**args else {
|
let [expr] = &**args else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
if as_nan_float_string_literal(expr).is_none() {
|
||||||
if !matches!(
|
|
||||||
value.to_str(),
|
|
||||||
"nan" | "NaN" | "NAN" | "Nan" | "nAn" | "naN" | "nAN" | "NAn"
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,3 +107,20 @@ nan_comparison.py:60:10: PLW0177 Comparing against a NaN value; use `math.isnan`
|
||||||
61 |
|
61 |
|
||||||
62 | # No errors
|
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 {
|
let Some(float) = float.as_string_literal_expr() else {
|
||||||
break 'short_circuit;
|
break 'short_circuit;
|
||||||
};
|
};
|
||||||
|
// FIXME: use `as_non_finite_float_string_literal` instead.
|
||||||
if !matches!(
|
if !matches!(
|
||||||
float.value.to_str().to_lowercase().as_str(),
|
float.value.to_str().to_lowercase().as_str(),
|
||||||
"inf" | "-inf" | "infinity" | "-infinity" | "nan"
|
"inf" | "-inf" | "infinity" | "-infinity" | "nan"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
|
|
||||||
use ruff_python_ast::{self as ast, Expr};
|
use ruff_python_ast::{self as ast, Expr};
|
||||||
use ruff_python_trivia::PythonWhitespace;
|
use ruff_python_trivia::PythonWhitespace;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::linter::float::as_non_finite_float_string_literal;
|
||||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||||
|
|
||||||
/// ## What it does
|
/// ## 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 {
|
let [float] = arguments.args.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(float) = float.as_string_literal_expr() else {
|
let Some(float_str) = as_non_finite_float_string_literal(float) else {
|
||||||
return;
|
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();
|
let mut replacement = checker.locator().slice(float).to_string();
|
||||||
// `Decimal(float("-nan")) == Decimal("nan")`
|
// `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.
|
// Here we do not attempt to remove just the '-' character.
|
||||||
// It may have been encoded (e.g. as '\N{hyphen-minus}')
|
// It may have been encoded (e.g. as '\N{hyphen-minus}')
|
||||||
// in the original source slice, and the added complexity
|
// in the original source slice, and the added complexity
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue