]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/misc_early.rs
Add autofixable suggestion for unseparated integer literal suffices
[rust.git] / clippy_lints / src / misc_early.rs
1 use crate::utils::{constants, snippet, snippet_opt, span_help_and_lint, span_lint, span_lint_and_then};
2 use if_chain::if_chain;
3 use rustc::lint::{in_external_macro, EarlyContext, EarlyLintPass, LintArray, LintContext, LintPass};
4 use rustc::{declare_lint_pass, declare_tool_lint};
5 use rustc_data_structures::fx::FxHashMap;
6 use rustc_errors::Applicability;
7 use std::char;
8 use syntax::ast::*;
9 use syntax::source_map::Span;
10 use syntax::visit::{walk_expr, FnKind, Visitor};
11
12 declare_clippy_lint! {
13     /// **What it does:** Checks for structure field patterns bound to wildcards.
14     ///
15     /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on
16     /// the fields that are actually bound.
17     ///
18     /// **Known problems:** None.
19     ///
20     /// **Example:**
21     /// ```ignore
22     /// let { a: _, b: ref b, c: _ } = ..
23     /// ```
24     pub UNNEEDED_FIELD_PATTERN,
25     style,
26     "struct fields bound to a wildcard instead of using `..`"
27 }
28
29 declare_clippy_lint! {
30     /// **What it does:** Checks for function arguments having the similar names
31     /// differing by an underscore.
32     ///
33     /// **Why is this bad?** It affects code readability.
34     ///
35     /// **Known problems:** None.
36     ///
37     /// **Example:**
38     /// ```rust
39     /// fn foo(a: i32, _a: i32) {}
40     /// ```
41     pub DUPLICATE_UNDERSCORE_ARGUMENT,
42     style,
43     "function arguments having names which only differ by an underscore"
44 }
45
46 declare_clippy_lint! {
47     /// **What it does:** Detects closures called in the same expression where they
48     /// are defined.
49     ///
50     /// **Why is this bad?** It is unnecessarily adding to the expression's
51     /// complexity.
52     ///
53     /// **Known problems:** None.
54     ///
55     /// **Example:**
56     /// ```rust,ignore
57     /// (|| 42)()
58     /// ```
59     pub REDUNDANT_CLOSURE_CALL,
60     complexity,
61     "throwaway closures called in the expression they are defined"
62 }
63
64 declare_clippy_lint! {
65     /// **What it does:** Detects expressions of the form `--x`.
66     ///
67     /// **Why is this bad?** It can mislead C/C++ programmers to think `x` was
68     /// decremented.
69     ///
70     /// **Known problems:** None.
71     ///
72     /// **Example:**
73     /// ```rust
74     /// let mut x = 3;
75     /// --x;
76     /// ```
77     pub DOUBLE_NEG,
78     style,
79     "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
80 }
81
82 declare_clippy_lint! {
83     /// **What it does:** Warns on hexadecimal literals with mixed-case letter
84     /// digits.
85     ///
86     /// **Why is this bad?** It looks confusing.
87     ///
88     /// **Known problems:** None.
89     ///
90     /// **Example:**
91     /// ```rust
92     /// let y = 0x1a9BAcD;
93     /// ```
94     pub MIXED_CASE_HEX_LITERALS,
95     style,
96     "hex literals whose letter digits are not consistently upper- or lowercased"
97 }
98
99 declare_clippy_lint! {
100     /// **What it does:** Warns if literal suffixes are not separated by an
101     /// underscore.
102     ///
103     /// **Why is this bad?** It is much less readable.
104     ///
105     /// **Known problems:** None.
106     ///
107     /// **Example:**
108     /// ```rust
109     /// let y = 123832i32;
110     /// ```
111     pub UNSEPARATED_LITERAL_SUFFIX,
112     pedantic,
113     "literals whose suffix is not separated by an underscore"
114 }
115
116 declare_clippy_lint! {
117     /// **What it does:** Warns if an integral constant literal starts with `0`.
118     ///
119     /// **Why is this bad?** In some languages (including the infamous C language
120     /// and most of its
121     /// family), this marks an octal constant. In Rust however, this is a decimal
122     /// constant. This could
123     /// be confusing for both the writer and a reader of the constant.
124     ///
125     /// **Known problems:** None.
126     ///
127     /// **Example:**
128     ///
129     /// In Rust:
130     /// ```rust
131     /// fn main() {
132     ///     let a = 0123;
133     ///     println!("{}", a);
134     /// }
135     /// ```
136     ///
137     /// prints `123`, while in C:
138     ///
139     /// ```c
140     /// #include <stdio.h>
141     ///
142     /// int main() {
143     ///     int a = 0123;
144     ///     printf("%d\n", a);
145     /// }
146     /// ```
147     ///
148     /// prints `83` (as `83 == 0o123` while `123 == 0o173`).
149     pub ZERO_PREFIXED_LITERAL,
150     complexity,
151     "integer literals starting with `0`"
152 }
153
154 declare_clippy_lint! {
155     /// **What it does:** Warns if a generic shadows a built-in type.
156     ///
157     /// **Why is this bad?** This gives surprising type errors.
158     ///
159     /// **Known problems:** None.
160     ///
161     /// **Example:**
162     ///
163     /// ```ignore
164     /// impl<u32> Foo<u32> {
165     ///     fn impl_func(&self) -> u32 {
166     ///         42
167     ///     }
168     /// }
169     /// ```
170     pub BUILTIN_TYPE_SHADOW,
171     style,
172     "shadowing a builtin type"
173 }
174
175 declare_lint_pass!(MiscEarlyLints => [
176     UNNEEDED_FIELD_PATTERN,
177     DUPLICATE_UNDERSCORE_ARGUMENT,
178     REDUNDANT_CLOSURE_CALL,
179     DOUBLE_NEG,
180     MIXED_CASE_HEX_LITERALS,
181     UNSEPARATED_LITERAL_SUFFIX,
182     ZERO_PREFIXED_LITERAL,
183     BUILTIN_TYPE_SHADOW
184 ]);
185
186 // Used to find `return` statements or equivalents e.g., `?`
187 struct ReturnVisitor {
188     found_return: bool,
189 }
190
191 impl ReturnVisitor {
192     fn new() -> Self {
193         Self { found_return: false }
194     }
195 }
196
197 impl<'ast> Visitor<'ast> for ReturnVisitor {
198     fn visit_expr(&mut self, ex: &'ast Expr) {
199         if let ExprKind::Ret(_) = ex.node {
200             self.found_return = true;
201         } else if let ExprKind::Try(_) = ex.node {
202             self.found_return = true;
203         }
204
205         walk_expr(self, ex)
206     }
207 }
208
209 impl EarlyLintPass for MiscEarlyLints {
210     fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
211         for param in &gen.params {
212             if let GenericParamKind::Type { .. } = param.kind {
213                 let name = param.ident.as_str();
214                 if constants::BUILTIN_TYPES.contains(&&*name) {
215                     span_lint(
216                         cx,
217                         BUILTIN_TYPE_SHADOW,
218                         param.ident.span,
219                         &format!("This generic shadows the built-in type `{}`", name),
220                     );
221                 }
222             }
223         }
224     }
225
226     fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
227         if let PatKind::Struct(ref npat, ref pfields, _) = pat.node {
228             let mut wilds = 0;
229             let type_name = npat
230                 .segments
231                 .last()
232                 .expect("A path must have at least one segment")
233                 .ident
234                 .name;
235
236             for field in pfields {
237                 if let PatKind::Wild = field.pat.node {
238                     wilds += 1;
239                 }
240             }
241             if !pfields.is_empty() && wilds == pfields.len() {
242                 span_help_and_lint(
243                     cx,
244                     UNNEEDED_FIELD_PATTERN,
245                     pat.span,
246                     "All the struct fields are matched to a wildcard pattern, consider using `..`.",
247                     &format!("Try with `{} {{ .. }}` instead", type_name),
248                 );
249                 return;
250             }
251             if wilds > 0 {
252                 let mut normal = vec![];
253
254                 for field in pfields {
255                     match field.pat.node {
256                         PatKind::Wild => {},
257                         _ => {
258                             if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) {
259                                 normal.push(n);
260                             }
261                         },
262                     }
263                 }
264                 for field in pfields {
265                     if let PatKind::Wild = field.pat.node {
266                         wilds -= 1;
267                         if wilds > 0 {
268                             span_lint(
269                                 cx,
270                                 UNNEEDED_FIELD_PATTERN,
271                                 field.span,
272                                 "You matched a field with a wildcard pattern. Consider using `..` instead",
273                             );
274                         } else {
275                             span_help_and_lint(
276                                 cx,
277                                 UNNEEDED_FIELD_PATTERN,
278                                 field.span,
279                                 "You matched a field with a wildcard pattern. Consider using `..` \
280                                  instead",
281                                 &format!("Try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
282                             );
283                         }
284                     }
285                 }
286             }
287         }
288     }
289
290     fn check_fn(&mut self, cx: &EarlyContext<'_>, _: FnKind<'_>, decl: &FnDecl, _: Span, _: NodeId) {
291         let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
292
293         for arg in &decl.inputs {
294             if let PatKind::Ident(_, ident, None) = arg.pat.node {
295                 let arg_name = ident.to_string();
296
297                 if arg_name.starts_with('_') {
298                     if let Some(correspondence) = registered_names.get(&arg_name[1..]) {
299                         span_lint(
300                             cx,
301                             DUPLICATE_UNDERSCORE_ARGUMENT,
302                             *correspondence,
303                             &format!(
304                                 "`{}` already exists, having another argument having almost the same \
305                                  name makes code comprehension and documentation more difficult",
306                                 arg_name[1..].to_owned()
307                             ),
308                         );
309                     }
310                 } else {
311                     registered_names.insert(arg_name, arg.pat.span);
312                 }
313             }
314         }
315     }
316
317     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
318         if in_external_macro(cx.sess(), expr.span) {
319             return;
320         }
321         match expr.node {
322             ExprKind::Call(ref paren, _) => {
323                 if let ExprKind::Paren(ref closure) = paren.node {
324                     if let ExprKind::Closure(_, _, _, ref decl, ref block, _) = closure.node {
325                         let mut visitor = ReturnVisitor::new();
326                         visitor.visit_expr(block);
327                         if !visitor.found_return {
328                             span_lint_and_then(
329                                 cx,
330                                 REDUNDANT_CLOSURE_CALL,
331                                 expr.span,
332                                 "Try not to call a closure in the expression where it is declared.",
333                                 |db| {
334                                     if decl.inputs.is_empty() {
335                                         let hint = snippet(cx, block.span, "..").into_owned();
336                                         db.span_suggestion(
337                                             expr.span,
338                                             "Try doing something like: ",
339                                             hint,
340                                             Applicability::MachineApplicable, // snippet
341                                         );
342                                     }
343                                 },
344                             );
345                         }
346                     }
347                 }
348             },
349             ExprKind::Unary(UnOp::Neg, ref inner) => {
350                 if let ExprKind::Unary(UnOp::Neg, _) = inner.node {
351                     span_lint(
352                         cx,
353                         DOUBLE_NEG,
354                         expr.span,
355                         "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op",
356                     );
357                 }
358             },
359             ExprKind::Lit(ref lit) => self.check_lit(cx, lit),
360             _ => (),
361         }
362     }
363
364     fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
365         for w in block.stmts.windows(2) {
366             if_chain! {
367                 if let StmtKind::Local(ref local) = w[0].node;
368                 if let Option::Some(ref t) = local.init;
369                 if let ExprKind::Closure(..) = t.node;
370                 if let PatKind::Ident(_, ident, _) = local.pat.node;
371                 if let StmtKind::Semi(ref second) = w[1].node;
372                 if let ExprKind::Assign(_, ref call) = second.node;
373                 if let ExprKind::Call(ref closure, _) = call.node;
374                 if let ExprKind::Path(_, ref path) = closure.node;
375                 then {
376                     if ident == path.segments[0].ident {
377                         span_lint(
378                             cx,
379                             REDUNDANT_CLOSURE_CALL,
380                             second.span,
381                             "Closure called just once immediately after it was declared",
382                         );
383                     }
384                 }
385             }
386         }
387     }
388 }
389
390 impl MiscEarlyLints {
391     fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
392         if_chain! {
393             if let LitKind::Int(value, ..) = lit.node;
394             if let Some(src) = snippet_opt(cx, lit.span);
395             if let Some(firstch) = src.chars().next();
396             if char::to_digit(firstch, 10).is_some();
397             then {
398                 let mut prev = '\0';
399                 for (idx, ch) in src.chars().enumerate() {
400                     if ch == 'i' || ch == 'u' {
401                         if prev != '_' {
402                             span_lint_and_then(
403                                 cx,
404                                 UNSEPARATED_LITERAL_SUFFIX,
405                                 lit.span,
406                                 "integer type suffix should be separated by an underscore",
407                                 |db| {
408                                     db.span_suggestion(
409                                         lit.span,
410                                         "add an underscore",
411                                         format!("{}_{}", &src[0..idx], &src[idx..]),
412                                         Applicability::MachineApplicable,
413                                     );
414                                 },
415                             );
416                         }
417                         break;
418                     }
419                     prev = ch;
420                 }
421                 if src.starts_with("0x") {
422                     let mut seen = (false, false);
423                     for ch in src.chars() {
424                         match ch {
425                             'a' ..= 'f' => seen.0 = true,
426                             'A' ..= 'F' => seen.1 = true,
427                             'i' | 'u'   => break,   // start of suffix already
428                             _ => ()
429                         }
430                     }
431                     if seen.0 && seen.1 {
432                         span_lint(cx, MIXED_CASE_HEX_LITERALS, lit.span,
433                                     "inconsistent casing in hexadecimal literal");
434                     }
435                 } else if src.starts_with("0b") || src.starts_with("0o") {
436                     /* nothing to do */
437                 } else if value != 0 && src.starts_with('0') {
438                     span_lint_and_then(cx,
439                                         ZERO_PREFIXED_LITERAL,
440                                         lit.span,
441                                         "this is a decimal constant",
442                                         |db| {
443                         db.span_suggestion(
444                             lit.span,
445                             "if you mean to use a decimal constant, remove the `0` to remove confusion",
446                             src.trim_start_matches(|c| c == '_' || c == '0').to_string(),
447                             Applicability::MaybeIncorrect,
448                         );
449                         db.span_suggestion(
450                             lit.span,
451                             "if you mean to use an octal constant, use `0o`",
452                             format!("0o{}", src.trim_start_matches(|c| c == '_' || c == '0')),
453                             Applicability::MaybeIncorrect,
454                         );
455                     });
456                 }
457             }
458         }
459         if_chain! {
460             if let LitKind::Float(..) = lit.node;
461             if let Some(src) = snippet_opt(cx, lit.span);
462             if let Some(firstch) = src.chars().next();
463             if char::to_digit(firstch, 10).is_some();
464             then {
465                 let mut prev = '\0';
466                 for (idx, ch) in src.chars().enumerate() {
467                     if ch == 'f' {
468                         if prev != '_' {
469                             span_lint_and_then(
470                                 cx,
471                                 UNSEPARATED_LITERAL_SUFFIX,
472                                 lit.span,
473                                 "float type suffix should be separated by an underscore",
474                                 |db| {
475                                     db.span_suggestion(
476                                         lit.span,
477                                         "add an underscore",
478                                         format!("{}_{}", &src[0..idx], &src[idx..]),
479                                         Applicability::MachineApplicable,
480                                     );
481                                 },
482                             );
483                         }
484                         break;
485                     }
486                     prev = ch;
487                 }
488             }
489         }
490     }
491 }