Skip to main content

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