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