Skip to main content

rustc_builtin_macros/deriving/
coerce_pointee.rs

1use ast::HasAttrs;
2use rustc_ast::mut_visit::MutVisitor;
3use rustc_ast::visit::{BoundKind, Visitor};
4use rustc_ast::{
5    self as ast, GenericArg, GenericBound, GenericParamKind, ItemKind, TraitBoundModifiers,
6    VariantData, WherePredicate,
7};
8use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
9use rustc_errors::E0802;
10use rustc_expand::base::ExtCtxt;
11use rustc_macros::Diagnostic;
12use rustc_span::{Ident, Span, Symbol, sym};
13use thin_vec::{ThinVec, thin_vec};
14
15use crate::deriving::generic::*;
16use crate::deriving::{new_path, path_std};
17use crate::diagnostics;
18
19pub(crate) fn expand_deriving_coerce_pointee(
20    cx: &ExtCtxt<'_>,
21    span: Span,
22    item: &ast::Item,
23    push: &mut dyn FnMut(Box<ast::Item>),
24    _is_const: bool,
25) {
26    DetectNonGenericPointeeAttr { cx }.visit_item(item);
27
28    let (name_ident, generics) = if let ItemKind::Struct(ident, g, struct_data) = &item.kind {
29        if !#[allow(non_exhaustive_omitted_patterns)] match struct_data {
    VariantData::Struct { fields, recovered: _ } |
        VariantData::Tuple(fields, _) if !fields.is_empty() => true,
    _ => false,
}matches!(
30            struct_data,
31            VariantData::Struct { fields, recovered: _ } | VariantData::Tuple(fields, _)
32                if !fields.is_empty())
33        {
34            cx.dcx().emit_err(RequireOneField { span });
35            return;
36        }
37        (*ident, g)
38    } else {
39        cx.dcx().emit_err(RequireTransparent { span });
40        return;
41    };
42
43    // Convert generic parameters (from the struct) into generic args.
44    let self_params: Vec<_> =
45        generics.params.iter().map(|p| generic_param_to_arg(cx, p, p.span())).collect();
46    let type_params: Vec<_> = generics
47        .params
48        .iter()
49        .enumerate()
50        .filter_map(|(idx, p)| {
51            if let GenericParamKind::Type { .. } = p.kind {
52                Some((idx, p.span(), p.attrs().iter().any(|attr| attr.has_name(sym::pointee))))
53            } else {
54                None
55            }
56        })
57        .collect();
58
59    let pointee_param_idx = if type_params.is_empty() {
60        // `#[derive(CoercePointee)]` requires at least one generic type on the target `struct`
61        cx.dcx().emit_err(RequireOneGeneric { span });
62        return;
63    } else if type_params.len() == 1 {
64        // Regardless of the only type param being designed as `#[pointee]` or not, we can just use it as such
65        type_params[0].0
66    } else {
67        let mut pointees = type_params
68            .iter()
69            .filter_map(|&(idx, span, is_pointee)| is_pointee.then_some((idx, span)));
70        match (pointees.next(), pointees.next()) {
71            (Some((idx, _span)), None) => idx,
72            (None, _) => {
73                cx.dcx().emit_err(RequireOnePointee { span });
74                return;
75            }
76            (Some((_, one)), Some((_, another))) => {
77                cx.dcx().emit_err(TooManyPointees { one, another });
78                return;
79            }
80        }
81    };
82
83    // Create the type of `self`.
84    let path = cx.path_all(span, false, ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [name_ident]))vec![name_ident], self_params.clone());
85    let self_type = cx.ty_path(path);
86
87    // Declare helper function that adds implementation blocks.
88    // FIXME(dingxiangfei2009): Investigate the set of attributes on target struct to be propagated to impls
89    let attrs = {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(cx.attr_word(sym::automatically_derived, span));
    vec
}thin_vec![cx.attr_word(sym::automatically_derived, span),];
90    // # Validity assertion which will be checked later in `rustc_hir_analysis::coherence::builtins`.
91    {
92        let trait_path = new_path(cx, span, { &[sym::marker, sym::CoercePointeeValidated] },
    ::alloc::vec::Vec::new())path_std!(cx, span, marker::CoercePointeeValidated);
93        push(cx.item_trait_impl(
94            span,
95            attrs.clone(),
96            generics_without_defaults(generics),
97            ast::Safety::Default,
98            false,
99            cx.trait_ref(trait_path),
100            self_type.clone(),
101            ThinVec::new(),
102        ));
103    }
104    let mut add_impl_block = |generics, trait_symbol, trait_args| {
105        let trait_path = new_path(cx, span, &[sym::ops, trait_symbol], trait_args);
106        let trait_ref = cx.trait_ref(trait_path);
107        let item = cx.item_trait_impl(
108            span,
109            attrs.clone(),
110            generics,
111            ast::Safety::Default,
112            false,
113            trait_ref,
114            self_type.clone(),
115            ThinVec::new(),
116        );
117        push(item);
118    };
119
120    // Create unsized `self`, that is, one where the `#[pointee]` type arg is replaced with `__S`. For
121    // example, instead of `MyType<'a, T>`, it will be `MyType<'a, __S>`.
122    let s_ty = cx.ty_ident(span, Ident::new(sym::__S, span));
123    let mut alt_self_params = self_params;
124    alt_self_params[pointee_param_idx] = GenericArg::Type(s_ty.clone());
125    let alt_self_type = cx.ty_path(cx.path_all(span, false, ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [name_ident]))vec![name_ident], alt_self_params));
126
127    // # Add `Unsize<__S>` bound to `#[pointee]` at the generic parameter location
128    //
129    // Find the `#[pointee]` parameter and add an `Unsize<__S>` bound to it.
130    let mut impl_generics = generics.clone();
131    let pointee_ty_ident = generics.params[pointee_param_idx].ident;
132    let mut self_bounds;
133    {
134        let pointee = &mut impl_generics.params[pointee_param_idx];
135        self_bounds = pointee.bounds.clone();
136        if !contains_maybe_sized_bound(&self_bounds)
137            && !contains_maybe_sized_bound_on_pointee(
138                &generics.where_clause.predicates,
139                pointee_ty_ident.name,
140            )
141        {
142            cx.dcx().emit_err(RequiresMaybeSized {
143                span: pointee_ty_ident.span,
144                name: pointee_ty_ident,
145            });
146            return;
147        }
148        let arg = GenericArg::Type(s_ty.clone());
149        let unsize = cx.path_all(span, true, cx.std_path(&[sym::marker, sym::Unsize]), ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [arg]))vec![arg]);
150        pointee.bounds.push(cx.trait_bound(unsize, false));
151        // Drop `#[pointee]` attribute since it should not be recognized outside `derive(CoercePointee)`
152        pointee.attrs.retain(|attr| !attr.has_name(sym::pointee));
153    }
154
155    // # Rewrite generic parameter bounds
156    // For each bound `U: ..` in `struct<U: ..>`, make a new bound with `__S` in place of `#[pointee]`
157    // Example:
158    // ```
159    // struct<
160    //     U: Trait<T>,
161    //     #[pointee] T: Trait<T> + ?Sized,
162    //     V: Trait<T>> ...
163    // ```
164    // ... generates this `impl` generic parameters
165    // ```
166    // impl<
167    //     U: Trait<T> + Trait<__S>,
168    //     T: Trait<T> + ?Sized + Unsize<__S>, // (**)
169    //     __S: Trait<__S> + ?Sized, // (*)
170    //     V: Trait<T> + Trait<__S>> ...
171    // ```
172    // The new bound marked with (*) has to be done separately.
173    // See next section
174    for (idx, (params, orig_params)) in
175        impl_generics.params.iter_mut().zip(&generics.params).enumerate()
176    {
177        // Default type parameters are rejected for `impl` block.
178        // We should drop them now.
179        match &mut params.kind {
180            ast::GenericParamKind::Const { default, .. } => *default = None,
181            ast::GenericParamKind::Type { default } => *default = None,
182            ast::GenericParamKind::Lifetime => {}
183        }
184        // We CANNOT rewrite `#[pointee]` type parameter bounds.
185        // This has been set in stone. (**)
186        // So we skip over it.
187        // Otherwise, we push extra bounds involving `__S`.
188        if idx != pointee_param_idx {
189            for bound in &orig_params.bounds {
190                let mut bound = bound.clone();
191                let mut substitution = TypeSubstitution {
192                    from_name: pointee_ty_ident.name,
193                    to_ty: &s_ty,
194                    rewritten: false,
195                };
196                substitution.visit_param_bound(&mut bound, BoundKind::Bound);
197                if substitution.rewritten {
198                    // We found use of `#[pointee]` somewhere,
199                    // so we make a new bound using `__S` in place of `#[pointee]`
200                    params.bounds.push(bound);
201                }
202            }
203        }
204    }
205
206    // # Insert `__S` type parameter
207    //
208    // We now insert `__S` with the missing bounds marked with (*) above.
209    // We should also write the bounds from `#[pointee]` to `__S` as required by `Unsize<__S>`.
210    {
211        let mut substitution =
212            TypeSubstitution { from_name: pointee_ty_ident.name, to_ty: &s_ty, rewritten: false };
213        for bound in &mut self_bounds {
214            substitution.visit_param_bound(bound, BoundKind::Bound);
215        }
216    }
217
218    // # Rewrite `where` clauses
219    //
220    // Move on to `where` clauses.
221    // Example:
222    // ```
223    // struct MyPointer<#[pointee] T, ..>
224    // where
225    //   U: Trait<V> + Trait<T>,
226    //   Companion<T>: Trait<T>,
227    //   T: Trait<T> + ?Sized,
228    // { .. }
229    // ```
230    // ... will have a impl prelude like so
231    // ```
232    // impl<..> ..
233    // where
234    //   U: Trait<V> + Trait<T>,
235    //   U: Trait<__S>,
236    //   Companion<T>: Trait<T>,
237    //   Companion<__S>: Trait<__S>,
238    //   T: Trait<T> + ?Sized,
239    //   __S: Trait<__S> + ?Sized,
240    // ```
241    //
242    // We should also write a few new `where` bounds from `#[pointee] T` to `__S`
243    // as well as any bound that indirectly involves the `#[pointee] T` type.
244    for predicate in &generics.where_clause.predicates {
245        if let ast::WherePredicateKind::BoundPredicate(bound) = &predicate.kind {
246            let mut substitution = TypeSubstitution {
247                from_name: pointee_ty_ident.name,
248                to_ty: &s_ty,
249                rewritten: false,
250            };
251            let mut kind = ast::WherePredicateKind::BoundPredicate(bound.clone());
252            substitution.visit_where_predicate_kind(&mut kind);
253            if substitution.rewritten {
254                let predicate = ast::WherePredicate {
255                    attrs: predicate.attrs.clone(),
256                    kind,
257                    span: predicate.span,
258                    id: ast::DUMMY_NODE_ID,
259                    is_placeholder: false,
260                };
261                impl_generics.where_clause.predicates.push(predicate);
262            }
263        }
264    }
265
266    let extra_param = cx.typaram(Ident::new(sym::__S, span), self_bounds, None);
267    impl_generics.params.insert(pointee_param_idx + 1, extra_param);
268
269    // Add the impl blocks for `DispatchFromDyn` and `CoerceUnsized`.
270    let gen_args = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [alt_self_type]))vec![alt_self_type];
271    add_impl_block(impl_generics.clone(), sym::DispatchFromDyn, gen_args.clone());
272    add_impl_block(impl_generics, sym::CoerceUnsized, gen_args);
273}
274
275fn contains_maybe_sized_bound_on_pointee(predicates: &[WherePredicate], pointee: Symbol) -> bool {
276    for bound in predicates {
277        if let ast::WherePredicateKind::BoundPredicate(bound) = &bound.kind
278            && bound.bounded_ty.kind.is_simple_path().is_some_and(|name| name == pointee)
279        {
280            if contains_maybe_sized_bound(&bound.bounds) {
281                return true;
282            }
283        }
284    }
285    false
286}
287
288fn is_maybe_sized_bound(bound: &GenericBound) -> bool {
289    if let GenericBound::Trait(trait_ref) = bound
290        && let TraitBoundModifiers { polarity: ast::BoundPolarity::Maybe(_), .. } =
291            trait_ref.modifiers
292        && is_sized_marker(&trait_ref.trait_ref.path)
293    {
294        true
295    } else {
296        false
297    }
298}
299
300fn contains_maybe_sized_bound(bounds: &[GenericBound]) -> bool {
301    bounds.iter().any(is_maybe_sized_bound)
302}
303
304fn is_sized_marker(path: &ast::Path) -> bool {
305    const CORE_UNSIZE: [Symbol; 3] = [sym::core, sym::marker, sym::Sized];
306    const STD_UNSIZE: [Symbol; 3] = [sym::std, sym::marker, sym::Sized];
307    let segments = || path.segments.iter().map(|segment| segment.ident.name);
308    if path.is_global() {
309        segments().skip(1).eq(CORE_UNSIZE) || segments().skip(1).eq(STD_UNSIZE)
310    } else {
311        segments().eq(CORE_UNSIZE) || segments().eq(STD_UNSIZE) || *path == sym::Sized
312    }
313}
314
315struct TypeSubstitution<'a> {
316    from_name: Symbol,
317    to_ty: &'a ast::Ty,
318    rewritten: bool,
319}
320
321impl<'a> ast::mut_visit::MutVisitor for TypeSubstitution<'a> {
322    fn visit_ty(&mut self, ty: &mut ast::Ty) {
323        if let Some(name) = ty.kind.is_simple_path()
324            && name == self.from_name
325        {
326            *ty = self.to_ty.clone();
327            self.rewritten = true;
328        } else {
329            ast::mut_visit::walk_ty(self, ty);
330        }
331    }
332
333    fn visit_where_predicate_kind(&mut self, kind: &mut ast::WherePredicateKind) {
334        match kind {
335            rustc_ast::WherePredicateKind::BoundPredicate(bound) => {
336                bound
337                    .bound_generic_params
338                    .flat_map_in_place(|param| self.flat_map_generic_param(param));
339                self.visit_ty(&mut bound.bounded_ty);
340                for bound in &mut bound.bounds {
341                    self.visit_param_bound(bound, BoundKind::Bound)
342                }
343            }
344            rustc_ast::WherePredicateKind::RegionPredicate(_) => {}
345        }
346    }
347}
348
349struct DetectNonGenericPointeeAttr<'a, 'b> {
350    cx: &'a ExtCtxt<'b>,
351}
352
353impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonGenericPointeeAttr<'a, 'b> {
354    fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) -> Self::Result {
355        if attr.has_name(sym::pointee) {
356            self.cx.dcx().emit_err(diagnostics::NonGenericPointee { span: attr.span });
357        }
358    }
359
360    fn visit_generic_param(&mut self, param: &'a rustc_ast::GenericParam) -> Self::Result {
361        let mut error_on_pointee = AlwaysErrorOnGenericParam { cx: self.cx };
362
363        match &param.kind {
364            GenericParamKind::Type { default } => {
365                // The `default` may end up containing a block expression.
366                // The problem is block expressions  may define structs with generics.
367                // A user may attach a #[pointee] attribute to one of these generics
368                // We want to catch that. The simple solution is to just
369                // always raise a `NonGenericPointee` error when this happens.
370                //
371                // This solution does reject valid rust programs but,
372                // such a code would have to, in order:
373                // - Define a smart pointer struct.
374                // - Somewhere in this struct definition use a type with a const generic argument.
375                // - Calculate this const generic in a expression block.
376                // - Define a new smart pointer type in this block.
377                // - Have this smart pointer type have more than 1 generic type.
378                // In this case, the inner smart pointer derive would be complaining that it
379                // needs a pointer attribute. Meanwhile, the outer macro would be complaining
380                // that we attached a #[pointee] to a generic type argument while helpfully
381                // informing the user that #[pointee] can only be attached to generic pointer arguments
382                if let Some(x) = default {
    match ::rustc_ast_ir::visit::VisitorResult::branch(error_on_pointee.visit_ty(x))
        {
        core::ops::ControlFlow::Continue(()) =>
            (),
            #[allow(unreachable_code)]
            core::ops::ControlFlow::Break(r) => {
            return ::rustc_ast_ir::visit::VisitorResult::from_residual(r);
        }
    };
};rustc_ast::visit::visit_opt!(error_on_pointee, visit_ty, default);
383            }
384
385            GenericParamKind::Const { .. } | GenericParamKind::Lifetime => {
386                rustc_ast::visit::walk_generic_param(&mut error_on_pointee, param);
387            }
388        }
389    }
390
391    fn visit_ty(&mut self, t: &'a rustc_ast::Ty) -> Self::Result {
392        let mut error_on_pointee = AlwaysErrorOnGenericParam { cx: self.cx };
393        error_on_pointee.visit_ty(t)
394    }
395}
396
397struct AlwaysErrorOnGenericParam<'a, 'b> {
398    cx: &'a ExtCtxt<'b>,
399}
400
401impl<'a, 'b> rustc_ast::visit::Visitor<'a> for AlwaysErrorOnGenericParam<'a, 'b> {
402    fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) -> Self::Result {
403        if attr.has_name(sym::pointee) {
404            self.cx.dcx().emit_err(diagnostics::NonGenericPointee { span: attr.span });
405        }
406    }
407}
408
409#[derive(const _: () =
    {
        impl<'_sess> rustc_errors::Diagnostic<'_sess> for RequireTransparent {
            #[track_caller]
            fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'_sess>,
                level: rustc_errors::Level) -> rustc_errors::Diag<'_sess> {
                match self {
                    RequireTransparent { span: __binding_0 } => {
                        let mut diag =
                            rustc_errors::Diag::new(dcx, level,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("`CoercePointee` can only be derived on `struct`s with `#[repr(transparent)]`")));
                        diag.code(E0802);
                        ;
                        diag.span(__binding_0);
                        diag
                    }
                }
            }
        }
    };Diagnostic)]
