Skip to content

Commit 788c98d

Browse files
committed
Auto merge of #111818 - Urgau:uplift_cmp_nan, r=cjgillot
Uplift `clippy::cmp_nan` lint This PR aims at uplifting the `clippy::cmp_nan` lint into rustc. ## `invalid_nan_comparisons` ~~(deny-by-default)~~ (warn-by-default) The `invalid_nan_comparisons` lint checks comparison with `f32::NAN` or `f64::NAN` as one of the operand. ### Example ```rust,compile_fail let a = 2.3f32; if a == f32::NAN {} ``` ### Explanation NaN does not compare meaningfully to anything – not even itself – so those comparisons are always false. ----- Mostly followed the instructions for uplifting a clippy lint described here: #99696 (review) `@rustbot` label: +I-lang-nominated r? compiler
2 parents 4b71d79 + a814537 commit 788c98d

25 files changed

+604
-308
lines changed

compiler/rustc_lint/messages.ftl

+5
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,11 @@ lint_invalid_from_utf8_checked = calls to `{$method}` with a invalid literal alw
314314
lint_invalid_from_utf8_unchecked = calls to `{$method}` with a invalid literal are undefined behavior
315315
.label = the literal was valid UTF-8 up to the {$valid_up_to} bytes
316316
317+
lint_invalid_nan_comparisons_eq_ne = incorrect NaN comparison, NaN cannot be directly compared to itself
318+
.suggestion = use `f32::is_nan()` or `f64::is_nan()` instead
319+
320+
lint_invalid_nan_comparisons_lt_le_gt_ge = incorrect NaN comparison, NaN is not orderable
321+
317322
lint_lintpass_by_hand = implementing `LintPass` by hand
318323
.help = try using `declare_lint_pass!` or `impl_lint_pass!` instead
319324

compiler/rustc_lint/src/lints.rs

+30
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,36 @@ pub struct OverflowingLiteral<'a> {
14341434
#[diag(lint_unused_comparisons)]
14351435
pub struct UnusedComparisons;
14361436

1437+
#[derive(LintDiagnostic)]
1438+
pub enum InvalidNanComparisons {
1439+
#[diag(lint_invalid_nan_comparisons_eq_ne)]
1440+
EqNe {
1441+
#[subdiagnostic]
1442+
suggestion: InvalidNanComparisonsSuggestion,
1443+
},
1444+
#[diag(lint_invalid_nan_comparisons_lt_le_gt_ge)]
1445+
LtLeGtGe,
1446+
}
1447+
1448+
#[derive(Subdiagnostic)]
1449+
pub enum InvalidNanComparisonsSuggestion {
1450+
#[multipart_suggestion(
1451+
lint_suggestion,
1452+
style = "verbose",
1453+
applicability = "machine-applicable"
1454+
)]
1455+
Spanful {
1456+
#[suggestion_part(code = "!")]
1457+
neg: Option<Span>,
1458+
#[suggestion_part(code = ".is_nan()")]
1459+
float: Span,
1460+
#[suggestion_part(code = "")]
1461+
nan_plus_binop: Span,
1462+
},
1463+
#[help(lint_suggestion)]
1464+
Spanless,
1465+
}
1466+
14371467
pub struct ImproperCTypes<'a> {
14381468
pub ty: Ty<'a>,
14391469
pub desc: &'a str,

compiler/rustc_lint/src/types.rs

+95-7
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use crate::{
22
fluent_generated as fluent,
33
lints::{
44
AtomicOrderingFence, AtomicOrderingLoad, AtomicOrderingStore, ImproperCTypes,
5-
InvalidAtomicOrderingDiag, OnlyCastu8ToChar, OverflowingBinHex, OverflowingBinHexSign,
6-
OverflowingBinHexSub, OverflowingInt, OverflowingIntHelp, OverflowingLiteral,
7-
OverflowingUInt, RangeEndpointOutOfRange, UnusedComparisons, UseInclusiveRange,
8-
VariantSizeDifferencesDiag,
5+
InvalidAtomicOrderingDiag, InvalidNanComparisons, InvalidNanComparisonsSuggestion,
6+
OnlyCastu8ToChar, OverflowingBinHex, OverflowingBinHexSign, OverflowingBinHexSub,
7+
OverflowingInt, OverflowingIntHelp, OverflowingLiteral, OverflowingUInt,
8+
RangeEndpointOutOfRange, UnusedComparisons, UseInclusiveRange, VariantSizeDifferencesDiag,
99
},
1010
};
1111
use crate::{LateContext, LateLintPass, LintContext};
@@ -113,13 +113,35 @@ declare_lint! {
113113
"detects enums with widely varying variant sizes"
114114
}
115115

