@@ -2,10 +2,10 @@ use crate::{
2
2
fluent_generated as fluent,
3
3
lints:: {
4
4
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 ,
9
9
} ,
10
10
} ;
11
11
use crate :: { LateContext , LateLintPass , LintContext } ;
@@ -113,13 +113,35 @@ declare_lint! {
113
113
"detects enums with widely varying variant sizes"
114
114
}
115
115
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
+
116
138
#[ derive( Copy , Clone ) ]
117
139
pub struct TypeLimits {
118
140
/// Id of the last visited negated expression
119
141
negated_expr_id : Option < hir:: HirId > ,
120
142
}
121
143
122
- impl_lint_pass ! ( TypeLimits => [ UNUSED_COMPARISONS , OVERFLOWING_LITERALS ] ) ;
144
+ impl_lint_pass ! ( TypeLimits => [ UNUSED_COMPARISONS , OVERFLOWING_LITERALS , INVALID_NAN_COMPARISONS ] ) ;
123
145
124
146
impl TypeLimits {
125
147
pub fn new ( ) -> TypeLimits {
@@ -486,6 +508,68 @@ fn lint_literal<'tcx>(
486
508
}
487
509
}
488
510
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
+
489
573
impl < ' tcx > LateLintPass < ' tcx > for TypeLimits {
490
574
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx hir:: Expr < ' tcx > ) {
491
575
match e. kind {
@@ -496,8 +580,12 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
496
580
}
497
581
}
498
582
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
+ }
501
589
}
502
590
}
503
591
hir:: ExprKind :: Lit ( ref lit) => lint_literal ( cx, self , e, lit) ,
0 commit comments