410#[diag("`CoercePointee` can only be derived on `struct`s with `#[repr(transparent)]`", code = E0802)]
411struct RequireTransparent {
412    #[primary_span]
413    span: Span,
414}
415
416#[derive(const _: () =
    {
        impl<'_sess> rustc_errors::Diagnostic<'_sess> for RequireOneField {
            #[track_caller]
            fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'_sess>,
                level: rustc_errors::Level) -> rustc_errors::Diag<'_sess> {
                match self {
                    RequireOneField { span: __binding_0 } => {
                        let mut diag =
                            rustc_errors::Diag::new(dcx, level,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("`CoercePointee` can only be derived on `struct`s with at least one field")));
                        diag.code(E0802);
                        ;
                        diag.span(__binding_0);
                        diag
                    }
                }
            }
        }
    };Diagnostic)]
417#[diag("`CoercePointee` can only be derived on `struct`s with at least one field", code = E0802)]
418struct RequireOneField {
419    #[primary_span]
420    span: Span,
421}
422
423#[derive(const _: () =
    {
        impl<'_sess> rustc_errors::Diagnostic<'_sess> for RequireOneGeneric {
            #[track_caller]
            fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'_sess>,
                level: rustc_errors::Level) -> rustc_errors::Diag<'_sess> {
                match self {
                    RequireOneGeneric { span: __binding_0 } => {
                        let mut diag =
                            rustc_errors::Diag::new(dcx, level,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("`CoercePointee` can only be derived on `struct`s that are generic over at least one type")));
                        diag.code(E0802);
                        ;
                        diag.span(__binding_0);
                        diag
                    }
                }
            }
        }
    };Diagnostic)]
