@@ -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} ;
1111use 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 ) ]
117139pub 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
124146impl 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+
489573impl < ' 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) ,
0 commit comments