rustc_hir_typeck/method/
prelude_edition_lints.rs

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