rustc_hir_typeck/method/
prelude_edition_lints.rs

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