rustc_builtin_macros/
eii.rs

1use rustc_ast::token::{Delimiter, TokenKind};
2use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
3use rustc_ast::{
4    DUMMY_NODE_ID, EiiExternTarget, EiiImpl, ItemKind, Stmt, StmtKind, ast, token, tokenstream,
5};
6use rustc_ast_pretty::pprust::path_to_string;
7use rustc_expand::base::{Annotatable, ExtCtxt};
8use rustc_span::{Ident, Span, kw, sym};
9use thin_vec::{ThinVec, thin_vec};
10
11use crate::errors::{
12    EiiExternTargetExpectedList, EiiExternTargetExpectedMacro, EiiExternTargetExpectedUnsafe,
13    EiiMacroExpectedMaxOneArgument, EiiOnlyOnce, EiiSharedMacroExpectedFunction,
14};
15
16/// ```rust
17/// #[eii]
18/// fn panic_handler();
19///
20/// // or:
21///
22/// #[eii(panic_handler)]
23/// fn panic_handler();
24///
25/// // expansion:
26///
27/// extern "Rust" {
28///     fn panic_handler();
29/// }
30///
31/// #[rustc_builtin_macro(eii_shared_macro)]
32/// #[eii_extern_target(panic_handler)]
33/// macro panic_handler() {}
34/// ```
35pub(crate) fn eii(
36    ecx: &mut ExtCtxt<'_>,
37    span: Span,
38    meta_item: &ast::MetaItem,
39    item: Annotatable,
40) -> Vec<Annotatable> {
41    eii_(ecx, span, meta_item, item, false)
42}
43
44pub(crate) fn unsafe_eii(
45    ecx: &mut ExtCtxt<'_>,
46    span: Span,
47    meta_item: &ast::MetaItem,
48    item: Annotatable,
49) -> Vec<Annotatable> {
50    eii_(ecx, span, meta_item, item, true)
51}
52
53fn eii_(
54    ecx: &mut ExtCtxt<'_>,
55    span: Span,
56    meta_item: &ast::MetaItem,
57    item: Annotatable,
58    impl_unsafe: bool,
59) -> Vec<Annotatable> {
60    let span = ecx.with_def_site_ctxt(span);
61
62    let (item, stmt) = if let Annotatable::Item(item) = item {
63        (item, false)
64    } else if let Annotatable::Stmt(ref stmt) = item
65        && let StmtKind::Item(ref item) = stmt.kind
66    {
67        (item.clone(), true)
68    } else {
69        ecx.dcx().emit_err(EiiSharedMacroExpectedFunction {
70            span,
71            name: path_to_string(&meta_item.path),
72        });
73        return vec![item];
74    };
75
76    let orig_item = item.clone();
77
78    let item = *item;
79
80    let ast::Item { attrs, id: _, span: item_span, vis, kind: ItemKind::Fn(mut func), tokens: _ } =
81        item
82    else {
83        ecx.dcx().emit_err(EiiSharedMacroExpectedFunction {
84            span,
85            name: path_to_string(&meta_item.path),
86        });
87        return vec![Annotatable::Item(Box::new(item))];
88    };
89
90    // Detect when this is the *second* eii attribute on an item.
91    let mut new_attrs = ThinVec::new();
92    for i in attrs {
93        if i.has_name(sym::eii) {
94            ecx.dcx().emit_err(EiiOnlyOnce {
95                span: i.span,
96                first_span: span,
97                name: path_to_string(&meta_item.path),
98            });
99        } else {
100            new_attrs.push(i);
101        }
102    }
103    let attrs = new_attrs;
104
105    let macro_name = if meta_item.is_word() {
106        func.ident
107    } else if let Some([first]) = meta_item.meta_item_list()
108        && let Some(m) = first.meta_item()
109        && m.path.segments.len() == 1
110    {
111        m.path.segments[0].ident
112    } else {
113        ecx.dcx().emit_err(EiiMacroExpectedMaxOneArgument {
114            span: meta_item.span,
115            name: path_to_string(&meta_item.path),
116        });
117        return vec![Annotatable::Item(orig_item)];
118    };
119
120    let mut return_items = Vec::new();
121
122    if func.body.is_some() {
123        let mut default_func = func.clone();
124        func.body = None;
125        default_func.eii_impls.push(ast::EiiImpl {
126            node_id: DUMMY_NODE_ID,
127            eii_macro_path: ast::Path::from_ident(macro_name),
128            impl_safety: if impl_unsafe { ast::Safety::Unsafe(span) } else { ast::Safety::Default },
129            span,
130            inner_span: macro_name.span,
131            is_default: true, // important!
132        });
133
134        return_items.push(Box::new(ast::Item {
135            attrs: ThinVec::new(),
136            id: ast::DUMMY_NODE_ID,
137            span,
138            vis: ast::Visibility { span, kind: ast::VisibilityKind::Inherited, tokens: None },
139            kind: ast::ItemKind::Const(Box::new(ast::ConstItem {
140                ident: Ident { name: kw::Underscore, span },
141                defaultness: ast::Defaultness::Final,
142                generics: ast::Generics::default(),
143                ty: Box::new(ast::Ty {
144                    id: DUMMY_NODE_ID,
145                    kind: ast::TyKind::Tup(ThinVec::new()),
146                    span,
147                    tokens: None,
148                }),
149                rhs: Some(ast::ConstItemRhs::Body(Box::new(ast::Expr {
150                    id: DUMMY_NODE_ID,
151                    kind: ast::ExprKind::Block(
152                        Box::new(ast::Block {
153                            stmts: thin_vec![ast::Stmt {
154                                id: DUMMY_NODE_ID,
155                                kind: ast::StmtKind::Item(Box::new(ast::Item {
156                                    attrs: thin_vec![], // FIXME: re-add some original attrs
157                                    id: DUMMY_NODE_ID,
158                                    span: item_span,
159                                    vis: ast::Visibility {
160                                        span,
161                                        kind: ast::VisibilityKind::Inherited,
162                                        tokens: None
163                                    },
164                                    kind: ItemKind::Fn(default_func),
165                                    tokens: None,
166                                })),
167                                span
168                            }],
169                            id: DUMMY_NODE_ID,
170                            rules: ast::BlockCheckMode::Default,
171                            span,
172                            tokens: None,
173                        }),
174                        None,
175                    ),
176                    span,
177                    attrs: ThinVec::new(),
178                    tokens: None,
179                }))),
180                define_opaque: None,
181            })),
182            tokens: None,
183        }))
184    }
185
186    let decl_span = span.to(func.sig.span);
187
188    let abi = match func.sig.header.ext {
189        // extern "X" fn  =>  extern "X" {}
190        ast::Extern::Explicit(lit, _) => Some(lit),
191        // extern fn  =>  extern {}
192        ast::Extern::Implicit(_) => None,
193        // fn  =>  extern "Rust" {}
194        ast::Extern::None => Some(ast::StrLit {
195            symbol: sym::Rust,
196            suffix: None,
197            symbol_unescaped: sym::Rust,
198            style: ast::StrStyle::Cooked,
199            span,
200        }),
201    };
202
203    // ABI has been moved to the extern {} block, so we remove it from the fn item.
204    func.sig.header.ext = ast::Extern::None;
205
206    // And mark safe functions explicitly as `safe fn`.
207    if func.sig.header.safety == ast::Safety::Default {
208        func.sig.header.safety = ast::Safety::Safe(func.sig.span);
209    }
210
211    // extern "…" { safe fn item(); }
212    let mut extern_item_attrs = attrs.clone();
213    extern_item_attrs.push(ast::Attribute {
214        kind: ast::AttrKind::Normal(Box::new(ast::NormalAttr {
215            item: ast::AttrItem {
216                unsafety: ast::Safety::Default,
217                // Add the rustc_eii_extern_item on the foreign item. Usually, foreign items are mangled.
218                // This attribute makes sure that we later know that this foreign item's symbol should not be.
219                path: ast::Path::from_ident(Ident::new(sym::rustc_eii_extern_item, span)),
220                args: ast::AttrArgs::Empty,
221                tokens: None,
222            },
223            tokens: None,
224        })),
225        id: ecx.sess.psess.attr_id_generator.mk_attr_id(),
226        style: ast::AttrStyle::Outer,
227        span,
228    });
229
230    let extern_block = Box::new(ast::Item {
231        attrs: ast::AttrVec::default(),
232        id: ast::DUMMY_NODE_ID,
233        span,
234        vis: ast::Visibility { span, kind: ast::VisibilityKind::Inherited, tokens: None },
235        kind: ast::ItemKind::ForeignMod(ast::ForeignMod {
236            extern_span: span,
237            safety: ast::Safety::Unsafe(span),
238            abi,
239            items: From::from([Box::new(ast::ForeignItem {
240                attrs: extern_item_attrs,
241                id: ast::DUMMY_NODE_ID,
242                span: item_span,
243                vis,
244                kind: ast::ForeignItemKind::Fn(func.clone()),
245                tokens: None,
246            })]),
247        }),
248        tokens: None,
249    });
250
251    let mut macro_attrs = attrs.clone();
252    macro_attrs.push(
253        // #[builtin_macro(eii_shared_macro)]
254        ast::Attribute {
255            kind: ast::AttrKind::Normal(Box::new(ast::NormalAttr {
256                item: ast::AttrItem {
257                    unsafety: ast::Safety::Default,
258                    path: ast::Path::from_ident(Ident::new(sym::rustc_builtin_macro, span)),
259                    args: ast::AttrArgs::Delimited(ast::DelimArgs {
260                        dspan: DelimSpan::from_single(span),
261                        delim: Delimiter::Parenthesis,
262                        tokens: TokenStream::new(vec![tokenstream::TokenTree::token_alone(
263                            token::TokenKind::Ident(sym::eii_shared_macro, token::IdentIsRaw::No),
264                            span,
265                        )]),
266                    }),
267                    tokens: None,
268                },
269                tokens: None,
270            })),
271            id: ecx.sess.psess.attr_id_generator.mk_attr_id(),
272            style: ast::AttrStyle::Outer,
273            span,
274        },
275    );
276
277    let macro_def = Box::new(ast::Item {
278        attrs: macro_attrs,
279        id: ast::DUMMY_NODE_ID,
280        span,
281        // pub
282        vis: ast::Visibility { span, kind: ast::VisibilityKind::Public, tokens: None },
283        kind: ast::ItemKind::MacroDef(
284            // macro macro_name
285            macro_name,
286            ast::MacroDef {
287                // { () => {} }
288                body: Box::new(ast::DelimArgs {
289                    dspan: DelimSpan::from_single(span),
290                    delim: Delimiter::Brace,
291                    tokens: TokenStream::from_iter([
292                        TokenTree::Delimited(
293                            DelimSpan::from_single(span),
294                            DelimSpacing::new(Spacing::Alone, Spacing::Alone),
295                            Delimiter::Parenthesis,
296                            TokenStream::default(),
297                        ),
298                        TokenTree::token_alone(TokenKind::FatArrow, span),
299                        TokenTree::Delimited(
300                            DelimSpan::from_single(span),
301                            DelimSpacing::new(Spacing::Alone, Spacing::Alone),
302                            Delimiter::Brace,
303                            TokenStream::default(),
304                        ),
305                    ]),
306                }),
307                macro_rules: false,
308                // #[eii_extern_target(func.ident)]
309                eii_extern_target: Some(ast::EiiExternTarget {
310                    extern_item_path: ast::Path::from_ident(func.ident),
311                    impl_unsafe,
312                    span: decl_span,
313                }),
314            },
315        ),
316        tokens: None,
317    });
318
319    return_items.push(extern_block);
320    return_items.push(macro_def);
321
322    if stmt {
323        return_items
324            .into_iter()
325            .map(|i| {
326                Annotatable::Stmt(Box::new(Stmt {
327                    id: DUMMY_NODE_ID,
328                    kind: StmtKind::Item(i),
329                    span,
330                }))
331            })
332            .collect()
333    } else {
334        return_items.into_iter().map(|i| Annotatable::Item(i)).collect()
335    }
336}
337
338pub(crate) fn eii_extern_target(
339    ecx: &mut ExtCtxt<'_>,
340    span: Span,
341    meta_item: &ast::MetaItem,
342    mut item: Annotatable,
343) -> Vec<Annotatable> {
344    let i = if let Annotatable::Item(ref mut item) = item {
345        item
346    } else if let Annotatable::Stmt(ref mut stmt) = item
347        && let StmtKind::Item(ref mut item) = stmt.kind
348    {
349        item
350    } else {
351        ecx.dcx().emit_err(EiiExternTargetExpectedMacro { span });
352        return vec![item];
353    };
354
355    let ItemKind::MacroDef(_, d) = &mut i.kind else {
356        ecx.dcx().emit_err(EiiExternTargetExpectedMacro { span });
357        return vec![item];
358    };
359
360    let Some(list) = meta_item.meta_item_list() else {
361        ecx.dcx().emit_err(EiiExternTargetExpectedList { span: meta_item.span });
362        return vec![item];
363    };
364
365    if list.len() > 2 {
366        ecx.dcx().emit_err(EiiExternTargetExpectedList { span: meta_item.span });
367        return vec![item];
368    }
369
370    let Some(extern_item_path) = list.get(0).and_then(|i| i.meta_item()).map(|i| i.path.clone())
371    else {
372        ecx.dcx().emit_err(EiiExternTargetExpectedList { span: meta_item.span });
373        return vec![item];
374    };
375
376    let impl_unsafe = if let Some(i) = list.get(1) {
377        if i.lit().and_then(|i| i.kind.str()).is_some_and(|i| i == kw::Unsafe) {
378            true
379        } else {
380            ecx.dcx().emit_err(EiiExternTargetExpectedUnsafe { span: i.span() });
381            return vec![item];
382        }
383    } else {
384        false
385    };
386
387    d.eii_extern_target = Some(EiiExternTarget { extern_item_path, impl_unsafe, span });
388
389    // Return the original item and the new methods.
390    vec![item]
391}
392
393/// all Eiis share this function as the implementation for their attribute.
394pub(crate) fn eii_shared_macro(
395    ecx: &mut ExtCtxt<'_>,
396    span: Span,
397    meta_item: &ast::MetaItem,
398    mut item: Annotatable,
399) -> Vec<Annotatable> {
400    let i = if let Annotatable::Item(ref mut item) = item {
401        item
402    } else if let Annotatable::Stmt(ref mut stmt) = item
403        && let StmtKind::Item(ref mut item) = stmt.kind
404    {
405        item
406    } else {
407        ecx.dcx().emit_err(EiiSharedMacroExpectedFunction {
408            span,
409            name: path_to_string(&meta_item.path),
410        });
411        return vec![item];
412    };
413
414    let ItemKind::Fn(f) = &mut i.kind else {
415        ecx.dcx().emit_err(EiiSharedMacroExpectedFunction {
416            span,
417            name: path_to_string(&meta_item.path),
418        });
419        return vec![item];
420    };
421
422    let is_default = if meta_item.is_word() {
423        false
424    } else if let Some([first]) = meta_item.meta_item_list()
425        && let Some(m) = first.meta_item()
426        && m.path.segments.len() == 1
427    {
428        m.path.segments[0].ident.name == kw::Default
429    } else {
430        ecx.dcx().emit_err(EiiMacroExpectedMaxOneArgument {
431            span: meta_item.span,
432            name: path_to_string(&meta_item.path),
433        });
434        return vec![item];
435    };
436
437    f.eii_impls.push(EiiImpl {
438        node_id: DUMMY_NODE_ID,
439        eii_macro_path: meta_item.path.clone(),
440        impl_safety: meta_item.unsafety,
441        span,
442        inner_span: meta_item.path.span,
443        is_default,
444    });
445
446    vec![item]
447}