116+
declare_lint! {
117+
/// The `invalid_nan_comparisons` lint checks comparison with `f32::NAN` or `f64::NAN`
118+
/// as one of the operand.
119+
///
120+
/// ### Example
121+
///
122+
/// ```rust
123+
/// let a = 2.3f32;
124+
/// if a == f32::NAN {}
125+
/// ```
126+
///
127+
/// {{produces}}
128+
///
129+
/// ### Explanation
130+
///
131+
/// NaN does not compare meaningfully to anything – not
132+
/// even itself – so those comparisons are always false.
133+
INVALID_NAN_COMPARISONS,
134+
Warn,
135+
"detects invalid floating point NaN comparisons"
136+
}
137+
116138
#[derive(Copy, Clone)]
117139
pub struct TypeLimits {
118140
/// Id of the last visited negated expression
119141
negated_expr_id: Option<hir::HirId>,
120142
}
121143

122-
impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS]);
144+
impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS, INVALID_NAN_COMPARISONS]);
123145

124146
impl TypeLimits {
125147
pub fn new() -> TypeLimits {
@@ -486,6 +508,68 @@ fn lint_literal<'tcx>(
486508
}
487509
}
488510

511+
fn lint_nan<'tcx>(
512+
cx: &LateContext<'tcx>,
513+
e: &'tcx hir::Expr<'tcx>,
514+
binop: hir::BinOp,
515+
l: &'tcx hir::Expr<'tcx>,
516+
r: &'tcx hir::Expr<'tcx>,
517+
) {
518+
fn is_nan(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
519+
let expr = expr.peel_blocks().peel_borrows();
520+
match expr.kind {
521+
ExprKind::Path(qpath) => {
522+
let Some(def_id) = cx.typeck_results().qpath_res(&qpath, expr.hir_id).opt_def_id() else { return false; };
523+
524+
matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::f32_nan | sym::f64_nan))
525+
}
526+
_ => false,
527+
}
528+
}
529+
530+
fn eq_ne(
531+
e: &hir::Expr<'_>,
532+
l: &hir::Expr<'_>,
533+
r: &hir::Expr<'_>,
534+
f: impl FnOnce(Span, Span) -> InvalidNanComparisonsSuggestion,
535+
) -> InvalidNanComparisons {
536+
let suggestion =
537+
if let Some(l_span) = l.span.find_ancestor_inside(e.span) &&
538+
let Some(r_span) = r.span.find_ancestor_inside(e.span) {
539+
f(l_span, r_span)
540+
} else {
541+
InvalidNanComparisonsSuggestion::Spanless
542+
};
543+
544+
InvalidNanComparisons::EqNe { suggestion }
545+
}
546+
547+
let lint = match binop.node {
548+
hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, l) => {
549+
eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful {
550+
nan_plus_binop: l_span.until(r_span),
551+
float: r_span.shrink_to_hi(),
552+
neg: (binop.node == hir::BinOpKind::Ne).then(|| r_span.shrink_to_lo()),
553+
})
554+
}
555+
hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, r) => {
556+
eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful {
557+
nan_plus_binop: l_span.shrink_to_hi().to(r_span),
558+
float: l_span.shrink_to_hi(),
559+
neg: (binop.node == hir::BinOpKind::Ne).then(|| l_span.shrink_to_lo()),
560+
})
561+
}
562+
hir::BinOpKind::Lt | hir::BinOpKind::Le | hir::BinOpKind::Gt | hir::BinOpKind::Ge
563+
if is_nan(cx, l) || is_nan(cx, r) =>
564+
{
565+
InvalidNanComparisons::LtLeGtGe
566+
}
567+
_ => return,
568+
};
569+
570+
cx.emit_spanned_lint(INVALID_NAN_COMPARISONS, e.span, lint);
571+
}
572+
489573
impl<'tcx> LateLintPass<'tcx> for TypeLimits {
490574
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
491575
match e.kind {
@@ -496,8 +580,12 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
496580
}
497581
}
498582
hir::ExprKind::Binary(binop, ref l, ref r) => {
499-
if is_comparison(binop) && !check_limits(cx, binop, &l, &r) {
500-
cx.emit_spanned_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons);
583+
if is_comparison(binop) {
584+
if !check_limits(cx, binop, &l, &r) {
585+
cx.emit_spanned_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons);
586+
} else {
587+
lint_nan(cx, e, binop, l, r);
588+
}
501589
}
502590
}
503591
hir::ExprKind::Lit(ref lit) => lint_literal(cx, self, e, lit),

compiler/rustc_span/src/symbol.rs

