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}