rustc_hir_analysis/hir_ty_lowering/
lint.rs

1use rustc_ast::TraitObjectSyntax;
2use rustc_errors::codes::*;
3use rustc_errors::{Diag, EmissionGuarantee, ErrorGuaranteed, StashKey, Suggestions};
4use rustc_hir as hir;
5use rustc_hir::def::{DefKind, Namespace, Res};
6use rustc_hir::def_id::DefId;
7use rustc_lint_defs::Applicability;
8use rustc_lint_defs::builtin::BARE_TRAIT_OBJECTS;
9use rustc_span::Span;
10use rustc_span::edit_distance::find_best_match_for_name;
11use rustc_trait_selection::error_reporting::traits::suggestions::NextTypeParamName;
12
13use super::HirTyLowerer;
14
15impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
16    /// Prohibit or lint against *bare* trait object types depending on the edition.
17    ///
18    /// *Bare* trait object types are ones that aren't preceded by the keyword `dyn`.
19    /// In edition 2021 and onward we emit a hard error for them.
20    pub(super) fn prohibit_or_lint_bare_trait_object_ty(
21        &self,
22        self_ty: &hir::Ty<'_>,
23    ) -> Option<ErrorGuaranteed> {
24        let tcx = self.tcx();
25
26        let poly_trait_ref = if let hir::TyKind::TraitObject([poly_trait_ref, ..], tagged_ptr) =
27            self_ty.kind
28            && let TraitObjectSyntax::None = tagged_ptr.tag()
29        {
30            poly_trait_ref
31        } else {
32            return None;
33        };
34
35        let in_path = match tcx.parent_hir_node(self_ty.hir_id) {
36            hir::Node::Ty(hir::Ty {
37                kind: hir::TyKind::Path(hir::QPath::TypeRelative(qself, _)),
38                ..
39            })
40            | hir::Node::Expr(hir::Expr {
41                kind: hir::ExprKind::Path(hir::QPath::TypeRelative(qself, _)),
42                ..
43            })
44            | hir::Node::PatExpr(hir::PatExpr {
45                kind: hir::PatExprKind::Path(hir::QPath::TypeRelative(qself, _)),
46                ..
47            }) if qself.hir_id == self_ty.hir_id => true,
48            _ => false,
49        };
50        let needs_bracket = in_path
51            && !tcx
52                .sess
53                .source_map()
54                .span_to_prev_source(self_ty.span)
55                .ok()
56                .is_some_and(|s| s.trim_end().ends_with('<'));
57
58        let is_global = poly_trait_ref.trait_ref.path.is_global();
59
60        let mut sugg = vec![(
61            self_ty.span.shrink_to_lo(),
62            format!(
63                "{}dyn {}",
64                if needs_bracket { "<" } else { "" },
65                if is_global { "(" } else { "" },
66            ),
67        )];
68
69        if is_global || needs_bracket {
70            sugg.push((
71                self_ty.span.shrink_to_hi(),
72                format!(
73                    "{}{}",
74                    if is_global { ")" } else { "" },
75                    if needs_bracket { ">" } else { "" },
76                ),
77            ));
78        }
79
80        if self_ty.span.edition().at_least_rust_2021() {
81            let mut diag = rustc_errors::struct_span_code_err!(
82                self.dcx(),
83                self_ty.span,
84                E0782,
85                "{}",
86                "expected a type, found a trait"
87            );
88            if self_ty.span.can_be_used_for_suggestions()
89                && !self.maybe_suggest_impl_trait(self_ty, &mut diag)
90                && !self.maybe_suggest_dyn_trait(self_ty, sugg, &mut diag)
91            {
92                self.maybe_suggest_add_generic_impl_trait(self_ty, &mut diag);
93            }
94            // Check if the impl trait that we are considering is an impl of a local trait.
95            self.maybe_suggest_blanket_trait_impl(self_ty, &mut diag);
96            self.maybe_suggest_assoc_ty_bound(self_ty, &mut diag);
97            self.maybe_suggest_typoed_method(
98                self_ty,
99                poly_trait_ref.trait_ref.trait_def_id(),
100                &mut diag,
101            );
102            // In case there is an associated type with the same name
103            // Add the suggestion to this error
104            if let Some(mut sugg) =
105                tcx.dcx().steal_non_err(self_ty.span, StashKey::AssociatedTypeSuggestion)
106                && let Suggestions::Enabled(ref mut s1) = diag.suggestions
107                && let Suggestions::Enabled(ref mut s2) = sugg.suggestions
108            {
109                s1.append(s2);
110                sugg.cancel();
111            }
112            Some(diag.emit())
113        } else {
114            tcx.node_span_lint(BARE_TRAIT_OBJECTS, self_ty.hir_id, self_ty.span, |lint| {
115                lint.primary_message("trait objects without an explicit `dyn` are deprecated");
116                if self_ty.span.can_be_used_for_suggestions() {
117                    lint.multipart_suggestion_verbose(
118                        "if this is a dyn-compatible trait, use `dyn`",
119                        sugg,
120                        Applicability::MachineApplicable,
121                    );
122                }
123                self.maybe_suggest_blanket_trait_impl(self_ty, lint);
124            });
125            None
126        }
127    }
128
129    /// For a struct or enum with an invalid bare trait object field, suggest turning
130    /// it into a generic type bound.
131    fn maybe_suggest_add_generic_impl_trait(
132        &self,
133        self_ty: &hir::Ty<'_>,
134        diag: &mut Diag<'_>,
135    ) -> bool {
136        let tcx = self.tcx();
137
138        let parent_hir_id = tcx.parent_hir_id(self_ty.hir_id);
139        let parent_item = tcx.hir_get_parent_item(self_ty.hir_id).def_id;
140
141        let generics = match tcx.hir_node_by_def_id(parent_item) {
142            hir::Node::Item(hir::Item {
143                kind: hir::ItemKind::Struct(_, variant, generics),
144                ..
145            }) => {
146                if !variant.fields().iter().any(|field| field.hir_id == parent_hir_id) {
147                    return false;
148                }
149                generics
150            }
151            hir::Node::Item(hir::Item { kind: hir::ItemKind::Enum(_, def, generics), .. }) => {
152                if !def
153                    .variants
154                    .iter()
155                    .flat_map(|variant| variant.data.fields().iter())
156                    .any(|field| field.hir_id == parent_hir_id)
157                {
158                    return false;
159                }
160                generics
161            }
162            _ => return false,
163        };
164
165        let Ok(rendered_ty) = tcx.sess.source_map().span_to_snippet(self_ty.span) else {
166            return false;
167        };
168
169        let param = "TUV"
170            .chars()
171            .map(|c| c.to_string())
172            .chain((0..).map(|i| format!("P{i}")))
173            .find(|s| !generics.params.iter().any(|param| param.name.ident().as_str() == s))
174            .expect("we definitely can find at least one param name to generate");
175        let mut sugg = vec![(self_ty.span, param.to_string())];
176        if let Some(insertion_span) = generics.span_for_param_suggestion() {
177            sugg.push((insertion_span, format!(", {param}: {}", rendered_ty)));
178        } else {
179            sugg.push((generics.where_clause_span, format!("<{param}: {}>", rendered_ty)));
180        }
181        diag.multipart_suggestion_verbose(
182            "you might be missing a type parameter",
183            sugg,
184            Applicability::MachineApplicable,
185        );
186        true
187    }
188    /// Make sure that we are in the condition to suggest the blanket implementation.
189    fn maybe_suggest_blanket_trait_impl<G: EmissionGuarantee>(
190        &self,
191        self_ty: &hir::Ty<'_>,
192        diag: &mut Diag<'_, G>,
193    ) {
194        let tcx = self.tcx();
195        let parent_id = tcx.hir_get_parent_item(self_ty.hir_id).def_id;
196        if let hir::Node::Item(hir::Item {
197            kind: hir::ItemKind::Impl(hir::Impl { self_ty: impl_self_ty, of_trait, generics, .. }),
198            ..
199        }) = tcx.hir_node_by_def_id(parent_id)
200            && self_ty.hir_id == impl_self_ty.hir_id
201        {
202            let Some(of_trait_ref) = of_trait else {
203                diag.span_suggestion_verbose(
204                    impl_self_ty.span.shrink_to_hi(),
205                    "you might have intended to implement this trait for a given type",
206                    format!(" for /* Type */"),
207                    Applicability::HasPlaceholders,
208                );
209                return;
210            };
211            if !of_trait_ref.trait_def_id().is_some_and(|def_id| def_id.is_local()) {
212                return;
213            }
214            let of_trait_span = of_trait_ref.path.span;
215            // make sure that we are not calling unwrap to abort during the compilation
216            let Ok(of_trait_name) = tcx.sess.source_map().span_to_snippet(of_trait_span) else {
217                return;
218            };
219
220            let Ok(impl_trait_name) = self.tcx().sess.source_map().span_to_snippet(self_ty.span)
221            else {
222                return;
223            };
224            let sugg = self.add_generic_param_suggestion(generics, self_ty.span, &impl_trait_name);
225            diag.multipart_suggestion(
226                format!(
227                    "alternatively use a blanket implementation to implement `{of_trait_name}` for \
228                     all types that also implement `{impl_trait_name}`"
229                ),
230                sugg,
231                Applicability::MaybeIncorrect,
232            );
233        }
234    }
235
236    /// Try our best to approximate when adding `dyn` would be helpful for a bare
237    /// trait object.
238    ///
239    /// Right now, this is if the type is either directly nested in another ty,
240    /// or if it's in the tail field within a struct. This approximates what the
241    /// user would've gotten on edition 2015, except for the case where we have
242    /// an *obvious* knock-on `Sized` error.
243    fn maybe_suggest_dyn_trait(
244        &self,
245        self_ty: &hir::Ty<'_>,
246        sugg: Vec<(Span, String)>,
247        diag: &mut Diag<'_>,
248    ) -> bool {
249        let tcx = self.tcx();
250
251        // Look at the direct HIR parent, since we care about the relationship between
252        // the type and the thing that directly encloses it.
253        match tcx.parent_hir_node(self_ty.hir_id) {
254            // These are all generally ok. Namely, when a trait object is nested
255            // into another expression or ty, it's either very certain that they
256            // missed the ty (e.g. `&Trait`) or it's not really possible to tell
257            // what their intention is, so let's not give confusing suggestions and
258            // just mention `dyn`. The user can make up their mind what to do here.
259            hir::Node::Ty(_)
260            | hir::Node::Expr(_)
261            | hir::Node::PatExpr(_)
262            | hir::Node::PathSegment(_)
263            | hir::Node::AssocItemConstraint(_)
264            | hir::Node::TraitRef(_)
265            | hir::Node::Item(_)
266            | hir::Node::WherePredicate(_) => {}
267
268            hir::Node::Field(field) => {
269                // Enums can't have unsized fields, fields can only have an unsized tail field.
270                if let hir::Node::Item(hir::Item {
271                    kind: hir::ItemKind::Struct(_, variant, _), ..
272                }) = tcx.parent_hir_node(field.hir_id)
273                    && variant
274                        .fields()
275                        .last()
276                        .is_some_and(|tail_field| tail_field.hir_id == field.hir_id)
277                {
278                    // Ok
279                } else {
280                    return false;
281                }
282            }
283            _ => return false,
284        }
285
286        // FIXME: Only emit this suggestion if the trait is dyn-compatible.
287        diag.multipart_suggestion_verbose(
288            "you can add the `dyn` keyword if you want a trait object",
289            sugg,
290            Applicability::MachineApplicable,
291        );
292        true
293    }
294
295    fn add_generic_param_suggestion(
296        &self,
297        generics: &hir::Generics<'_>,
298        self_ty_span: Span,
299        impl_trait_name: &str,
300    ) -> Vec<(Span, String)> {
301        // check if the trait has generics, to make a correct suggestion
302        let param_name = generics.params.next_type_param_name(None);
303
304        let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() {
305            (span, format!(", {param_name}: {impl_trait_name}"))
306        } else {
307            (generics.span, format!("<{param_name}: {impl_trait_name}>"))
308        };
309        vec![(self_ty_span, param_name), add_generic_sugg]
310    }
311
312    /// Make sure that we are in the condition to suggest `impl Trait`.
313    fn maybe_suggest_impl_trait(&self, self_ty: &hir::Ty<'_>, diag: &mut Diag<'_>) -> bool {
314        let tcx = self.tcx();
315        let parent_id = tcx.hir_get_parent_item(self_ty.hir_id).def_id;
316        // FIXME: If `type_alias_impl_trait` is enabled, also look for `Trait0<Ty = Trait1>`
317        //        and suggest `Trait0<Ty = impl Trait1>`.
318        // Functions are found in three different contexts.
319        // 1. Independent functions
320        // 2. Functions inside trait blocks
321        // 3. Functions inside impl blocks
322        let (sig, generics) = match tcx.hir_node_by_def_id(parent_id) {
323            hir::Node::Item(hir::Item {
324                kind: hir::ItemKind::Fn { sig, generics, .. }, ..
325            }) => (sig, generics),
326            hir::Node::TraitItem(hir::TraitItem {
327                kind: hir::TraitItemKind::Fn(sig, _),
328                generics,
329                ..
330            }) => (sig, generics),
331            hir::Node::ImplItem(hir::ImplItem {
332                kind: hir::ImplItemKind::Fn(sig, _),
333                generics,
334                ..
335            }) => (sig, generics),
336            _ => return false,
337        };
338        let Ok(trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else {
339            return false;
340        };
341        let impl_sugg = vec![(self_ty.span.shrink_to_lo(), "impl ".to_string())];
342        // Check if trait object is safe for suggesting dynamic dispatch.
343        let is_dyn_compatible = match self_ty.kind {
344            hir::TyKind::TraitObject(objects, ..) => {
345                objects.iter().all(|o| match o.trait_ref.path.res {
346                    Res::Def(DefKind::Trait, id) => tcx.is_dyn_compatible(id),
347                    _ => false,
348                })
349            }
350            _ => false,
351        };
352
353        let borrowed = matches!(
354            tcx.parent_hir_node(self_ty.hir_id),
355            hir::Node::Ty(hir::Ty { kind: hir::TyKind::Ref(..), .. })
356        );
357
358        // Suggestions for function return type.
359        if let hir::FnRetTy::Return(ty) = sig.decl.output
360            && ty.peel_refs().hir_id == self_ty.hir_id
361        {
362            let pre = if !is_dyn_compatible {
363                format!("`{trait_name}` is dyn-incompatible, ")
364            } else {
365                String::new()
366            };
367            let msg = format!(
368                "{pre}use `impl {trait_name}` to return an opaque type, as long as you return a \
369                 single underlying type",
370            );
371
372            diag.multipart_suggestion_verbose(msg, impl_sugg, Applicability::MachineApplicable);
373
374            // Suggest `Box<dyn Trait>` for return type
375            if is_dyn_compatible {
376                // If the return type is `&Trait`, we don't want
377                // the ampersand to be displayed in the `Box<dyn Trait>`
378                // suggestion.
379                let suggestion = if borrowed {
380                    vec![(ty.span, format!("Box<dyn {trait_name}>"))]
381                } else {
382                    vec![
383                        (ty.span.shrink_to_lo(), "Box<dyn ".to_string()),
384                        (ty.span.shrink_to_hi(), ">".to_string()),
385                    ]
386                };
387
388                diag.multipart_suggestion_verbose(
389                    "alternatively, you can return an owned trait object",
390                    suggestion,
391                    Applicability::MachineApplicable,
392                );
393            }
394            return true;
395        }
396
397        // Suggestions for function parameters.
398        for ty in sig.decl.inputs {
399            if ty.peel_refs().hir_id != self_ty.hir_id {
400                continue;
401            }
402            let sugg = self.add_generic_param_suggestion(generics, self_ty.span, &trait_name);
403            diag.multipart_suggestion_verbose(
404                format!("use a new generic type parameter, constrained by `{trait_name}`"),
405                sugg,
406                Applicability::MachineApplicable,
407            );
408            diag.multipart_suggestion_verbose(
409                "you can also use an opaque type, but users won't be able to specify the type \
410                 parameter when calling the `fn`, having to rely exclusively on type inference",
411                impl_sugg,
412                Applicability::MachineApplicable,
413            );
414            if !is_dyn_compatible {
415                diag.note(format!(
416                    "`{trait_name}` is dyn-incompatible, otherwise a trait object could be used"
417                ));
418            } else {
419                // No ampersand in suggestion if it's borrowed already
420                let (dyn_str, paren_dyn_str) =
421                    if borrowed { ("dyn ", "(dyn ") } else { ("&dyn ", "&(dyn ") };
422
423                let sugg = if let hir::TyKind::TraitObject([_, _, ..], _) = self_ty.kind {
424                    // There is more than one trait bound, we need surrounding parentheses.
425                    vec![
426                        (self_ty.span.shrink_to_lo(), paren_dyn_str.to_string()),
427                        (self_ty.span.shrink_to_hi(), ")".to_string()),
428                    ]
429                } else {
430                    vec![(self_ty.span.shrink_to_lo(), dyn_str.to_string())]
431                };
432                diag.multipart_suggestion_verbose(
433                    format!(
434                        "alternatively, use a trait object to accept any type that implements \
435                         `{trait_name}`, accessing its methods at runtime using dynamic dispatch",
436                    ),
437                    sugg,
438                    Applicability::MachineApplicable,
439                );
440            }
441            return true;
442        }
443        false
444    }
445
446    fn maybe_suggest_assoc_ty_bound(&self, self_ty: &hir::Ty<'_>, diag: &mut Diag<'_>) {
447        let mut parents = self.tcx().hir_parent_iter(self_ty.hir_id);
448
449        if let Some((_, hir::Node::AssocItemConstraint(constraint))) = parents.next()
450            && let Some(obj_ty) = constraint.ty()
451        {
452            if let Some((_, hir::Node::TraitRef(..))) = parents.next()
453                && let Some((_, hir::Node::Ty(ty))) = parents.next()
454                && let hir::TyKind::TraitObject(..) = ty.kind
455            {
456                // Assoc ty bounds aren't permitted inside trait object types.
457                return;
458            }
459
460            let lo = if constraint.gen_args.span_ext.is_dummy() {
461                constraint.ident.span
462            } else {
463                constraint.gen_args.span_ext
464            };
465            let hi = obj_ty.span;
466
467            if !lo.eq_ctxt(hi) {
468                return;
469            }
470
471            diag.span_suggestion_verbose(
472                lo.between(hi),
473                "you might have meant to write a bound here",
474                ": ",
475                Applicability::MaybeIncorrect,
476            );
477        }
478    }
479
480    fn maybe_suggest_typoed_method(
481        &self,
482        self_ty: &hir::Ty<'_>,
483        trait_def_id: Option<DefId>,
484        diag: &mut Diag<'_>,
485    ) {
486        let tcx = self.tcx();
487        let Some(trait_def_id) = trait_def_id else {
488            return;
489        };
490        let hir::Node::Expr(hir::Expr {
491            kind: hir::ExprKind::Path(hir::QPath::TypeRelative(path_ty, segment)),
492            ..
493        }) = tcx.parent_hir_node(self_ty.hir_id)
494        else {
495            return;
496        };
497        if path_ty.hir_id != self_ty.hir_id {
498            return;
499        }
500        let names: Vec<_> = tcx
501            .associated_items(trait_def_id)
502            .in_definition_order()
503            .filter(|assoc| assoc.kind.namespace() == Namespace::ValueNS)
504            .map(|cand| cand.name)
505            .collect();
506        if let Some(typo) = find_best_match_for_name(&names, segment.ident.name, None) {
507            diag.span_suggestion_verbose(
508                segment.ident.span,
509                format!(
510                    "you may have misspelled this associated item, causing `{}` \
511                    to be interpreted as a type rather than a trait",
512                    tcx.item_name(trait_def_id),
513                ),
514                typo,
515                Applicability::MaybeIncorrect,
516            );
517        }
518    }
519}