Skip to main content

rustc_builtin_macros/
rustdoc_skip_body.rs

1//! The expansion from a function when the code would not compile with rustdoc: so if `cfg(doc)`,
2//! expands to a `loop {}`.
3
4use rustc_ast::token::{self, Delimiter, IdentKind};
5use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
6use rustc_ast::{
7    AssocItemKind, AttrArgs, AttrItem, Attribute, DelimArgs, ExprKind, Fn, ItemKind, MetaItem,
8    Safety,
9};
10use rustc_expand::base::*;
11use rustc_span::{Ident, Span, sym};
12use thin_vec::ThinVec;
13
14pub(crate) fn expand(
15    cx: &mut ExtCtxt<'_>,
16    attr_path_sp: Span,
17    meta_item: &MetaItem,
18    mut item: Annotatable,
19) -> Vec<Annotatable> {
20    if !meta_item.is_word() {
21        cx.dcx().struct_span_err(attr_path_sp, "`rustdoc_skip_body` doesn't take arguments").emit();
22    }
23    let mut is_expected_item = false;
24    match item {
25        Annotatable::Item(ref mut item) => {
26            if let ItemKind::Fn(ref mut fn_) = item.kind {
27                expand_macro(cx, attr_path_sp, fn_);
28                is_expected_item = true;
29            }
30        }
31        Annotatable::AssocItem(ref mut item, _) => {
32            if let AssocItemKind::Fn(ref mut fn_) = item.kind {
33                expand_macro(cx, attr_path_sp, fn_);
34                is_expected_item = true;
35            }
36        }
37        _ => {}
38    }
39    if !is_expected_item {
40        cx.dcx()
41            .struct_span_err(attr_path_sp, "`rustdoc_skip_body` can only be used on functions")
42            .emit();
43    }
44    ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [item]))vec![item]
45}
46
47fn cfg_not_doc(cx: &mut ExtCtxt<'_>, sp: Span) -> Attribute {
48    // `not(doc)`
49    let not_doc = TokenStream::new(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [TokenTree::token_alone(token::Ident(sym::not, IdentKind::Normal),
                    sp),
                TokenTree::Delimited(DelimSpan::from_single(sp),
                    DelimSpacing::new(Spacing::JointHidden, Spacing::Alone),
                    Delimiter::Parenthesis,
                    TokenStream::token_alone(token::Ident(sym::doc,
                            IdentKind::Normal), sp))]))vec![
50        TokenTree::token_alone(token::Ident(sym::not, IdentKind::Normal), sp),
51        TokenTree::Delimited(
52            DelimSpan::from_single(sp),
53            DelimSpacing::new(Spacing::JointHidden, Spacing::Alone),
54            Delimiter::Parenthesis,
55            TokenStream::token_alone(token::Ident(sym::doc, IdentKind::Normal), sp),
56        ),
57    ]);
58
59    cx.attr_nested(
60        AttrItem {
61            unsafety: Safety::Default,
62            path: cx.path_ident(sp, Ident::new(sym::cfg, sp)),
63            args: AttrArgs::Delimited(DelimArgs {
64                dspan: DelimSpan::from_single(sp),
65                delim: Delimiter::Parenthesis,
66                tokens: not_doc,
67            }),
68            span: sp,
69        },
70        sp,
71    )
72}
73
74fn expand_macro(cx: &mut ExtCtxt<'_>, sp: Span, fn_: &mut Fn) {
75    let Some(old_body) = fn_.body.take() else {
76        // No body... Well, no possibility to not compile I guess so all good.
77        return;
78    };
79    let mut old_body = cx.expr_block(old_body);
80    // We add on the function's body `#[cfg(not(doc))]`.
81    old_body.attrs.push(cfg_not_doc(cx, sp));
82
83    // We create the `#[cfg(doc)] loop {}` "block".
84    let mut loop_expr = cx.expr(sp, ExprKind::Loop(cx.block(sp, ThinVec::new()), None, sp));
85    loop_expr.attrs.push(cx.attr_nested_word(sym::cfg, sym::doc, sp));
86
87    let mut stmts = ThinVec::new();
88    stmts.push(cx.stmt_expr(loop_expr));
89    stmts.push(cx.stmt_expr(old_body));
90
91    // We create a new function body containing both blocks.
92    fn_.body = Some(cx.block(sp, stmts));
93}