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, Symbol, kw, sym};
16use smallvec::smallvec;
17use thin_vec::{ThinVec, thin_vec};
18
19use crate::errors;
20
21struct ProcMacroDerive {
22 id: NodeId,
23 trait_name: Symbol,
24 function_ident: Ident,
25 span: Span,
26 attrs: ThinVec<Symbol>,
27}
28
29struct ProcMacroDef {
30 id: NodeId,
31 function_ident: Ident,
32 span: Span,
33}
34
35enum ProcMacro {
36 Derive(ProcMacroDerive),
37 Attr(ProcMacroDef),
38 Bang(ProcMacroDef),
39}
40
41struct CollectProcMacros<'a> {
42 macros: Vec<ProcMacro>,
43 in_root: bool,
44 dcx: DiagCtxtHandle<'a>,
45 session: &'a Session,
46 source_map: &'a SourceMap,
47 is_proc_macro_crate: bool,
48 is_test_crate: bool,
49}
50
51pub fn inject(
52 krate: &mut ast::Crate,
53 sess: &Session,
54 features: &Features,
55 resolver: &mut dyn ResolverExpand,
56 is_proc_macro_crate: bool,
57 has_proc_macro_decls: bool,
58 is_test_crate: bool,
59 dcx: DiagCtxtHandle<'_>,
60) {
61 let ecfg = ExpansionConfig::default(sym::proc_macro, features);
62 let mut cx = ExtCtxt::new(sess, ecfg, resolver, None);
63
64 let mut collect = CollectProcMacros {
65 macros: Vec::new(),
66 in_root: true,
67 dcx,
68 session: sess,
69 source_map: sess.source_map(),
70 is_proc_macro_crate,
71 is_test_crate,
72 };
73
74 if has_proc_macro_decls || is_proc_macro_crate {
75 visit::walk_crate(&mut collect, krate);
76 }
77 let macros = collect.macros;
78
79 if !is_proc_macro_crate {
80 return;
81 }
82
83 if is_test_crate {
84 return;
85 }
86
87 let decls = mk_decls(&mut cx, ¯os);
88 krate.items.push(decls);
89}
90
91impl<'a> CollectProcMacros<'a> {
92 fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) {
93 if self.is_proc_macro_crate && self.in_root && vis.kind.is_pub() {
94 self.dcx.emit_err(errors::ProcMacro { span: sp });
95 }
96 }
97
98 fn collect_custom_derive(
99 &mut self,
100 item: &'a ast::Item,
101 function_ident: Ident,
102 attr: &'a ast::Attribute,
103 ) {
104 let Some(rustc_hir::Attribute::Parsed(AttributeKind::ProcMacroDerive {
105 trait_name,
106 helper_attrs,
107 ..
108 })) = AttributeParser::parse_limited(
109 self.session,
110 slice::from_ref(attr),
111 &[sym::proc_macro_derive],
112 )
113 else {
114 return;
115 };
116
117 if self.in_root && item.vis.kind.is_pub() {
118 self.macros.push(ProcMacro::Derive(ProcMacroDerive {
119 id: item.id,
120 span: item.span,
121 trait_name,
122 function_ident,
123 attrs: helper_attrs,
124 }));
125 } else {
126 let msg = if !self.in_root {
127 "functions tagged with `#[proc_macro_derive]` must \
128 currently reside in the root of the crate"
129 } else {
130 "functions tagged with `#[proc_macro_derive]` must be `pub`"
131 };
132 self.dcx.span_err(self.source_map.guess_head_span(item.span), msg);
133 }
134 }
135
136 fn collect_attr_proc_macro(&mut self, item: &'a ast::Item, function_ident: Ident) {
137 if self.in_root && item.vis.kind.is_pub() {
138 self.macros.push(ProcMacro::Attr(ProcMacroDef {
139 id: item.id,
140 span: item.span,
141 function_ident,
142 }));
143 } else {
144 let msg = if !self.in_root {
145 "functions tagged with `#[proc_macro_attribute]` must \
146 currently reside in the root of the crate"
147 } else {
148 "functions tagged with `#[proc_macro_attribute]` must be `pub`"
149 };
150 self.dcx.span_err(self.source_map.guess_head_span(item.span), msg);
151 }
152 }
153
154 fn collect_bang_proc_macro(&mut self, item: &'a ast::Item, function_ident: Ident) {
155 if self.in_root && item.vis.kind.is_pub() {
156 self.macros.push(ProcMacro::Bang(ProcMacroDef {
157 id: item.id,
158 span: item.span,
159 function_ident,
160 }));
161 } else {
162 let msg = if !self.in_root {
163 "functions tagged with `#[proc_macro]` must \
164 currently reside in the root of the crate"
165 } else {
166 "functions tagged with `#[proc_macro]` must be `pub`"
167 };
168 self.dcx.span_err(self.source_map.guess_head_span(item.span), msg);
169 }
170 }
171}
172
173impl<'a> Visitor<'a> for CollectProcMacros<'a> {
174 fn visit_item(&mut self, item: &'a ast::Item) {
175 if let ast::ItemKind::MacroDef(..) = item.kind {
176 if self.is_proc_macro_crate && attr::contains_name(&item.attrs, sym::macro_export) {
177 self.dcx.emit_err(errors::ExportMacroRules {
178 span: self.source_map.guess_head_span(item.span),
179 });
180 }
181 }
182
183 let mut found_attr: Option<&'a ast::Attribute> = None;
184
185 for attr in &item.attrs {
186 if attr.is_proc_macro_attr() {
187 if let Some(prev_attr) = found_attr {
188 let prev_item = prev_attr.get_normal_item();
189 let item = attr.get_normal_item();
190 let path_str = pprust::path_to_string(&item.path);
191 let msg = if item.path.segments[0].ident.name
192 == prev_item.path.segments[0].ident.name
193 {
194 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("only one `#[{0}]` attribute is allowed on any given function",
path_str))
})format!(
195 "only one `#[{path_str}]` attribute is allowed on any given function",
196 )
197 } else {
198 ::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!(
199 "`#[{}]` and `#[{}]` attributes cannot both be applied
200 to the same function",
201 path_str,
202 pprust::path_to_string(&prev_item.path),
203 )
204 };
205
206 self.dcx
207 .struct_span_err(attr.span, msg)
208 .with_span_label(prev_attr.span, "previous attribute here")
209 .emit();
210
211 return;
212 }
213
214 found_attr = Some(attr);
215 }
216 }
217
218 let Some(attr) = found_attr else {
219 self.check_not_pub_in_root(&item.vis, self.source_map.guess_head_span(item.span));
220 let prev_in_root = mem::replace(&mut self.in_root, false);
221 visit::walk_item(self, item);
222 self.in_root = prev_in_root;
223 return;
224 };
225
226 let fn_ident = if let ast::ItemKind::Fn(fn_) = &item.kind {
229 fn_.ident
230 } else {
231 return;
233 };
234
235 if self.is_test_crate {
236 return;
237 }
238
239 if !self.is_proc_macro_crate {
240 self.dcx
241 .create_err(errors::AttributeOnlyUsableWithCrateType {
242 span: attr.span,
243 path: &pprust::path_to_string(&attr.get_normal_item().path),
244 })
245 .emit();
246 return;
247 }
248
249 if attr.has_name(sym::proc_macro_derive) {
251 self.collect_custom_derive(item, fn_ident, attr);
252 } else if attr.has_name(sym::proc_macro_attribute) {
253 self.collect_attr_proc_macro(item, fn_ident);
254 } else if attr.has_name(sym::proc_macro) {
255 self.collect_bang_proc_macro(item, fn_ident);
256 };
257
258 let prev_in_root = mem::replace(&mut self.in_root, false);
259 visit::walk_item(self, item);
260 self.in_root = prev_in_root;
261 }
262}
263
264fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> Box<ast::Item> {
281 let expn_id = cx.resolver.expansion_for_ast_pass(
282 DUMMY_SP,
283 AstPass::ProcMacroHarness,
284 &[sym::rustc_attrs, sym::proc_macro_internals],
285 None,
286 );
287 let span = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
288
289 let proc_macro = Ident::new(sym::proc_macro, span);
290 let krate = cx.item(span, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None, proc_macro));
291
292 let bridge = Ident::new(sym::bridge, span);
293 let client = Ident::new(sym::client, span);
294 let proc_macro_ty = Ident::new(sym::ProcMacro, span);
295 let custom_derive = Ident::new(sym::custom_derive, span);
296 let attr = Ident::new(sym::attr, span);
297 let bang = Ident::new(sym::bang, span);
298
299 let decls = macros
304 .iter()
305 .map(|m| {
306 let harness_span = span;
307 let span = match m {
308 ProcMacro::Derive(m) => m.span,
309 ProcMacro::Attr(m) | ProcMacro::Bang(m) => m.span,
310 };
311 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]));
312 let proc_macro_ty_method_path = |cx: &ExtCtxt<'_>, method| {
313 cx.expr_path(cx.path(
314 span.with_ctxt(harness_span.ctxt()),
315 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[proc_macro, bridge, client, proc_macro_ty, method]))vec![proc_macro, bridge, client, proc_macro_ty, method],
316 ))
317 };
318 match m {
319 ProcMacro::Derive(cd) => {
320 cx.resolver.declare_proc_macro(cd.id);
321 cx.expr_call(
324 harness_span,
325 proc_macro_ty_method_path(cx, custom_derive),
326 {
let len = [(), (), ()].len();
let mut vec = ::thin_vec::ThinVec::with_capacity(len);
vec.push(cx.expr_str(span, cd.trait_name));
vec.push(cx.expr_array_ref(span,
cd.attrs.iter().map(|&s|
cx.expr_str(span, s)).collect::<ThinVec<_>>()));
vec.push(local_path(cx, cd.function_ident));
vec
}thin_vec![
327 cx.expr_str(span, cd.trait_name),
328 cx.expr_array_ref(
329 span,
330 cd.attrs
331 .iter()
332 .map(|&s| cx.expr_str(span, s))
333 .collect::<ThinVec<_>>(),
334 ),
335 local_path(cx, cd.function_ident),
336 ],
337 )
338 }
339 ProcMacro::Attr(ca) | ProcMacro::Bang(ca) => {
340 cx.resolver.declare_proc_macro(ca.id);
341 let ident = match m {
342 ProcMacro::Attr(_) => attr,
343 ProcMacro::Bang(_) => bang,
344 ProcMacro::Derive(_) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
345 };
346
347 cx.expr_call(
350 harness_span,
351 proc_macro_ty_method_path(cx, ident),
352 {
let len = [(), ()].len();
let mut vec = ::thin_vec::ThinVec::with_capacity(len);
vec.push(cx.expr_str(span, ca.function_ident.name));
vec.push(local_path(cx, ca.function_ident));
vec
}thin_vec![
353 cx.expr_str(span, ca.function_ident.name),
354 local_path(cx, ca.function_ident),
355 ],
356 )
357 }
358 }
359 })
360 .collect();
361
362 let mut decls_static = cx.item_static(
363 span,
364 Ident::new(sym::_DECLS, span),
365 cx.ty_ref(
366 span,
367 cx.ty(
368 span,
369 ast::TyKind::Slice(
370 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, proc_macro_ty]))vec![proc_macro, bridge, client, proc_macro_ty])),
371 ),
372 ),
373 None,
374 ast::Mutability::Not,
375 ),
376 ast::Mutability::Not,
377 cx.expr_array_ref(span, decls),
378 );
379 decls_static.attrs.extend([
380 cx.attr_word(sym::rustc_proc_macro_decls, span),
381 cx.attr_word(sym::used, span),
382 cx.attr_nested_word(sym::allow, sym::deprecated, span),
383 ]);
384
385 let block = ast::ConstItemRhsKind::new_body(cx.expr_block(
386 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)]),
387 ));
388
389 let anon_constant = cx.item_const(
390 span,
391 Ident::new(kw::Underscore, span),
392 cx.ty(span, ast::TyKind::Tup(ThinVec::new())),
393 block,
394 );
395
396 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]);
398 cx.monotonic_expander().fully_expand_fragment(items).make_items().pop().unwrap()
399}