]>
Commit | Line | Data |
---|---|---|
136023e0 XL |
1 | use hir::def_id::DefId; |
2 | use hir::HirId; | |
3 | use hir::ItemKind; | |
4 | use rustc_ast::Mutability; | |
5 | use rustc_errors::Applicability; | |
6 | use rustc_hir as hir; | |
7 | use rustc_middle::ty::subst::InternalSubsts; | |
94222f64 | 8 | use rustc_middle::ty::{Adt, Array, Ref, Ty}; |
136023e0 | 9 | use rustc_session::lint::builtin::RUST_2021_PRELUDE_COLLISIONS; |
94222f64 | 10 | use rustc_span::symbol::kw::{Empty, Underscore}; |
136023e0 XL |
11 | use rustc_span::symbol::{sym, Ident}; |
12 | use rustc_span::Span; | |
13 | use rustc_trait_selection::infer::InferCtxtExt; | |
14 | ||
15 | use crate::check::{ | |
16 | method::probe::{self, Pick}, | |
17 | FnCtxt, | |
18 | }; | |
19 | ||
20 | impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | |
21 | pub(super) fn lint_dot_call_from_2018( | |
22 | &self, | |
23 | self_ty: Ty<'tcx>, | |
24 | segment: &hir::PathSegment<'_>, | |
25 | span: Span, | |
26 | call_expr: &'tcx hir::Expr<'tcx>, | |
27 | self_expr: &'tcx hir::Expr<'tcx>, | |
28 | pick: &Pick<'tcx>, | |
29 | args: &'tcx [hir::Expr<'tcx>], | |
30 | ) { | |
31 | debug!( | |
32 | "lookup(method_name={}, self_ty={:?}, call_expr={:?}, self_expr={:?})", | |
33 | segment.ident, self_ty, call_expr, self_expr | |
34 | ); | |
35 | ||
36 | // Rust 2021 and later is already using the new prelude | |
37 | if span.rust_2021() { | |
38 | return; | |
39 | } | |
40 | ||
94222f64 XL |
41 | let prelude_or_array_lint = match segment.ident.name { |
42 | // `try_into` was added to the prelude in Rust 2021. | |
43 | sym::try_into => RUST_2021_PRELUDE_COLLISIONS, | |
44 | // `into_iter` wasn't added to the prelude, | |
45 | // but `[T; N].into_iter()` doesn't resolve to IntoIterator::into_iter | |
46 | // before Rust 2021, which results in the same problem. | |
47 | // It is only a problem for arrays. | |
48 | sym::into_iter if let Array(..) = self_ty.kind() => { | |
49 | // In this case, it wasn't really a prelude addition that was the problem. | |
50 | // Instead, the problem is that the array-into_iter hack will no longer apply in Rust 2021. | |
51 | rustc_lint::ARRAY_INTO_ITER | |
52 | } | |
53 | _ => return, | |
54 | }; | |
136023e0 XL |
55 | |
56 | // No need to lint if method came from std/core, as that will now be in the prelude | |
57 | if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) { | |
58 | return; | |
59 | } | |
60 | ||
61 | if matches!(pick.kind, probe::PickKind::InherentImplPick | probe::PickKind::ObjectPick) { | |
62 | // avoid repeatedly adding unneeded `&*`s | |
63 | if pick.autoderefs == 1 | |
64 | && matches!( | |
65 | pick.autoref_or_ptr_adjustment, | |
66 | Some(probe::AutorefOrPtrAdjustment::Autoref { .. }) | |
67 | ) | |
68 | && matches!(self_ty.kind(), Ref(..)) | |
69 | { | |
70 | return; | |
71 | } | |
72 | ||
73 | // if it's an inherent `self` method (not `&self` or `&mut self`), it will take | |
74 | // precedence over the `TryInto` impl, and thus won't break in 2021 edition | |
75 | if pick.autoderefs == 0 && pick.autoref_or_ptr_adjustment.is_none() { | |
76 | return; | |
77 | } | |
78 | ||
79 | // Inherent impls only require not relying on autoref and autoderef in order to | |
80 | // ensure that the trait implementation won't be used | |
81 | self.tcx.struct_span_lint_hir( | |
94222f64 | 82 | prelude_or_array_lint, |
136023e0 XL |
83 | self_expr.hir_id, |
84 | self_expr.span, | |
85 | |lint| { | |
86 | let sp = self_expr.span; | |
87 | ||
88 | let mut lint = lint.build(&format!( | |
89 | "trait method `{}` will become ambiguous in Rust 2021", | |
90 | segment.ident.name | |
91 | )); | |
92 | ||
93 | let derefs = "*".repeat(pick.autoderefs); | |
94 | ||
95 | let autoref = match pick.autoref_or_ptr_adjustment { | |
96 | Some(probe::AutorefOrPtrAdjustment::Autoref { | |
97 | mutbl: Mutability::Mut, | |
98 | .. | |
99 | }) => "&mut ", | |
100 | Some(probe::AutorefOrPtrAdjustment::Autoref { | |
101 | mutbl: Mutability::Not, | |
102 | .. | |
103 | }) => "&", | |
104 | Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "", | |
105 | }; | |
106 | if let Ok(self_expr) = self.sess().source_map().span_to_snippet(self_expr.span) | |
107 | { | |
108 | let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) = | |
109 | pick.autoref_or_ptr_adjustment | |
110 | { | |
111 | format!("{}{} as *const _", derefs, self_expr) | |
112 | } else { | |
113 | format!("{}{}{}", autoref, derefs, self_expr) | |
114 | }; | |
115 | ||
116 | lint.span_suggestion( | |
117 | sp, | |
118 | "disambiguate the method call", | |
119 | format!("({})", self_adjusted), | |
120 | Applicability::MachineApplicable, | |
121 | ); | |
122 | } else { | |
123 | let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) = | |
124 | pick.autoref_or_ptr_adjustment | |
125 | { | |
126 | format!("{}(...) as *const _", derefs) | |
127 | } else { | |
128 | format!("{}{}...", autoref, derefs) | |
129 | }; | |
130 | lint.span_help( | |
131 | sp, | |
132 | &format!("disambiguate the method call with `({})`", self_adjusted,), | |
133 | ); | |
134 | } | |
135 | ||
136 | lint.emit(); | |
137 | }, | |
138 | ); | |
139 | } else { | |
140 | // trait implementations require full disambiguation to not clash with the new prelude | |
141 | // additions (i.e. convert from dot-call to fully-qualified call) | |
142 | self.tcx.struct_span_lint_hir( | |
94222f64 | 143 | prelude_or_array_lint, |
136023e0 XL |
144 | call_expr.hir_id, |
145 | call_expr.span, | |
146 | |lint| { | |
147 | let sp = call_expr.span; | |
148 | let trait_name = self.trait_path_or_bare_name( | |
149 | span, | |
150 | call_expr.hir_id, | |
151 | pick.item.container.id(), | |
152 | ); | |
153 | ||
154 | let mut lint = lint.build(&format!( | |
155 | "trait method `{}` will become ambiguous in Rust 2021", | |
156 | segment.ident.name | |
157 | )); | |
158 | ||
94222f64 | 159 | let (self_adjusted, precise) = self.adjust_expr(pick, self_expr, sp); |
136023e0 XL |
160 | if precise { |
161 | let args = args | |
162 | .iter() | |
163 | .skip(1) | |
164 | .map(|arg| { | |
94222f64 | 165 | let span = arg.span.find_ancestor_inside(sp).unwrap_or_default(); |
136023e0 XL |
166 | format!( |
167 | ", {}", | |
94222f64 | 168 | self.sess().source_map().span_to_snippet(span).unwrap() |
136023e0 XL |
169 | ) |
170 | }) | |
171 | .collect::<String>(); | |
172 | ||
173 | lint.span_suggestion( | |
174 | sp, | |
175 | "disambiguate the associated function", | |
176 | format!( | |
94222f64 XL |
177 | "{}::{}{}({}{})", |
178 | trait_name, | |
179 | segment.ident.name, | |
180 | if let Some(args) = segment.args.as_ref().and_then(|args| self | |
181 | .sess() | |
182 | .source_map() | |
183 | .span_to_snippet(args.span_ext) | |
184 | .ok()) | |
185 | { | |
186 | // Keep turbofish. | |
187 | format!("::{}", args) | |
188 | } else { | |
189 | String::new() | |
190 | }, | |
191 | self_adjusted, | |
192 | args, | |
136023e0 XL |
193 | ), |
194 | Applicability::MachineApplicable, | |
195 | ); | |
196 | } else { | |
197 | lint.span_help( | |
198 | sp, | |
199 | &format!( | |
200 | "disambiguate the associated function with `{}::{}(...)`", | |
201 | trait_name, segment.ident, | |
202 | ), | |
203 | ); | |
204 | } | |
205 | ||
206 | lint.emit(); | |
207 | }, | |
208 | ); | |
209 | } | |
210 | } | |
211 | ||
212 | pub(super) fn lint_fully_qualified_call_from_2018( | |
213 | &self, | |
214 | span: Span, | |
215 | method_name: Ident, | |
216 | self_ty: Ty<'tcx>, | |
217 | self_ty_span: Span, | |
218 | expr_id: hir::HirId, | |
219 | pick: &Pick<'tcx>, | |
220 | ) { | |
221 | // Rust 2021 and later is already using the new prelude | |
222 | if span.rust_2021() { | |
223 | return; | |
224 | } | |
225 | ||
226 | // These are the fully qualified methods added to prelude in Rust 2021 | |
227 | if !matches!(method_name.name, sym::try_into | sym::try_from | sym::from_iter) { | |
228 | return; | |
229 | } | |
230 | ||
231 | // No need to lint if method came from std/core, as that will now be in the prelude | |
232 | if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) { | |
233 | return; | |
234 | } | |
235 | ||
236 | // For from_iter, check if the type actually implements FromIterator. | |
237 | // If we know it does not, we don't need to warn. | |
238 | if method_name.name == sym::from_iter { | |
239 | if let Some(trait_def_id) = self.tcx.get_diagnostic_item(sym::FromIterator) { | |
240 | if !self | |
241 | .infcx | |
242 | .type_implements_trait( | |
243 | trait_def_id, | |
244 | self_ty, | |
245 | InternalSubsts::empty(), | |
246 | self.param_env, | |
247 | ) | |
248 | .may_apply() | |
249 | { | |
250 | return; | |
251 | } | |
252 | } | |
253 | } | |
254 | ||
255 | // No need to lint if this is an inherent method called on a specific type, like `Vec::foo(...)`, | |
256 | // since such methods take precedence over trait methods. | |
257 | if matches!(pick.kind, probe::PickKind::InherentImplPick) { | |
258 | return; | |
259 | } | |
260 | ||
261 | self.tcx.struct_span_lint_hir(RUST_2021_PRELUDE_COLLISIONS, expr_id, span, |lint| { | |
262 | // "type" refers to either a type or, more likely, a trait from which | |
263 | // the associated function or method is from. | |
264 | let trait_path = self.trait_path_or_bare_name(span, expr_id, pick.item.container.id()); | |
265 | let trait_generics = self.tcx.generics_of(pick.item.container.id()); | |
266 | ||
94222f64 XL |
267 | let trait_name = |
268 | if trait_generics.params.len() <= trait_generics.has_self as usize { | |
269 | trait_path | |
270 | } else { | |
271 | let counts = trait_generics.own_counts(); | |
272 | format!( | |
273 | "{}<{}>", | |
274 | trait_path, | |
275 | std::iter::repeat("'_") | |
276 | .take(counts.lifetimes) | |
277 | .chain(std::iter::repeat("_").take( | |
278 | counts.types + counts.consts - trait_generics.has_self as usize | |
279 | )) | |
280 | .collect::<Vec<_>>() | |
281 | .join(", ") | |
282 | ) | |
283 | }; | |
136023e0 XL |
284 | |
285 | let mut lint = lint.build(&format!( | |
286 | "trait-associated function `{}` will become ambiguous in Rust 2021", | |
287 | method_name.name | |
288 | )); | |
289 | ||
94222f64 XL |
290 | let mut self_ty_name = self_ty_span |
291 | .find_ancestor_inside(span) | |
292 | .and_then(|span| self.sess().source_map().span_to_snippet(span).ok()) | |
293 | .unwrap_or_else(|| self_ty.to_string()); | |
294 | ||
295 | // Get the number of generics the self type has (if an Adt) unless we can determine that | |
296 | // the user has written the self type with generics already which we (naively) do by looking | |
297 | // for a "<" in `self_ty_name`. | |
298 | if !self_ty_name.contains('<') { | |
299 | if let Adt(def, _) = self_ty.kind() { | |
300 | let generics = self.tcx.generics_of(def.did); | |
301 | if !generics.params.is_empty() { | |
302 | let counts = generics.own_counts(); | |
303 | self_ty_name += &format!( | |
304 | "<{}>", | |
305 | std::iter::repeat("'_") | |
306 | .take(counts.lifetimes) | |
307 | .chain(std::iter::repeat("_").take(counts.types + counts.consts)) | |
308 | .collect::<Vec<_>>() | |
309 | .join(", ") | |
310 | ); | |
311 | } | |
312 | } | |
313 | } | |
136023e0 XL |
314 | lint.span_suggestion( |
315 | span, | |
316 | "disambiguate the associated function", | |
94222f64 | 317 | format!("<{} as {}>::{}", self_ty_name, trait_name, method_name.name,), |
136023e0 XL |
318 | Applicability::MachineApplicable, |
319 | ); | |
320 | ||
321 | lint.emit(); | |
322 | }); | |
323 | } | |
324 | ||
325 | fn trait_path_or_bare_name( | |
326 | &self, | |
327 | span: Span, | |
328 | expr_hir_id: HirId, | |
329 | trait_def_id: DefId, | |
330 | ) -> String { | |
331 | self.trait_path(span, expr_hir_id, trait_def_id).unwrap_or_else(|| { | |
332 | let key = self.tcx.def_key(trait_def_id); | |
333 | format!("{}", key.disambiguated_data.data) | |
334 | }) | |
335 | } | |
336 | ||
337 | fn trait_path(&self, span: Span, expr_hir_id: HirId, trait_def_id: DefId) -> Option<String> { | |
338 | let applicable_traits = self.tcx.in_scope_traits(expr_hir_id)?; | |
339 | let applicable_trait = applicable_traits.iter().find(|t| t.def_id == trait_def_id)?; | |
340 | if applicable_trait.import_ids.is_empty() { | |
341 | // The trait was declared within the module, we only need to use its name. | |
342 | return None; | |
343 | } | |
344 | ||
345 | let import_items: Vec<_> = applicable_trait | |
346 | .import_ids | |
347 | .iter() | |
a2a8927a | 348 | .map(|&import_id| self.tcx.hir().expect_item(import_id)) |
136023e0 XL |
349 | .collect(); |
350 | ||
351 | // Find an identifier with which this trait was imported (note that `_` doesn't count). | |
352 | let any_id = import_items | |
353 | .iter() | |
354 | .filter_map(|item| if item.ident.name != Underscore { Some(item.ident) } else { None }) | |
355 | .next(); | |
356 | if let Some(any_id) = any_id { | |
94222f64 XL |
357 | if any_id.name == Empty { |
358 | // Glob import, so just use its name. | |
359 | return None; | |
360 | } else { | |
361 | return Some(format!("{}", any_id)); | |
362 | } | |
136023e0 XL |
363 | } |
364 | ||
365 | // All that is left is `_`! We need to use the full path. It doesn't matter which one we pick, | |
366 | // so just take the first one. | |
367 | match import_items[0].kind { | |
368 | ItemKind::Use(path, _) => Some( | |
369 | path.segments | |
370 | .iter() | |
371 | .map(|segment| segment.ident.to_string()) | |
372 | .collect::<Vec<_>>() | |
373 | .join("::"), | |
374 | ), | |
375 | _ => { | |
376 | span_bug!(span, "unexpected item kind, expected a use: {:?}", import_items[0].kind); | |
377 | } | |
378 | } | |
379 | } | |
380 | ||
381 | /// Creates a string version of the `expr` that includes explicit adjustments. | |
382 | /// Returns the string and also a bool indicating whther this is a *precise* | |
383 | /// suggestion. | |
94222f64 XL |
384 | fn adjust_expr( |
385 | &self, | |
386 | pick: &Pick<'tcx>, | |
387 | expr: &hir::Expr<'tcx>, | |
388 | outer: Span, | |
389 | ) -> (String, bool) { | |
136023e0 XL |
390 | let derefs = "*".repeat(pick.autoderefs); |
391 | ||
392 | let autoref = match pick.autoref_or_ptr_adjustment { | |
393 | Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl: Mutability::Mut, .. }) => "&mut ", | |
394 | Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl: Mutability::Not, .. }) => "&", | |
395 | Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "", | |
396 | }; | |
397 | ||
94222f64 XL |
398 | let (expr_text, precise) = if let Some(expr_text) = expr |
399 | .span | |
400 | .find_ancestor_inside(outer) | |
401 | .and_then(|span| self.sess().source_map().span_to_snippet(span).ok()) | |
402 | { | |
403 | (expr_text, true) | |
404 | } else { | |
405 | ("(..)".to_string(), false) | |
406 | }; | |
136023e0 XL |
407 | |
408 | let adjusted_text = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) = | |
409 | pick.autoref_or_ptr_adjustment | |
410 | { | |
411 | format!("{}{} as *const _", derefs, expr_text) | |
412 | } else { | |
413 | format!("{}{}{}", autoref, derefs, expr_text) | |
414 | }; | |
415 | ||
416 | (adjusted_text, precise) | |
417 | } | |
418 | } |