]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/misc_early.rs
Auto merge of #71780 - jcotton42:string_remove_matches, r=joshtriplett
[rust.git] / src / tools / clippy / clippy_lints / src / misc_early.rs
1 use crate::utils::{snippet_opt, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
2 use rustc_ast::ast::{
3     BindingMode, Expr, ExprKind, GenericParamKind, Generics, Lit, LitFloatType, LitIntType, LitKind, Mutability,
4     NodeId, Pat, PatKind, UnOp,
5 };
6 use rustc_ast::visit::FnKind;
7 use rustc_data_structures::fx::FxHashMap;
8 use rustc_errors::Applicability;
9 use rustc_hir::PrimTy;
10 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
11 use rustc_middle::lint::in_external_macro;
12 use rustc_session::{declare_lint_pass, declare_tool_lint};
13 use rustc_span::source_map::Span;
14
15 declare_clippy_lint! {
16     /// **What it does:** Checks for structure field patterns bound to wildcards.
17     ///
18     /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on
19     /// the fields that are actually bound.
20     ///
21     /// **Known problems:** None.
22     ///
23     /// **Example:**
24     /// ```rust
25     /// # struct Foo {
26     /// #     a: i32,
27     /// #     b: i32,
28     /// #     c: i32,
29     /// # }
30     /// let f = Foo { a: 0, b: 0, c: 0 };
31     ///
32     /// // Bad
33     /// match f {
34     ///     Foo { a: _, b: 0, .. } => {},
35     ///     Foo { a: _, b: _, c: _ } => {},
36     /// }
37     ///
38     /// // Good
39     /// match f {
40     ///     Foo { b: 0, .. } => {},
41     ///     Foo { .. } => {},
42     /// }
43     /// ```
44     pub UNNEEDED_FIELD_PATTERN,
45     restriction,
46     "struct fields bound to a wildcard instead of using `..`"
47 }
48
49 declare_clippy_lint! {
50     /// **What it does:** Checks for function arguments having the similar names
51     /// differing by an underscore.
52     ///
53     /// **Why is this bad?** It affects code readability.
54     ///
55     /// **Known problems:** None.
56     ///
57     /// **Example:**
58     /// ```rust
59     /// // Bad
60     /// fn foo(a: i32, _a: i32) {}
61     ///
62     /// // Good
63     /// fn bar(a: i32, _b: i32) {}
64     /// ```
65     pub DUPLICATE_UNDERSCORE_ARGUMENT,
66     style,
67     "function arguments having names which only differ by an underscore"
68 }
69
70 declare_clippy_lint! {
71     /// **What it does:** Detects expressions of the form `--x`.
72     ///
73     /// **Why is this bad?** It can mislead C/C++ programmers to think `x` was
74     /// decremented.
75     ///
76     /// **Known problems:** None.
77     ///
78     /// **Example:**
79     /// ```rust
80     /// let mut x = 3;
81     /// --x;
82     /// ```
83     pub DOUBLE_NEG,
84     style,
85     "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
86 }
87
88 declare_clippy_lint! {
89     /// **What it does:** Warns on hexadecimal literals with mixed-case letter
90     /// digits.
91     ///
92     /// **Why is this bad?** It looks confusing.
93     ///
94     /// **Known problems:** None.
95     ///
96     /// **Example:**
97     /// ```rust
98     /// // Bad
99     /// let y = 0x1a9BAcD;
100     ///
101     /// // Good
102     /// let y = 0x1A9BACD;
103     /// ```
104     pub MIXED_CASE_HEX_LITERALS,
105     style,
106     "hex literals whose letter digits are not consistently upper- or lowercased"
107 }
108
109 declare_clippy_lint! {
110     /// **What it does:** Warns if literal suffixes are not separated by an
111     /// underscore.
112     ///
113     /// **Why is this bad?** It is much less readable.
114     ///
115     /// **Known problems:** None.
116     ///
117     /// **Example:**
118     /// ```rust
119     /// // Bad
120     /// let y = 123832i32;
121     ///
122     /// // Good
123     /// let y = 123832_i32;
124     /// ```
125     pub UNSEPARATED_LITERAL_SUFFIX,
126     pedantic,
127     "literals whose suffix is not separated by an underscore"
128 }
129
130 declare_clippy_lint! {
131     /// **What it does:** Warns if an integral constant literal starts with `0`.
132     ///
133     /// **Why is this bad?** In some languages (including the infamous C language
134     /// and most of its
135     /// family), this marks an octal constant. In Rust however, this is a decimal
136     /// constant. This could
137     /// be confusing for both the writer and a reader of the constant.
138     ///
139     /// **Known problems:** None.
140     ///
141     /// **Example:**
142     ///
143     /// In Rust:
144     /// ```rust
145     /// fn main() {
146     ///     let a = 0123;
147     ///     println!("{}", a);
148     /// }
149     /// ```
150     ///
151     /// prints `123`, while in C:
152     ///
153     /// ```c
154     /// #include <stdio.h>
155     ///
156     /// int main() {
157     ///     int a = 0123;
158     ///     printf("%d\n", a);
159     /// }
160     /// ```
161     ///
162     /// prints `83` (as `83 == 0o123` while `123 == 0o173`).
163     pub ZERO_PREFIXED_LITERAL,
164     complexity,
165     "integer literals starting with `0`"
166 }
167
168 declare_clippy_lint! {
169     /// **What it does:** Warns if a generic shadows a built-in type.
170     ///
171     /// **Why is this bad?** This gives surprising type errors.
172     ///
173     /// **Known problems:** None.
174     ///
175     /// **Example:**
176     ///
177     /// ```ignore
178     /// impl<u32> Foo<u32> {
179     ///     fn impl_func(&self) -> u32 {
180     ///         42
181     ///     }
182     /// }
183     /// ```
184     pub BUILTIN_TYPE_SHADOW,
185     style,
186     "shadowing a builtin type"
187 }
188
189 declare_clippy_lint! {
190     /// **What it does:** Checks for patterns in the form `name @ _`.
191     ///
192     /// **Why is this bad?** It's almost always more readable to just use direct
193     /// bindings.
194     ///
195     /// **Known problems:** None.
196     ///
197     /// **Example:**
198     /// ```rust
199     /// # let v = Some("abc");
200     ///
201     /// // Bad
202     /// match v {
203     ///     Some(x) => (),
204     ///     y @ _ => (),
205     /// }
206     ///
207     /// // Good
208     /// match v {
209     ///     Some(x) => (),
210     ///     y => (),
211     /// }
212     /// ```
213     pub REDUNDANT_PATTERN,
214     style,
215     "using `name @ _` in a pattern"
216 }
217
218 declare_clippy_lint! {
219     /// **What it does:** Checks for tuple patterns with a wildcard
220     /// pattern (`_`) is next to a rest pattern (`..`).
221     ///
222     /// _NOTE_: While `_, ..` means there is at least one element left, `..`
223     /// means there are 0 or more elements left. This can make a difference
224     /// when refactoring, but shouldn't result in errors in the refactored code,
225     /// since the wildcard pattern isn't used anyway.
226     /// **Why is this bad?** The wildcard pattern is unneeded as the rest pattern
227     /// can match that element as well.
228     ///
229     /// **Known problems:** None.
230     ///
231     /// **Example:**
232     /// ```rust
233     /// # struct TupleStruct(u32, u32, u32);
234     /// # let t = TupleStruct(1, 2, 3);
235     /// // Bad
236     /// match t {
237     ///     TupleStruct(0, .., _) => (),
238     ///     _ => (),
239     /// }
240     ///
241     /// // Good
242     /// match t {
243     ///     TupleStruct(0, ..) => (),
244     ///     _ => (),
245     /// }
246     /// ```
247     pub UNNEEDED_WILDCARD_PATTERN,
248     complexity,
249     "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
250 }
251
252 declare_lint_pass!(MiscEarlyLints => [
253     UNNEEDED_FIELD_PATTERN,
254     DUPLICATE_UNDERSCORE_ARGUMENT,
255     DOUBLE_NEG,
256     MIXED_CASE_HEX_LITERALS,
257     UNSEPARATED_LITERAL_SUFFIX,
258     ZERO_PREFIXED_LITERAL,
259     BUILTIN_TYPE_SHADOW,
260     REDUNDANT_PATTERN,
261     UNNEEDED_WILDCARD_PATTERN,
262 ]);
263
264 impl EarlyLintPass for MiscEarlyLints {
265     fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
266         for param in &gen.params {
267             if let GenericParamKind::Type { .. } = param.kind {
268                 if let Some(prim_ty) = PrimTy::from_name(param.ident.name) {
269                     span_lint(
270                         cx,
271                         BUILTIN_TYPE_SHADOW,
272                         param.ident.span,
273                         &format!("this generic shadows the built-in type `{}`", prim_ty.name()),
274                     );
275                 }
276             }
277         }
278     }
279
280     fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
281         if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind {
282             let mut wilds = 0;
283             let type_name = npat
284                 .segments
285                 .last()
286                 .expect("A path must have at least one segment")
287                 .ident
288                 .name;
289
290             for field in pfields {
291                 if let PatKind::Wild = field.pat.kind {
292                     wilds += 1;
293                 }
294             }
295             if !pfields.is_empty() && wilds == pfields.len() {
296                 span_lint_and_help(
297                     cx,
298                     UNNEEDED_FIELD_PATTERN,
299                     pat.span,
300                     "all the struct fields are matched to a wildcard pattern, consider using `..`",
301                     None,
302                     &format!("try with `{} {{ .. }}` instead", type_name),
303                 );
304                 return;
305             }
306             if wilds > 0 {
307                 for field in pfields {
308                     if let PatKind::Wild = field.pat.kind {
309                         wilds -= 1;
310                         if wilds > 0 {
311                             span_lint(
312                                 cx,
313                                 UNNEEDED_FIELD_PATTERN,
314                                 field.span,
315                                 "you matched a field with a wildcard pattern, consider using `..` instead",
316                             );
317                         } else {
318                             let mut normal = vec![];
319
320                             for field in pfields {
321                                 match field.pat.kind {
322                                     PatKind::Wild => {},
323                                     _ => {
324                                         if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) {
325                                             normal.push(n);
326                                         }
327                                     },
328                                 }
329                             }
330
331                             span_lint_and_help(
332                                 cx,
333                                 UNNEEDED_FIELD_PATTERN,
334                                 field.span,
335                                 "you matched a field with a wildcard pattern, consider using `..` \
336                                  instead",
337                                 None,
338                                 &format!("try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
339                             );
340                         }
341                     }
342                 }
343             }
344         }
345
346         if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind {
347             let left_binding = match left {
348                 BindingMode::ByRef(Mutability::Mut) => "ref mut ",
349                 BindingMode::ByRef(Mutability::Not) => "ref ",
350                 BindingMode::ByValue(..) => "",
351             };
352
353             if let PatKind::Wild = right.kind {
354                 span_lint_and_sugg(
355                     cx,
356                     REDUNDANT_PATTERN,
357                     pat.span,
358                     &format!(
359                         "the `{} @ _` pattern can be written as just `{}`",
360                         ident.name, ident.name,
361                     ),
362                     "try",
363                     format!("{}{}", left_binding, ident.name),
364                     Applicability::MachineApplicable,
365                 );
366             }
367         }
368
369         check_unneeded_wildcard_pattern(cx, pat);
370     }
371
372     fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
373         let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
374
375         for arg in &fn_kind.decl().inputs {
376             if let PatKind::Ident(_, ident, None) = arg.pat.kind {
377                 let arg_name = ident.to_string();
378
379                 if let Some(arg_name) = arg_name.strip_prefix('_') {
380                     if let Some(correspondence) = registered_names.get(arg_name) {
381                         span_lint(
382                             cx,
383                             DUPLICATE_UNDERSCORE_ARGUMENT,
384                             *correspondence,
385                             &format!(
386                                 "`{}` already exists, having another argument having almost the same \
387                                  name makes code comprehension and documentation more difficult",
388                                 arg_name
389                             ),
390                         );
391                     }
392                 } else {
393                     registered_names.insert(arg_name, arg.pat.span);
394                 }
395             }
396         }
397     }
398
399     fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
400         if in_external_macro(cx.sess(), expr.span) {
401             return;
402         }
403         match expr.kind {
404             ExprKind::Unary(UnOp::Neg, ref inner) => {
405                 if let ExprKind::Unary(UnOp::Neg, _) = inner.kind {
406                     span_lint(
407                         cx,
408                         DOUBLE_NEG,
409                         expr.span,
410                         "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op",
411                     );
412                 }
413             },
414             ExprKind::Lit(ref lit) => Self::check_lit(cx, lit),
415             _ => (),
416         }
417     }
418 }
419
420 impl MiscEarlyLints {
421     fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
422         // We test if first character in snippet is a number, because the snippet could be an expansion
423         // from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`.
424         // Note that this check also covers special case that `line!()` is eagerly expanded by compiler.
425         // See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
426         // FIXME: Find a better way to detect those cases.
427         let lit_snip = match snippet_opt(cx, lit.span) {
428             Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip,
429             _ => return,
430         };
431
432         if let LitKind::Int(value, lit_int_type) = lit.kind {
433             let suffix = match lit_int_type {
434                 LitIntType::Signed(ty) => ty.name_str(),
435                 LitIntType::Unsigned(ty) => ty.name_str(),
436                 LitIntType::Unsuffixed => "",
437             };
438
439             let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
440                 val
441             } else {
442                 return; // It's useless so shouldn't lint.
443             };
444             // Do not lint when literal is unsuffixed.
445             if !suffix.is_empty() && lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' {
446                 span_lint_and_sugg(
447                     cx,
448                     UNSEPARATED_LITERAL_SUFFIX,
449                     lit.span,
450                     "integer type suffix should be separated by an underscore",
451                     "add an underscore",
452                     format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix),
453                     Applicability::MachineApplicable,
454                 );
455             }
456
457             if lit_snip.starts_with("0x") {
458                 if maybe_last_sep_idx <= 2 {
459                     // It's meaningless or causes range error.
460                     return;
461                 }
462                 let mut seen = (false, false);
463                 for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() {
464                     match ch {
465                         b'a'..=b'f' => seen.0 = true,
466                         b'A'..=b'F' => seen.1 = true,
467                         _ => {},
468                     }
469                     if seen.0 && seen.1 {
470                         span_lint(
471                             cx,
472                             MIXED_CASE_HEX_LITERALS,
473                             lit.span,
474                             "inconsistent casing in hexadecimal literal",
475                         );
476                         break;
477                     }
478                 }
479             } else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
480                 /* nothing to do */
481             } else if value != 0 && lit_snip.starts_with('0') {
482                 span_lint_and_then(
483                     cx,
484                     ZERO_PREFIXED_LITERAL,
485                     lit.span,
486                     "this is a decimal constant",
487                     |diag| {
488                         diag.span_suggestion(
489                             lit.span,
490                             "if you mean to use a decimal constant, remove the `0` to avoid confusion",
491                             lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(),
492                             Applicability::MaybeIncorrect,
493                         );
494                         diag.span_suggestion(
495                             lit.span,
496                             "if you mean to use an octal constant, use `0o`",
497                             format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')),
498                             Applicability::MaybeIncorrect,
499                         );
500                     },
501                 );
502             }
503         } else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind {
504             let suffix = float_ty.name_str();
505             let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
506                 val
507             } else {
508                 return; // It's useless so shouldn't lint.
509             };
510             if lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' {
511                 span_lint_and_sugg(
512                     cx,
513                     UNSEPARATED_LITERAL_SUFFIX,
514                     lit.span,
515                     "float type suffix should be separated by an underscore",
516                     "add an underscore",
517                     format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix),
518                     Applicability::MachineApplicable,
519                 );
520             }
521         }
522     }
523 }
524
525 fn check_unneeded_wildcard_pattern(cx: &EarlyContext<'_>, pat: &Pat) {
526     if let PatKind::TupleStruct(_, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind {
527         fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) {
528             span_lint_and_sugg(
529                 cx,
530                 UNNEEDED_WILDCARD_PATTERN,
531                 span,
532                 if only_one {
533                     "this pattern is unneeded as the `..` pattern can match that element"
534                 } else {
535                     "these patterns are unneeded as the `..` pattern can match those elements"
536                 },
537                 if only_one { "remove it" } else { "remove them" },
538                 "".to_string(),
539                 Applicability::MachineApplicable,
540             );
541         }
542
543         if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) {
544             if let Some((left_index, left_pat)) = patterns[..rest_index]
545                 .iter()
546                 .rev()
547                 .take_while(|pat| matches!(pat.kind, PatKind::Wild))
548                 .enumerate()
549                 .last()
550             {
551                 span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0);
552             }
553
554             if let Some((right_index, right_pat)) = patterns[rest_index + 1..]
555                 .iter()
556                 .take_while(|pat| matches!(pat.kind, PatKind::Wild))
557                 .enumerate()
558                 .last()
559             {
560                 span_lint(
561                     cx,
562                     patterns[rest_index].span.shrink_to_hi().to(right_pat.span),
563                     right_index == 0,
564                 );
565             }
566         }
567     }
568 }