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.emit_err(diagnostics::AttributeOnlyUsableWithCrateType {
235                span: path.span,
236                path: &pprust::path_to_string(&attr.get_normal_item().path),
237            });
238            return;
239        }
240
241        // Try to locate a `#[proc_macro_derive]` attribute.
242        if attr.has_name(sym::proc_macro_derive) {
243            self.collect_custom_derive(item, fn_ident, attr);
244        } else if attr.has_name(sym::proc_macro_attribute) {
245            self.collect_attr_proc_macro(item, fn_ident);
246        } else if attr.has_name(sym::proc_macro) {
247            self.collect_bang_proc_macro(item, fn_ident);
248        };
249
250        let prev_in_root = mem::replace(&mut self.in_root, false);
251        visit::walk_item(self, item);
252        self.in_root = prev_in_root;
253    }
254}
255
256// Creates a new module which looks like:
257//
258//      const _: () = {
259//          extern crate proc_macro;
260//
261//          use proc_macro::bridge::client::ProcMacro;
262//
263//          #[rustc_proc_macro_decls]
264//          #[used]
265//          #[allow(deprecated)]
266//          static DECLS: &[ProcMacro] = &[
267//              ProcMacro::custom_derive($name_trait1, &[], ::$name1);
268//              ProcMacro::custom_derive($name_trait2, &["attribute_name"], ::$name2);
269//              // ...
270//          ];
271//      }
272fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> Box<ast::Item> {
273    let expn_id = cx.resolver.expansion_for_ast_pass(
274        DUMMY_SP,
275        AstPass::ProcMacroHarness,
276        &[sym::rustc_attrs, sym::proc_macro_internals],
277        None,
278    );
279    let span = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
280
281    let proc_macro = Ident::new(sym::proc_macro, span);
282    let krate = cx.item(span, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None, proc_macro));
283
284    let bridge = Ident::new(sym::bridge, span);
285    let client = Ident::new(sym::client, span);
286    let client_ty = Ident::new(sym::Client, span);
287    let expand1 = Ident::new(sym::expand1, span);
288    let expand2 = Ident::new(sym::expand2, span);
289
290    // We add NodeIds to 'resolver.proc_macros' in the order
291    // that we generate expressions. The position of each NodeId
292    // in the 'proc_macros' Vec corresponds to its position
293    // in the static array that will be generated
294    let decls = macros
295        .iter()
296        .map(|m| {
297            let harness_span = span;
298            let span = match m {
299                ProcMacro::Derive(m) => m.span,
300                ProcMacro::Attr(m) | ProcMacro::Bang(m) => m.span,
301            };
302            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]));
303            let proc_macro_ty_method_path = |cx: &ExtCtxt<'_>, method| {
304                cx.expr_path(cx.path(
305                    span.with_ctxt(harness_span.ctxt()),
306                    ::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],
307                ))
308            };
309            match m {
310                ProcMacro::Derive(cd) => {
311                    cx.resolver.declare_proc_macro(cd.id);
312                    // The call needs to use `harness_span` so that the const stability checker
313                    // accepts it.
314                    cx.expr_call(
315                        harness_span,
316                        proc_macro_ty_method_path(cx, expand1),
317                        {
    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)],
318                    )
319                }
320                ProcMacro::Attr(ca) | ProcMacro::Bang(ca) => {
321                    cx.resolver.declare_proc_macro(ca.id);
322                    let ident = match m {
323                        ProcMacro::Attr(_) => expand2,
324                        ProcMacro::Bang(_) => expand1,
325                        ProcMacro::Derive(_) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
326                    };
327
328                    // The call needs to use `harness_span` so that the const stability checker
329                    // accepts it.
330                    cx.expr_call(
331                        harness_span,
332                        proc_macro_ty_method_path(cx, ident),
333                        {
    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)],
334                    )
335                }
336            }
337        })
338        .collect();
339
340    let mut decls_static = cx.item_static(
341        span,
342        Ident::new(sym::_DECLS, span),
343        cx.ty_ref(
344            span,
345            cx.ty(
346                span,
347                ast::TyKind::Slice(
348                    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])),
349                ),
350            ),
351            None,
352            ast::Mutability::Not,
353        ),
354        ast::Mutability::Not,
355        cx.expr_array_ref(span, decls),
356    );
357    decls_static.attrs.extend([
358        cx.attr_word(sym::rustc_proc_macro_decls, span),
359        cx.attr_word(sym::used, span),
360        cx.attr_nested_word(sym::allow, sym::deprecated, span),
361    ]);
362
363    let block = cx.expr_block(
364        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)]),
365    );
366
367    let anon_constant = cx.item_const(
368        span,
369        Ident::new(kw::Underscore, span),
370        cx.ty(span, ast::TyKind::Tup(ThinVec::new())),
371        Some(block),
372    );
373
374    // Integrate the new item into existing module structures.
375    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]);
376    cx.monotonic_expander().fully_expand_fragment(items).make_items().pop().unwrap()
377}