Skip to main content

rustc_lint/
opaque_hidden_inferred_bound.rs

1use rustc_hir::{self as hir, AmbigArg};
2use rustc_infer::infer::TyCtxtInferExt;
3use rustc_macros::{Diagnostic, Subdiagnostic};
4use rustc_middle::ty::print::{PrintTraitPredicateExt as _, TraitPredPrintModifiersAndPath};
5use rustc_middle::ty::{self, BottomUpFolder, Ty, TypeFoldable, Unnormalized};
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
12#[doc =
r" The `opaque_hidden_inferred_bound` lint detects cases in which nested"]
#[doc =
r" `impl Trait` in associated type bounds are not written generally enough"]
#[doc = r" to satisfy the bounds of the associated type."]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" This functionality was removed in #97346, but then rolled back in #99860"]
#[doc = r" because it caused regressions."]
#[doc = r""]
#[doc =
r" We plan on reintroducing this as a hard error, but in the meantime,"]
#[doc =
r" this lint serves to warn and suggest fixes for any use-cases which rely"]
#[doc = r" on this behavior."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust"]
#[doc = r" #![feature(type_alias_impl_trait)]"]
#[doc = r""]
#[doc = r" trait Duh {}"]
#[doc = r""]
#[doc = r" impl Duh for i32 {}"]
#[doc = r""]
#[doc = r" trait Trait {"]
#[doc = r"     type Assoc: Duh;"]
#[doc = r" }"]
#[doc = r""]
#[doc = r" impl<F: Duh> Trait for F {"]
#[doc = r"     type Assoc = F;"]
#[doc = r" }"]
#[doc = r""]
#[doc = r" type Tait = impl Sized;"]
#[doc = r""]
#[doc = r" #[define_opaque(Tait)]"]
#[doc = r" fn test() -> impl Trait<Assoc = Tait> {"]
#[doc = r"     42"]
#[doc = r" }"]
#[doc = r""]
#[doc = r" fn main() {}"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc =
r" In this example, `test` declares that the associated type `Assoc` for"]
#[doc =
r" `impl Trait` is `impl Sized`, which does not satisfy the bound `Duh`"]
#[doc = r" on the associated type."]
#[doc = r""]
#[doc =
r" Although the hidden type, `i32` does satisfy this bound, we do not"]
#[doc =
r" consider the return type to be well-formed with this lint. It can be"]
#[doc =
r" fixed by changing `Tait = impl Sized` into `Tait = impl Sized + Duh`."]
pub static OPAQUE_HIDDEN_INFERRED_BOUND: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "OPAQUE_HIDDEN_INFERRED_BOUND",
            default_level: ::rustc_lint_defs::Warn,
            desc: "detects the use of nested `impl Trait` types in associated type bounds that are not general enough",
            is_externally_loaded: false,
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_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
67pub struct OpaqueHiddenInferredBound;
#[automatically_derived]
impl ::core::marker::Copy for OpaqueHiddenInferredBound { }
#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for OpaqueHiddenInferredBound { }
#[automatically_derived]
impl ::core::clone::Clone for OpaqueHiddenInferredBound {
    #[inline]
    fn clone(&self) -> OpaqueHiddenInferredBound { *self }
}
impl ::rustc_lint_defs::LintPass for OpaqueHiddenInferredBound {
    fn name(&self) -> &'static str { "OpaqueHiddenInferredBound" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [OPAQUE_HIDDEN_INFERRED_BOUND]))
    }
}
impl OpaqueHiddenInferredBound {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [OPAQUE_HIDDEN_INFERRED_BOUND]))
    }
}declare_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
92            .tcx
93            .explicit_item_bounds(def_id)
94            .iter_identity_copied()
95            .map(Unnormalized::skip_norm_wip)
96        {
97            infcx.enter_forall(pred.kind(), |predicate| {
98                let ty::ClauseKind::Projection(proj) = predicate else {
99                    return;
100                };
101                // Only check types, since those are the only things that may
102                // have opaques in them anyways.
103                let Some(proj_term) = proj.term.as_type() else { return };
104
105                // HACK: `impl Trait<Assoc = impl Trait2>` from an RPIT is "ok"...
106                if let ty::Alias(ty::AliasTy { kind: ty::Opaque { def_id: opaque_def_id }, .. }) =
107                    *proj_term.kind()
108                    && cx.tcx.parent(opaque_def_id) == def_id
109                    && #[allow(non_exhaustive_omitted_patterns)] match opaque.origin {
    hir::OpaqueTyOrigin::FnReturn { .. } | hir::OpaqueTyOrigin::AsyncFn { .. }
        => true,
    _ => false,
}matches!(
110                        opaque.origin,
111                        hir::OpaqueTyOrigin::FnReturn { .. } | hir::OpaqueTyOrigin::AsyncFn { .. }
112                    )
113                {
114                    return;
115                }
116
117                // HACK: `async fn() -> Self` in traits is "ok"...
118                // This is not really that great, but it's similar to why the `-> Self`
119                // return type is well-formed in traits even when `Self` isn't sized.
120                if let ty::Param(param_ty) = *proj_term.kind()
121                    && param_ty.name == kw::SelfUpper
122                    && #[allow(non_exhaustive_omitted_patterns)] match opaque.origin {
    hir::OpaqueTyOrigin::AsyncFn {
        in_trait_or_impl: Some(hir::RpitContext::Trait), .. } => true,
    _ => false,
}matches!(
123                        opaque.origin,
124                        hir::OpaqueTyOrigin::AsyncFn {
125                            in_trait_or_impl: Some(hir::RpitContext::Trait),
126                            ..
127                        }
128                    )
129                {
130                    return;
131                }
132
133                let proj_ty = Ty::new_projection_from_args(
134                    cx.tcx,
135                    proj.projection_term.def_id(),
136                    proj.projection_term.args,
137                );
138                // For every instance of the projection type in the bounds,
139                // replace them with the term we're assigning to the associated
140                // type in our opaque type.
141                let proj_replacer = &mut BottomUpFolder {
142                    tcx: cx.tcx,
143                    ty_op: |ty| if ty == proj_ty { proj_term } else { ty },
144                    lt_op: |lt| lt,
145                    ct_op: |ct| ct,
146                };
147                // For example, in `impl Trait<Assoc = impl Send>`, for all of the bounds on `Assoc`,
148                // e.g. `type Assoc: OtherTrait`, replace `<impl Trait as Trait>::Assoc: OtherTrait`
149                // with `impl Send: OtherTrait`.
150                for (assoc_pred, assoc_pred_span) in cx
151                    .tcx
152                    .explicit_item_bounds(proj.projection_term.def_id())
153                    .iter_instantiated_copied(cx.tcx, proj.projection_term.args)
154                    .map(Unnormalized::skip_norm_wip)
155                {
156                    let assoc_pred = assoc_pred.fold_with(proj_replacer);
157
158                    let ocx = ObligationCtxt::new(infcx);
159                    let assoc_pred = ocx.normalize(
160                        &traits::ObligationCause::dummy(),
161                        cx.param_env,
162                        Unnormalized::new_wip(assoc_pred),
163                    );
164                    if !ocx.evaluate_obligations_error_on_ambiguity().is_empty() {
165                        // Can't normalize for some reason...?
166                        continue;
167                    }
168
169                    ocx.register_obligation(traits::Obligation::new(
170                        cx.tcx,
171                        traits::ObligationCause::dummy(),
172                        cx.param_env,
173                        assoc_pred,
174                    ));
175
176                    // If that predicate doesn't hold modulo regions (but passed during type-check),
177                    // then we must've taken advantage of the hack in `project_and_unify_types` where
178                    // we replace opaques with inference vars. Emit a warning!
179                    if !ocx.evaluate_obligations_error_on_ambiguity().is_empty() {
180                        // If it's a trait bound and an opaque that doesn't satisfy it,
181                        // then we can emit a suggestion to add the bound.
182                        let add_bound = match (proj_term.kind(), assoc_pred.kind().skip_binder()) {
183                            (
184                                ty::Alias(ty::AliasTy { kind: ty::Opaque { def_id }, .. }),
185                                ty::ClauseKind::Trait(trait_pred),
186                            ) => Some(AddBound {
187                                suggest_span: cx.tcx.def_span(*def_id).shrink_to_hi(),
188                                trait_ref: trait_pred.print_modifiers_and_trait_path(),
189                            }),
190                            _ => None,
191                        };
192
193                        cx.emit_span_lint(
194                            OPAQUE_HIDDEN_INFERRED_BOUND,
195                            pred_span,
196                            OpaqueHiddenInferredBoundLint {
197                                ty: Ty::new_opaque(
198                                    cx.tcx,
199                                    def_id,
200                                    ty::GenericArgs::identity_for_item(cx.tcx, def_id),
201                                ),
202                                proj_ty: proj_term,
203                                assoc_pred_span,
204                                add_bound,
205                            },
206                        );
207                    }
208                }
209            });
210        }
211    }
212}
213
214#[derive(const _: () =
    {
        impl<'_sess, 'tcx, G> rustc_errors::Diagnostic<'_sess, G> for
            OpaqueHiddenInferredBoundLint<'tcx> where
            G: rustc_errors::EmissionGuarantee {
            #[track_caller]
            fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'_sess>,
                level: rustc_errors::Level) -> rustc_errors::Diag<'_sess, G> {
                match self {
                    OpaqueHiddenInferredBoundLint {
                        ty: __binding_0,
                        proj_ty: __binding_1,
                        assoc_pred_span: __binding_2,
                        add_bound: __binding_3 } => {
                        let mut diag =
                            rustc_errors::Diag::new(dcx, level,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("opaque type `{$ty}` does not satisfy its associated type bounds")));
                        ;
                        diag.arg("ty", __binding_0);
                        diag.arg("proj_ty", __binding_1);
                        diag.span_label(__binding_2,
                            rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("this associated type bound is unsatisfied for `{$proj_ty}`")));
                        if let Some(__binding_3) = __binding_3 {
                            diag.subdiagnostic(__binding_3);
                        }
                        diag
                    }
                }
            }
        }
    };Diagnostic)]
