]>
Commit | Line | Data |
---|---|---|
dfeec247 | 1 | use crate::{LateContext, LateLintPass, LintContext}; |
dfeec247 XL |
2 | use rustc_errors::Applicability; |
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. |
5099ac24 | 64 | if let hir::ExprKind::MethodCall(call, args, _) = &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. |
60c5eb7d | 78 | let receiver_arg = &args[0]; |
94222f64 XL |
79 | let receiver_ty = cx.typeck_results().expr_ty(receiver_arg); |
80 | let adjustments = cx.typeck_results().expr_adjustments(receiver_arg); | |
60c5eb7d | 81 | |
a2a8927a XL |
82 | let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else { |
83 | return | |
94222f64 | 84 | }; |
dfeec247 | 85 | |
94222f64 XL |
86 | let types = |
87 | std::iter::once(receiver_ty).chain(adjustments.iter().map(|adj| adj.target)); | |
88 | ||
89 | let mut found_array = false; | |
90 | ||
91 | for ty in types { | |
92 | match ty.kind() { | |
93 | // If we run into a &[T; N] or &[T] first, there's nothing to warn about. | |
94 | // It'll resolve to the reference version. | |
95 | ty::Ref(_, inner_ty, _) if inner_ty.is_array() => return, | |
96 | ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => return, | |
97 | // Found an actual array type without matching a &[T; N] first. | |
98 | // This is the problematic case. | |
99 | ty::Array(..) => { | |
100 | found_array = true; | |
101 | break; | |
102 | } | |
103 | _ => {} | |
104 | } | |
60c5eb7d XL |
105 | } |
106 | ||
94222f64 XL |
107 | if !found_array { |
108 | return; | |
60c5eb7d XL |
109 | } |
110 | ||
111 | // Emit lint diagnostic. | |
94222f64 | 112 | let target = match *target.kind() { |
1b1a35ee XL |
113 | ty::Ref(_, inner_ty, _) if inner_ty.is_array() => "[T; N]", |
114 | ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => "[T]", | |
60c5eb7d XL |
115 | // We know the original first argument type is an array type, |
116 | // we know that the first adjustment was an autoref coercion | |
117 | // and we know that `IntoIterator` is the trait involved. The | |
118 | // array cannot be coerced to something other than a reference | |
119 | // to an array or to a slice. | |
120 | _ => bug!("array type coerced to something other than array or slice"), | |
121 | }; | |
5099ac24 | 122 | cx.struct_span_lint(ARRAY_INTO_ITER, call.ident.span, |lint| { |
136023e0 XL |
123 | let mut diag = lint.build(&format!( |
124 | "this method call resolves to `<&{} as IntoIterator>::into_iter` \ | |
125 | (due to backwards compatibility), \ | |
c295e0f8 | 126 | but will resolve to <{} as IntoIterator>::into_iter in Rust 2021", |
136023e0 XL |
127 | target, target, |
128 | )); | |
129 | diag.span_suggestion( | |
60c5eb7d XL |
130 | call.ident.span, |
131 | "use `.iter()` instead of `.into_iter()` to avoid ambiguity", | |
132 | "iter".into(), | |
133 | Applicability::MachineApplicable, | |
136023e0 XL |
134 | ); |
135 | if self.for_expr_span == expr.span { | |
136023e0 | 136 | diag.span_suggestion( |
3c0e092e | 137 | receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()), |
136023e0 XL |
138 | "or remove `.into_iter()` to iterate by value", |
139 | String::new(), | |
140 | Applicability::MaybeIncorrect, | |
141 | ); | |
94222f64 | 142 | } else if receiver_ty.is_array() { |
136023e0 XL |
143 | diag.multipart_suggestion( |
144 | "or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value", | |
145 | vec![ | |
146 | (expr.span.shrink_to_lo(), "IntoIterator::into_iter(".into()), | |
147 | (receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()), ")".into()), | |
148 | ], | |
149 | Applicability::MaybeIncorrect, | |
150 | ); | |
151 | } | |
152 | diag.emit(); | |
74b04a01 | 153 | }) |
60c5eb7d XL |
154 | } |
155 | } | |
156 | } |