|
1 | | -use clippy_utils::diagnostics::span_lint_and_sugg; |
| 1 | +use clippy_utils::diagnostics::span_lint_and_then; |
2 | 2 | use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; |
3 | 3 | use rustc_errors::Applicability; |
4 | 4 | use rustc_hir::def::{DefKind, Res}; |
5 | 5 | use rustc_hir::{Expr, ExprKind}; |
6 | 6 | use rustc_lint::{LateContext, LateLintPass}; |
7 | 7 | use rustc_session::declare_lint_pass; |
| 8 | +use rustc_span::SyntaxContext; |
8 | 9 | use std::borrow::Cow; |
9 | | -use std::cmp::Reverse; |
10 | | -use std::collections::BinaryHeap; |
11 | 10 |
|
12 | 11 | declare_clippy_lint! { |
13 | 12 | /// ### What it does |
@@ -44,38 +43,56 @@ declare_lint_pass!(NumberedFields => [INIT_NUMBERED_FIELDS]); |
44 | 43 |
|
45 | 44 | impl<'tcx> LateLintPass<'tcx> for NumberedFields { |
46 | 45 | fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { |
47 | | - if let ExprKind::Struct(path, fields, None) = e.kind { |
48 | | - if !fields.is_empty() |
49 | | - && !e.span.from_expansion() |
50 | | - && fields |
51 | | - .iter() |
52 | | - .all(|f| f.ident.as_str().as_bytes().iter().all(u8::is_ascii_digit)) |
53 | | - && !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias, ..)) |
54 | | - { |
55 | | - let expr_spans = fields |
56 | | - .iter() |
57 | | - .map(|f| (Reverse(f.ident.as_str().parse::<usize>().unwrap()), f.expr.span)) |
58 | | - .collect::<BinaryHeap<_>>(); |
59 | | - let mut appl = Applicability::MachineApplicable; |
60 | | - let snippet = format!( |
61 | | - "{}({})", |
62 | | - snippet_with_applicability(cx, path.span(), "..", &mut appl), |
63 | | - expr_spans |
64 | | - .into_iter_sorted() |
65 | | - .map(|(_, span)| snippet_with_context(cx, span, path.span().ctxt(), "..", &mut appl).0) |
66 | | - .intersperse(Cow::Borrowed(", ")) |
67 | | - .collect::<String>() |
68 | | - ); |
69 | | - span_lint_and_sugg( |
70 | | - cx, |
71 | | - INIT_NUMBERED_FIELDS, |
72 | | - e.span, |
73 | | - "used a field initializer for a tuple struct", |
74 | | - "try", |
75 | | - snippet, |
76 | | - appl, |
77 | | - ); |
78 | | - } |
| 46 | + if let ExprKind::Struct(path, fields @ [field, ..], None) = e.kind |
| 47 | + // If the first character of any field is a digit it has to be a tuple. |
| 48 | + && field.ident.as_str().as_bytes().first().is_some_and(u8::is_ascii_digit) |
| 49 | + // Type aliases can't be used as functions. |
| 50 | + && !matches!( |
| 51 | + cx.qpath_res(path, e.hir_id), |
| 52 | + Res::Def(DefKind::TyAlias | DefKind::AssocTy, _) |
| 53 | + ) |
| 54 | + // This is the only syntax macros can use that works for all struct types. |
| 55 | + && !e.span.from_expansion() |
| 56 | + && let mut has_side_effects = false |
| 57 | + && let Ok(mut expr_spans) = fields |
| 58 | + .iter() |
| 59 | + .map(|f| { |
| 60 | + has_side_effects |= f.expr.can_have_side_effects(); |
| 61 | + f.ident.as_str().parse::<usize>().map(|x| (x, f.expr.span)) |
| 62 | + }) |
| 63 | + .collect::<Result<Vec<_>, _>>() |
| 64 | + // We can only reorder the expressions if there are no side effects. |
| 65 | + && (!has_side_effects || expr_spans.is_sorted_by_key(|&(idx, _)| idx)) |
| 66 | + { |
| 67 | + span_lint_and_then( |
| 68 | + cx, |
| 69 | + INIT_NUMBERED_FIELDS, |
| 70 | + e.span, |
| 71 | + "used a field initializer for a tuple struct", |
| 72 | + |diag| { |
| 73 | + if !has_side_effects { |
| 74 | + // We already checked the order if there are side effects. |
| 75 | + expr_spans.sort_by_key(|&(idx, _)| idx); |
| 76 | + } |
| 77 | + let mut app = Applicability::MachineApplicable; |
| 78 | + diag.span_suggestion( |
| 79 | + e.span, |
| 80 | + "use tuple initialization", |
| 81 | + format!( |
| 82 | + "{}({})", |
| 83 | + snippet_with_applicability(cx, path.span(), "..", &mut app), |
| 84 | + expr_spans |
| 85 | + .into_iter() |
| 86 | + .map( |
| 87 | + |(_, span)| snippet_with_context(cx, span, SyntaxContext::root(), "..", &mut app).0 |
| 88 | + ) |
| 89 | + .intersperse(Cow::Borrowed(", ")) |
| 90 | + .collect::<String>() |
| 91 | + ), |
| 92 | + app, |
| 93 | + ); |
| 94 | + }, |
| 95 | + ); |
79 | 96 | } |
80 | 97 | } |
81 | 98 | } |
0 commit comments