215#[diag("opaque type `{$ty}` does not satisfy its associated type bounds")]
216struct OpaqueHiddenInferredBoundLint<'tcx> {
217    ty: Ty<'tcx>,
218    proj_ty: Ty<'tcx>,
219    #[label("this associated type bound is unsatisfied for `{$proj_ty}`")]
220    assoc_pred_span: Span,
221    #[subdiagnostic]
222    add_bound: Option<AddBound<'tcx>>,
223}
224
225#[derive(const _: () =
    {
        impl<'tcx> rustc_errors::Subdiagnostic for AddBound<'tcx> {
            fn add_to_diag<__G>(self, diag: &mut rustc_errors::Diag<'_, __G>)
                where __G: rustc_errors::EmissionGuarantee {
                match self {
                    AddBound { suggest_span: __binding_0, trait_ref: __binding_1
                        } => {
                        let __code_94 =
                            [::alloc::__export::must_use({
                                                ::alloc::fmt::format(format_args!(" + {0}", __binding_1))
                                            })].into_iter();
                        let mut sub_args = rustc_errors::DiagArgMap::default();
                        let __message =
                            rustc_errors::format_diag_message(&rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("add this bound")),
                                &sub_args);
                        diag.span_suggestions_with_style(__binding_0, __message,
                            __code_94, rustc_errors::Applicability::MachineApplicable,
                            rustc_errors::SuggestionStyle::ShowAlways);
                    }
                }
            }
        }
    };Subdiagnostic)]
226#[suggestion(
227    "add this bound",
228    style = "verbose",
229    applicability = "machine-applicable",
230    code = " + {trait_ref}"
231)]
232struct AddBound<'tcx> {
233    #[primary_span]
234    suggest_span: Span,
235    #[skip_arg]
236    trait_ref: TraitPredPrintModifiersAndPath<'tcx>,
237}