rustc_builtin_macros/
eii.rs

1use rustc_ast::token::{Delimiter, TokenKind};
2use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
3use rustc_ast::{
4    Attribute, DUMMY_NODE_ID, EiiDecl, EiiImpl, ItemKind, MetaItem, Path, Stmt, StmtKind,
5    Visibility, ast,
6};
7use rustc_ast_pretty::pprust::path_to_string;
8use rustc_expand::base::{Annotatable, ExtCtxt};
9use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym};
10use thin_vec::{ThinVec, thin_vec};
11
12use crate::errors::{
13    EiiExternTargetExpectedList, EiiExternTargetExpectedMacro, EiiExternTargetExpectedUnsafe,
14    EiiMacroExpectedMaxOneArgument, EiiOnlyOnce, EiiSharedMacroExpectedFunction,
15};
16
17/// ```rust
18/// #[eii]
19/// fn panic_handler();
20///
21/// // or:
22///
23/// #[eii(panic_handler)]
24/// fn panic_handler();
25///
26/// // expansion:
27///
28/// extern "Rust" {
29///     fn panic_handler();
30/// }
31///
32/// #[rustc_builtin_macro(eii_shared_macro)]
33/// #[eii_declaration(panic_handler)]
34/// macro panic_handler() {}
35/// ```
36pub(crate) fn eii(
37    ecx: &mut ExtCtxt<'_>,
38    span: Span,
39    meta_item: &ast::MetaItem,
40    item: Annotatable,
41) -> Vec<Annotatable> {
42    eii_(ecx, span, meta_item, item, false)
43}
44
45pub(crate) fn unsafe_eii(
46    ecx: &mut ExtCtxt<'_>,
47    span: Span,
48    meta_item: &ast::MetaItem,
49    item: Annotatable,
50) -> Vec<Annotatable> {
51    eii_(ecx, span, meta_item, item, true)
52}
53
54fn eii_(
55    ecx: &mut ExtCtxt<'_>,
56    eii_attr_span: Span,
57    meta_item: &ast::MetaItem,
58    item: Annotatable,
59    impl_unsafe: bool,
60) -> Vec<Annotatable> {
61    let eii_attr_span = ecx.with_def_site_ctxt(eii_attr_span);
62
63    let (item, wrap_item): (_, &dyn Fn(_) -> _) = if let Annotatable::Item(item) = item {
64        (item, &Annotatable::Item)
65    } else if let Annotatable::Stmt(ref stmt) = item
66        && let StmtKind::Item(ref item) = stmt.kind
67    {
68        (item.clone(), &|item| {
69            Annotatable::Stmt(Box::new(Stmt {
70                id: DUMMY_NODE_ID,
71                kind: StmtKind::Item(item),
72                span: eii_attr_span,
73            }))
74        })
75    } else {
76        ecx.dcx().emit_err(EiiSharedMacroExpectedFunction {
77            span: eii_attr_span,
78            name: path_to_string(&meta_item.path),
79        });
80        return vec![item];
81    };
82
83    let ast::Item { attrs, id: _, span: _, vis, kind: ItemKind::Fn(func), tokens: _ } =
84        item.as_ref()
85    else {
86        ecx.dcx().emit_err(EiiSharedMacroExpectedFunction {
87            span: eii_attr_span,
88            name: path_to_string(&meta_item.path),
89        });
90        return vec![wrap_item(item)];
91    };
92    // only clone what we need
93    let attrs = attrs.clone();
94    let func = (**func).clone();
95    let vis = vis.clone();
96
97    let attrs_from_decl =
98        filter_attrs_for_multiple_eii_attr(ecx, attrs, eii_attr_span, &meta_item.path);
99
100    let Ok(macro_name) = name_for_impl_macro(ecx, &func, &meta_item) else {
101        return vec![wrap_item(item)];
102    };
103
104    // span of the declaring item without attributes
105    let item_span = func.sig.span;
106    let foreign_item_name = func.ident;
107
108    let mut return_items = Vec::new();
109
110    if func.body.is_some() {
111        return_items.push(generate_default_impl(
112            ecx,
113            &func,
114            impl_unsafe,
115            macro_name,
116            eii_attr_span,
117            item_span,
118            foreign_item_name,
119        ))
120    }
121
122    return_items.push(generate_foreign_item(
123        ecx,
124        eii_attr_span,
125        item_span,
126        func,
127        vis,
128        &attrs_from_decl,
129    ));
130    return_items.push(generate_attribute_macro_to_implement(
131        ecx,
132        eii_attr_span,
133        macro_name,
134        foreign_item_name,
135        impl_unsafe,
136        &attrs_from_decl,
137    ));
138
139    return_items.into_iter().map(wrap_item).collect()
140}
141
142/// Decide on the name of the macro that can be used to implement the EII.
143/// This is either an explicitly given name, or the name of the item in the
144/// declaration of the EII.
145fn name_for_impl_macro(
146    ecx: &mut ExtCtxt<'_>,
147    func: &ast::Fn,
148    meta_item: &MetaItem,
149) -> Result<Ident, ErrorGuaranteed> {
150    if meta_item.is_word() {
151        Ok(func.ident)
152    } else if let Some([first]) = meta_item.meta_item_list()
153        && let Some(m) = first.meta_item()
154        && m.path.segments.len() == 1
155    {
156        Ok(m.path.segments[0].ident)
157    } else {
158        Err(ecx.dcx().emit_err(EiiMacroExpectedMaxOneArgument {
159            span: meta_item.span,
160            name: path_to_string(&meta_item.path),
161        }))
162    }
163}
164
165/// Ensure that in the list of attrs, there's only a single `eii` attribute.
166fn filter_attrs_for_multiple_eii_attr(
167    ecx: &mut ExtCtxt<'_>,
168    attrs: ThinVec<Attribute>,
169    eii_attr_span: Span,
170    eii_attr_path: &Path,
171) -> ThinVec<Attribute> {
172    attrs
173        .into_iter()
174        .filter(|i| {
175            if i.has_name(sym::eii) {
176                ecx.dcx().emit_err(EiiOnlyOnce {
177                    span: i.span,
178                    first_span: eii_attr_span,
179                    name: path_to_string(eii_attr_path),
180                });
181                false
182            } else {
183                true
184            }
185        })
186        .collect()
187}
188
189fn generate_default_impl(
190    ecx: &mut ExtCtxt<'_>,
191    func: &ast::Fn,
192    impl_unsafe: bool,
193    macro_name: Ident,
194    eii_attr_span: Span,
195    item_span: Span,
196    foreign_item_name: Ident,
197) -> Box<ast::Item> {
198    // FIXME: re-add some original attrs
199    let attrs = ThinVec::new();
200
201    let mut default_func = func.clone();
202    default_func.eii_impls.push(EiiImpl {
203        node_id: DUMMY_NODE_ID,
204        inner_span: macro_name.span,
205        eii_macro_path: ast::Path::from_ident(macro_name),
206        impl_safety: if impl_unsafe {
207            ast::Safety::Unsafe(eii_attr_span)
208        } else {
209            ast::Safety::Default
210        },
211        span: eii_attr_span,
212        is_default: true,
213        known_eii_macro_resolution: Some(ast::EiiDecl {
214            foreign_item: ecx.path(
215                foreign_item_name.span,
216                // prefix super to escape the `dflt` module generated below
217                vec![Ident::from_str_and_span("super", foreign_item_name.span), foreign_item_name],
218            ),
219            impl_unsafe,
220        }),
221    });
222
223    let item_mod = |span: Span, name: Ident, items: ThinVec<Box<ast::Item>>| {
224        ecx.item(
225            item_span,
226            ThinVec::new(),
227            ItemKind::Mod(
228                ast::Safety::Default,
229                name,
230                ast::ModKind::Loaded(
231                    items,
232                    ast::Inline::Yes,
233                    ast::ModSpans { inner_span: span, inject_use_span: span },
234                ),
235            ),
236        )
237    };
238
239    let anon_mod = |span: Span, stmts: ThinVec<ast::Stmt>| {
240        let unit = ecx.ty(item_span, ast::TyKind::Tup(ThinVec::new()));
241        let underscore = Ident::new(kw::Underscore, item_span);
242        ecx.item_const(
243            span,
244            underscore,
245            unit,
246            ast::ConstItemRhs::Body(ecx.expr_block(ecx.block(span, stmts))),
247        )
248    };
249
250    // const _: () = {
251    //     mod dflt {
252    //         use super::*;
253    //         <orig fn>
254    //     }
255    // }
256    anon_mod(
257        item_span,
258        thin_vec![ecx.stmt_item(
259            item_span,
260            item_mod(
261                item_span,
262                Ident::from_str_and_span("dflt", item_span),
263                thin_vec![
264                    ecx.item(
265                        item_span,
266                        thin_vec![ecx.attr_nested_word(sym::allow, sym::unused_imports, item_span)],
267                        ItemKind::Use(ast::UseTree {
268                            prefix: ast::Path::from_ident(Ident::from_str_and_span(
269                                "super", item_span,
270                            )),
271                            kind: ast::UseTreeKind::Glob,
272                            span: item_span,
273                        })
274                    ),
275                    ecx.item(item_span, attrs, ItemKind::Fn(Box::new(default_func)))
276                ]
277            )
278        ),],
279    )
280}
281
282/// Generates a foreign item, like
283///
284/// ```rust, ignore
285/// extern "…" { safe fn item(); }
286/// ```
287fn generate_foreign_item(
288    ecx: &mut ExtCtxt<'_>,
289    eii_attr_span: Span,
290    item_span: Span,
291    mut func: ast::Fn,
292    vis: Visibility,
293    attrs_from_decl: &[Attribute],
294) -> Box<ast::Item> {
295    let mut foreign_item_attrs = ThinVec::new();
296    foreign_item_attrs.extend_from_slice(attrs_from_decl);
297
298    // Add the rustc_eii_foreign_item on the foreign item. Usually, foreign items are mangled.
299    // This attribute makes sure that we later know that this foreign item's symbol should not be.
300    foreign_item_attrs.push(ecx.attr_word(sym::rustc_eii_foreign_item, eii_attr_span));
301
302    let abi = match func.sig.header.ext {
303        // extern "X" fn  =>  extern "X" {}
304        ast::Extern::Explicit(lit, _) => Some(lit),
305        // extern fn  =>  extern {}
306        ast::Extern::Implicit(_) => None,
307        // fn  =>  extern "Rust" {}
308        ast::Extern::None => Some(ast::StrLit {
309            symbol: sym::Rust,
310            suffix: None,
311            symbol_unescaped: sym::Rust,
312            style: ast::StrStyle::Cooked,
313            span: eii_attr_span,
314        }),
315    };
316
317    // ABI has been moved to the extern {} block, so we remove it from the fn item.
318    func.sig.header.ext = ast::Extern::None;
319    func.body = None;
320
321    // And mark safe functions explicitly as `safe fn`.
322    if func.sig.header.safety == ast::Safety::Default {
323        func.sig.header.safety = ast::Safety::Safe(func.sig.span);
324    }
325
326    ecx.item(
327        eii_attr_span,
328        ThinVec::new(),
329        ast::ItemKind::ForeignMod(ast::ForeignMod {
330            extern_span: eii_attr_span,
331            safety: ast::Safety::Unsafe(eii_attr_span),
332            abi,
333            items: From::from([Box::new(ast::ForeignItem {
334                attrs: foreign_item_attrs,
335                id: ast::DUMMY_NODE_ID,
336                span: item_span,
337                vis,
338                kind: ast::ForeignItemKind::Fn(Box::new(func.clone())),
339                tokens: None,
340            })]),
341        }),
342    )
343}
344
345/// Generate a stub macro (a bit like in core) that will roughly look like:
346///
347/// ```rust, ignore, example
348/// // Since this a stub macro, the actual code that expands it lives in the compiler.
349/// // This attribute tells the compiler that
350/// #[builtin_macro(eii_shared_macro)]
351/// // the metadata to link this macro to the generated foreign item.
352/// #[eii_declaration(<related_foreign_item>)]
353/// macro macro_name { () => {} }
354/// ```
355fn generate_attribute_macro_to_implement(
356    ecx: &mut ExtCtxt<'_>,
357    span: Span,
358    macro_name: Ident,
359    foreign_item_name: Ident,
360    impl_unsafe: bool,
361    attrs_from_decl: &[Attribute],
362) -> Box<ast::Item> {
363    let mut macro_attrs = ThinVec::new();
364
365    // To avoid e.g. `error: attribute macro has missing stability attribute`
366    // errors for eii's in std.
367    macro_attrs.extend_from_slice(attrs_from_decl);
368
369    // #[builtin_macro(eii_shared_macro)]
370    macro_attrs.push(ecx.attr_nested_word(sym::rustc_builtin_macro, sym::eii_shared_macro, span));
371
372    // cant use ecx methods here to construct item since we need it to be public
373    Box::new(ast::Item {
374        attrs: macro_attrs,
375        id: ast::DUMMY_NODE_ID,
376        span,
377        // pub
378        vis: ast::Visibility { span, kind: ast::VisibilityKind::Public, tokens: None },
379        kind: ast::ItemKind::MacroDef(
380            // macro macro_name
381            macro_name,
382            ast::MacroDef {
383                // { () => {} }
384                body: Box::new(ast::DelimArgs {
385                    dspan: DelimSpan::from_single(span),
386                    delim: Delimiter::Brace,
387                    tokens: TokenStream::from_iter([
388                        TokenTree::Delimited(
389                            DelimSpan::from_single(span),
390                            DelimSpacing::new(Spacing::Alone, Spacing::Alone),
391                            Delimiter::Parenthesis,
392                            TokenStream::default(),
393                        ),
394                        TokenTree::token_alone(TokenKind::FatArrow, span),
395                        TokenTree::Delimited(
396                            DelimSpan::from_single(span),
397                            DelimSpacing::new(Spacing::Alone, Spacing::Alone),
398                            Delimiter::Brace,
399                            TokenStream::default(),
400                        ),
401                    ]),
402                }),
403                macro_rules: false,
404                // #[eii_declaration(foreign_item_ident)]
405                eii_declaration: Some(ast::EiiDecl {
406                    foreign_item: ast::Path::from_ident(foreign_item_name),
407                    impl_unsafe,
408                }),
409            },
410        ),
411        tokens: None,
412    })
413}
414
415pub(crate) fn eii_declaration(
416    ecx: &mut ExtCtxt<'_>,
417    span: Span,
418    meta_item: &ast::MetaItem,
419    mut item: Annotatable,
420) -> Vec<Annotatable> {
421    let i = if let Annotatable::Item(ref mut item) = item {
422        item
423    } else if let Annotatable::Stmt(ref mut stmt) = item
424        && let StmtKind::Item(ref mut item) = stmt.kind
425    {
426        item
427    } else {
428        ecx.dcx().emit_err(EiiExternTargetExpectedMacro { span });
429        return vec![item];
430    };
431
432    let ItemKind::MacroDef(_, d) = &mut i.kind else {
433        ecx.dcx().emit_err(EiiExternTargetExpectedMacro { span });
434        return vec![item];
435    };
436
437    let Some(list) = meta_item.meta_item_list() else {
438        ecx.dcx().emit_err(EiiExternTargetExpectedList { span: meta_item.span });
439        return vec![item];
440    };
441
442    if list.len() > 2 {
443        ecx.dcx().emit_err(EiiExternTargetExpectedList { span: meta_item.span });
444        return vec![item];
445    }
446
447    let Some(extern_item_path) = list.get(0).and_then(|i| i.meta_item()).map(|i| i.path.clone())
448    else {
449        ecx.dcx().emit_err(EiiExternTargetExpectedList { span: meta_item.span });
450        return vec![item];
451    };
452
453    let impl_unsafe = if let Some(i) = list.get(1) {
454        if i.lit().and_then(|i| i.kind.str()).is_some_and(|i| i == kw::Unsafe) {
455            true
456        } else {
457            ecx.dcx().emit_err(EiiExternTargetExpectedUnsafe { span: i.span() });
458            return vec![item];
459        }
460    } else {
461        false
462    };
463
464    d.eii_declaration = Some(EiiDecl { foreign_item: extern_item_path, impl_unsafe });
465
466    // Return the original item and the new methods.
467    vec![item]
468}
469
470/// all Eiis share this function as the implementation for their attribute.
471pub(crate) fn eii_shared_macro(
472    ecx: &mut ExtCtxt<'_>,
473    span: Span,
474    meta_item: &ast::MetaItem,
475    mut item: Annotatable,
476) -> Vec<Annotatable> {
477    let i = if let Annotatable::Item(ref mut item) = item {
478        item
479    } else if let Annotatable::Stmt(ref mut stmt) = item
480        && let StmtKind::Item(ref mut item) = stmt.kind
481    {
482        item
483    } else {
484        ecx.dcx().emit_err(EiiSharedMacroExpectedFunction {
485            span,
486            name: path_to_string(&meta_item.path),
487        });
488        return vec![item];
489    };
490
491    let ItemKind::Fn(f) = &mut i.kind else {
492        ecx.dcx().emit_err(EiiSharedMacroExpectedFunction {
493            span,
494            name: path_to_string(&meta_item.path),
495        });
496        return vec![item];
497    };
498
499    let is_default = if meta_item.is_word() {
500        false
501    } else if let Some([first]) = meta_item.meta_item_list()
502        && let Some(m) = first.meta_item()
503        && m.path.segments.len() == 1
504    {
505        m.path.segments[0].ident.name == kw::Default
506    } else {
507        ecx.dcx().emit_err(EiiMacroExpectedMaxOneArgument {
508            span: meta_item.span,
509            name: path_to_string(&meta_item.path),
510        });
511        return vec![item];
512    };
513
514    f.eii_impls.push(EiiImpl {
515        node_id: DUMMY_NODE_ID,
516        inner_span: meta_item.path.span,
517        eii_macro_path: meta_item.path.clone(),
518        impl_safety: meta_item.unsafety,
519        span,
520        is_default,
521        known_eii_macro_resolution: None,
522    });
523
524    vec![item]
525}