+2
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,9 @@ symbols! {
701701
f,
702702
f16c_target_feature,
703703
f32,
704+
f32_nan,
704705
f64,
706+
f64_nan,
705707
fabsf32,
706708
fabsf64,
707709
fadd_fast,

library/core/src/num/f32.rs

+1
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ impl f32 {
403403
/// and the stability of its representation over Rust versions
404404
/// and target platforms isn't guaranteed.
405405
#[stable(feature = "assoc_int_consts", since = "1.43.0")]
406+
#[rustc_diagnostic_item = "f32_nan"]
406407
pub const NAN: f32 = 0.0_f32 / 0.0_f32;
407408
/// Infinity (∞).
408409
#[stable(feature = "assoc_int_consts", since = "1.43.0")]

library/core/src/num/f64.rs

+1
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ impl f64 {
401401
/// This constant isn't guaranteed to equal to any specific NaN bitpattern,
402402
/// and the stability of its representation over Rust versions
403403
/// and target platforms isn't guaranteed.
404+
#[rustc_diagnostic_item = "f64_nan"]
404405
#[stable(feature = "assoc_int_consts", since = "1.43.0")]
405406
pub const NAN: f64 = 0.0_f64 / 0.0_f64;
406407
/// Infinity (∞).

src/tools/clippy/clippy_lints/src/declared_lints.rs

-1
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
476476
crate::operators::ARITHMETIC_SIDE_EFFECTS_INFO,
477477
crate::operators::ASSIGN_OP_PATTERN_INFO,
478478
crate::operators::BAD_BIT_MASK_INFO,
479-
crate::operators::CMP_NAN_INFO,
480479
crate::operators::CMP_OWNED_INFO,
481480
crate::operators::DOUBLE_COMPARISONS_INFO,
482481
crate::operators::DURATION_SUBSEC_INFO,

src/tools/clippy/clippy_lints/src/operators/cmp_nan.rs

-30
This file was deleted.

src/tools/clippy/clippy_lints/src/operators/mod.rs

-28
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
mod absurd_extreme_comparisons;
22
mod assign_op_pattern;
33
mod bit_mask;
4-
mod cmp_nan;
54
mod cmp_owned;
65
mod double_comparison;
76
mod duration_subsec;
@@ -485,31 +484,6 @@ declare_clippy_lint! {
485484
"integer division may cause loss of precision"
486485
}
487486

488-
declare_clippy_lint! {
489-
/// ### What it does
490-
/// Checks for comparisons to NaN.
491-
///
492-
/// ### Why is this bad?
493-
/// NaN does not compare meaningfully to anything – not
494-
/// even itself – so those comparisons are simply wrong.
495-
///
496-
/// ### Example
497-
/// ```rust
498-
/// # let x = 1.0;
499-
/// if x == f32::NAN { }
500-
/// ```
501-
///
502-
/// Use instead:
503-
/// ```rust
504-
/// # let x = 1.0f32;
505-
/// if x.is_nan() { }
506-
/// ```
507-
#[clippy::version = "pre 1.29.0"]
508-
pub CMP_NAN,
509-
correctness,
510-
"comparisons to `NAN`, which will always return false, probably not intended"
511-
}
512-
513487
declare_clippy_lint! {
514488
/// ### What it does
515489
/// Checks for conversions to owned values just for the sake
@@ -775,7 +749,6 @@ impl_lint_pass!(Operators => [
775749
FLOAT_EQUALITY_WITHOUT_ABS,
776750
IDENTITY_OP,
777751
INTEGER_DIVISION,
778-
CMP_NAN,
779752
CMP_OWNED,
780753
FLOAT_CMP,
781754
FLOAT_CMP_CONST,
@@ -816,7 +789,6 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
816789
duration_subsec::check(cx, e, op.node, lhs, rhs);
817790
float_equality_without_abs::check(cx, e, op.node, lhs, rhs);
818791
integer_division::check(cx, e, op.node, lhs, rhs);
819-
cmp_nan::check(cx, e, op.node, lhs, rhs);
820792
cmp_owned::check(cx, op.node, lhs, rhs);
821793
float_cmp::check(cx, e, op.node, lhs, rhs);
822794
modulo_one::check(cx, e, op.node, rhs);

src/tools/clippy/clippy_lints/src/renamed_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
3333
("clippy::zero_width_space", "clippy::invisible_characters"),
3434
("clippy::cast_ref_to_mut", "cast_ref_to_mut"),
3535
("clippy::clone_double_ref", "suspicious_double_ref_op"),
36+
("clippy::cmp_nan", "invalid_nan_comparisons"),
3637
("clippy::drop_bounds", "drop_bounds"),
3738
("clippy::drop_copy", "dropping_copy_types"),
3839
("clippy::drop_ref", "dropping_references"),

src/tools/clippy/tests/ui/cmp_nan.rs

-34
This file was deleted.

0 commit comments

Comments
 (0)