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