]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
136023e0 XL |
2 | use clippy_utils::numeric_literal; |
3 | use clippy_utils::source::snippet_opt; | |
cdc7bbd5 | 4 | use if_chain::if_chain; |
f20569fa XL |
5 | use rustc_ast::ast::{LitFloatType, LitIntType, LitKind}; |
6 | use rustc_errors::Applicability; | |
7 | use rustc_hir::{ | |
8 | intravisit::{walk_expr, walk_stmt, NestedVisitorMap, Visitor}, | |
9 | Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind, | |
10 | }; | |
136023e0 | 11 | use rustc_lint::{LateContext, LateLintPass, LintContext}; |
f20569fa XL |
12 | use rustc_middle::{ |
13 | hir::map::Map, | |
136023e0 | 14 | lint::in_external_macro, |
f20569fa XL |
15 | ty::{self, FloatTy, IntTy, PolyFnSig, Ty}, |
16 | }; | |
17 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
cdc7bbd5 | 18 | use std::iter; |
f20569fa XL |
19 | |
20 | declare_clippy_lint! { | |
21 | /// **What it does:** Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type | |
22 | /// inference. | |
23 | /// | |
24 | /// Default numeric fallback means that if numeric types have not yet been bound to concrete | |
25 | /// types at the end of type inference, then integer type is bound to `i32`, and similarly | |
26 | /// floating type is bound to `f64`. | |
27 | /// | |
28 | /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback. | |
29 | /// | |
30 | /// **Why is this bad?** For those who are very careful about types, default numeric fallback | |
31 | /// can be a pitfall that cause unexpected runtime behavior. | |
32 | /// | |
33 | /// **Known problems:** This lint can only be allowed at the function level or above. | |
34 | /// | |
35 | /// **Example:** | |
36 | /// ```rust | |
37 | /// let i = 10; | |
38 | /// let f = 1.23; | |
39 | /// ``` | |
40 | /// | |
41 | /// Use instead: | |
42 | /// ```rust | |
43 | /// let i = 10i32; | |
44 | /// let f = 1.23f64; | |
45 | /// ``` | |
46 | pub DEFAULT_NUMERIC_FALLBACK, | |
47 | restriction, | |
48 | "usage of unconstrained numeric literals which may cause default numeric fallback." | |
49 | } | |
50 | ||
51 | declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]); | |
52 | ||
53 | impl LateLintPass<'_> for DefaultNumericFallback { | |
54 | fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { | |
55 | let mut visitor = NumericFallbackVisitor::new(cx); | |
56 | visitor.visit_body(body); | |
57 | } | |
58 | } | |
59 | ||
60 | struct NumericFallbackVisitor<'a, 'tcx> { | |
61 | /// Stack manages type bound of exprs. The top element holds current expr type. | |
62 | ty_bounds: Vec<TyBound<'tcx>>, | |
63 | ||
64 | cx: &'a LateContext<'tcx>, | |
65 | } | |
66 | ||
67 | impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { | |
68 | fn new(cx: &'a LateContext<'tcx>) -> Self { | |
69 | Self { | |
70 | ty_bounds: vec![TyBound::Nothing], | |
71 | cx, | |
72 | } | |
73 | } | |
74 | ||
75 | /// Check whether a passed literal has potential to cause fallback or not. | |
76 | fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) { | |
77 | if_chain! { | |
136023e0 | 78 | if !in_external_macro(self.cx.sess(), lit.span); |
f20569fa XL |
79 | if let Some(ty_bound) = self.ty_bounds.last(); |
80 | if matches!(lit.node, | |
81 | LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)); | |
136023e0 | 82 | if !ty_bound.is_numeric(); |
f20569fa | 83 | then { |
136023e0 XL |
84 | let (suffix, is_float) = match lit_ty.kind() { |
85 | ty::Int(IntTy::I32) => ("i32", false), | |
86 | ty::Float(FloatTy::F64) => ("f64", true), | |
f20569fa XL |
87 | // Default numeric fallback never results in other types. |
88 | _ => return, | |
89 | }; | |
90 | ||
136023e0 XL |
91 | let src = if let Some(src) = snippet_opt(self.cx, lit.span) { |
92 | src | |
93 | } else { | |
94 | match lit.node { | |
95 | LitKind::Int(src, _) => format!("{}", src), | |
96 | LitKind::Float(src, _) => format!("{}", src), | |
97 | _ => return, | |
98 | } | |
99 | }; | |
100 | let sugg = numeric_literal::format(&src, Some(suffix), is_float); | |
f20569fa XL |
101 | span_lint_and_sugg( |
102 | self.cx, | |
103 | DEFAULT_NUMERIC_FALLBACK, | |
104 | lit.span, | |
105 | "default numeric fallback might occur", | |
106 | "consider adding suffix", | |
107 | sugg, | |
108 | Applicability::MaybeIncorrect, | |
109 | ); | |
110 | } | |
111 | } | |
112 | } | |
113 | } | |
114 | ||
115 | impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { | |
116 | type Map = Map<'tcx>; | |
117 | ||
118 | #[allow(clippy::too_many_lines)] | |
119 | fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | |
120 | match &expr.kind { | |
121 | ExprKind::Call(func, args) => { | |
122 | if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) { | |
cdc7bbd5 | 123 | for (expr, bound) in iter::zip(*args, fn_sig.skip_binder().inputs()) { |
f20569fa XL |
124 | // Push found arg type, then visit arg. |
125 | self.ty_bounds.push(TyBound::Ty(bound)); | |
126 | self.visit_expr(expr); | |
127 | self.ty_bounds.pop(); | |
128 | } | |
129 | return; | |
130 | } | |
131 | }, | |
132 | ||
133 | ExprKind::MethodCall(_, _, args, _) => { | |
134 | if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) { | |
135 | let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder(); | |
cdc7bbd5 | 136 | for (expr, bound) in iter::zip(*args, fn_sig.inputs()) { |
f20569fa XL |
137 | self.ty_bounds.push(TyBound::Ty(bound)); |
138 | self.visit_expr(expr); | |
139 | self.ty_bounds.pop(); | |
140 | } | |
141 | return; | |
142 | } | |
143 | }, | |
144 | ||
145 | ExprKind::Struct(_, fields, base) => { | |
cdc7bbd5 | 146 | let ty = self.cx.typeck_results().expr_ty(expr); |
f20569fa | 147 | if_chain! { |
f20569fa XL |
148 | if let Some(adt_def) = ty.ty_adt_def(); |
149 | if adt_def.is_struct(); | |
150 | if let Some(variant) = adt_def.variants.iter().next(); | |
151 | then { | |
152 | let fields_def = &variant.fields; | |
153 | ||
154 | // Push field type then visit each field expr. | |
155 | for field in fields.iter() { | |
156 | let bound = | |
157 | fields_def | |
158 | .iter() | |
159 | .find_map(|f_def| { | |
160 | if f_def.ident == field.ident | |
161 | { Some(self.cx.tcx.type_of(f_def.did)) } | |
162 | else { None } | |
163 | }); | |
164 | self.ty_bounds.push(bound.into()); | |
165 | self.visit_expr(field.expr); | |
166 | self.ty_bounds.pop(); | |
167 | } | |
168 | ||
169 | // Visit base with no bound. | |
170 | if let Some(base) = base { | |
171 | self.ty_bounds.push(TyBound::Nothing); | |
172 | self.visit_expr(base); | |
173 | self.ty_bounds.pop(); | |
174 | } | |
175 | return; | |
176 | } | |
177 | } | |
178 | }, | |
179 | ||
180 | ExprKind::Lit(lit) => { | |
181 | let ty = self.cx.typeck_results().expr_ty(expr); | |
182 | self.check_lit(lit, ty); | |
183 | return; | |
184 | }, | |
185 | ||
186 | _ => {}, | |
187 | } | |
188 | ||
189 | walk_expr(self, expr); | |
190 | } | |
191 | ||
192 | fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { | |
193 | match stmt.kind { | |
194 | StmtKind::Local(local) => { | |
195 | if local.ty.is_some() { | |
17df50a5 | 196 | self.ty_bounds.push(TyBound::Any); |
f20569fa | 197 | } else { |
17df50a5 | 198 | self.ty_bounds.push(TyBound::Nothing); |
f20569fa XL |
199 | } |
200 | }, | |
201 | ||
202 | _ => self.ty_bounds.push(TyBound::Nothing), | |
203 | } | |
204 | ||
205 | walk_stmt(self, stmt); | |
206 | self.ty_bounds.pop(); | |
207 | } | |
208 | ||
209 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { | |
210 | NestedVisitorMap::None | |
211 | } | |
212 | } | |
213 | ||
214 | fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<'tcx>> { | |
215 | let node_ty = cx.typeck_results().node_type_opt(hir_id)?; | |
216 | // We can't use `TyS::fn_sig` because it automatically performs substs, this may result in FNs. | |
217 | match node_ty.kind() { | |
218 | ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)), | |
219 | ty::FnPtr(fn_sig) => Some(*fn_sig), | |
220 | _ => None, | |
221 | } | |
222 | } | |
223 | ||
224 | #[derive(Debug, Clone, Copy)] | |
225 | enum TyBound<'tcx> { | |
226 | Any, | |
227 | Ty(Ty<'tcx>), | |
228 | Nothing, | |
229 | } | |
230 | ||
231 | impl<'tcx> TyBound<'tcx> { | |
136023e0 | 232 | fn is_numeric(self) -> bool { |
f20569fa XL |
233 | match self { |
234 | TyBound::Any => true, | |
136023e0 | 235 | TyBound::Ty(t) => t.is_numeric(), |
f20569fa XL |
236 | TyBound::Nothing => false, |
237 | } | |
238 | } | |
239 | } | |
240 | ||
241 | impl<'tcx> From<Option<Ty<'tcx>>> for TyBound<'tcx> { | |
242 | fn from(v: Option<Ty<'tcx>>) -> Self { | |
243 | match v { | |
244 | Some(t) => TyBound::Ty(t), | |
245 | None => TyBound::Nothing, | |
246 | } | |
247 | } | |
248 | } |