From 6608f6ace7a0267cff3920216f1b697cabc3ca67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 19 Dec 2025 13:57:34 +0100 Subject: [PATCH 1/3] split up expansion code of eii macro into functions --- compiler/rustc_builtin_macros/src/eii.rs | 404 +++++++++++++---------- 1 file changed, 238 insertions(+), 166 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/eii.rs b/compiler/rustc_builtin_macros/src/eii.rs index b97cb1daec534..e29b29fc6ccb0 100644 --- a/compiler/rustc_builtin_macros/src/eii.rs +++ b/compiler/rustc_builtin_macros/src/eii.rs @@ -1,11 +1,12 @@ use rustc_ast::token::{Delimiter, TokenKind}; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_ast::{ - DUMMY_NODE_ID, EiiExternTarget, EiiImpl, ItemKind, Stmt, StmtKind, ast, token, tokenstream, + Attribute, DUMMY_NODE_ID, EiiExternTarget, EiiImpl, ItemKind, MetaItem, Path, Stmt, StmtKind, + Visibility, ast, }; use rustc_ast_pretty::pprust::path_to_string; use rustc_expand::base::{Annotatable, ExtCtxt}; -use rustc_span::{Ident, Span, kw, sym}; +use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym}; use thin_vec::{ThinVec, thin_vec}; use crate::errors::{ @@ -52,12 +53,12 @@ pub(crate) fn unsafe_eii( fn eii_( ecx: &mut ExtCtxt<'_>, - span: Span, + eii_attr_span: Span, meta_item: &ast::MetaItem, item: Annotatable, impl_unsafe: bool, ) -> Vec { - let span = ecx.with_def_site_ctxt(span); + let eii_attr_span = ecx.with_def_site_ctxt(eii_attr_span); let (item, stmt) = if let Annotatable::Item(item) = item { (item, false) @@ -67,7 +68,7 @@ fn eii_( (item.clone(), true) } else { ecx.dcx().emit_err(EiiSharedMacroExpectedFunction { - span, + span: eii_attr_span, name: path_to_string(&meta_item.path), }); return vec![item]; @@ -77,113 +78,218 @@ fn eii_( let item = *item; - let ast::Item { attrs, id: _, span: item_span, vis, kind: ItemKind::Fn(mut func), tokens: _ } = - item - else { + let ast::Item { attrs, id: _, span: _, vis, kind: ItemKind::Fn(func), tokens: _ } = item else { ecx.dcx().emit_err(EiiSharedMacroExpectedFunction { - span, + span: eii_attr_span, name: path_to_string(&meta_item.path), }); return vec![Annotatable::Item(Box::new(item))]; }; - // Detect when this is the *second* eii attribute on an item. - let mut new_attrs = ThinVec::new(); - for i in attrs { - if i.has_name(sym::eii) { - ecx.dcx().emit_err(EiiOnlyOnce { - span: i.span, - first_span: span, - name: path_to_string(&meta_item.path), - }); - } else { - new_attrs.push(i); - } + let attrs_from_decl = + filter_attrs_for_multiple_eii_attr(ecx, attrs, eii_attr_span, &meta_item.path); + + let Ok(macro_name) = name_for_impl_macro(ecx, &func, &meta_item) else { + return vec![Annotatable::Item(orig_item)]; + }; + + // span of the declaring item without attributes + let item_span = func.sig.span; + // span of the eii attribute and the item below it, i.e. the full declaration + let decl_span = eii_attr_span.to(item_span); + let foreign_item_name = func.ident; + + let mut return_items = Vec::new(); + + if func.body.is_some() { + return_items.push(Box::new(generate_default_impl( + &func, + impl_unsafe, + macro_name, + eii_attr_span, + item_span, + ))) + } + + return_items.push(Box::new(generate_foreign_item( + ecx, + eii_attr_span, + item_span, + *func, + vis, + &attrs_from_decl, + ))); + return_items.push(Box::new(generate_attribute_macro_to_implement( + ecx, + eii_attr_span, + macro_name, + foreign_item_name, + impl_unsafe, + decl_span, + ))); + + if stmt { + return_items + .into_iter() + .map(|i| { + Annotatable::Stmt(Box::new(Stmt { + id: DUMMY_NODE_ID, + kind: StmtKind::Item(i), + span: eii_attr_span, + })) + }) + .collect() + } else { + return_items.into_iter().map(|i| Annotatable::Item(i)).collect() } - let attrs = new_attrs; +} - let macro_name = if meta_item.is_word() { - func.ident +/// Decide on the name of the macro that can be used to implement the EII. +/// This is either an explicitly given name, or the name of the item in the +/// declaration of the EII. +fn name_for_impl_macro( + ecx: &mut ExtCtxt<'_>, + func: &ast::Fn, + meta_item: &MetaItem, +) -> Result { + if meta_item.is_word() { + Ok(func.ident) } else if let Some([first]) = meta_item.meta_item_list() && let Some(m) = first.meta_item() && m.path.segments.len() == 1 { - m.path.segments[0].ident + Ok(m.path.segments[0].ident) } else { - ecx.dcx().emit_err(EiiMacroExpectedMaxOneArgument { + Err(ecx.dcx().emit_err(EiiMacroExpectedMaxOneArgument { span: meta_item.span, name: path_to_string(&meta_item.path), - }); - return vec![Annotatable::Item(orig_item)]; - }; + })) + } +} - let mut return_items = Vec::new(); +/// Ensure that in the list of attrs, there's only a single `eii` attribute. +fn filter_attrs_for_multiple_eii_attr( + ecx: &mut ExtCtxt<'_>, + attrs: ThinVec, + eii_attr_span: Span, + eii_attr_path: &Path, +) -> ThinVec { + attrs + .into_iter() + .filter(|i| { + if i.has_name(sym::eii) { + ecx.dcx().emit_err(EiiOnlyOnce { + span: i.span, + first_span: eii_attr_span, + name: path_to_string(eii_attr_path), + }); + false + } else { + true + } + }) + .collect() +} - if func.body.is_some() { - let mut default_func = func.clone(); - func.body = None; - default_func.eii_impls.push(ast::EiiImpl { - node_id: DUMMY_NODE_ID, - eii_macro_path: ast::Path::from_ident(macro_name), - impl_safety: if impl_unsafe { ast::Safety::Unsafe(span) } else { ast::Safety::Default }, - span, - inner_span: macro_name.span, - is_default: true, // important! - }); +fn generate_default_impl( + func: &ast::Fn, + impl_unsafe: bool, + macro_name: Ident, + eii_attr_span: Span, + item_span: Span, +) -> ast::Item { + // FIXME: re-add some original attrs + let attrs = ThinVec::new(); + + let mut default_func = func.clone(); + default_func.eii_impls.push(EiiImpl { + node_id: DUMMY_NODE_ID, + inner_span: macro_name.span, + eii_macro_path: ast::Path::from_ident(macro_name), + impl_safety: if impl_unsafe { + ast::Safety::Unsafe(eii_attr_span) + } else { + ast::Safety::Default + }, + span: eii_attr_span, + is_default: true, + }); - return_items.push(Box::new(ast::Item { - attrs: ThinVec::new(), - id: ast::DUMMY_NODE_ID, - span, - vis: ast::Visibility { span, kind: ast::VisibilityKind::Inherited, tokens: None }, - kind: ast::ItemKind::Const(Box::new(ast::ConstItem { - ident: Ident { name: kw::Underscore, span }, - defaultness: ast::Defaultness::Final, - generics: ast::Generics::default(), - ty: Box::new(ast::Ty { - id: DUMMY_NODE_ID, - kind: ast::TyKind::Tup(ThinVec::new()), - span, - tokens: None, - }), - rhs: Some(ast::ConstItemRhs::Body(Box::new(ast::Expr { - id: DUMMY_NODE_ID, - kind: ast::ExprKind::Block( - Box::new(ast::Block { - stmts: thin_vec![ast::Stmt { - id: DUMMY_NODE_ID, - kind: ast::StmtKind::Item(Box::new(ast::Item { - attrs: thin_vec![], // FIXME: re-add some original attrs - id: DUMMY_NODE_ID, - span: item_span, - vis: ast::Visibility { - span, - kind: ast::VisibilityKind::Inherited, - tokens: None - }, - kind: ItemKind::Fn(default_func), - tokens: None, - })), - span - }], - id: DUMMY_NODE_ID, - rules: ast::BlockCheckMode::Default, - span, - tokens: None, - }), - None, - ), - span, - attrs: ThinVec::new(), - tokens: None, - }))), - define_opaque: None, - })), + ast::Item { + attrs: ThinVec::new(), + id: ast::DUMMY_NODE_ID, + span: eii_attr_span, + vis: ast::Visibility { + span: eii_attr_span, + kind: ast::VisibilityKind::Inherited, tokens: None, - })) + }, + kind: ast::ItemKind::Const(Box::new(ast::ConstItem { + ident: Ident { name: kw::Underscore, span: eii_attr_span }, + defaultness: ast::Defaultness::Final, + generics: ast::Generics::default(), + ty: Box::new(ast::Ty { + id: DUMMY_NODE_ID, + kind: ast::TyKind::Tup(ThinVec::new()), + span: eii_attr_span, + tokens: None, + }), + rhs: Some(ast::ConstItemRhs::Body(Box::new(ast::Expr { + id: DUMMY_NODE_ID, + kind: ast::ExprKind::Block( + Box::new(ast::Block { + stmts: thin_vec![ast::Stmt { + id: DUMMY_NODE_ID, + kind: ast::StmtKind::Item(Box::new(ast::Item { + attrs, + id: DUMMY_NODE_ID, + span: item_span, + vis: ast::Visibility { + span: eii_attr_span, + kind: ast::VisibilityKind::Inherited, + tokens: None + }, + kind: ItemKind::Fn(Box::new(default_func)), + tokens: None, + })), + span: eii_attr_span + }], + id: DUMMY_NODE_ID, + rules: ast::BlockCheckMode::Default, + span: eii_attr_span, + tokens: None, + }), + None, + ), + span: eii_attr_span, + attrs: ThinVec::new(), + tokens: None, + }))), + define_opaque: None, + })), + tokens: None, } +} - let decl_span = span.to(func.sig.span); +/// Generates a foreign item, like +/// +/// ```rust, ignore +/// extern "…" { safe fn item(); } +/// ``` +fn generate_foreign_item( + ecx: &mut ExtCtxt<'_>, + eii_attr_span: Span, + item_span: Span, + mut func: ast::Fn, + vis: Visibility, + attrs_from_decl: &[Attribute], +) -> ast::Item { + let mut foreign_item_attrs = ThinVec::new(); + foreign_item_attrs.extend_from_slice(attrs_from_decl); + + // Add the rustc_eii_extern_item on the foreign item. Usually, foreign items are mangled. + // This attribute makes sure that we later know that this foreign item's symbol should not be. + foreign_item_attrs.push(ecx.attr_word(sym::rustc_eii_extern_item, eii_attr_span)); let abi = match func.sig.header.ext { // extern "X" fn => extern "X" {} @@ -196,85 +302,69 @@ fn eii_( suffix: None, symbol_unescaped: sym::Rust, style: ast::StrStyle::Cooked, - span, + span: eii_attr_span, }), }; // ABI has been moved to the extern {} block, so we remove it from the fn item. func.sig.header.ext = ast::Extern::None; + func.body = None; // And mark safe functions explicitly as `safe fn`. if func.sig.header.safety == ast::Safety::Default { func.sig.header.safety = ast::Safety::Safe(func.sig.span); } - // extern "…" { safe fn item(); } - let mut extern_item_attrs = attrs.clone(); - extern_item_attrs.push(ast::Attribute { - kind: ast::AttrKind::Normal(Box::new(ast::NormalAttr { - item: ast::AttrItem { - unsafety: ast::Safety::Default, - // Add the rustc_eii_extern_item on the foreign item. Usually, foreign items are mangled. - // This attribute makes sure that we later know that this foreign item's symbol should not be. - path: ast::Path::from_ident(Ident::new(sym::rustc_eii_extern_item, span)), - args: ast::AttrArgs::Empty, - tokens: None, - }, - tokens: None, - })), - id: ecx.sess.psess.attr_id_generator.mk_attr_id(), - style: ast::AttrStyle::Outer, - span, - }); - - let extern_block = Box::new(ast::Item { + ast::Item { attrs: ast::AttrVec::default(), id: ast::DUMMY_NODE_ID, - span, - vis: ast::Visibility { span, kind: ast::VisibilityKind::Inherited, tokens: None }, + span: eii_attr_span, + vis: ast::Visibility { + span: eii_attr_span, + kind: ast::VisibilityKind::Inherited, + tokens: None, + }, kind: ast::ItemKind::ForeignMod(ast::ForeignMod { - extern_span: span, - safety: ast::Safety::Unsafe(span), + extern_span: eii_attr_span, + safety: ast::Safety::Unsafe(eii_attr_span), abi, items: From::from([Box::new(ast::ForeignItem { - attrs: extern_item_attrs, + attrs: foreign_item_attrs, id: ast::DUMMY_NODE_ID, span: item_span, vis, - kind: ast::ForeignItemKind::Fn(func.clone()), + kind: ast::ForeignItemKind::Fn(Box::new(func.clone())), tokens: None, })]), }), tokens: None, - }); + } +} - let mut macro_attrs = attrs.clone(); - macro_attrs.push( - // #[builtin_macro(eii_shared_macro)] - ast::Attribute { - kind: ast::AttrKind::Normal(Box::new(ast::NormalAttr { - item: ast::AttrItem { - unsafety: ast::Safety::Default, - path: ast::Path::from_ident(Ident::new(sym::rustc_builtin_macro, span)), - args: ast::AttrArgs::Delimited(ast::DelimArgs { - dspan: DelimSpan::from_single(span), - delim: Delimiter::Parenthesis, - tokens: TokenStream::new(vec![tokenstream::TokenTree::token_alone( - token::TokenKind::Ident(sym::eii_shared_macro, token::IdentIsRaw::No), - span, - )]), - }), - tokens: None, - }, - tokens: None, - })), - id: ecx.sess.psess.attr_id_generator.mk_attr_id(), - style: ast::AttrStyle::Outer, - span, - }, - ); +/// Generate a stub macro (a bit like in core) that will roughly look like: +/// +/// ```rust, ignore, example +/// // Since this a stub macro, the actual code that expands it lives in the compiler. +/// // This attribute tells the compiler that +/// #[builtin_macro(eii_shared_macro)] +/// // the metadata to link this macro to the generated foreign item. +/// #[eii_extern_target()] +/// macro macro_name { () => {} } +/// ``` +fn generate_attribute_macro_to_implement( + ecx: &mut ExtCtxt<'_>, + span: Span, + macro_name: Ident, + foreign_item_name: Ident, + impl_unsafe: bool, + decl_span: Span, +) -> ast::Item { + let mut macro_attrs = ThinVec::new(); + + // #[builtin_macro(eii_shared_macro)] + macro_attrs.push(ecx.attr_nested_word(sym::rustc_builtin_macro, sym::eii_shared_macro, span)); - let macro_def = Box::new(ast::Item { + ast::Item { attrs: macro_attrs, id: ast::DUMMY_NODE_ID, span, @@ -305,33 +395,15 @@ fn eii_( ]), }), macro_rules: false, - // #[eii_extern_target(func.ident)] + // #[eii_extern_target(foreign_item_ident)] eii_extern_target: Some(ast::EiiExternTarget { - extern_item_path: ast::Path::from_ident(func.ident), + extern_item_path: ast::Path::from_ident(foreign_item_name), impl_unsafe, span: decl_span, }), }, ), tokens: None, - }); - - return_items.push(extern_block); - return_items.push(macro_def); - - if stmt { - return_items - .into_iter() - .map(|i| { - Annotatable::Stmt(Box::new(Stmt { - id: DUMMY_NODE_ID, - kind: StmtKind::Item(i), - span, - })) - }) - .collect() - } else { - return_items.into_iter().map(|i| Annotatable::Item(i)).collect() } } @@ -436,10 +508,10 @@ pub(crate) fn eii_shared_macro( f.eii_impls.push(EiiImpl { node_id: DUMMY_NODE_ID, + inner_span: meta_item.path.span, eii_macro_path: meta_item.path.clone(), impl_safety: meta_item.unsafety, span, - inner_span: meta_item.path.span, is_default, }); From 897e88c63ded83e8b6be47ea09c661f7bd8c0ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 19 Dec 2025 12:34:59 +0100 Subject: [PATCH 2/3] add test for 149980 --- tests/ui/eii/error_statement_position.rs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/ui/eii/error_statement_position.rs diff --git a/tests/ui/eii/error_statement_position.rs b/tests/ui/eii/error_statement_position.rs new file mode 100644 index 0000000000000..a9c606991574b --- /dev/null +++ b/tests/ui/eii/error_statement_position.rs @@ -0,0 +1,6 @@ +#![feature(extern_item_impls)] + +fn main() { + #[eii] + impl Bar {} +} From c316c05dc512bba366d461e1719f0a8620a07453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 19 Dec 2025 14:42:20 +0100 Subject: [PATCH 3/3] refactor how eii expansion is wrapped fixing 149980 --- compiler/rustc_builtin_macros/src/eii.rs | 45 +++++++++----------- tests/ui/eii/error_statement_position.rs | 10 +++++ tests/ui/eii/error_statement_position.stderr | 8 ++++ 3 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 tests/ui/eii/error_statement_position.stderr diff --git a/compiler/rustc_builtin_macros/src/eii.rs b/compiler/rustc_builtin_macros/src/eii.rs index e29b29fc6ccb0..9049639925ddb 100644 --- a/compiler/rustc_builtin_macros/src/eii.rs +++ b/compiler/rustc_builtin_macros/src/eii.rs @@ -60,12 +60,18 @@ fn eii_( ) -> Vec { let eii_attr_span = ecx.with_def_site_ctxt(eii_attr_span); - let (item, stmt) = if let Annotatable::Item(item) = item { - (item, false) + let (item, wrap_item): (_, &dyn Fn(_) -> _) = if let Annotatable::Item(item) = item { + (item, &Annotatable::Item) } else if let Annotatable::Stmt(ref stmt) = item && let StmtKind::Item(ref item) = stmt.kind { - (item.clone(), true) + (item.clone(), &|item| { + Annotatable::Stmt(Box::new(Stmt { + id: DUMMY_NODE_ID, + kind: StmtKind::Item(item), + span: eii_attr_span, + })) + }) } else { ecx.dcx().emit_err(EiiSharedMacroExpectedFunction { span: eii_attr_span, @@ -74,23 +80,25 @@ fn eii_( return vec![item]; }; - let orig_item = item.clone(); - - let item = *item; - - let ast::Item { attrs, id: _, span: _, vis, kind: ItemKind::Fn(func), tokens: _ } = item else { + let ast::Item { attrs, id: _, span: _, vis, kind: ItemKind::Fn(func), tokens: _ } = + item.as_ref() + else { ecx.dcx().emit_err(EiiSharedMacroExpectedFunction { span: eii_attr_span, name: path_to_string(&meta_item.path), }); - return vec![Annotatable::Item(Box::new(item))]; + return vec![wrap_item(item)]; }; + // only clone what we need + let attrs = attrs.clone(); + let func = (**func).clone(); + let vis = vis.clone(); let attrs_from_decl = filter_attrs_for_multiple_eii_attr(ecx, attrs, eii_attr_span, &meta_item.path); let Ok(macro_name) = name_for_impl_macro(ecx, &func, &meta_item) else { - return vec![Annotatable::Item(orig_item)]; + return vec![wrap_item(item)]; }; // span of the declaring item without attributes @@ -115,7 +123,7 @@ fn eii_( ecx, eii_attr_span, item_span, - *func, + func, vis, &attrs_from_decl, ))); @@ -128,20 +136,7 @@ fn eii_( decl_span, ))); - if stmt { - return_items - .into_iter() - .map(|i| { - Annotatable::Stmt(Box::new(Stmt { - id: DUMMY_NODE_ID, - kind: StmtKind::Item(i), - span: eii_attr_span, - })) - }) - .collect() - } else { - return_items.into_iter().map(|i| Annotatable::Item(i)).collect() - } + return_items.into_iter().map(wrap_item).collect() } /// Decide on the name of the macro that can be used to implement the EII. diff --git a/tests/ui/eii/error_statement_position.rs b/tests/ui/eii/error_statement_position.rs index a9c606991574b..cf81e7e6a8b23 100644 --- a/tests/ui/eii/error_statement_position.rs +++ b/tests/ui/eii/error_statement_position.rs @@ -1,6 +1,16 @@ #![feature(extern_item_impls)] +// EIIs can, despite not being super useful, be declared in statement position +// nested inside items. Items in statement position, when expanded as part of a macro, +// need to be wrapped slightly differently (in an `ast::Statement`). +// We did this on the happy path (no errors), but when there was an error, we'd +// replace it with *just* an `ast::Item` not wrapped in an `ast::Statement`. +// This caused an ICE (https://github.com/rust-lang/rust/issues/149980). +// this test fails to build, but demonstrates that no ICE is produced. fn main() { + struct Bar; + #[eii] + //~^ ERROR `#[eii]` is only valid on functions impl Bar {} } diff --git a/tests/ui/eii/error_statement_position.stderr b/tests/ui/eii/error_statement_position.stderr new file mode 100644 index 0000000000000..01b7394ef00fa --- /dev/null +++ b/tests/ui/eii/error_statement_position.stderr @@ -0,0 +1,8 @@ +error: `#[eii]` is only valid on functions + --> $DIR/error_statement_position.rs:13:5 + | +LL | #[eii] + | ^^^^^^ + +error: aborting due to 1 previous error +