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