|
1 |
| -use rustc_ast::{LitFloatType, LitIntType, LitKind}; |
2 |
| -use rustc_data_structures::fx::FxHashSet; |
3 |
| -use rustc_hir::{Expr, ExprKind, HirId, Stmt, StmtKind}; |
| 1 | +use rustc_ast::ast::{LitFloatType, LitIntType, LitKind}; |
| 2 | +use rustc_hir::{ |
| 3 | + intravisit::{walk_expr, walk_stmt, NestedVisitorMap, Visitor}, |
| 4 | + Body, Expr, ExprKind, Lit, Stmt, StmtKind, |
| 5 | +}; |
4 | 6 | use rustc_lint::{LateContext, LateLintPass};
|
5 |
| -use rustc_middle::ty::{self, FloatTy, IntTy}; |
6 |
| -use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 7 | +use rustc_middle::{ |
| 8 | + hir::map::Map, |
| 9 | + ty::{self, FloatTy, IntTy, Ty}, |
| 10 | +}; |
| 11 | +use rustc_session::{declare_lint_pass, declare_tool_lint}; |
7 | 12 |
|
8 | 13 | use if_chain::if_chain;
|
9 | 14 |
|
@@ -33,53 +38,141 @@ declare_clippy_lint! {
|
33 | 38 | /// Use instead:
|
34 | 39 | /// ```rust
|
35 | 40 | /// let i = 10i32;
|
36 |
| - /// let f: f64 = 1.23; |
| 41 | + /// let f = 1.23f64; |
37 | 42 | /// ```
|
38 | 43 | pub DEFAULT_NUMERIC_FALLBACK,
|
39 | 44 | restriction,
|
40 | 45 | "usage of unconstrained numeric literals which may cause default numeric fallback."
|
41 | 46 | }
|
42 | 47 |
|
43 |
| -#[derive(Default)] |
44 |
| -pub struct DefaultNumericFallback { |
45 |
| - /// Hold `init` in `Local` if `Local` has a type annotation. |
46 |
| - bounded_inits: FxHashSet<HirId>, |
| 48 | +declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]); |
| 49 | + |
| 50 | +impl LateLintPass<'_> for DefaultNumericFallback { |
| 51 | + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { |
| 52 | + let mut visitor = NumericFallbackVisitor::new(cx); |
| 53 | + visitor.visit_body(body); |
| 54 | + } |
47 | 55 | }
|
48 | 56 |
|
49 |
| -impl_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]); |
| 57 | +struct NumericFallbackVisitor<'a, 'tcx> { |
| 58 | + /// Stack manages type bound of exprs. The top element holds current expr type. |
| 59 | + ty_bounds: Vec<TyBound<'tcx>>, |
50 | 60 |
|
51 |
| -impl LateLintPass<'_> for DefaultNumericFallback { |
52 |
| - fn check_stmt(&mut self, _: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { |
53 |
| - if_chain! { |
54 |
| - if let StmtKind::Local(local) = stmt.kind; |
55 |
| - if local.ty.is_some(); |
56 |
| - if let Some(init) = local.init; |
57 |
| - then { |
58 |
| - self.bounded_inits.insert(init.hir_id); |
59 |
| - } |
| 61 | + cx: &'a LateContext<'tcx>, |
| 62 | +} |
| 63 | + |
| 64 | +impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { |
| 65 | + fn new(cx: &'a LateContext<'tcx>) -> Self { |
| 66 | + Self { |
| 67 | + ty_bounds: vec![TyBound::Nothing], |
| 68 | + cx, |
60 | 69 | }
|
61 | 70 | }
|
62 | 71 |
|
63 |
| - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
64 |
| - let expr_ty = cx.typeck_results().expr_ty(expr); |
65 |
| - let hir_id = expr.hir_id; |
| 72 | + /// Check whether a passed literal has potential to cause fallback or not. |
| 73 | + fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) { |
| 74 | + let ty_bound = self.ty_bounds.last().unwrap(); |
66 | 75 | if_chain! {
|
67 |
| - if let ExprKind::Lit(ref lit) = expr.kind; |
68 |
| - if matches!(lit.node, |
69 |
| - LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)); |
70 |
| - if matches!(expr_ty.kind(), ty::Int(IntTy::I32) | ty::Float(FloatTy::F64)); |
71 |
| - if !self.bounded_inits.contains(&hir_id); |
72 |
| - if !cx.tcx.hir().parent_iter(hir_id).any(|(ref hir_id, _)| self.bounded_inits.contains(hir_id)); |
73 |
| - then { |
74 |
| - span_lint_and_help( |
75 |
| - cx, |
76 |
| - DEFAULT_NUMERIC_FALLBACK, |
77 |
| - lit.span, |
78 |
| - "default numeric fallback might occur", |
79 |
| - None, |
80 |
| - "consider adding suffix to avoid default numeric fallback", |
81 |
| - ) |
82 |
| - } |
| 76 | + if matches!(lit.node, |
| 77 | + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)); |
| 78 | + if matches!(lit_ty.kind(), ty::Int(IntTy::I32) | ty::Float(FloatTy::F64)); |
| 79 | + if !ty_bound.is_integral(); |
| 80 | + then { |
| 81 | + span_lint_and_help( |
| 82 | + self.cx, |
| 83 | + DEFAULT_NUMERIC_FALLBACK, |
| 84 | + lit.span, |
| 85 | + "default numeric fallback might occur", |
| 86 | + None, |
| 87 | + "consider adding suffix to avoid default numeric fallback", |
| 88 | + ); |
| 89 | + } |
| 90 | + } |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { |
| 95 | + type Map = Map<'tcx>; |
| 96 | + |
| 97 | + #[allow(clippy::too_many_lines)] |
| 98 | + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { |
| 99 | + match &expr.kind { |
| 100 | + ExprKind::Call(func, args) => { |
| 101 | + if_chain! { |
| 102 | + if let ExprKind::Path(ref func_path) = func.kind; |
| 103 | + if let Some(def_id) = self.cx.qpath_res(func_path, func.hir_id).opt_def_id(); |
| 104 | + then { |
| 105 | + let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder(); |
| 106 | + for (expr, bound) in args.iter().zip(fn_sig.inputs().iter()) { |
| 107 | + // Push found arg type, then visit arg. |
| 108 | + self.ty_bounds.push(TyBound::Ty(bound)); |
| 109 | + self.visit_expr(expr); |
| 110 | + self.ty_bounds.pop(); |
| 111 | + } |
| 112 | + return; |
| 113 | + } |
| 114 | + } |
| 115 | + }, |
| 116 | + |
| 117 | + ExprKind::MethodCall(_, _, args, _) => { |
| 118 | + if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) { |
| 119 | + let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder(); |
| 120 | + for (expr, bound) in args.iter().zip(fn_sig.inputs().iter()) { |
| 121 | + self.ty_bounds.push(TyBound::Ty(bound)); |
| 122 | + self.visit_expr(expr); |
| 123 | + self.ty_bounds.pop(); |
| 124 | + } |
| 125 | + return; |
| 126 | + } |
| 127 | + }, |
| 128 | + |
| 129 | + ExprKind::Lit(lit) => { |
| 130 | + let ty = self.cx.typeck_results().expr_ty(expr); |
| 131 | + self.check_lit(lit, ty); |
| 132 | + return; |
| 133 | + }, |
| 134 | + |
| 135 | + _ => {}, |
| 136 | + } |
| 137 | + |
| 138 | + walk_expr(self, expr); |
| 139 | + } |
| 140 | + |
| 141 | + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { |
| 142 | + match stmt.kind { |
| 143 | + StmtKind::Local(local) => { |
| 144 | + if local.ty.is_some() { |
| 145 | + self.ty_bounds.push(TyBound::Any) |
| 146 | + } else { |
| 147 | + self.ty_bounds.push(TyBound::Nothing) |
| 148 | + } |
| 149 | + }, |
| 150 | + |
| 151 | + _ => self.ty_bounds.push(TyBound::Nothing), |
| 152 | + } |
| 153 | + |
| 154 | + walk_stmt(self, stmt); |
| 155 | + self.ty_bounds.pop(); |
| 156 | + } |
| 157 | + |
| 158 | + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { |
| 159 | + NestedVisitorMap::None |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 163 | +#[derive(Debug, Clone, Copy)] |
| 164 | +enum TyBound<'ctx> { |
| 165 | + Any, |
| 166 | + Ty(Ty<'ctx>), |
| 167 | + Nothing, |
| 168 | +} |
| 169 | + |
| 170 | +impl<'ctx> TyBound<'ctx> { |
| 171 | + fn is_integral(self) -> bool { |
| 172 | + match self { |
| 173 | + TyBound::Any => true, |
| 174 | + TyBound::Ty(t) => t.is_integral(), |
| 175 | + TyBound::Nothing => false, |
83 | 176 | }
|
84 | 177 | }
|
85 | 178 | }
|
0 commit comments