424#[diag("`CoercePointee` can only be derived on `struct`s that are generic over at least one type", code = E0802)]
425struct RequireOneGeneric {
426    #[primary_span]
427    span: Span,
428}
429
430#[derive(const _: () =
    {
        impl<'_sess> rustc_errors::Diagnostic<'_sess> for RequireOnePointee {
            #[track_caller]
            fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'_sess>,
                level: rustc_errors::Level) -> rustc_errors::Diag<'_sess> {
                match self {
                    RequireOnePointee { span: __binding_0 } => {
                        let mut diag =
                            rustc_errors::Diag::new(dcx, level,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("exactly one generic type parameter must be marked as `#[pointee]` to derive `CoercePointee` traits")));
                        diag.code(E0802);
                        ;
                        diag.span(__binding_0);
                        diag
                    }
                }
            }
        }
    };Diagnostic)]
431#[diag("exactly one generic type parameter must be marked as `#[pointee]` to derive `CoercePointee` traits", code = E0802)]
432struct RequireOnePointee {
433    #[primary_span]
434    span: Span,
435}
436
437#[derive(const _: () =
    {
        impl<'_sess> rustc_errors::Diagnostic<'_sess> for TooManyPointees {
            #[track_caller]
            fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'_sess>,
                level: rustc_errors::Level) -> rustc_errors::Diag<'_sess> {
                match self {
                    TooManyPointees { one: __binding_0, another: __binding_1 }
                        => {
                        let mut diag =
                            rustc_errors::Diag::new(dcx, level,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("only one type parameter can be marked as `#[pointee]` when deriving `CoercePointee` traits")));
                        diag.code(E0802);
                        ;
                        diag.span(__binding_0);
                        diag.span_label(__binding_1,
                            rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("here another type parameter is marked as `#[pointee]`")));
                        diag
                    }
                }
            }
        }
    };Diagnostic)]
438#[diag("only one type parameter can be marked as `#[pointee]` when deriving `CoercePointee` traits", code = E0802)]
439struct TooManyPointees {
440    #[primary_span]
441    one: Span,
442    #[label("here another type parameter is marked as `#[pointee]`")]
443    another: Span,
444}
445
446#[derive(const _: () =
    {
        impl<'_sess> rustc_errors::Diagnostic<'_sess> for RequiresMaybeSized {
            #[track_caller]
            fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'_sess>,
                level: rustc_errors::Level) -> rustc_errors::Diag<'_sess> {
                match self {
                    RequiresMaybeSized { span: __binding_0, name: __binding_1 }
                        => {
                        let mut diag =
                            rustc_errors::Diag::new(dcx, level,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("`derive(CoercePointee)` requires `{$name}` to be marked `?Sized`")));
                        diag.code(E0802);
                        ;
                        diag.arg("name", __binding_1);
                        diag.span(__binding_0);
                        diag
                    }
                }
            }
        }
    };Diagnostic)]
447#[diag("`derive(CoercePointee)` requires `{$name}` to be marked `?Sized`", code = E0802)]
448struct RequiresMaybeSized {
449    #[primary_span]
450    span: Span,
451    name: Ident,
452}