rustc_ty_utils/
opaque_types.rs

1use rustc_data_structures::fx::FxHashSet;
2use rustc_hir::def::DefKind;
3use rustc_hir::def_id::LocalDefId;
4use rustc_hir::intravisit;
5use rustc_hir::intravisit::Visitor;
6use rustc_middle::query::Providers;
7use rustc_middle::ty::util::{CheckRegions, NotUniqueParam};
8use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
9use rustc_middle::{bug, span_bug};
10use rustc_span::Span;
11use tracing::{instrument, trace};
12
13use crate::errors::{DuplicateArg, NotParam};
14
15struct OpaqueTypeCollector<'tcx> {
16    tcx: TyCtxt<'tcx>,
17    opaques: Vec<LocalDefId>,
18    /// The `DefId` of the item which we are collecting opaque types for.
19    item: LocalDefId,
20
21    /// Avoid infinite recursion due to recursive declarations.
22    seen: FxHashSet<LocalDefId>,
23
24    span: Option<Span>,
25
26    mode: CollectionMode,
27}
28
29enum CollectionMode {
30    /// For impl trait in assoc types we only permit collecting them from
31    /// associated types of the same impl block.
32    ImplTraitInAssocTypes,
33    /// When collecting for an explicit `#[define_opaque]` attribute, find all TAITs
34    Taits,
35    /// The default case, only collect RPITs and AsyncFn return types, as these are
36    /// always defined by the current item.
37    RpitAndAsyncFnOnly,
38}
39
40impl<'tcx> OpaqueTypeCollector<'tcx> {
41    fn new(tcx: TyCtxt<'tcx>, item: LocalDefId) -> Self {
42        let mode = match tcx.def_kind(item) {
43            DefKind::AssocConst | DefKind::AssocFn | DefKind::AssocTy => {
44                CollectionMode::ImplTraitInAssocTypes
45            }
46            DefKind::TyAlias => CollectionMode::Taits,
47            _ => CollectionMode::RpitAndAsyncFnOnly,
48        };
49        Self { tcx, opaques: Vec::new(), item, seen: Default::default(), span: None, mode }
50    }
51
52    fn span(&self) -> Span {
53        self.span.unwrap_or_else(|| {
54            self.tcx.def_ident_span(self.item).unwrap_or_else(|| self.tcx.def_span(self.item))
55        })
56    }
57
58    fn visit_spanned(&mut self, span: Span, value: impl TypeVisitable<TyCtxt<'tcx>>) {
59        let old = self.span;
60        self.span = Some(span);
61        value.visit_with(self);
62        self.span = old;
63    }
64
65    fn parent_impl_trait_ref(&self) -> Option<ty::TraitRef<'tcx>> {
66        let parent = self.parent()?;
67        if matches!(self.tcx.def_kind(parent), DefKind::Impl { .. }) {
68            Some(self.tcx.impl_trait_ref(parent)?.instantiate_identity())
69        } else {
70            None
71        }
72    }
73
74    fn parent(&self) -> Option<LocalDefId> {
75        match self.tcx.def_kind(self.item) {
76            DefKind::AssocFn | DefKind::AssocTy | DefKind::AssocConst => {
77                Some(self.tcx.local_parent(self.item))
78            }
79            _ => None,
80        }
81    }
82
83    #[instrument(level = "trace", skip(self))]
84    fn collect_taits_declared_in_body(&mut self) {
85        let body = self.tcx.hir_body_owned_by(self.item).value;
86        struct TaitInBodyFinder<'a, 'tcx> {
87            collector: &'a mut OpaqueTypeCollector<'tcx>,
88        }
89        impl<'v> intravisit::Visitor<'v> for TaitInBodyFinder<'_, '_> {
90            #[instrument(level = "trace", skip(self))]
91            fn visit_nested_item(&mut self, id: rustc_hir::ItemId) {
92                let id = id.owner_id.def_id;
93                if let DefKind::TyAlias = self.collector.tcx.def_kind(id) {
94                    let items = self.collector.tcx.opaque_types_defined_by(id);
95                    self.collector.opaques.extend(items);
96                }
97            }
98            #[instrument(level = "trace", skip(self))]
99            // Recurse into these, as they are type checked with their parent
100            fn visit_nested_body(&mut self, id: rustc_hir::BodyId) {
101                let body = self.collector.tcx.hir_body(id);
102                self.visit_body(body);
103            }
104        }
105        TaitInBodyFinder { collector: self }.visit_expr(body);
106    }
107
108    #[instrument(level = "debug", skip(self))]
109    fn visit_opaque_ty(&mut self, alias_ty: ty::AliasTy<'tcx>) {
110        if !self.seen.insert(alias_ty.def_id.expect_local()) {
111            return;
112        }
113
114        // TAITs outside their defining scopes are ignored.
115        match self.tcx.local_opaque_ty_origin(alias_ty.def_id.expect_local()) {
116            rustc_hir::OpaqueTyOrigin::FnReturn { .. }
117            | rustc_hir::OpaqueTyOrigin::AsyncFn { .. } => {}
118            rustc_hir::OpaqueTyOrigin::TyAlias { in_assoc_ty, .. } => match self.mode {
119                // If we are collecting opaques in an assoc method, we are only looking at assoc types
120                // mentioned in the assoc method and only at opaques defined in there. We do not
121                // want to collect TAITs
122                CollectionMode::ImplTraitInAssocTypes => {
123                    if !in_assoc_ty {
124                        return;
125                    }
126                }
127                // If we are collecting opaques referenced from a `define_opaque` attribute, we
128                // do not want to look at opaques defined in associated types. Those can only be
129                // defined by methods on the same impl.
130                CollectionMode::Taits => {
131                    if in_assoc_ty {
132                        return;
133                    }
134                }
135                CollectionMode::RpitAndAsyncFnOnly => return,
136            },
137        }
138
139        trace!(?alias_ty, "adding");
140        self.opaques.push(alias_ty.def_id.expect_local());
141
142        let parent_count = self.tcx.generics_of(alias_ty.def_id).parent_count;
143        // Only check that the parent generics of the TAIT/RPIT are unique.
144        // the args owned by the opaque are going to always be duplicate
145        // lifetime params for RPITs, and empty for TAITs.
146        match self
147            .tcx
148            .uses_unique_generic_params(&alias_ty.args[..parent_count], CheckRegions::FromFunction)
149        {
150            Ok(()) => {
151                // FIXME: implement higher kinded lifetime bounds on nested opaque types. They are not
152                // supported at all, so this is sound to do, but once we want to support them, you'll
153                // start seeing the error below.
154
155                // Collect opaque types nested within the associated type bounds of this opaque type.
156                // We use identity args here, because we already know that the opaque type uses
157                // only generic parameters, and thus instantiating would not give us more information.
158                for (pred, span) in
159                    self.tcx.explicit_item_bounds(alias_ty.def_id).iter_identity_copied()
160                {
161                    trace!(?pred);
162                    self.visit_spanned(span, pred);
163                }
164            }
165            Err(NotUniqueParam::NotParam(arg)) => {
166                self.tcx.dcx().emit_err(NotParam {
167                    arg,
168                    span: self.span(),
169                    opaque_span: self.tcx.def_span(alias_ty.def_id),
170                });
171            }
172            Err(NotUniqueParam::DuplicateParam(arg)) => {
173                self.tcx.dcx().emit_err(DuplicateArg {
174                    arg,
175                    span: self.span(),
176                    opaque_span: self.tcx.def_span(alias_ty.def_id),
177                });
178            }
179        }
180    }
181
182    /// Checks the `#[define_opaque]` attributes on items and collects opaques to define
183    /// from the referenced types.
184    #[instrument(level = "trace", skip(self))]
185    fn collect_taits_from_defines_attr(&mut self) {
186        let hir_id = self.tcx.local_def_id_to_hir_id(self.item);
187        if !hir_id.is_owner() {
188            return;
189        }
190        let Some(defines) = self.tcx.hir_attr_map(hir_id.owner).define_opaque else {
191            return;
192        };
193        for &(span, define) in defines {
194            trace!(?define);
195            let mode = std::mem::replace(&mut self.mode, CollectionMode::Taits);
196            let n = self.opaques.len();
197            super::sig_types::walk_types(self.tcx, define, self);
198            if n == self.opaques.len() {
199                self.tcx.dcx().span_err(span, "item does not contain any opaque types");
200            }
201            self.mode = mode;
202        }
203        // Allow using `#[define_opaque]` on assoc methods and type aliases to override the default collection mode in
204        // case it was capturing too much.
205        self.mode = CollectionMode::RpitAndAsyncFnOnly;
206    }
207}
208
209impl<'tcx> super::sig_types::SpannedTypeVisitor<'tcx> for OpaqueTypeCollector<'tcx> {
210    #[instrument(skip(self), ret, level = "trace")]
211    fn visit(&mut self, span: Span, value: impl TypeVisitable<TyCtxt<'tcx>>) {
212        self.visit_spanned(span, value);
213    }
214}
215
216impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for OpaqueTypeCollector<'tcx> {
217    #[instrument(skip(self), ret, level = "trace")]
218    fn visit_ty(&mut self, t: Ty<'tcx>) {
219        t.super_visit_with(self);
220        match *t.kind() {
221            ty::Alias(ty::Opaque, alias_ty) if alias_ty.def_id.is_local() => {
222                self.visit_opaque_ty(alias_ty);
223            }
224            // Skips type aliases, as they are meant to be transparent.
225            // FIXME(type_alias_impl_trait): can we require mentioning nested type aliases explicitly?
226            ty::Alias(ty::Free, alias_ty) if let Some(def_id) = alias_ty.def_id.as_local() => {
227                if !self.seen.insert(def_id) {
228                    return;
229                }
230                self.tcx
231                    .type_of(alias_ty.def_id)
232                    .instantiate(self.tcx, alias_ty.args)
233                    .visit_with(self);
234            }
235            ty::Alias(ty::Projection, alias_ty) => {
236                // This avoids having to do normalization of `Self::AssocTy` by only
237                // supporting the case of a method defining opaque types from assoc types
238                // in the same impl block.
239                if let Some(impl_trait_ref) = self.parent_impl_trait_ref() {
240                    // If the trait ref of the associated item and the impl differs,
241                    // then we can't use the impl's identity args below, so
242                    // just skip.
243                    if alias_ty.trait_ref(self.tcx) == impl_trait_ref {
244                        let parent = self.parent().expect("we should have a parent here");
245
246                        for &assoc in self.tcx.associated_items(parent).in_definition_order() {
247                            trace!(?assoc);
248                            if assoc.trait_item_def_id != Some(alias_ty.def_id) {
249                                continue;
250                            }
251
252                            // If the type is further specializable, then the type_of
253                            // is not actually correct below.
254                            if !assoc.defaultness(self.tcx).is_final() {
255                                continue;
256                            }
257
258                            if !self.seen.insert(assoc.def_id.expect_local()) {
259                                return;
260                            }
261
262                            let alias_args = alias_ty.args.rebase_onto(
263                                self.tcx,
264                                impl_trait_ref.def_id,
265                                ty::GenericArgs::identity_for_item(self.tcx, parent),
266                            );
267
268                            if self.tcx.check_args_compatible(assoc.def_id, alias_args) {
269                                self.tcx
270                                    .type_of(assoc.def_id)
271                                    .instantiate(self.tcx, alias_args)
272                                    .visit_with(self);
273                                return;
274                            } else {
275                                self.tcx.dcx().span_delayed_bug(
276                                    self.tcx.def_span(assoc.def_id),
277                                    "item had incorrect args",
278                                );
279                            }
280                        }
281                    }
282                } else if let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, .. }) =
283                    self.tcx.opt_rpitit_info(alias_ty.def_id)
284                    && fn_def_id == self.item.into()
285                {
286                    // RPITIT in trait definitions get desugared to an associated type. For
287                    // default methods we also create an opaque type this associated type
288                    // normalizes to. The associated type is only known to normalize to the
289                    // opaque if it is fully concrete. There could otherwise be an impl
290                    // overwriting the default method.
291                    //
292                    // However, we have to be able to normalize the associated type while inside
293                    // of the default method. This is normally handled by adding an unchecked
294                    // `Projection(<Self as Trait>::synthetic_assoc_ty, trait_def::opaque)`
295                    // assumption to the `param_env` of the default method. We also separately
296                    // rely on that assumption here.
297                    let ty = self.tcx.type_of(alias_ty.def_id).instantiate(self.tcx, alias_ty.args);
298                    let ty::Alias(ty::Opaque, alias_ty) = *ty.kind() else { bug!("{ty:?}") };
299                    self.visit_opaque_ty(alias_ty);
300                }
301            }
302            _ => trace!(kind=?t.kind()),
303        }
304    }
305}
306
307fn opaque_types_defined_by<'tcx>(
308    tcx: TyCtxt<'tcx>,
309    item: LocalDefId,
310) -> &'tcx ty::List<LocalDefId> {
311    let kind = tcx.def_kind(item);
312    trace!(?kind);
313    let mut collector = OpaqueTypeCollector::new(tcx, item);
314    collector.collect_taits_from_defines_attr();
315    super::sig_types::walk_types(tcx, item, &mut collector);
316
317    match kind {
318        DefKind::AssocFn
319        | DefKind::Fn
320        | DefKind::Static { .. }
321        | DefKind::Const
322        | DefKind::AssocConst
323        | DefKind::AnonConst => {
324            collector.collect_taits_declared_in_body();
325        }
326        // Closures and coroutines are type checked with their parent
327        // Note that we also support `SyntheticCoroutineBody` since we create
328        // a MIR body for the def kind, and some MIR passes (like promotion)
329        // may require doing analysis using its typing env.
330        DefKind::Closure | DefKind::InlineConst | DefKind::SyntheticCoroutineBody => {
331            collector.opaques.extend(tcx.opaque_types_defined_by(tcx.local_parent(item)));
332        }
333        DefKind::AssocTy | DefKind::TyAlias | DefKind::GlobalAsm => {}
334        DefKind::OpaqueTy
335        | DefKind::Mod
336        | DefKind::Struct
337        | DefKind::Union
338        | DefKind::Enum
339        | DefKind::Variant
340        | DefKind::Trait
341        | DefKind::ForeignTy
342        | DefKind::TraitAlias
343        | DefKind::TyParam
344        | DefKind::ConstParam
345        | DefKind::Ctor(_, _)
346        | DefKind::Macro(_)
347        | DefKind::ExternCrate
348        | DefKind::Use
349        | DefKind::ForeignMod
350        | DefKind::Field
351        | DefKind::LifetimeParam
352        | DefKind::Impl { .. } => {
353            span_bug!(
354                tcx.def_span(item),
355                "`opaque_types_defined_by` not defined for {} `{item:?}`",
356                kind.descr(item.to_def_id())
357            );
358        }
359    }
360    tcx.mk_local_def_ids(&collector.opaques)
361}
362
363pub(super) fn provide(providers: &mut Providers) {
364    *providers = Providers { opaque_types_defined_by, ..*providers };
365}