Skip to main content

rustc_builtin_macros/
proc_macro_harness.rs

1use std::{mem, slice};
2
3use rustc_ast::visit::{self, Visitor};
4use rustc_ast::{self as ast, NodeId, attr};
5use rustc_ast_pretty::pprust;
6use rustc_attr_ir::AttributeKind;
7use rustc_attr_parsing::AttributeParser;
8use rustc_errors::DiagCtxtHandle;
9use rustc_expand::base::{ExtCtxt, ResolverExpand};
10use rustc_expand::expand::{AstFragment, ExpansionConfig};
11use rustc_feature::Features;
12use rustc_session::Session;
13use rustc_span::hygiene::AstPass;
14use rustc_span::source_map::SourceMap;
15use rustc_span::{DUMMY_SP, Ident, Span, kw, sym};
16use smallvec::smallvec;
17use thin_vec::{ThinVec, thin_vec};
18
19use crate::diagnostics;
20
21struct ProcMacroDerive {
22    id: NodeId,
23    function_ident: Ident,
24    span: Span,
25}
26
27struct ProcMacroDef {
28    id: NodeId,
29    function_ident: Ident,
30    span: Span,
31}
32
33enum ProcMacro {
34    Derive(ProcMacroDerive),
35    Attr(ProcMacroDef),
36    Bang(ProcMacroDef),
37}
38
39struct CollectProcMacros<'a> {
40    macros: Vec<ProcMacro>,
41    in_root: bool,
42    dcx: DiagCtxtHandle<'a>,
43    session: &'a Session,
44    source_map: &'a SourceMap,
45    is_proc_macro_crate: bool,
46    is_test_crate: bool,
47}
48
49pub fn inject(
50    krate: &mut ast::Crate,
51    sess: &Session,
52    features: &Features,
53    resolver: &mut dyn ResolverExpand,
54    is_proc_macro_crate: bool,
55    has_proc_macro_decls: bool,
56    is_test_crate: bool,
57    dcx: DiagCtxtHandle<'_>,
58) {
59    let ecfg = ExpansionConfig::default(sym::proc_macro, features);
60    let mut cx = ExtCtxt::new(sess, ecfg, resolver, None);
61
62    let mut collect = CollectProcMacros {
63        macros: Vec::new(),
64        in_root: true,
65        dcx,
66        session: sess,
67        source_map: sess.source_map(),
68        is_proc_macro_crate,
69        is_test_crate,
70    };
71
72    if has_proc_macro_decls || is_proc_macro_crate {
73        visit::walk_crate(&mut collect, krate);
74    }
75    let macros = collect.macros;
76
77    if !is_proc_macro_crate {
78        return;
79    }
80
81    if is_test_crate {
82        return;
83    }
84
85    let decls = mk_decls(&mut cx, &macros);
86    krate.items.push(decls);
87}
88
89impl<'a> CollectProcMacros<'a> {
90    fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) {
91        if self.is_proc_macro_crate && self.in_root && vis.kind.is_pub() {
92            self.dcx.emit_err(diagnostics::ProcMacro { span: sp });
93        }
94    }
95
96    fn collect_custom_derive(
97        &mut self,
98        item: &'a ast::Item,
99        function_ident: Ident,
100        attr: &'a ast::Attribute,
101    ) {
102        let Some(rustc_attr_ir::Attribute::Parsed(AttributeKind::ProcMacroDerive { .. })) =
103            AttributeParser::parse_limited_sym(
104                self.session,
105                slice::from_ref(attr),
106                &[sym::proc_macro_derive],
107            )
108        else {
109            return;
110        };
111
112        if self.in_root && item.vis.kind.is_pub() {
113            self.macros.push(ProcMacro::Derive(ProcMacroDerive {
114                id: item.id,
115                span: item.span,
116                function_ident,
117            }));
118        } else {
119            let msg = if !self.in_root {
120                "functions tagged with `#[proc_macro_derive]` must \
121                 currently reside in the root of the crate"
122            } else {
123                "functions tagged with `#[proc_macro_derive]` must be `pub`"
124            };
125            self.dcx.span_err(self.source_map.guess_head_span(item.span), msg);
126        }
127    }
128
129    fn collect_attr_proc_macro(&mut self, item: &'a ast::Item, function_ident: Ident) {
130        if self.in_root && item.vis.kind.is_pub() {
131            self.macros.push(ProcMacro::Attr(ProcMacroDef {
132                id: item.id,
133                span: item.span,
134                function_ident,
135            }));
136        } else {
137            let msg = if !self.in_root {
138                "functions tagged with `#[proc_macro_attribute]` must \
139                 currently reside in the root of the crate"
140            } else {
141                "functions tagged with `#[proc_macro_attribute]` must be `pub`"
142            };
143            self.dcx.span_err(self.source_map.guess_head_span(item.span), msg);
144        }
145    }
146
147    fn collect_bang_proc_macro(&mut self, item: &'a ast::Item, function_ident: Ident) {
148        if self.in_root && item.vis.kind.is_pub() {
149            self.macros.push(ProcMacro::Bang(ProcMacroDef {
150                id: item.id,
151                span: item.span,
152                function_ident,
153            }));
154        } else {
155            let msg = if !self.in_root {
156                "functions tagged with `#[proc_macro]` must \
157                 currently reside in the root of the crate"
158            } else {
159                "functions tagged with `#[proc_macro]` must be `pub`"
160            };
161            self.dcx.span_err(self.source_map.guess_head_span(item.span), msg);
162        }
163    }
164}
165
166impl<'a> Visitor<'a> for CollectProcMacros<'a> {
167    fn visit_item(&mut self, item: &'a ast::Item) {
168        if let ast::ItemKind::MacroDef(..) = item.kind {
169            if self.is_proc_macro_crate && attr::contains_name(&item.attrs, sym::macro_export) {
170                self.dcx.emit_err(diagnostics::ExportMacroRules {
171                    span: self.source_map.guess_head_span(item.span),
172                });
173            }
174        }
175
176        let mut found_attr: Option<&'a ast::Attribute> = None;
177
178        for attr in &item.attrs {
179            if attr.is_proc_macro_attr() {
180                if let Some(prev_attr) = found_attr {
181                    let prev_item = prev_attr.get_normal_item();
182                    let item = attr.get_normal_item();
183                    let path_str = pprust::path_to_string(&item.path);
184                    let msg = if item.path.segments[0].ident.name
185                        == prev_item.path.segments[0].ident.name
186                    {
187                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("only one `#[{0}]` attribute is allowed on any given function",
                path_str))
    })format!(
188                            "only one `#[{path_str}]` attribute is allowed on any given function",
189                        )
190                    } else {
191                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`#[{0}]` and `#[{1}]` attributes cannot both be applied\n                            to the same function",
                path_str, pprust::path_to_string(&prev_item.path)))
    })format!(
192                            "`#[{}]` and `#[{}]` attributes cannot both be applied
193                            to the same function",
194                            path_str,
195                            pprust::path_to_string(&prev_item.path),
196                        )
197                    };
198
199                    self.dcx
200                        .struct_span_err(attr.span, msg)
201                        .with_span_label(prev_attr.span, "previous attribute here")
202                        .emit();
203
204                    return;
205                }
206
207                found_attr = Some(attr);
208            }
209        }
210
211        let Some(attr) = found_attr else {
212            self.check_not_pub_in_root(&item.vis, self.source_map.guess_head_span(item.span));
213            let prev_in_root = mem::replace(&mut self.in_root, false);
214            visit::walk_item(self, item);
215            self.in_root = prev_in_root;
216            return;
217        };
218
219        // Make sure we're checking a bare function. If we're not then we're
220        // just not interested any further in this item.
221        let fn_ident = if let ast::ItemKind::Fn(fn_) = &item.kind {
222            fn_.ident
223        } else {
224            // Error handled by general target checking logic
225            return;
226        };
227
228        if self.is_test_crate {
229            return;
230        }
231
232        if !self.is_proc_macro_crate {
233            let path = &attr.get_normal_item().path;
234            self.dcx
235                .create_err(diagnostics::AttributeOnlyUsableWithCrateType {
236                    span: path.span,
237                    path: &pprust::path_to_string(&attr.get_normal_item().path),
238                })
239                .emit();
240            return;
241        }
242
243        // Try to locate a `#[proc_macro_derive]` attribute.
244        if attr.has_name(sym::proc_macro_derive) {
245            self.collect_custom_derive(item, fn_ident, attr);
246        } else if attr.has_name(sym::proc_macro_attribute) {
247            self.collect_attr_proc_macro(item, fn_ident);
248        } else if attr.has_name(sym::proc_macro) {
249            self.collect_bang_proc_macro(item, fn_ident);
250        };
251
252        let prev_in_root = mem::replace(&mut self.in_root, false);
253        visit::walk_item(self, item);
254        self.in_root = prev_in_root;
255    }
256}
257
258// Creates a new module which looks like:
259//
260//      const _: () = {
261//          extern crate proc_macro;
262//
263//          use proc_macro::bridge::client::ProcMacro;
264//
265//          #[rustc_proc_macro_decls]
266//          #[used]
267//          #[allow(deprecated)]
268//          static DECLS: &[ProcMacro] = &[
269//              ProcMacro::custom_derive($name_trait1, &[], ::$name1);
270//              ProcMacro::custom_derive($name_trait2, &["attribute_name"], ::$name2);
271//              // ...
272//          ];
273//      }
274fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> Box<ast::Item> {
275    let expn_id = cx.resolver.expansion_for_ast_pass(
276        DUMMY_SP,
277        AstPass::ProcMacroHarness,
278        &[sym::rustc_attrs, sym::proc_macro_internals],
279        None,
280    );
281    let span = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
282
283    let proc_macro = Ident::new(sym::proc_macro, span);
284    let krate = cx.item(span, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None, proc_macro));
285
286    let bridge = Ident::new(sym::bridge, span);
287    let client = Ident::new(sym::client, span);
288    let client_ty = Ident::new(sym::Client, span);
289    let expand1 = Ident::new(sym::expand1, span);
290    let expand2 = Ident::new(sym::expand2, span);
291
292    // We add NodeIds to 'resolver.proc_macros' in the order
293    // that we generate expressions. The position of each NodeId
294    // in the 'proc_macros' Vec corresponds to its position
295    // in the static array that will be generated
296    let decls = macros
297        .iter()
298        .map(|m| {
299            let harness_span = span;
300            let span = match m {
301                ProcMacro::Derive(m) => m.span,
302                ProcMacro::Attr(m) | ProcMacro::Bang(m) => m.span,
303            };
304            let local_path = |cx: &ExtCtxt<'_>, ident| cx.expr_path(cx.path(span, ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ident]))vec![ident]));
305            let proc_macro_ty_method_path = |cx: &ExtCtxt<'_>, method| {
306                cx.expr_path(cx.path(
307                    span.with_ctxt(harness_span.ctxt()),
308                    ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [proc_macro, bridge, client, client_ty, method]))vec![proc_macro, bridge, client, client_ty, method],
309                ))
310            };
311            match m {
312                ProcMacro::Derive(cd) => {
313                    cx.resolver.declare_proc_macro(cd.id);
314                    // The call needs to use `harness_span` so that the const stability checker
315                    // accepts it.
316                    cx.expr_call(
317                        harness_span,
318                        proc_macro_ty_method_path(cx, expand1),
319                        {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(local_path(cx, cd.function_ident));
    vec
}thin_vec![local_path(cx, cd.function_ident)],
320                    )
321                }
322                ProcMacro::Attr(ca) | ProcMacro::Bang(ca) => {
323                    cx.resolver.declare_proc_macro(ca.id);
324                    let ident = match m {
325                        ProcMacro::Attr(_) => expand2,
326                        ProcMacro::Bang(_) => expand1,
327                        ProcMacro::Derive(_) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
328                    };
329
330                    // The call needs to use `harness_span` so that the const stability checker
331                    // accepts it.
332                    cx.expr_call(
333                        harness_span,
334                        proc_macro_ty_method_path(cx, ident),
335                        {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(local_path(cx, ca.function_ident));
    vec
}thin_vec![local_path(cx, ca.function_ident)],
336                    )
337                }
338            }
339        })
340        .collect();
341
342    let mut decls_static = cx.item_static(
343        span,
344        Ident::new(sym::_DECLS, span),
345        cx.ty_ref(
346            span,
347            cx.ty(
348                span,
349                ast::TyKind::Slice(
350                    cx.ty_path(cx.path(span, ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [proc_macro, bridge, client, client_ty]))vec![proc_macro, bridge, client, client_ty])),
351                ),
352            ),
353            None,
354            ast::Mutability::Not,
355        ),
356        ast::Mutability::Not,
357        cx.expr_array_ref(span, decls),
358    );
359    decls_static.attrs.extend([
360        cx.attr_word(sym::rustc_proc_macro_decls, span),
361        cx.attr_word(sym::used, span),
362        cx.attr_nested_word(sym::allow, sym::deprecated, span),
363    ]);
364
365    let block = cx.expr_block(
366        cx.block(span, {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(cx.stmt_item(span, krate));
    vec.push(cx.stmt_item(span, decls_static));
    vec
}thin_vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static)]),
367    );
368
369    let anon_constant = cx.item_const(
370        span,
371        Ident::new(kw::Underscore, span),
372        cx.ty(span, ast::TyKind::Tup(ThinVec::new())),
373        Some(block),
374        ast::ConstItemKind::Body,
375    );
376
377    // Integrate the new item into existing module structures.
378    let items = AstFragment::Items({
    let count = 0usize + 1usize;
    let mut vec = ::smallvec::SmallVec::new();
    if count <= vec.inline_size() {
        vec.push(anon_constant);
        vec
    } else {
        ::smallvec::SmallVec::from_vec(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                    [anon_constant])))
    }
}smallvec![anon_constant]);
379    cx.monotonic_expander().fully_expand_fragment(items).make_items().pop().unwrap()
380}