rustc_lint/
opaque_hidden_inferred_bound.rs

1use rustc_hir::{self as hir, AmbigArg};
2use rustc_infer::infer::TyCtxtInferExt;
3use rustc_macros::{LintDiagnostic, Subdiagnostic};
4use rustc_middle::ty::print::{PrintTraitPredicateExt as _, TraitPredPrintModifiersAndPath};
5use rustc_middle::ty::{self, BottomUpFolder, Ty, TypeFoldable};
6use rustc_session::{declare_lint, declare_lint_pass};
7use rustc_span::{Span, kw};
8use rustc_trait_selection::traits::{self, ObligationCtxt};
9
10use crate::{LateContext, LateLintPass, LintContext};
11
12declare_lint! {
13    /// The `opaque_hidden_inferred_bound` lint detects cases in which nested
14    /// `impl Trait` in associated type bounds are not written generally enough
15    /// to satisfy the bounds of the associated type.
16    ///
17    /// ### Explanation
18    ///
19    /// This functionality was removed in #97346, but then rolled back in #99860
20    /// because it caused regressions.
21    ///
22    /// We plan on reintroducing this as a hard error, but in the meantime,
23    /// this lint serves to warn and suggest fixes for any use-cases which rely
24    /// on this behavior.
25    ///
26    /// ### Example
27    ///
28    /// ```rust
29    /// #![feature(type_alias_impl_trait)]
30    ///
31    /// trait Duh {}
32    ///
33    /// impl Duh for i32 {}
34    ///
35    /// trait Trait {
36    ///     type Assoc: Duh;
37    /// }
38    ///
39    /// impl<F: Duh> Trait for F {
40    ///     type Assoc = F;
41    /// }
42    ///
43    /// type Tait = impl Sized;
44    ///
45    /// #[define_opaque(Tait)]
46    /// fn test() -> impl Trait<Assoc = Tait> {
47    ///     42
48    /// }
49    ///
50    /// fn main() {}
51    /// ```
52    ///
53    /// {{produces}}
54    ///
55    /// In this example, `test` declares that the associated type `Assoc` for
56    /// `impl Trait` is `impl Sized`, which does not satisfy the bound `Duh`
57    /// on the associated type.
58    ///
59    /// Although the hidden type, `i32` does satisfy this bound, we do not
60    /// consider the return type to be well-formed with this lint. It can be
61    /// fixed by changing `Tait = impl Sized` into `Tait = impl Sized + Duh`.
62    pub OPAQUE_HIDDEN_INFERRED_BOUND,
63    Warn,
64    "detects the use of nested `impl Trait` types in associated type bounds that are not general enough"
65}
66
67declare_lint_pass!(OpaqueHiddenInferredBound => [OPAQUE_HIDDEN_INFERRED_BOUND]);
68
69impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound {
70    fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, AmbigArg>) {
71        let hir::TyKind::OpaqueDef(opaque) = &ty.kind else {
72            return;
73        };
74
75        // If this is an RPITIT from a trait method with no body, then skip.
76        // That's because although we may have an opaque type on the function,
77        // it won't have a hidden type, so proving predicates about it is
78        // not really meaningful.
79        if let hir::OpaqueTyOrigin::FnReturn { parent: method_def_id, .. } = opaque.origin
80            && let hir::Node::TraitItem(trait_item) = cx.tcx.hir_node_by_def_id(method_def_id)
81            && !trait_item.defaultness.has_value()
82        {
83            return;
84        }
85
86        let def_id = opaque.def_id.to_def_id();
87        let infcx = &cx.tcx.infer_ctxt().build(cx.typing_mode());
88        // For every projection predicate in the opaque type's explicit bounds,
89        // check that the type that we're assigning actually satisfies the bounds
90        // of the associated type.
91        for (pred, pred_span) in cx.tcx.explicit_item_bounds(def_id).iter_identity_copied() {
92            infcx.enter_forall(pred.kind(), |predicate| {
93                let ty::ClauseKind::Projection(proj) = predicate else {
94                    return;
95                };
96                // Only check types, since those are the only things that may
97                // have opaques in them anyways.
98                let Some(proj_term) = proj.term.as_type() else { return };
99
100                // HACK: `impl Trait<Assoc = impl Trait2>` from an RPIT is "ok"...
101                if let ty::Alias(ty::Opaque, opaque_ty) = *proj_term.kind()
102                    && cx.tcx.parent(opaque_ty.def_id) == def_id
103                    && matches!(
104                        opaque.origin,
105                        hir::OpaqueTyOrigin::FnReturn { .. } | hir::OpaqueTyOrigin::AsyncFn { .. }
106                    )
107                {
108                    return;
109                }
110
111                // HACK: `async fn() -> Self` in traits is "ok"...
112                // This is not really that great, but it's similar to why the `-> Self`
113                // return type is well-formed in traits even when `Self` isn't sized.
114                if let ty::Param(param_ty) = *proj_term.kind()
115                    && param_ty.name == kw::SelfUpper
116                    && matches!(
117                        opaque.origin,
118                        hir::OpaqueTyOrigin::AsyncFn {
119                            in_trait_or_impl: Some(hir::RpitContext::Trait),
120                            ..
121                        }
122                    )
123                {
124                    return;
125                }
126
127                let proj_ty = Ty::new_projection_from_args(
128                    cx.tcx,
129                    proj.projection_term.def_id,
130                    proj.projection_term.args,
131                );
132                // For every instance of the projection type in the bounds,
133                // replace them with the term we're assigning to the associated
134                // type in our opaque type.
135                let proj_replacer = &mut BottomUpFolder {
136                    tcx: cx.tcx,
137                    ty_op: |ty| if ty == proj_ty { proj_term } else { ty },
138                    lt_op: |lt| lt,
139                    ct_op: |ct| ct,
140                };
141                // For example, in `impl Trait<Assoc = impl Send>`, for all of the bounds on `Assoc`,
142                // e.g. `type Assoc: OtherTrait`, replace `<impl Trait as Trait>::Assoc: OtherTrait`
143                // with `impl Send: OtherTrait`.
144                for (assoc_pred, assoc_pred_span) in cx
145                    .tcx
146                    .explicit_item_bounds(proj.projection_term.def_id)
147                    .iter_instantiated_copied(cx.tcx, proj.projection_term.args)
148                {
149                    let assoc_pred = assoc_pred.fold_with(proj_replacer);
150
151                    let ocx = ObligationCtxt::new(infcx);
152                    let assoc_pred =
153                        ocx.normalize(&traits::ObligationCause::dummy(), cx.param_env, assoc_pred);
154                    if !ocx.select_all_or_error().is_empty() {
155                        // Can't normalize for some reason...?
156                        continue;
157                    }
158
159                    ocx.register_obligation(traits::Obligation::new(
160                        cx.tcx,
161                        traits::ObligationCause::dummy(),
162                        cx.param_env,
163                        assoc_pred,
164                    ));
165
166                    // If that predicate doesn't hold modulo regions (but passed during type-check),
167                    // then we must've taken advantage of the hack in `project_and_unify_types` where
168                    // we replace opaques with inference vars. Emit a warning!
169                    if !ocx.select_all_or_error().is_empty() {
170                        // If it's a trait bound and an opaque that doesn't satisfy it,
171                        // then we can emit a suggestion to add the bound.
172                        let add_bound = match (proj_term.kind(), assoc_pred.kind().skip_binder()) {
173                            (
174                                ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }),
175                                ty::ClauseKind::Trait(trait_pred),
176                            ) => Some(AddBound {
177                                suggest_span: cx.tcx.def_span(*def_id).shrink_to_hi(),
178                                trait_ref: trait_pred.print_modifiers_and_trait_path(),
179                            }),
180                            _ => None,
181                        };
182
183                        cx.emit_span_lint(
184                            OPAQUE_HIDDEN_INFERRED_BOUND,
185                            pred_span,
186                            OpaqueHiddenInferredBoundLint {
187                                ty: Ty::new_opaque(
188                                    cx.tcx,
189                                    def_id,
190                                    ty::GenericArgs::identity_for_item(cx.tcx, def_id),
191                                ),
192                                proj_ty: proj_term,
193                                assoc_pred_span,
194                                add_bound,
195                            },
196                        );
197                    }
198                }
199            });
200        }
201    }
202}
203
204#[derive(LintDiagnostic)]
205#[diag(lint_opaque_hidden_inferred_bound)]
206struct OpaqueHiddenInferredBoundLint<'tcx> {
207    ty: Ty<'tcx>,
208    proj_ty: Ty<'tcx>,
209    #[label(lint_specifically)]
210    assoc_pred_span: Span,
211    #[subdiagnostic]
212    add_bound: Option<AddBound<'tcx>>,
213}
214
215#[derive(Subdiagnostic)]
216#[suggestion(
217    lint_opaque_hidden_inferred_bound_sugg,
218    style = "verbose",
219    applicability = "machine-applicable",
220    code = " + {trait_ref}"
221)]
222struct AddBound<'tcx> {
223    #[primary_span]
224    suggest_span: Span,
225    #[skip_arg]
226    trait_ref: TraitPredPrintModifiersAndPath<'tcx>,
227}