]>
Commit | Line | Data |
---|---|---|
dfeec247 | 1 | use crate::{LateContext, LateLintPass, LintContext}; |
064997fb | 2 | use rustc_errors::{fluent, Applicability}; |
dfeec247 | 3 | use rustc_hir as hir; |
ba9703b0 XL |
4 | use rustc_middle::ty; |
5 | use rustc_middle::ty::adjustment::{Adjust, Adjustment}; | |
136023e0 XL |
6 | use rustc_session::lint::FutureIncompatibilityReason; |
7 | use rustc_span::edition::Edition; | |
dfeec247 | 8 | use rustc_span::symbol::sym; |
136023e0 | 9 | use rustc_span::Span; |
60c5eb7d XL |
10 | |
11 | declare_lint! { | |
1b1a35ee XL |
12 | /// The `array_into_iter` lint detects calling `into_iter` on arrays. |
13 | /// | |
14 | /// ### Example | |
15 | /// | |
c295e0f8 | 16 | /// ```rust,edition2018 |
1b1a35ee XL |
17 | /// # #![allow(unused)] |
18 | /// [1, 2, 3].into_iter().for_each(|n| { *n; }); | |
19 | /// ``` | |
20 | /// | |
21 | /// {{produces}} | |
22 | /// | |
23 | /// ### Explanation | |
24 | /// | |
136023e0 XL |
25 | /// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid |
26 | /// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still | |
27 | /// behave as `(&array).into_iter()`, returning an iterator over | |
28 | /// references, just like in Rust 1.52 and earlier. | |
29 | /// This only applies to the method call syntax `array.into_iter()`, not to | |
30 | /// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`. | |
60c5eb7d XL |
31 | pub ARRAY_INTO_ITER, |
32 | Warn, | |
136023e0 | 33 | "detects calling `into_iter` on arrays in Rust 2015 and 2018", |
60c5eb7d | 34 | @future_incompatible = FutureIncompatibleInfo { |
94222f64 | 35 | reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>", |
136023e0 | 36 | reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021), |
60c5eb7d XL |
37 | }; |
38 | } | |
39 | ||
136023e0 XL |
40 | #[derive(Copy, Clone, Default)] |
41 | pub struct ArrayIntoIter { | |
42 | for_expr_span: Span, | |
43 | } | |
44 | ||
45 | impl_lint_pass!(ArrayIntoIter => [ARRAY_INTO_ITER]); | |
60c5eb7d | 46 | |
f035d41b XL |
47 | impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { |
48 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { | |
136023e0 XL |
49 | // Save the span of expressions in `for _ in expr` syntax, |
50 | // so we can give a better suggestion for those later. | |
51 | if let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) = &expr.kind { | |
52 | if let hir::ExprKind::Call(path, [arg]) = &arg.kind { | |
53 | if let hir::ExprKind::Path(hir::QPath::LangItem( | |
54 | hir::LangItem::IntoIterIntoIter, | |
a2a8927a | 55 | .., |
136023e0 XL |
56 | )) = &path.kind |
57 | { | |
58 | self.for_expr_span = arg.span; | |
59 | } | |
60 | } | |
61 | } | |
62 | ||
60c5eb7d | 63 | // We only care about method call expressions. |
f2b60f7d | 64 | if let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &expr.kind { |
60c5eb7d XL |
65 | if call.ident.name != sym::into_iter { |
66 | return; | |
67 | } | |
68 | ||
69 | // Check if the method call actually calls the libcore | |
70 | // `IntoIterator::into_iter`. | |
3dfed10e | 71 | let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); |
60c5eb7d | 72 | match cx.tcx.trait_of_item(def_id) { |
dfeec247 | 73 | Some(trait_id) if cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id) => {} |
60c5eb7d XL |
74 | _ => return, |
75 | }; | |
76 | ||
94222f64 | 77 | // As this is a method call expression, we have at least one argument. |
94222f64 XL |
78 | let receiver_ty = cx.typeck_results().expr_ty(receiver_arg); |
79 | let adjustments = cx.typeck_results().expr_adjustments(receiver_arg); | |
60c5eb7d | 80 | |
a2a8927a XL |
81 | let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else { |
82 | return | |
94222f64 | 83 | }; |
dfeec247 | 84 | |
94222f64 XL |
85 | let types = |
86 | std::iter::once(receiver_ty).chain(adjustments.iter().map(|adj| adj.target)); | |
87 | ||
88 | let mut found_array = false; | |
89 | ||
90 | for ty in types { | |
91 | match ty.kind() { | |
92 | // If we run into a &[T; N] or &[T] first, there's nothing to warn about. | |
93 | // It'll resolve to the reference version. | |
94 | ty::Ref(_, inner_ty, _) if inner_ty.is_array() => return, | |
95 | ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => return, | |
96 | // Found an actual array type without matching a &[T; N] first. | |
97 | // This is the problematic case. | |
98 | ty::Array(..) => { | |
99 | found_array = true; | |
100 | break; | |
101 | } | |
102 | _ => {} | |
103 | } | |
60c5eb7d XL |
104 | } |
105 | ||
94222f64 XL |
106 | if !found_array { |
107 | return; | |
60c5eb7d XL |
108 | } |
109 | ||
110 | // Emit lint diagnostic. | |
94222f64 | 111 | let target = match *target.kind() { |
1b1a35ee XL |
112 | ty::Ref(_, inner_ty, _) if inner_ty.is_array() => "[T; N]", |
113 | ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => "[T]", | |
60c5eb7d XL |
114 | // We know the original first argument type is an array type, |
115 | // we know that the first adjustment was an autoref coercion | |
116 | // and we know that `IntoIterator` is the trait involved. The | |
117 | // array cannot be coerced to something other than a reference | |
118 | // to an array or to a slice. | |
119 | _ => bug!("array type coerced to something other than array or slice"), | |
120 | }; | |
2b03887a FG |
121 | cx.struct_span_lint( |
122 | ARRAY_INTO_ITER, | |
123 | call.ident.span, | |
124 | fluent::lint_array_into_iter, | |
125 | |diag| { | |
126 | diag.set_arg("target", target); | |
136023e0 | 127 | diag.span_suggestion( |
2b03887a FG |
128 | call.ident.span, |
129 | fluent::use_iter_suggestion, | |
130 | "iter", | |
131 | Applicability::MachineApplicable, | |
136023e0 | 132 | ); |
2b03887a FG |
133 | if self.for_expr_span == expr.span { |
134 | diag.span_suggestion( | |
135 | receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()), | |
136 | fluent::remove_into_iter_suggestion, | |
137 | "", | |
138 | Applicability::MaybeIncorrect, | |
139 | ); | |
140 | } else if receiver_ty.is_array() { | |
141 | diag.multipart_suggestion( | |
142 | fluent::use_explicit_into_iter_suggestion, | |
143 | vec![ | |
144 | (expr.span.shrink_to_lo(), "IntoIterator::into_iter(".into()), | |
145 | ( | |
146 | receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()), | |
147 | ")".into(), | |
148 | ), | |
149 | ], | |
150 | Applicability::MaybeIncorrect, | |
151 | ); | |
152 | } | |
153 | diag | |
154 | }, | |
155 | ) | |
60c5eb7d XL |
156 | } |
157 | } | |
158 | } |