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, ¯os);
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 let fn_ident = if let ast::ItemKind::Fn(fn_) = &item.kind {
222 fn_.ident
223 } else {
224 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 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
258fn 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 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 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 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 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}