diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 3e8fddd9954e2..91731d9d1cbab 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2066,6 +2066,19 @@ pub struct MacroDef { pub body: Box, /// `true` if macro was defined with `macro_rules`. pub macro_rules: bool, + + /// If this is a macro used for externally implementable items, + /// it refers to an extern item which is its "target". This requires + /// name resolution so can't just be an attribute, so we store it in this field. + pub eii_extern_target: Option, +} + +#[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic, Walkable)] +pub struct EiiExternTarget { + /// path to the extern item we're targetting + pub extern_item_path: Path, + pub impl_unsafe: bool, + pub span: Span, } #[derive(Clone, Encodable, Decodable, Debug, Copy, Hash, Eq, PartialEq)] @@ -3714,6 +3727,21 @@ pub struct Fn { pub contract: Option>, pub define_opaque: Option>, pub body: Option>, + + /// This function is an implementation of an externally implementable item (EII). + /// This means, there was an EII declared somewhere and this function is the + /// implementation that should be run when the declaration is called. + pub eii_impls: ThinVec, +} + +#[derive(Clone, Encodable, Decodable, Debug, Walkable)] +pub struct EiiImpl { + pub node_id: NodeId, + pub eii_macro_path: Path, + pub impl_safety: Safety, + pub span: Span, + pub inner_span: Span, + pub is_default: bool, } #[derive(Clone, Encodable, Decodable, Debug, Walkable)] @@ -4061,7 +4089,7 @@ mod size_asserts { static_assert_size!(Block, 32); static_assert_size!(Expr, 72); static_assert_size!(ExprKind, 40); - static_assert_size!(Fn, 184); + static_assert_size!(Fn, 192); static_assert_size!(ForeignItem, 80); static_assert_size!(ForeignItemKind, 16); static_assert_size!(GenericArg, 24); diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 68b3d2b036865..89e28cca3f988 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -392,6 +392,7 @@ macro_rules! common_visitor_and_walkers { ThinVec>, ThinVec>, ThinVec>, + ThinVec, ); // This macro generates `impl Visitable` and `impl MutVisitable` that forward to `Walkable` @@ -485,6 +486,8 @@ macro_rules! common_visitor_and_walkers { WhereEqPredicate, WhereRegionPredicate, YieldKind, + EiiExternTarget, + EiiImpl, ); /// Each method of this trait is a hook to be potentially @@ -914,13 +917,13 @@ macro_rules! common_visitor_and_walkers { _ctxt, // Visibility is visited as a part of the item. _vis, - Fn { defaultness, ident, sig, generics, contract, body, define_opaque }, + Fn { defaultness, ident, sig, generics, contract, body, define_opaque, eii_impls }, ) => { let FnSig { header, decl, span } = sig; visit_visitable!($($mut)? vis, defaultness, ident, header, generics, decl, - contract, body, span, define_opaque - ) + contract, body, span, define_opaque, eii_impls + ); } FnKind::Closure(binder, coroutine_kind, decl, body) => visit_visitable!($($mut)? vis, binder, coroutine_kind, decl, body), diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 53351f91c46bc..391762eb50892 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -2,7 +2,7 @@ use rustc_abi::ExternAbi; use rustc_ast::visit::AssocCtxt; use rustc_ast::*; use rustc_errors::{E0570, ErrorGuaranteed, struct_span_code_err}; -use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::{AttributeKind, EiiDecl}; use rustc_hir::def::{DefKind, PerNS, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; use rustc_hir::{ @@ -11,6 +11,7 @@ use rustc_hir::{ use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::span_bug; use rustc_middle::ty::{ResolverAstLowering, TyCtxt}; +use rustc_span::def_id::DefId; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::{DUMMY_SP, DesugaringKind, Ident, Span, Symbol, kw, sym}; use smallvec::{SmallVec, smallvec}; @@ -135,10 +136,92 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + fn generate_extra_attrs_for_item_kind( + &mut self, + id: NodeId, + i: &ItemKind, + ) -> Vec { + match i { + ItemKind::Fn(box Fn { eii_impls, .. }) if eii_impls.is_empty() => Vec::new(), + ItemKind::Fn(box Fn { eii_impls, .. }) => { + vec![hir::Attribute::Parsed(AttributeKind::EiiImpls( + eii_impls + .iter() + .flat_map( + |EiiImpl { + node_id, + eii_macro_path, + impl_safety, + span, + inner_span, + is_default, + }| { + self.lower_path_simple_eii(*node_id, eii_macro_path).map(|did| { + hir::attrs::EiiImpl { + eii_macro: did, + span: self.lower_span(*span), + inner_span: self.lower_span(*inner_span), + impl_marked_unsafe: self + .lower_safety(*impl_safety, hir::Safety::Safe) + .is_unsafe(), + is_default: *is_default, + } + }) + }, + ) + .collect(), + ))] + } + ItemKind::MacroDef( + _, + MacroDef { + eii_extern_target: Some(EiiExternTarget { extern_item_path, impl_unsafe, span }), + .. + }, + ) => self + .lower_path_simple_eii(id, extern_item_path) + .map(|did| { + vec![hir::Attribute::Parsed(AttributeKind::EiiExternTarget(EiiDecl { + eii_extern_target: did, + impl_unsafe: *impl_unsafe, + span: self.lower_span(*span), + }))] + }) + .unwrap_or_default(), + ItemKind::ExternCrate(..) + | ItemKind::Use(..) + | ItemKind::Static(..) + | ItemKind::Const(..) + | ItemKind::Mod(..) + | ItemKind::ForeignMod(..) + | ItemKind::GlobalAsm(..) + | ItemKind::TyAlias(..) + | ItemKind::Enum(..) + | ItemKind::Struct(..) + | ItemKind::Union(..) + | ItemKind::Trait(..) + | ItemKind::TraitAlias(..) + | ItemKind::Impl(..) + | ItemKind::MacCall(..) + | ItemKind::MacroDef(..) + | ItemKind::Delegation(..) + | ItemKind::DelegationMac(..) => Vec::new(), + } + } + fn lower_item(&mut self, i: &Item) -> &'hir hir::Item<'hir> { let vis_span = self.lower_span(i.vis.span); let hir_id = hir::HirId::make_owner(self.current_hir_id_owner.def_id); - let attrs = self.lower_attrs(hir_id, &i.attrs, i.span, Target::from_ast_item(i)); + + let extra_hir_attributes = self.generate_extra_attrs_for_item_kind(i.id, &i.kind); + let attrs = self.lower_attrs_with_extra( + hir_id, + &i.attrs, + i.span, + Target::from_ast_item(i), + &extra_hir_attributes, + ); + let kind = self.lower_item_kind(i.span, i.id, hir_id, attrs, vis_span, &i.kind); let item = hir::Item { owner_id: hir_id.expect_owner(), @@ -146,6 +229,10 @@ impl<'hir> LoweringContext<'_, 'hir> { vis_span, span: self.lower_span(i.span), has_delayed_lints: !self.delayed_lints.is_empty(), + eii: find_attr!( + attrs, + AttributeKind::EiiImpls(..) | AttributeKind::EiiExternTarget(..) + ), }; self.arena.alloc(item) } @@ -433,7 +520,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ); hir::ItemKind::TraitAlias(ident, generics, bounds) } - ItemKind::MacroDef(ident, MacroDef { body, macro_rules }) => { + ItemKind::MacroDef(ident, MacroDef { body, macro_rules, eii_extern_target: _ }) => { let ident = self.lower_ident(*ident); let body = Box::new(self.lower_delim_args(body)); let def_id = self.local_def_id(id); @@ -444,7 +531,11 @@ impl<'hir> LoweringContext<'_, 'hir> { def_kind.descr(def_id.to_def_id()) ); }; - let macro_def = self.arena.alloc(ast::MacroDef { body, macro_rules: *macro_rules }); + let macro_def = self.arena.alloc(ast::MacroDef { + body, + macro_rules: *macro_rules, + eii_extern_target: None, + }); hir::ItemKind::Macro(ident, macro_def, macro_kinds) } ItemKind::Delegation(box delegation) => { @@ -463,6 +554,16 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + fn lower_path_simple_eii(&mut self, id: NodeId, path: &Path) -> Option { + let res = self.resolver.get_partial_res(id)?; + let Some(did) = res.expect_full_res().opt_def_id() else { + self.dcx().span_delayed_bug(path.span, "should have errored in resolve"); + return None; + }; + + Some(did) + } + fn lower_const_item( &mut self, ty: &Ty, @@ -582,6 +683,10 @@ impl<'hir> LoweringContext<'_, 'hir> { vis_span, span: this.lower_span(use_tree.span), has_delayed_lints: !this.delayed_lints.is_empty(), + eii: find_attr!( + attrs, + AttributeKind::EiiImpls(..) | AttributeKind::EiiExternTarget(..) + ), }; hir::OwnerNode::Item(this.arena.alloc(item)) }); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 4e2243e87873c..1ced565d9e512 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -945,11 +945,23 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { target_span: Span, target: Target, ) -> &'hir [hir::Attribute] { - if attrs.is_empty() { + self.lower_attrs_with_extra(id, attrs, target_span, target, &[]) + } + + fn lower_attrs_with_extra( + &mut self, + id: HirId, + attrs: &[Attribute], + target_span: Span, + target: Target, + extra_hir_attributes: &[hir::Attribute], + ) -> &'hir [hir::Attribute] { + if attrs.is_empty() && extra_hir_attributes.is_empty() { &[] } else { - let lowered_attrs = + let mut lowered_attrs = self.lower_attrs_vec(attrs, self.lower_span(target_span), id, target); + lowered_attrs.extend(extra_hir_attributes.iter().cloned()); assert_eq!(id.owner, self.current_hir_id_owner); let ret = self.arena.alloc_from_iter(lowered_attrs); diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index dc221c2fb1abc..cee9411fdd4a4 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -1084,11 +1084,16 @@ impl<'a> Visitor<'a> for AstValidator<'a> { contract: _, body, define_opaque: _, + eii_impls, }, ) => { self.visit_attrs_vis_ident(&item.attrs, &item.vis, ident); self.check_defaultness(item.span, *defaultness); + for EiiImpl { eii_macro_path, .. } in eii_impls { + self.visit_path(eii_macro_path); + } + let is_intrinsic = item.attrs.iter().any(|a| a.has_name(sym::rustc_intrinsic)); if body.is_none() && !is_intrinsic && !self.is_sdylib_interface { self.dcx().emit_err(errors::FnWithoutBody { diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 41b520b04c993..f9417ed216bb8 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -852,6 +852,17 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere sp: Span, print_visibility: impl FnOnce(&mut Self), ) { + if let Some(eii_extern_target) = ¯o_def.eii_extern_target { + self.word("#[eii_extern_target("); + self.print_path(&eii_extern_target.extern_item_path, false, 0); + if eii_extern_target.impl_unsafe { + self.word(","); + self.space(); + self.word("unsafe"); + } + self.word(")]"); + self.hardbreak(); + } let (kw, has_bang) = if macro_def.macro_rules { ("macro_rules", true) } else { @@ -2141,6 +2152,15 @@ impl<'a> State<'a> { fn print_meta_item(&mut self, item: &ast::MetaItem) { let ib = self.ibox(INDENT_UNIT); + + match item.unsafety { + ast::Safety::Unsafe(_) => { + self.word("unsafe"); + self.popen(); + } + ast::Safety::Default | ast::Safety::Safe(_) => {} + } + match &item.kind { ast::MetaItemKind::Word => self.print_path(&item.path, false, 0), ast::MetaItemKind::NameValue(value) => { @@ -2156,6 +2176,12 @@ impl<'a> State<'a> { self.pclose(); } } + + match item.unsafety { + ast::Safety::Unsafe(_) => self.pclose(), + ast::Safety::Default | ast::Safety::Safe(_) => {} + } + self.end(ib); } diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index ab402cbb8dc12..77063952bd5ac 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -1,7 +1,6 @@ use ast::StaticItem; use itertools::{Itertools, Position}; -use rustc_ast as ast; -use rustc_ast::ModKind; +use rustc_ast::{self as ast, EiiImpl, ModKind, Safety}; use rustc_span::Ident; use crate::pp::BoxMarker; @@ -675,10 +674,25 @@ impl<'a> State<'a> { } fn print_fn_full(&mut self, vis: &ast::Visibility, attrs: &[ast::Attribute], func: &ast::Fn) { - let ast::Fn { defaultness, ident, generics, sig, contract, body, define_opaque } = func; + let ast::Fn { defaultness, ident, generics, sig, contract, body, define_opaque, eii_impls } = + func; self.print_define_opaques(define_opaque.as_deref()); + for EiiImpl { eii_macro_path, impl_safety, .. } in eii_impls { + self.word("#["); + if let Safety::Unsafe(..) = impl_safety { + self.word("unsafe"); + self.popen(); + } + self.print_path(eii_macro_path, false, 0); + if let Safety::Unsafe(..) = impl_safety { + self.pclose(); + } + self.word("]"); + self.hardbreak(); + } + let body_cb_ib = body.as_ref().map(|body| (body, self.head(""))); self.print_visibility(vis); diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index 7c1a5f44e165d..9abcba805f243 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -305,3 +305,10 @@ builtin_macros_unexpected_lit = expected path to a trait, found literal .other = for example, write `#[derive(Debug)]` for `Debug` builtin_macros_unnameable_test_items = cannot test inner items + +builtin_macros_eii_shared_macro_expected_function = `#[{$name}]` is only valid on functions +builtin_macros_eii_extern_target_expected_list = `#[eii_extern_target(...)]` expects a list of one or two elements +builtin_macros_eii_extern_target_expected_macro = `#[eii_extern_target(...)]` is only valid on macros +builtin_macros_eii_shared_macro_expected_max_one_argument = `#[{$name}]` expected no arguments or a single argument: `#[{$name}(default)]` +builtin_macros_eii_extern_target_expected_unsafe = expected this argument to be "unsafe" + .note = the second argument is optional diff --git a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs index 35ef6be095e9c..e26f49998455d 100644 --- a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs +++ b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs @@ -89,6 +89,7 @@ fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span contract: None, body, define_opaque: None, + eii_impls: ThinVec::new(), })); let attrs = thin_vec![cx.attr_word(sym::rustc_std_internal_symbol, span)]; diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 48d0795af5ee2..6c07bfb07e296 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -345,6 +345,7 @@ mod llvm_enzyme { contract: None, body: Some(d_body), define_opaque: None, + eii_impls: ThinVec::new(), }); let mut rustc_ad_attr = Box::new(ast::NormalAttr::from_ident(Ident::with_dummy_span(sym::rustc_autodiff))); diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 3fcf9da945070..b9c51e1cadb1d 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -1086,6 +1086,7 @@ impl<'a> MethodDef<'a> { contract: None, body: Some(body_block), define_opaque: None, + eii_impls: ThinVec::new(), })), tokens: None, }) diff --git a/compiler/rustc_builtin_macros/src/eii.rs b/compiler/rustc_builtin_macros/src/eii.rs new file mode 100644 index 0000000000000..62fcd278103e9 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/eii.rs @@ -0,0 +1,414 @@ +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, +}; +use rustc_ast_pretty::pprust::path_to_string; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::{Ident, Span, kw, sym}; +use thin_vec::{ThinVec, thin_vec}; + +use crate::errors::{ + EiiExternTargetExpectedList, EiiExternTargetExpectedMacro, EiiExternTargetExpectedUnsafe, + EiiMacroExpectedMaxOneArgument, EiiSharedMacroExpectedFunction, +}; + +/// ```rust +/// #[eii] +/// fn panic_handler(); +/// +/// // or: +/// +/// #[eii(panic_handler)] +/// fn panic_handler(); +/// +/// // expansion: +/// +/// extern "Rust" { +/// fn panic_handler(); +/// } +/// +/// #[rustc_builtin_macro(eii_shared_macro)] +/// #[eii_extern_target(panic_handler)] +/// macro panic_handler() {} +/// ``` +pub(crate) fn eii( + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec { + eii_(ecx, span, meta_item, item, false) +} + +pub(crate) fn unsafe_eii( + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec { + eii_(ecx, span, meta_item, item, true) +} + +fn eii_( + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, + impl_unsafe: bool, +) -> Vec { + let span = ecx.with_def_site_ctxt(span); + + let (item, stmt) = if let Annotatable::Item(item) = item { + (item, false) + } else if let Annotatable::Stmt(ref stmt) = item + && let StmtKind::Item(ref item) = stmt.kind + { + (item.clone(), true) + } else { + ecx.dcx().emit_err(EiiSharedMacroExpectedFunction { + span, + name: path_to_string(&meta_item.path), + }); + return vec![item]; + }; + + let orig_item = item.clone(); + + let item = *item; + + let ast::Item { attrs, id: _, span: item_span, vis, kind: ItemKind::Fn(mut func), tokens: _ } = + item + else { + ecx.dcx().emit_err(EiiSharedMacroExpectedFunction { + span, + name: path_to_string(&meta_item.path), + }); + return vec![Annotatable::Item(Box::new(item))]; + }; + + let macro_name = if meta_item.is_word() { + 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 + } else { + 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(); + + 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! + }); + + 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, + }), + expr: Some(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, + })), + tokens: None, + })) + } + + let decl_span = span.to(func.sig.span); + + let abi = match func.sig.header.ext { + // extern "X" fn => extern "X" {} + ast::Extern::Explicit(lit, _) => Some(lit), + // extern fn => extern {} + ast::Extern::Implicit(_) => None, + // fn => extern "Rust" {} + ast::Extern::None => Some(ast::StrLit { + symbol: sym::Rust, + suffix: None, + symbol_unescaped: sym::Rust, + style: ast::StrStyle::Cooked, + span, + }), + }; + + // ABI has been moved to the extern {} block, so we remove it from the fn item. + func.sig.header.ext = ast::Extern::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 extern_block = Box::new(ast::Item { + attrs: ast::AttrVec::default(), + id: ast::DUMMY_NODE_ID, + span, + vis: ast::Visibility { span, kind: ast::VisibilityKind::Inherited, tokens: None }, + kind: ast::ItemKind::ForeignMod(ast::ForeignMod { + extern_span: span, + safety: ast::Safety::Unsafe(span), + abi, + items: From::from([Box::new(ast::ForeignItem { + attrs: attrs.clone(), + id: ast::DUMMY_NODE_ID, + span: item_span, + vis, + kind: ast::ForeignItemKind::Fn(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, + }, + ); + + let macro_def = Box::new(ast::Item { + attrs: macro_attrs, + id: ast::DUMMY_NODE_ID, + span, + // pub + vis: ast::Visibility { span, kind: ast::VisibilityKind::Public, tokens: None }, + kind: ast::ItemKind::MacroDef( + // macro macro_name + macro_name, + ast::MacroDef { + // { () => {} } + body: Box::new(ast::DelimArgs { + dspan: DelimSpan::from_single(span), + delim: Delimiter::Brace, + tokens: TokenStream::from_iter([ + TokenTree::Delimited( + DelimSpan::from_single(span), + DelimSpacing::new(Spacing::Alone, Spacing::Alone), + Delimiter::Parenthesis, + TokenStream::default(), + ), + TokenTree::token_alone(TokenKind::FatArrow, span), + TokenTree::Delimited( + DelimSpan::from_single(span), + DelimSpacing::new(Spacing::Alone, Spacing::Alone), + Delimiter::Brace, + TokenStream::default(), + ), + ]), + }), + macro_rules: false, + // #[eii_extern_target(func.ident)] + eii_extern_target: Some(ast::EiiExternTarget { + extern_item_path: ast::Path::from_ident(func.ident), + 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() + } +} + +pub(crate) fn eii_extern_target( + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &ast::MetaItem, + mut item: Annotatable, +) -> Vec { + let i = if let Annotatable::Item(ref mut item) = item { + item + } else if let Annotatable::Stmt(ref mut stmt) = item + && let StmtKind::Item(ref mut item) = stmt.kind + { + item + } else { + ecx.dcx().emit_err(EiiExternTargetExpectedMacro { span }); + return vec![item]; + }; + + let ItemKind::MacroDef(_, d) = &mut i.kind else { + ecx.dcx().emit_err(EiiExternTargetExpectedMacro { span }); + return vec![item]; + }; + + let Some(list) = meta_item.meta_item_list() else { + ecx.dcx().emit_err(EiiExternTargetExpectedList { span: meta_item.span }); + return vec![item]; + }; + + if list.len() > 2 { + ecx.dcx().emit_err(EiiExternTargetExpectedList { span: meta_item.span }); + return vec![item]; + } + + let Some(extern_item_path) = list.get(0).and_then(|i| i.meta_item()).map(|i| i.path.clone()) + else { + ecx.dcx().emit_err(EiiExternTargetExpectedList { span: meta_item.span }); + return vec![item]; + }; + + let impl_unsafe = if let Some(i) = list.get(1) { + if i.lit().and_then(|i| i.kind.str()).is_some_and(|i| i == kw::Unsafe) { + true + } else { + ecx.dcx().emit_err(EiiExternTargetExpectedUnsafe { span: i.span() }); + return vec![item]; + } + } else { + false + }; + + d.eii_extern_target = Some(EiiExternTarget { extern_item_path, impl_unsafe, span }); + + // Return the original item and the new methods. + vec![item] +} + +/// all Eiis share this function as the implementation for their attribute. +pub(crate) fn eii_shared_macro( + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &ast::MetaItem, + mut item: Annotatable, +) -> Vec { + let i = if let Annotatable::Item(ref mut item) = item { + item + } else if let Annotatable::Stmt(ref mut stmt) = item + && let StmtKind::Item(ref mut item) = stmt.kind + { + item + } else { + ecx.dcx().emit_err(EiiSharedMacroExpectedFunction { + span, + name: path_to_string(&meta_item.path), + }); + return vec![item]; + }; + + let ItemKind::Fn(f) = &mut i.kind else { + ecx.dcx().emit_err(EiiSharedMacroExpectedFunction { + span, + name: path_to_string(&meta_item.path), + }); + return vec![item]; + }; + + let is_default = if meta_item.is_word() { + false + } 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.name == kw::Default + } else { + ecx.dcx().emit_err(EiiMacroExpectedMaxOneArgument { + span: meta_item.span, + name: path_to_string(&meta_item.path), + }); + return vec![item]; + }; + + f.eii_impls.push(EiiImpl { + node_id: DUMMY_NODE_ID, + eii_macro_path: meta_item.path.clone(), + impl_safety: meta_item.unsafety, + span, + inner_span: meta_item.path.span, + is_default, + }); + + vec![item] +} diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index 0993fdc5be453..947fa3d818891 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -1002,3 +1002,41 @@ pub(crate) struct CfgSelectUnreachable { #[label] pub wildcard_span: Span, } + +#[derive(Diagnostic)] +#[diag(builtin_macros_eii_extern_target_expected_macro)] +pub(crate) struct EiiExternTargetExpectedMacro { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_eii_extern_target_expected_list)] +pub(crate) struct EiiExternTargetExpectedList { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_eii_extern_target_expected_unsafe)] +pub(crate) struct EiiExternTargetExpectedUnsafe { + #[primary_span] + #[note] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_eii_shared_macro_expected_function)] +pub(crate) struct EiiSharedMacroExpectedFunction { + #[primary_span] + pub span: Span, + pub name: String, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_eii_shared_macro_expected_max_one_argument)] +pub(crate) struct EiiMacroExpectedMaxOneArgument { + #[primary_span] + pub span: Span, + pub name: String, +} diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs index f14b1920722f4..ae00b24ae6fa9 100644 --- a/compiler/rustc_builtin_macros/src/global_allocator.rs +++ b/compiler/rustc_builtin_macros/src/global_allocator.rs @@ -84,6 +84,7 @@ impl AllocFnFactory<'_, '_> { contract: None, body, define_opaque: None, + eii_impls: ThinVec::new(), })); let item = self.cx.item(self.span, self.attrs(), kind); self.cx.stmt_item(self.ty_span, item) diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 1bcea95fbb7b0..4a68f5a065275 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -40,6 +40,7 @@ mod define_opaque; mod derive; mod deriving; mod edition_panic; +mod eii; mod env; mod errors; mod format; @@ -119,9 +120,13 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { define_opaque: define_opaque::expand, derive: derive::Expander { is_const: false }, derive_const: derive::Expander { is_const: true }, + eii: eii::eii, + eii_extern_target: eii::eii_extern_target, + eii_shared_macro: eii::eii_shared_macro, global_allocator: global_allocator::expand, test: test::expand_test, test_case: test::expand_test_case, + unsafe_eii: eii::unsafe_eii, // tidy-alphabetical-end } diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index 51089e5a1d3f1..f4959630b8b6a 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -344,6 +344,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> Box { contract: None, body: Some(main_body), define_opaque: None, + eii_impls: ThinVec::new(), })); let main = Box::new(ast::Item { diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 0679f55ab7f08..926f543467114 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2689,4 +2689,12 @@ unsafe extern "C" { pub(crate) fn LLVMRustSetNoSanitizeAddress(Global: &Value); pub(crate) fn LLVMRustSetNoSanitizeHWAddress(Global: &Value); + + pub(crate) fn LLVMAddAlias2<'ll>( + M: &'ll Module, + ValueTy: &Type, + AddressSpace: c_uint, + Aliasee: &Value, + Name: *const c_char, + ) -> &'ll Value; } diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index d6974e22c85c5..77ac5fbf78703 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -6,7 +6,7 @@ use std::ptr; use std::string::FromUtf8Error; use libc::c_uint; -use rustc_abi::{Align, Size, WrappingRange}; +use rustc_abi::{AddressSpace, Align, Size, WrappingRange}; use rustc_llvm::RustString; pub(crate) use self::CallConv::*; @@ -437,3 +437,14 @@ pub(crate) fn append_module_inline_asm<'ll>(llmod: &'ll Module, asm: &[u8]) { LLVMAppendModuleInlineAsm(llmod, asm.as_ptr(), asm.len()); } } + +/// Safe wrapper for `LLVMAddAlias2` +pub(crate) fn add_alias<'ll>( + module: &'ll Module, + ty: &Type, + address_space: AddressSpace, + aliasee: &Value, + name: &CStr, +) -> &'ll Value { + unsafe { LLVMAddAlias2(module, ty, address_space.0, aliasee, name.as_ptr()) } +} diff --git a/compiler/rustc_codegen_llvm/src/mono_item.rs b/compiler/rustc_codegen_llvm/src/mono_item.rs index 52eefe2d4d243..7241ab1f6a0dd 100644 --- a/compiler/rustc_codegen_llvm/src/mono_item.rs +++ b/compiler/rustc_codegen_llvm/src/mono_item.rs @@ -1,3 +1,6 @@ +use std::ffi::CString; + +use rustc_abi::AddressSpace; use rustc_codegen_ssa::traits::*; use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; @@ -7,6 +10,7 @@ use rustc_middle::mir::mono::Visibility; use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv, LayoutOf}; use rustc_middle::ty::{self, Instance, TypeVisitableExt}; use rustc_session::config::CrateType; +use rustc_span::Symbol; use rustc_target::spec::RelocModel; use tracing::debug; @@ -41,6 +45,9 @@ impl<'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { llvm::set_visibility(g, base::visibility_to_llvm(visibility)); self.assume_dso_local(g, false); + let attrs = self.tcx.codegen_instance_attrs(instance.def); + self.add_aliases(g, &attrs.foreign_item_symbol_aliases); + self.instances.borrow_mut().insert(instance, g); } @@ -78,11 +85,31 @@ impl<'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { self.assume_dso_local(lldecl, false); + self.add_aliases(lldecl, &attrs.foreign_item_symbol_aliases); + self.instances.borrow_mut().insert(instance, lldecl); } } impl CodegenCx<'_, '_> { + fn add_aliases(&self, aliasee: &llvm::Value, aliases: &[(Symbol, Linkage, Visibility)]) { + let ty = self.get_type_of_global(aliasee); + + for (alias, linkage, visibility) in aliases { + tracing::debug!("ALIAS: {alias:?} {linkage:?} {visibility:?}"); + let lldecl = llvm::add_alias( + self.llmod, + ty, + AddressSpace::ZERO, + aliasee, + &CString::new(alias.as_str()).unwrap(), + ); + + llvm::set_visibility(lldecl, base::visibility_to_llvm(*visibility)); + llvm::set_linkage(lldecl, base::linkage_to_llvm(*linkage)); + } + } + /// Whether a definition or declaration can be assumed to be local to a group of /// libraries that form a single DSO or executable. /// Marks the local as DSO if so. diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index dc500c363f4a7..f4b117156de3d 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -3,19 +3,20 @@ use std::str::FromStr; use rustc_abi::{Align, ExternAbi}; use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, DiffActivity, DiffMode}; use rustc_ast::{LitKind, MetaItem, MetaItemInner, attr}; -use rustc_hir::attrs::{AttributeKind, InlineAttr, InstructionSetAttr, UsedBy}; +use rustc_hir::attrs::{AttributeKind, InlineAttr, InstructionSetAttr, Linkage, UsedBy}; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::{self as hir, Attribute, LangItem, find_attr, lang_items}; use rustc_middle::middle::codegen_fn_attrs::{ CodegenFnAttrFlags, CodegenFnAttrs, PatchableFunctionEntry, }; +use rustc_middle::mir::mono::Visibility; use rustc_middle::query::Providers; use rustc_middle::span_bug; -use rustc_middle::ty::{self as ty, TyCtxt}; +use rustc_middle::ty::{self as ty, Instance, TyCtxt}; use rustc_session::lint; use rustc_session::parse::feature_err; -use rustc_span::{Ident, Span, sym}; +use rustc_span::{Ident, Span, Symbol, sym}; use rustc_target::spec::SanitizerSet; use crate::errors; @@ -296,6 +297,40 @@ fn process_builtin_attrs( AttributeKind::Sanitize { span, .. } => { interesting_spans.sanitize = Some(*span); } + AttributeKind::EiiImpls(impls) => { + for i in impls { + let extern_item = find_attr!( + tcx.get_all_attrs(i.eii_macro), + AttributeKind::EiiExternTarget(target) => target.eii_extern_target + ) + .expect("eii should have declaration macro with extern target attribute"); + + let symbol_name = tcx.symbol_name(Instance::mono(tcx, extern_item)); + + // this is to prevent a bug where a single crate defines both the default and explicit implementation + // for an EII. In that case, both of them may be part of the same final object file. I'm not 100% sure + // what happens, either rustc deduplicates the symbol or llvm, or it's random/order-dependent. + // However, the fact that the default one of has weak linkage isn't considered and you sometimes get that + // the default implementation is used while an explicit implementation is given. + if + // if this is a default impl + i.is_default + // iterate over all implementations *in the current crate* + // (this is ok since we generate codegen fn attrs in the local crate) + // if any of them is *not default* then don't emit the alias. + && tcx.externally_implementable_items(LOCAL_CRATE).get(&i.eii_macro).expect("at least one").1.iter().any(|(_, imp)| !imp.is_default) + { + continue; + } + + codegen_fn_attrs.foreign_item_symbol_aliases.push(( + Symbol::intern(symbol_name.name), + if i.is_default { Linkage::LinkOnceAny } else { Linkage::External }, + Visibility::Default, + )); + codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL; + } + } _ => {} } } diff --git a/compiler/rustc_error_codes/src/error_codes/E0806.md b/compiler/rustc_error_codes/src/error_codes/E0806.md new file mode 100644 index 0000000000000..990367138af6b --- /dev/null +++ b/compiler/rustc_error_codes/src/error_codes/E0806.md @@ -0,0 +1,64 @@ +An externally implementable item is not compatible with its declaration. + +Erroneous code example: + +```rust,edition2021,compile_fail,E0806 +#![feature(eii)] + +#[eii(foo)] +fn x(); + +#[foo] +fn y(a: u64) -> u64 { +//~^ ERROR E0806 + a +} + + +fn main() {} +``` + +To fix this, `y`'s signature must match that of `x`: + +```rust,edition2021 +#![feature(eii)] + +#[eii(foo)] +fn x(); + +#[foo] +fn y() {} + + +fn main() {} +``` + +One common way this can be triggered is by using the wrong +signature for `#[panic_handler]`. +The signature is provided by `core`. + +```rust,edition2021,ignore +#![no_std] + +#[panic_handler] +fn on_panic() -> ! { +//~^ ERROR E0806 + + loop {} +} + +fn main() {} +``` + +Should be: + +```rust,edition2021,ignore +#![no_std] + +#[panic_handler] +fn on_panic(info: &core::panic::PanicInfo<'_>) -> ! { + loop {} +} + +fn main() {} +``` diff --git a/compiler/rustc_error_codes/src/lib.rs b/compiler/rustc_error_codes/src/lib.rs index 0aff1c06e0a8c..c2103808f3194 100644 --- a/compiler/rustc_error_codes/src/lib.rs +++ b/compiler/rustc_error_codes/src/lib.rs @@ -548,6 +548,7 @@ E0802: 0802, E0803: 0803, E0804: 0804, E0805: 0805, +E0806: 0806, ); ) } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 93e5588146e14..6c2cff94c2b70 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -211,6 +211,8 @@ declare_features! ( (internal, compiler_builtins, "1.13.0", None), /// Allows writing custom MIR (internal, custom_mir, "1.65.0", None), + /// Implementation details of externally implementatble items + (unstable, eii_internals, "CURRENT_RUSTC_VERSION", None), /// Outputs useful `assert!` messages (unstable, generic_assert, "1.63.0", None), /// Allows using the #[rustc_intrinsic] attribute. @@ -482,6 +484,8 @@ declare_features! ( (unstable, doc_masked, "1.21.0", Some(44027)), /// Allows features to allow target_feature to better interact with traits. (incomplete, effective_target_features, "CURRENT_RUSTC_VERSION", Some(143352)), + /// Externally implementatble items + (unstable, eii, "CURRENT_RUSTC_VERSION", Some(125418)), /// Allows the .use postfix syntax `x.use` and use closures `use |x| { ... }` (incomplete, ergonomic_clones, "1.87.0", Some(132290)), /// Allows exhaustive pattern matching on types that contain uninhabited types. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index ea11a99efbc35..388c30e19521b 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -17,6 +17,23 @@ use crate::attrs::pretty_printing::PrintAttribute; use crate::limit::Limit; use crate::{DefaultBodyStability, PartialConstStability, RustcVersion, Stability}; +#[derive(Copy, Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub struct EiiImpl { + pub eii_macro: DefId, + pub impl_marked_unsafe: bool, + pub span: Span, + pub inner_span: Span, + pub is_default: bool, +} + +#[derive(Copy, Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub struct EiiDecl { + pub eii_extern_target: DefId, + /// whether or not it is unsafe to implement this EII + pub impl_unsafe: bool, + pub span: Span, +} + #[derive(Copy, Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic, PrintAttribute)] pub enum InlineAttr { None, @@ -503,6 +520,12 @@ pub enum AttributeKind { /// Represents `#[rustc_dummy]`. Dummy, + /// Implementation detail of `#[eii]` + EiiExternTarget(EiiDecl), + + /// Implementation detail of `#[eii]` + EiiImpls(ThinVec), + /// Represents [`#[export_name]`](https://doc.rust-lang.org/reference/abi.html#the-export_name-attribute). ExportName { /// The name to export this item with. diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 55521c1585402..a0d76de39e716 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -43,6 +43,8 @@ impl AttributeKind { DoNotImplementViaObject(..) => No, DocComment { .. } => Yes, Dummy => No, + EiiExternTarget(_) => Yes, + EiiImpls(..) => No, ExportName { .. } => Yes, ExportStable => No, FfiConst(..) => No, diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index d13cd3fd1dab0..05c75492cfe85 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -4,6 +4,7 @@ use rustc_abi::Align; use rustc_ast::token::CommentKind; use rustc_ast::{AttrStyle, IntTy, UintTy}; use rustc_ast_pretty::pp::Printer; +use rustc_span::def_id::DefId; use rustc_span::hygiene::Transparency; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use rustc_target::spec::SanitizerSet; @@ -159,4 +160,5 @@ print_debug!( CommentKind, Transparency, SanitizerSet, + DefId, ); diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 493236718a86a..e7fd3d7b5da99 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -4163,6 +4163,9 @@ pub struct Item<'hir> { pub span: Span, pub vis_span: Span, pub has_delayed_lints: bool, + /// hint to speed up collection: true if the item is a static or function and has + /// either an `EiiImpls` or `EiiExternTarget` attribute + pub eii: bool, } impl<'hir> Item<'hir> { diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index eb682f32111a5..27a93173613b7 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -528,7 +528,7 @@ pub fn walk_param<'v, V: Visitor<'v>>(visitor: &mut V, param: &'v Param<'v>) -> } pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) -> V::Result { - let Item { owner_id: _, kind, span: _, vis_span: _, has_delayed_lints: _ } = item; + let Item { owner_id: _, kind, span: _, vis_span: _, has_delayed_lints: _, eii: _ } = item; try_visit!(visitor.visit_id(item.hir_id())); match *kind { ItemKind::ExternCrate(orig_name, ident) => { diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index 06f2ec512ab1f..5e7c323cc000d 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -612,3 +612,14 @@ hir_analysis_wrong_number_of_generic_arguments_to_intrinsic = [one] parameter *[other] parameters } + +hir_analysis_lifetimes_or_bounds_mismatch_on_eii = + lifetime parameters or bounds of `{$ident}` do not match the declaration + .label = lifetimes do not match + .generics_label = lifetimes in impl do not match this signature + .where_label = this `where` clause might not match the one in the declaration + .bounds_label = this bound might be missing in the implementation + +hir_analysis_eii_with_generics = + #[{$eii_name}] cannot have generic parameters other than lifetimes + .label = required by this attribute diff --git a/compiler/rustc_hir_analysis/src/check/compare_eii.rs b/compiler/rustc_hir_analysis/src/check/compare_eii.rs new file mode 100644 index 0000000000000..429b6e1a3bc21 --- /dev/null +++ b/compiler/rustc_hir_analysis/src/check/compare_eii.rs @@ -0,0 +1,400 @@ +//! This module is very similar to `compare_impl_item`. +//! Most logic is taken from there, +//! since in a very similar way we're comparing some declaration of a signature to an implementation. +//! The major difference is that we don't bother with self types, since for EIIs we're comparing freestanding item. + +use std::borrow::Cow; +use std::iter; + +use rustc_data_structures::fx::FxIndexSet; +use rustc_errors::{Applicability, E0806, struct_span_code_err}; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::{self as hir, FnSig, HirId, ItemKind}; +use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt}; +use rustc_infer::traits::{ObligationCause, ObligationCauseCode}; +use rustc_middle::ty::error::{ExpectedFound, TypeError}; +use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt, TypingMode}; +use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; +use rustc_trait_selection::error_reporting::InferCtxtErrorExt; +use rustc_trait_selection::regions::InferCtxtRegionExt; +use rustc_trait_selection::traits::{self, ObligationCtxt}; +use tracing::{debug, instrument}; + +use super::potentially_plural_count; +use crate::check::compare_impl_item::{CheckRegionBoundsOnItemOutput, check_region_bounds_on_item}; +use crate::errors::{EiiWithGenerics, LifetimesOrBoundsMismatchOnEii}; + +/// checks whether the signature of some `external_impl`, matches +/// the signature of `declaration`, which it is supposed to be compatible +/// with in order to implement the item. +pub(crate) fn compare_eii_function_types<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + declaration: DefId, + eii_name: Symbol, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + check_is_structurally_compatible(tcx, external_impl, declaration, eii_name, eii_attr_span)?; + + let external_impl_span = tcx.def_span(external_impl); + let cause = ObligationCause::new( + external_impl_span, + external_impl, + ObligationCauseCode::CompareEii { external_impl, declaration }, + ); + + // no trait bounds + let param_env = ty::ParamEnv::empty(); + + let infcx = &tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + let ocx = ObligationCtxt::new_with_diagnostics(infcx); + + // We now need to check that the signature of the impl method is + // compatible with that of the trait method. We do this by + // checking that `impl_fty <: trait_fty`. + // + // FIXME: We manually instantiate the trait method here as we need + // to manually compute its implied bounds. Otherwise this could just + // be ocx.sub(impl_sig, trait_sig). + + let wf_tys = FxIndexSet::default(); + let norm_cause = ObligationCause::misc(external_impl_span, external_impl); + + let declaration_sig = tcx.fn_sig(declaration).instantiate_identity(); + let declaration_sig = infcx.enter_forall_and_leak_universe(declaration_sig); + let declaration_sig = ocx.normalize(&norm_cause, param_env, declaration_sig); + + let unnormalized_external_impl_sig = infcx.instantiate_binder_with_fresh_vars( + external_impl_span, + infer::BoundRegionConversionTime::HigherRankedType, + tcx.fn_sig(external_impl).instantiate( + tcx, + infcx.fresh_args_for_item(external_impl_span, external_impl.to_def_id()), + ), + ); + let external_impl_sig = ocx.normalize(&norm_cause, param_env, unnormalized_external_impl_sig); + debug!(?external_impl_sig); + + // FIXME: We'd want to keep more accurate spans than "the method signature" when + // processing the comparison between the trait and impl fn, but we sadly lose them + // and point at the whole signature when a trait bound or specific input or output + // type would be more appropriate. In other places we have a `Vec` + // corresponding to their `Vec`, but we don't have that here. + // Fixing this would improve the output of test `issue-83765.rs`. + let result = ocx.sup(&cause, param_env, declaration_sig, external_impl_sig); + + if let Err(terr) = result { + debug!(?external_impl_sig, ?declaration_sig, ?terr, "sub_types failed"); + + let emitted = report_eii_mismatch( + infcx, + cause, + param_env, + terr, + (declaration, declaration_sig), + (external_impl, external_impl_sig), + eii_attr_span, + eii_name, + ); + return Err(emitted); + } + + if !(declaration_sig, external_impl_sig).references_error() { + for ty in unnormalized_external_impl_sig.inputs_and_output { + ocx.register_obligation(traits::Obligation::new( + infcx.tcx, + cause.clone(), + param_env, + ty::ClauseKind::WellFormed(ty.into()), + )); + } + } + + // Check that all obligations are satisfied by the implementation's + // version. + let errors = ocx.select_all_or_error(); + if !errors.is_empty() { + let reported = infcx.err_ctxt().report_fulfillment_errors(errors); + return Err(reported); + } + + // Finally, resolve all regions. This catches wily misuses of + // lifetime parameters. + let errors = infcx.resolve_regions(external_impl, param_env, wf_tys); + if !errors.is_empty() { + return Err(infcx + .tainted_by_errors() + .unwrap_or_else(|| infcx.err_ctxt().report_region_errors(external_impl, &errors))); + } + + Ok(()) +} + +/// Checks a bunch of different properties of the impl/trait methods for +/// compatibility, such as asyncness, number of argument, self receiver kind, +/// and number of early- and late-bound generics. +/// +/// Corresponds to `check_method_is_structurally_compatible` for impl method compatibility checks. +fn check_is_structurally_compatible<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + declaration: DefId, + eii_name: Symbol, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + check_no_generics(tcx, external_impl, declaration, eii_name, eii_attr_span)?; + compare_number_of_method_arguments(tcx, external_impl, declaration, eii_name, eii_attr_span)?; + check_region_bounds_on_eii(tcx, external_impl, declaration, eii_attr_span)?; + Ok(()) +} + +fn check_no_generics<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + _declaration: DefId, + eii_name: Symbol, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + let generics = tcx.generics_of(external_impl); + if generics.own_requires_monomorphization() { + tcx.dcx().emit_err(EiiWithGenerics { + span: tcx.def_span(external_impl), + attr: eii_attr_span, + eii_name, + }); + } + + Ok(()) +} + +fn check_region_bounds_on_eii<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + declaration: DefId, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + let external_impl_generics = tcx.generics_of(external_impl.to_def_id()); + let external_impl_params = external_impl_generics.own_counts().lifetimes; + + let declaration_generics = tcx.generics_of(declaration); + let declaration_params = declaration_generics.own_counts().lifetimes; + + let Some(CheckRegionBoundsOnItemOutput { span, generics_span, bounds_span, where_span }) = + check_region_bounds_on_item( + tcx, + external_impl, + declaration, + external_impl_generics, + external_impl_params, + declaration_generics, + declaration_params, + ) + else { + return Ok(()); + }; + + let mut diag = tcx.dcx().create_err(LifetimesOrBoundsMismatchOnEii { + span, + ident: tcx.item_name(external_impl.to_def_id()), + generics_span, + bounds_span, + where_span, + }); + + diag.span_label(eii_attr_span, format!("required because of this attribute")); + return Err(diag.emit()); +} + +fn compare_number_of_method_arguments<'tcx>( + tcx: TyCtxt<'tcx>, + external_impl: LocalDefId, + declaration: DefId, + eii_name: Symbol, + eii_attr_span: Span, +) -> Result<(), ErrorGuaranteed> { + let external_impl_fty = tcx.fn_sig(external_impl); + let declaration_fty = tcx.fn_sig(declaration); + let declaration_number_args = declaration_fty.skip_binder().inputs().skip_binder().len(); + let external_impl_number_args = external_impl_fty.skip_binder().inputs().skip_binder().len(); + + // if the number of args are equal, we're trivially done + if declaration_number_args == external_impl_number_args { + return Ok(()); + } + + let external_impl_name = tcx.item_name(external_impl.to_def_id()); + + let declaration_span = declaration + .as_local() + .and_then(|def_id| { + let declaration_sig = get_declaration_sig(tcx, def_id).expect("foreign item sig"); + let pos = declaration_number_args.saturating_sub(1); + declaration_sig.decl.inputs.get(pos).map(|arg| { + if pos == 0 { + arg.span + } else { + arg.span.with_lo(declaration_sig.decl.inputs[0].span.lo()) + } + }) + }) + .or_else(|| tcx.hir_span_if_local(declaration)) + .unwrap_or_else(|| tcx.def_span(declaration)); + + let (_, external_impl_sig, _, _) = &tcx.hir_expect_item(external_impl).expect_fn(); + let pos = external_impl_number_args.saturating_sub(1); + let impl_span = external_impl_sig + .decl + .inputs + .get(pos) + .map(|arg| { + if pos == 0 { + arg.span + } else { + arg.span.with_lo(external_impl_sig.decl.inputs[0].span.lo()) + } + }) + .unwrap_or_else(|| tcx.def_span(external_impl)); + + let mut err = struct_span_code_err!( + tcx.dcx(), + impl_span, + E0806, + "`{external_impl_name}` has {} but #[{eii_name}] requires it to have {}", + potentially_plural_count(external_impl_number_args, "parameter"), + declaration_number_args + ); + + err.span_label( + declaration_span, + format!("requires {}", potentially_plural_count(declaration_number_args, "parameter")), + ); + + err.span_label( + impl_span, + format!( + "expected {}, found {}", + potentially_plural_count(declaration_number_args, "parameter"), + external_impl_number_args + ), + ); + + err.span_label(eii_attr_span, format!("required because of this attribute")); + + Err(err.emit()) +} + +fn report_eii_mismatch<'tcx>( + infcx: &InferCtxt<'tcx>, + mut cause: ObligationCause<'tcx>, + param_env: ty::ParamEnv<'tcx>, + terr: TypeError<'tcx>, + (declaration_did, declaration_sig): (DefId, ty::FnSig<'tcx>), + (external_impl_did, external_impl_sig): (LocalDefId, ty::FnSig<'tcx>), + eii_attr_span: Span, + eii_name: Symbol, +) -> ErrorGuaranteed { + let tcx = infcx.tcx; + let (impl_err_span, trait_err_span, external_impl_name) = + extract_spans_for_error_reporting(infcx, terr, &cause, declaration_did, external_impl_did); + + let mut diag = struct_span_code_err!( + tcx.dcx(), + impl_err_span, + E0806, + "function `{}` has a type that is incompatible with the declaration of `#[{eii_name}]`", + external_impl_name + ); + + diag.span_note(eii_attr_span, "expected this because of this attribute"); + + match &terr { + TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(_, i) => { + if declaration_sig.inputs().len() == *i { + // Suggestion to change output type. We do not suggest in `async` functions + // to avoid complex logic or incorrect output. + if let ItemKind::Fn { sig, .. } = &tcx.hir_expect_item(external_impl_did).kind + && !sig.header.asyncness.is_async() + { + let msg = "change the output type to match the declaration"; + let ap = Applicability::MachineApplicable; + match sig.decl.output { + hir::FnRetTy::DefaultReturn(sp) => { + let sugg = format!(" -> {}", declaration_sig.output()); + diag.span_suggestion_verbose(sp, msg, sugg, ap); + } + hir::FnRetTy::Return(hir_ty) => { + let sugg = declaration_sig.output(); + diag.span_suggestion_verbose(hir_ty.span, msg, sugg, ap); + } + }; + }; + } else if let Some(trait_ty) = declaration_sig.inputs().get(*i) { + diag.span_suggestion_verbose( + impl_err_span, + "change the parameter type to match the declaration", + trait_ty, + Applicability::MachineApplicable, + ); + } + } + _ => {} + } + + cause.span = impl_err_span; + infcx.err_ctxt().note_type_err( + &mut diag, + &cause, + trait_err_span.map(|sp| (sp, Cow::from("type in declaration"), false)), + Some(param_env.and(infer::ValuePairs::PolySigs(ExpectedFound { + expected: ty::Binder::dummy(declaration_sig), + found: ty::Binder::dummy(external_impl_sig), + }))), + terr, + false, + None, + ); + + diag.emit() +} + +#[instrument(level = "debug", skip(infcx))] +fn extract_spans_for_error_reporting<'tcx>( + infcx: &infer::InferCtxt<'tcx>, + terr: TypeError<'_>, + cause: &ObligationCause<'tcx>, + declaration: DefId, + external_impl: LocalDefId, +) -> (Span, Option, Ident) { + let tcx = infcx.tcx; + let (mut external_impl_args, external_impl_name) = { + let item = tcx.hir_expect_item(external_impl); + let (ident, sig, _, _) = item.expect_fn(); + (sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span())), ident) + }; + + let declaration_args = declaration.as_local().map(|def_id| { + if let Some(sig) = get_declaration_sig(tcx, def_id) { + sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span())) + } else { + panic!("expected {def_id:?} to be a foreign function"); + } + }); + + match terr { + TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(ExpectedFound { .. }, i) => ( + external_impl_args.nth(i).unwrap(), + declaration_args.and_then(|mut args| args.nth(i)), + external_impl_name, + ), + _ => ( + cause.span, + tcx.hir_span_if_local(declaration).or_else(|| Some(tcx.def_span(declaration))), + external_impl_name, + ), + } +} + +fn get_declaration_sig<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Option<&'tcx FnSig<'tcx>> { + let hir_id: HirId = tcx.local_def_id_to_hir_id(def_id); + tcx.hir_fn_sig_by_hir_id(hir_id) +} diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index 946c4936bb64d..5bf0c26f81fb9 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -13,8 +13,9 @@ use rustc_infer::infer::{self, BoundRegionConversionTime, InferCtxt, TyCtxtInfer use rustc_infer::traits::util; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::{ - self, BottomUpFolder, GenericArgs, GenericParamDefKind, Ty, TyCtxt, TypeFoldable, TypeFolder, - TypeSuperFoldable, TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, Upcast, + self, BottomUpFolder, GenericArgs, GenericParamDefKind, Generics, Ty, TyCtxt, TypeFoldable, + TypeFolder, TypeSuperFoldable, TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, + Upcast, }; use rustc_middle::{bug, span_bug}; use rustc_span::{DUMMY_SP, Span}; @@ -1074,6 +1075,55 @@ fn check_region_bounds_on_impl_item<'tcx>( let trait_generics = tcx.generics_of(trait_m.def_id); let trait_params = trait_generics.own_counts().lifetimes; + let Some(CheckRegionBoundsOnItemOutput { span, generics_span, bounds_span, where_span }) = + check_region_bounds_on_item( + tcx, + impl_m.def_id.expect_local(), + trait_m.def_id, + impl_generics, + impl_params, + trait_generics, + trait_params, + ) + else { + return Ok(()); + }; + + if !delay && let Some(guar) = check_region_late_boundedness(tcx, impl_m, trait_m) { + return Err(guar); + } + + let reported = tcx + .dcx() + .create_err(LifetimesOrBoundsMismatchOnTrait { + span, + item_kind: impl_m.descr(), + ident: impl_m.ident(tcx), + generics_span, + bounds_span, + where_span, + }) + .emit_unless_delay(delay); + + Err(reported) +} + +pub(super) struct CheckRegionBoundsOnItemOutput { + pub(super) span: Span, + pub(super) generics_span: Option, + pub(super) bounds_span: Vec, + pub(super) where_span: Option, +} + +pub(super) fn check_region_bounds_on_item<'tcx>( + tcx: TyCtxt<'tcx>, + impl_def_id: LocalDefId, + trait_def_id: DefId, + impl_generics: &Generics, + impl_params: usize, + trait_generics: &Generics, + trait_params: usize, +) -> Option { debug!(?trait_generics, ?impl_generics); // Must have same number of early-bound lifetime parameters. @@ -1086,15 +1136,11 @@ fn check_region_bounds_on_impl_item<'tcx>( // are zero. Since I don't quite know how to phrase things at // the moment, give a kind of vague error message. if trait_params == impl_params { - return Ok(()); - } - - if !delay && let Some(guar) = check_region_late_boundedness(tcx, impl_m, trait_m) { - return Err(guar); + return None; } let span = tcx - .hir_get_generics(impl_m.def_id.expect_local()) + .hir_get_generics(impl_def_id) .expect("expected impl item to have generics or else we can't compare them") .span; @@ -1102,7 +1148,7 @@ fn check_region_bounds_on_impl_item<'tcx>( let mut bounds_span = vec![]; let mut where_span = None; - if let Some(trait_node) = tcx.hir_get_if_local(trait_m.def_id) + if let Some(trait_node) = tcx.hir_get_if_local(trait_def_id) && let Some(trait_generics) = trait_node.generics() { generics_span = Some(trait_generics.span); @@ -1127,7 +1173,7 @@ fn check_region_bounds_on_impl_item<'tcx>( _ => {} } } - if let Some(impl_node) = tcx.hir_get_if_local(impl_m.def_id) + if let Some(impl_node) = tcx.hir_get_if_local(impl_def_id.into()) && let Some(impl_generics) = impl_node.generics() { let mut impl_bounds = 0; @@ -1158,19 +1204,7 @@ fn check_region_bounds_on_impl_item<'tcx>( } } - let reported = tcx - .dcx() - .create_err(LifetimesOrBoundsMismatchOnTrait { - span, - item_kind: impl_m.descr(), - ident: impl_m.ident(tcx), - generics_span, - bounds_span, - where_span, - }) - .emit_unless_delay(delay); - - Err(reported) + Some(CheckRegionBoundsOnItemOutput { span, generics_span, bounds_span, where_span }) } #[allow(unused)] diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index 63d0f400aefc4..1279a63a2f760 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -64,6 +64,7 @@ a type parameter). pub mod always_applicable; mod check; +mod compare_eii; mod compare_impl_item; mod entry; pub mod intrinsic; diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index d33f1f3e12afe..27e6beffe795e 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -6,10 +6,11 @@ use rustc_abi::ExternAbi; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::codes::*; use rustc_errors::{Applicability, ErrorGuaranteed, pluralize, struct_span_code_err}; +use rustc_hir::attrs::{AttributeKind, EiiDecl, EiiImpl}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::lang_items::LangItem; -use rustc_hir::{AmbigArg, ItemKind}; +use rustc_hir::{AmbigArg, ItemKind, find_attr}; use rustc_infer::infer::outlives::env::OutlivesEnvironment; use rustc_infer::infer::{self, InferCtxt, SubregionOrigin, TyCtxtInferExt}; use rustc_lint_defs::builtin::SUPERTRAIT_ITEM_SHADOWING_DEFINITION; @@ -38,6 +39,7 @@ use rustc_trait_selection::traits::{ use tracing::{debug, instrument}; use {rustc_ast as ast, rustc_hir as hir}; +use super::compare_eii::compare_eii_function_types; use crate::autoderef::Autoderef; use crate::constrained_generic_params::{Parameter, identify_constrained_generic_params}; use crate::errors::InvalidReceiverTyHint; @@ -1172,12 +1174,41 @@ fn check_item_fn( decl: &hir::FnDecl<'_>, ) -> Result<(), ErrorGuaranteed> { enter_wf_checking_ctxt(tcx, def_id, |wfcx| { + check_eiis(tcx, def_id); + let sig = tcx.fn_sig(def_id).instantiate_identity(); check_fn_or_method(wfcx, sig, decl, def_id); Ok(()) }) } +fn check_eiis(tcx: TyCtxt<'_>, def_id: LocalDefId) { + // does the function have an EiiImpl attribute? that contains the defid of a *macro* + // that was used to mark the implementation. This is a two step process. + for EiiImpl { eii_macro, span, .. } in + find_attr!(tcx.get_all_attrs(def_id), AttributeKind::EiiImpls(impls) => impls) + .into_iter() + .flatten() + { + // we expect this macro to have the `EiiMacroFor` attribute, that points to a function + // signature that we'd like to compare the function we're currently checking with + if let Some(eii_extern_target) = find_attr!(tcx.get_all_attrs(*eii_macro), AttributeKind::EiiExternTarget(EiiDecl {eii_extern_target, ..}) => *eii_extern_target) + { + let _ = compare_eii_function_types( + tcx, + def_id, + eii_extern_target, + tcx.item_name(*eii_macro), + *span, + ); + } else { + panic!( + "EII impl macro {eii_macro:?} did not have an eii extern target attribute pointing to a foreign function" + ) + } + } +} + #[instrument(level = "debug", skip(tcx))] pub(crate) fn check_static_item<'tcx>( tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 3b6367219b7ff..420b4fda3ee85 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -1707,3 +1707,28 @@ pub(crate) struct AsyncDropWithoutSyncDrop { #[primary_span] pub span: Span, } + +#[derive(Diagnostic)] +#[diag(hir_analysis_lifetimes_or_bounds_mismatch_on_eii)] +pub(crate) struct LifetimesOrBoundsMismatchOnEii { + #[primary_span] + #[label] + pub span: Span, + #[label(hir_analysis_generics_label)] + pub generics_span: Option, + #[label(hir_analysis_where_label)] + pub where_span: Option, + #[label(hir_analysis_bounds_label)] + pub bounds_span: Vec, + pub ident: Symbol, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_eii_with_generics)] +pub(crate) struct EiiWithGenerics { + #[primary_span] + pub span: Span, + #[label] + pub attr: Span, + pub eii_name: Symbol, +} diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index d39219bfd660f..56131999e500f 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -1054,6 +1054,9 @@ fn run_required_analyses(tcx: TyCtxt<'_>) { parallel!( { sess.time("looking_for_entry_point", || tcx.ensure_ok().entry_fn(())); + sess.time("check_externally_implementable_items", || { + tcx.ensure_ok().check_externally_implementable_items(()) + }); sess.time("looking_for_derive_registrar", || { tcx.ensure_ok().proc_macro_decls_static(()) diff --git a/compiler/rustc_metadata/src/eii.rs b/compiler/rustc_metadata/src/eii.rs new file mode 100644 index 0000000000000..5558ff9308512 --- /dev/null +++ b/compiler/rustc_metadata/src/eii.rs @@ -0,0 +1,47 @@ +use rustc_data_structures::fx::FxIndexMap; +use rustc_hir::attrs::{AttributeKind, EiiDecl, EiiImpl}; +use rustc_hir::def_id::DefId; +use rustc_hir::find_attr; +use rustc_middle::query::LocalCrate; +use rustc_middle::ty::TyCtxt; + +// basically the map below but flattened out +pub(crate) type EiiMapEncodedKeyValue = (DefId, (EiiDecl, Vec<(DefId, EiiImpl)>)); + +pub(crate) type EiiMap = FxIndexMap< + DefId, // the defid of the macro that declared the eii + ( + // the corresponding declaration + EiiDecl, + // all the given implementations, indexed by defid. + // We expect there to be only one, but collect them all to give errors if there are more + // (or if there are none) in the final crate we build. + FxIndexMap, + ), +>; + +pub(crate) fn collect<'tcx>(tcx: TyCtxt<'tcx>, LocalCrate: LocalCrate) -> EiiMap { + let mut eiis = EiiMap::default(); + + // iterate over all items in the current crate + for id in tcx.hir_crate_items(()).eiis() { + for i in + find_attr!(tcx.get_all_attrs(id), AttributeKind::EiiImpls(e) => e).into_iter().flatten() + { + eiis.entry(i.eii_macro) + .or_insert_with(|| { + // find the decl for this one if it wasn't in yet (maybe it's from the local crate? not very useful but not illegal) + (find_attr!(tcx.get_all_attrs(i.eii_macro), AttributeKind::EiiExternTarget(d) => *d).unwrap(), Default::default()) + }).1.insert(id.into(), *i); + } + + // if we find a new declaration, add it to the list without a known implementation + if let Some(decl) = + find_attr!(tcx.get_all_attrs(id), AttributeKind::EiiExternTarget(d) => *d) + { + eiis.entry(id.into()).or_insert((decl, Default::default())); + } + } + + eiis +} diff --git a/compiler/rustc_metadata/src/lib.rs b/compiler/rustc_metadata/src/lib.rs index 3e50689b5accb..3afbb483f43ea 100644 --- a/compiler/rustc_metadata/src/lib.rs +++ b/compiler/rustc_metadata/src/lib.rs @@ -18,6 +18,7 @@ pub use rmeta::provide; mod dependency_format; +mod eii; mod foreign_modules; mod native_libs; mod rmeta; diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 0c8d1f32e991e..78bb4aaedfcc4 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -38,6 +38,7 @@ use rustc_span::{ use tracing::debug; use crate::creader::CStore; +use crate::eii::EiiMapEncodedKeyValue; use crate::rmeta::table::IsDefault; use crate::rmeta::*; @@ -1486,6 +1487,13 @@ impl<'a> CrateMetadataRef<'a> { ) } + fn get_externally_implementable_items( + self, + sess: &'a Session, + ) -> impl Iterator { + self.root.externally_implementable_items.decode((self, sess)) + } + fn get_missing_lang_items<'tcx>(self, tcx: TyCtxt<'tcx>) -> &'tcx [LangItem] { tcx.arena.alloc_from_iter(self.root.lang_items_missing.decode(self)) } @@ -1521,7 +1529,7 @@ impl<'a> CrateMetadataRef<'a> { let macro_rules = self.root.tables.is_macro_rules.get(self, id); let body = self.root.tables.macro_definition.get(self, id).unwrap().decode((self, sess)); - ast::MacroDef { macro_rules, body: Box::new(body) } + ast::MacroDef { macro_rules, body: Box::new(body), eii_extern_target: None } } _ => bug!(), } diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 11fef3be5d09b..51a760e47404b 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -24,7 +24,7 @@ use super::{Decodable, DecodeContext, DecodeIterator}; use crate::creader::{CStore, LoadedMacro}; use crate::rmeta::AttrFlags; use crate::rmeta::table::IsDefault; -use crate::{foreign_modules, native_libs}; +use crate::{eii, foreign_modules, native_libs}; trait ProcessQueryValue<'tcx, T> { fn process_decoded(self, _tcx: TyCtxt<'tcx>, _err: impl Fn() -> !) -> T; @@ -328,9 +328,22 @@ provide! { tcx, def_id, other, cdata, is_private_dep => { cdata.private_dep } is_panic_runtime => { cdata.root.panic_runtime } is_compiler_builtins => { cdata.root.compiler_builtins } + + // FIXME: to be replaced with externally_implementable_items below has_global_allocator => { cdata.root.has_global_allocator } + // FIXME: to be replaced with externally_implementable_items below has_alloc_error_handler => { cdata.root.has_alloc_error_handler } + // FIXME: to be replaced with externally_implementable_items below has_panic_handler => { cdata.root.has_panic_handler } + + externally_implementable_items => { + cdata.get_externally_implementable_items(tcx.sess) + .map(|(decl_did, (decl, impls))| ( + decl_did, + (decl, impls.into_iter().collect()) + )).collect() + } + is_profiler_runtime => { cdata.root.profiler_runtime } required_panic_strategy => { cdata.root.required_panic_strategy } panic_in_drop_strategy => { cdata.root.panic_in_drop_strategy } @@ -427,6 +440,7 @@ pub(in crate::rmeta) fn provide(providers: &mut Providers) { }, native_libraries: native_libs::collect, foreign_modules: foreign_modules::collect, + externally_implementable_items: eii::collect, // Returns a map from a sufficiently visible external item (i.e., an // external item that is visible from at least one local module) to a diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index db66938457f09..47fc7429b1e8f 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -35,6 +35,7 @@ use rustc_span::{ }; use tracing::{debug, instrument, trace}; +use crate::eii::EiiMapEncodedKeyValue; use crate::errors::{FailCreateFileEncoder, FailWriteFile}; use crate::rmeta::*; @@ -620,6 +621,9 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // We have already encoded some things. Get their combined size from the current position. stats.push(("preamble", self.position())); + let externally_implementable_items = stat!("externally-implementable-items", || self + .encode_externally_implementable_items()); + let (crate_deps, dylib_dependency_formats) = stat!("dep", || (self.encode_crate_deps(), self.encode_dylib_dependency_formats())); @@ -738,6 +742,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { attrs, sym::default_lib_allocator, ), + externally_implementable_items, proc_macro_data, debugger_visualizers, compiler_builtins: ast::attr::contains_name(attrs, sym::compiler_builtins), @@ -1628,6 +1633,15 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } + fn encode_externally_implementable_items(&mut self) -> LazyArray { + empty_proc_macro!(self); + let externally_implementable_items = self.tcx.externally_implementable_items(LOCAL_CRATE); + + self.lazy_array(externally_implementable_items.iter().map(|(decl_did, (decl, impls))| { + (*decl_did, (decl.clone(), impls.iter().map(|(impl_did, i)| (*impl_did, *i)).collect())) + })) + } + #[instrument(level = "trace", skip(self))] fn encode_info_for_adt(&mut self, local_def_id: LocalDefId) { let def_id = local_def_id.to_def_id(); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 720970bbaf93f..fa98bc41313e3 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -42,6 +42,7 @@ use table::TableBuilder; use {rustc_ast as ast, rustc_hir as hir}; use crate::creader::CrateMetadataRef; +use crate::eii::EiiMapEncodedKeyValue; mod decoder; mod def_path_hash_map; @@ -248,6 +249,7 @@ pub(crate) struct CrateRoot { has_alloc_error_handler: bool, has_panic_handler: bool, has_default_lib_allocator: bool, + externally_implementable_items: LazyArray, crate_deps: LazyArray, dylib_dependency_formats: LazyArray>, diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index 4b2dc2c814e57..10af6b66a829e 100644 --- a/compiler/rustc_metadata/src/rmeta/parameterized.rs +++ b/compiler/rustc_metadata/src/rmeta/parameterized.rs @@ -87,6 +87,8 @@ trivially_parameterized_over_tcx! { rustc_hir::Safety, rustc_hir::Stability, rustc_hir::attrs::Deprecation, + rustc_hir::attrs::EiiDecl, + rustc_hir::attrs::EiiImpl, rustc_hir::attrs::StrippedCfgItem, rustc_hir::def::DefKind, rustc_hir::def::DocLinkResMap, diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index 4370816d38e5f..e476d7986b802 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -1225,6 +1225,7 @@ pub(super) fn hir_module_items(tcx: TyCtxt<'_>, module_id: LocalModDefId) -> Mod body_owners, opaques, nested_bodies, + eiis, .. } = collector; ModuleItems { @@ -1238,6 +1239,7 @@ pub(super) fn hir_module_items(tcx: TyCtxt<'_>, module_id: LocalModDefId) -> Mod opaques: opaques.into_boxed_slice(), nested_bodies: nested_bodies.into_boxed_slice(), delayed_lint_items: Box::new([]), + eiis: eiis.into_boxed_slice(), } } @@ -1260,6 +1262,7 @@ pub(crate) fn hir_crate_items(tcx: TyCtxt<'_>, _: ()) -> ModuleItems { opaques, nested_bodies, mut delayed_lint_items, + eiis, .. } = collector; @@ -1282,6 +1285,7 @@ pub(crate) fn hir_crate_items(tcx: TyCtxt<'_>, _: ()) -> ModuleItems { opaques: opaques.into_boxed_slice(), nested_bodies: nested_bodies.into_boxed_slice(), delayed_lint_items: delayed_lint_items.into_boxed_slice(), + eiis: eiis.into_boxed_slice(), } } @@ -1299,6 +1303,7 @@ struct ItemCollector<'tcx> { opaques: Vec, nested_bodies: Vec, delayed_lint_items: Vec, + eiis: Vec, } impl<'tcx> ItemCollector<'tcx> { @@ -1315,6 +1320,7 @@ impl<'tcx> ItemCollector<'tcx> { opaques: Vec::default(), nested_bodies: Vec::default(), delayed_lint_items: Vec::default(), + eiis: Vec::default(), } } } @@ -1336,6 +1342,12 @@ impl<'hir> Visitor<'hir> for ItemCollector<'hir> { self.delayed_lint_items.push(item.item_id().owner_id); } + if let ItemKind::Static(..) | ItemKind::Fn { .. } | ItemKind::Macro(..) = &item.kind + && item.eii + { + self.eiis.push(item.owner_id.def_id) + } + // Items that are modules are handled here instead of in visit_mod. if let ItemKind::Mod(_, module) = &item.kind { self.submodules.push(item.owner_id); diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index 67bc89692ff79..9aa285a237680 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -37,6 +37,9 @@ pub struct ModuleItems { nested_bodies: Box<[LocalDefId]>, // only filled with hir_crate_items, not with hir_module_items delayed_lint_items: Box<[OwnerId]>, + + /// Statics and functions with an `EiiImpls` or `EiiExternTarget` attribute + eiis: Box<[LocalDefId]>, } impl ModuleItems { @@ -58,6 +61,10 @@ impl ModuleItems { self.delayed_lint_items.iter().copied() } + pub fn eiis(&self) -> impl Iterator { + self.eiis.iter().copied() + } + /// Returns all items that are associated with some `impl` block (both inherent and trait impl /// blocks). pub fn impl_items(&self) -> impl Iterator { diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs index 8b4503073b0de..e7c73d3b0281e 100644 --- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs +++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs @@ -6,6 +6,7 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable}; use rustc_span::Symbol; use rustc_target::spec::SanitizerSet; +use crate::mir::mono::Visibility; use crate::ty::{InstanceKind, TyCtxt}; impl<'tcx> TyCtxt<'tcx> { @@ -38,6 +39,12 @@ pub struct CodegenFnAttrs { /// using the `#[export_name = "..."]` or `#[link_name = "..."]` attribute /// depending on if this is a function definition or foreign function. pub symbol_name: Option, + /// Defids of foreign items somewhere that this function should "satisfy". + /// i.e., if a foreign function has some symbol foo, + /// generate this function under its real name, + /// but *also* under the same name as this foreign function so that the foreign function has an implementation. + // FIXME: make "SymbolName<'tcx>" + pub foreign_item_symbol_aliases: Vec<(Symbol, Linkage, Visibility)>, /// The `#[link_ordinal = "..."]` attribute, indicating an ordinal an /// imported function has in the dynamic library. Note that this must not /// be set when `link_name` is set. This is for foreign items with the @@ -177,6 +184,7 @@ impl CodegenFnAttrs { symbol_name: None, link_ordinal: None, target_features: vec![], + foreign_item_symbol_aliases: vec![], safe_target_features: false, linkage: None, import_linkage: None, diff --git a/compiler/rustc_middle/src/mir/mono.rs b/compiler/rustc_middle/src/mir/mono.rs index 6b45b2dc3ff50..c43768c05906f 100644 --- a/compiler/rustc_middle/src/mir/mono.rs +++ b/compiler/rustc_middle/src/mir/mono.rs @@ -371,7 +371,7 @@ pub struct MonoItemData { /// Visibility doesn't have any effect when linkage is internal. /// /// DSO means dynamic shared object, that is a dynamically linked executable or dylib. -#[derive(Copy, Clone, PartialEq, Debug, HashStable)] +#[derive(Copy, Clone, PartialEq, Debug, HashStable, TyEncodable, TyDecodable)] pub enum Visibility { /// Export the symbol from the DSO and apply overrides of the symbol by outside DSOs to within /// the DSO if the object file format supports this. diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index 4c00b769237f3..382dc090e1a73 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -279,6 +279,8 @@ trivial! { rustc_ast::expand::allocator::AllocatorKind, rustc_hir::DefaultBodyStability, rustc_hir::attrs::Deprecation, + rustc_hir::attrs::EiiDecl, + rustc_hir::attrs::EiiImpl, rustc_data_structures::svh::Svh, rustc_errors::ErrorGuaranteed, rustc_hir::Constness, diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 0e645a3aae45b..287b41a11fdb5 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -77,7 +77,7 @@ use rustc_data_structures::steal::Steal; use rustc_data_structures::svh::Svh; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::ErrorGuaranteed; -use rustc_hir::attrs::StrippedCfgItem; +use rustc_hir::attrs::{EiiDecl, EiiImpl, StrippedCfgItem}; use rustc_hir::def::{DefKind, DocLinkResMap}; use rustc_hir::def_id::{ CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdMap, LocalDefIdSet, LocalModDefId, @@ -2728,6 +2728,17 @@ rustc_queries! { desc { |tcx| "checking what set of sanitizers are enabled on `{}`", tcx.def_path_str(key) } feedable } + + query check_externally_implementable_items(_: ()) { + desc { "check externally implementable items" } + } + + /// Returns a list of all `externally implementable items` crate. + query externally_implementable_items(_: CrateNum) -> &'tcx FxIndexMap)> { + arena_cache + desc { "looking up the externally implementable items of a crate" } + separate_provide_extern + } } rustc_with_all_queries! { define_callbacks! } diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index ab8a314295392..b106f1c11bde5 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -420,6 +420,13 @@ pub enum ObligationCauseCode<'tcx> { /// Only reachable if the `unsized_fn_params` feature is used. Unsized function arguments must /// be place expressions because we can't store them in MIR locals as temporaries. UnsizedNonPlaceExpr(Span), + + /// Error derived when checking an impl item is compatible with + /// its corresponding trait item's definition + CompareEii { + external_impl: LocalDefId, + declaration: DefId, + }, } /// Whether a value can be extracted into a const. diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index eb264f59fedbe..ffca0e15632f6 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -224,6 +224,7 @@ impl<'a> Parser<'a> { contract, body, define_opaque: None, + eii_impls: ThinVec::new(), })) } else if self.eat_keyword(exp!(Extern)) { if self.eat_keyword(exp!(Crate)) { @@ -2245,7 +2246,10 @@ impl<'a> Parser<'a> { }; self.psess.gated_spans.gate(sym::decl_macro, lo.to(self.prev_token.span)); - Ok(ItemKind::MacroDef(ident, ast::MacroDef { body, macro_rules: false })) + Ok(ItemKind::MacroDef( + ident, + ast::MacroDef { body, macro_rules: false, eii_extern_target: None }, + )) } /// Is this a possibly malformed start of a `macro_rules! foo` item definition? @@ -2292,7 +2296,10 @@ impl<'a> Parser<'a> { self.eat_semi_for_macro_if_needed(&body); self.complain_if_pub_macro(vis, true); - Ok(ItemKind::MacroDef(ident, ast::MacroDef { body, macro_rules: true })) + Ok(ItemKind::MacroDef( + ident, + ast::MacroDef { body, macro_rules: true, eii_extern_target: None }, + )) } /// Item macro invocations or `macro_rules!` definitions need inherited visibility. diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 75537caa8946a..e496c8749d853 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -712,3 +712,34 @@ passes_useless_stability = this stability annotation is useless .label = useless stability annotation .item = the stability attribute annotates this item + +passes_eii_fn_with_target_feature = + `#[{$name}]` is not allowed to have `#[target_feature]` + .label = `#[{$name}]` is not allowed to have `#[target_feature]` + +passes_eii_fn_with_track_caller = + `#[{$name}]` is not allowed to have `#[track_caller]` + .label = `#[{$name}]` is not allowed to have `#[track_caller]` + +passes_eii_impl_not_function = + `eii_macro_for` is only valid on functions + +passes_eii_impl_requires_unsafe = + `#[{$name}]` is unsafe to implement +passes_eii_impl_requires_unsafe_suggestion = wrap the attribute in `unsafe(...)` + +passes_eii_without_impl = + `#[{$name}]` required, but not found + .label = expected because `#[{$name}]` was declared here in crate `{$decl_crate_name}` + .help = expected at least one implementation in crate `{$current_crate_name}` or any of its dependencies + +passes_duplicate_eii_impls = + multiple implementations of `#[{$name}]` + .first = first implemented here in crate `{$first_crate}` + .second = also implemented here in crate `{$second_crate}` + .note = in addition to these two, { $num_additional_crates -> + [one] another implementation was found in crate {$additional_crate_names} + *[other] more implementations were also found in the following crates: {$additional_crate_names} + } + + .help = an "externally implementable item" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 2562d2e0b83c5..c411f18b74c12 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -18,7 +18,9 @@ use rustc_feature::{ ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, }; -use rustc_hir::attrs::{AttributeKind, InlineAttr, MirDialect, MirPhase, ReprAttr, SanitizerSet}; +use rustc_hir::attrs::{ + AttributeKind, EiiDecl, EiiImpl, InlineAttr, MirDialect, MirPhase, ReprAttr, SanitizerSet, +}; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalModDefId; use rustc_hir::intravisit::{self, Visitor}; @@ -218,8 +220,12 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Parsed(AttributeKind::Link(_, attr_span)) => { self.check_link(hir_id, *attr_span, span, target) } + Attribute::Parsed(AttributeKind::EiiImpls(impls)) => { + self.check_eii_impl(impls, target) + }, Attribute::Parsed( - AttributeKind::BodyStability { .. } + AttributeKind::EiiExternTarget { .. } + | AttributeKind::BodyStability { .. } | AttributeKind::ConstStabilityIndirect | AttributeKind::MacroTransparency(_) | AttributeKind::Pointee(..) @@ -456,6 +462,30 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ); } + fn check_eii_impl(&self, impls: &[EiiImpl], target: Target) { + for EiiImpl { span, inner_span, eii_macro, impl_marked_unsafe, is_default: _ } in impls { + match target { + Target::Fn => {} + _ => { + self.dcx().emit_err(errors::EiiImplNotFunction { span: *span }); + } + } + + if find_attr!(self.tcx.get_all_attrs(*eii_macro), AttributeKind::EiiExternTarget(EiiDecl { impl_unsafe, .. }) if *impl_unsafe) + && !impl_marked_unsafe + { + self.dcx().emit_err(errors::EiiImplRequiresUnsafe { + span: *span, + name: self.tcx.item_name(*eii_macro), + suggestion: errors::EiiImplRequiresUnsafeSuggestion { + left: inner_span.shrink_to_lo(), + right: inner_span.shrink_to_hi(), + }, + }); + } + } + } + /// Checks if `#[diagnostic::do_not_recommend]` is applied on a trait impl and that it has no /// arguments. fn check_do_not_recommend( @@ -655,6 +685,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> { sig_span: sig.span, }); } + + if let Some(impls) = find_attr!(attrs, AttributeKind::EiiImpls(impls) => impls) { + let sig = self.tcx.hir_node(hir_id).fn_sig().unwrap(); + for i in impls { + self.dcx().emit_err(errors::EiiWithTrackCaller { + attr_span, + name: self.tcx.item_name(i.eii_macro), + sig_span: sig.span, + }); + } + } } _ => {} } diff --git a/compiler/rustc_passes/src/eii.rs b/compiler/rustc_passes/src/eii.rs new file mode 100644 index 0000000000000..24f10b8f3f828 --- /dev/null +++ b/compiler/rustc_passes/src/eii.rs @@ -0,0 +1,157 @@ +//! Validity checking for weak lang items + +use std::iter; + +use rustc_data_structures::fx::FxIndexMap; +use rustc_hir::attrs::{EiiDecl, EiiImpl}; +use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; +use rustc_middle::ty::TyCtxt; +use rustc_session::config::CrateType; + +use crate::errors::{DuplicateEiiImpls, EiiWithoutImpl}; + +#[derive(Clone, Copy, Debug)] +enum CheckingMode { + CheckDuplicates, + CheckExistence, +} + +fn get_checking_mode(tcx: TyCtxt<'_>) -> CheckingMode { + // if any of the crate types is not rlib or dylib, we must check for existence. + if tcx.crate_types().iter().any(|i| !matches!(i, CrateType::Rlib | CrateType::Dylib)) { + CheckingMode::CheckExistence + } else { + CheckingMode::CheckDuplicates + } +} + +/// Checks for a given crate, what EIIs need to be generated in it. +/// This is usually a small subset of all EIIs. +/// +/// EII implementations come in two varieties: explicit and default. +/// This query is called once for every crate, to check whether there aren't any duplicate explicit implementations. +/// A duplicate may be caused by an implementation in the current crate, +/// though it's also entirely possible that the source is two dependencies with an explicit implementation. +/// Those work fine on their own but the combination of the two is a conflict. +/// +/// However, if the current crate is a "root" crate, one that generates a final artifact like a binary, +/// then we check one more thing, namely that every EII actually has an implementation, either default or not. +/// If one EII has no implementation, that's an error at that point. +/// +/// These two behaviors are implemented using `CheckingMode`. +pub(crate) fn check_externally_implementable_items<'tcx>(tcx: TyCtxt<'tcx>, (): ()) { + let checking_mode = get_checking_mode(tcx); + + #[derive(Debug)] + struct FoundImpl { + imp: EiiImpl, + impl_crate: CrateNum, + } + + #[derive(Debug)] + struct FoundEii { + decl: EiiDecl, + decl_crate: CrateNum, + impls: FxIndexMap, + } + + let mut eiis = FxIndexMap::::default(); + + // collect all the EII declarations, and possibly implementations from all descendent crates + for &cnum in tcx.crates(()).iter().chain(iter::once(&LOCAL_CRATE)) { + // get the eiis for the crate we're currently looking at + let crate_eiis = tcx.externally_implementable_items(cnum); + + // update or insert the corresponding entries + for (did, (decl, impls)) in crate_eiis { + eiis.entry(*did) + .or_insert_with(|| FoundEii { + decl: *decl, + decl_crate: cnum, + impls: Default::default(), + }) + .impls + .extend( + impls + .into_iter() + .map(|(did, i)| (*did, FoundImpl { imp: *i, impl_crate: cnum })), + ); + } + } + + // now we have all eiis! For each of them, choose one we want to actually generate. + for (decl_did, FoundEii { decl, decl_crate, impls }) in eiis { + let mut default_impls = Vec::new(); + let mut explicit_impls = Vec::new(); + + for (impl_did, FoundImpl { imp, impl_crate }) in impls { + if imp.is_default { + default_impls.push((impl_did, impl_crate)); + } else { + explicit_impls.push((impl_did, impl_crate)); + } + } + + // more than one explicit implementation (across all crates) + // is instantly an error. + if explicit_impls.len() > 1 { + tcx.dcx().emit_err(DuplicateEiiImpls { + name: tcx.item_name(decl_did), + first_span: tcx.def_span(explicit_impls[0].0), + first_crate: tcx.crate_name(explicit_impls[0].1), + second_span: tcx.def_span(explicit_impls[1].0), + second_crate: tcx.crate_name(explicit_impls[1].1), + + help: (), + + additional_crates: (explicit_impls.len() > 2).then_some(()), + num_additional_crates: explicit_impls.len() - 2, + additional_crate_names: explicit_impls[2..] + .iter() + .map(|i| format!("`{}`", tcx.crate_name(i.1))) + .collect::>() + .join(", "), + }); + } + + if default_impls.len() > 1 { + panic!("multiple not supported right now"); + } + + let (local_impl, is_default) = + // note, for a single crate we never need to generate both a default and an explicit implementation. + // In that case, generating the explicit implementation is enough! + match (checking_mode, explicit_impls.first(), default_impls.first()) { + // If we find an explicit implementation, it's instantly the chosen implementation. + (_, Some((explicit, _)), _) => (explicit, false), + // if we find a default implementation, we can emit it but the alias should be weak + (_, _, Some((deflt, _))) => (deflt, true), + + // if we find no explicit implementation, + // that's fine if we're only checking for duplicates. + // The existence will be checked somewhere else in a crate downstream. + (CheckingMode::CheckDuplicates, None, _) => continue, + + // We have a target to generate, but no impl to put in it. error! + (CheckingMode::CheckExistence, None, None) => { + tcx.dcx().emit_err(EiiWithoutImpl { + current_crate_name: tcx.crate_name(LOCAL_CRATE), + decl_crate_name: tcx.crate_name(decl_crate), + name: tcx.item_name(decl_did), + span: decl.span, + help: (), + }); + + continue; + } + }; + + // if it's not local, who cares about generating it. + // That's the local crates' responsibility + let Some(chosen_impl) = local_impl.as_local() else { + continue; + }; + + tracing::debug!("generating EII {chosen_impl:?} (default={is_default})"); + } +} diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 2da4b6f52cf2c..37f3ebae2ea21 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1639,3 +1639,80 @@ pub(crate) struct CustomMirIncompatibleDialectAndPhase { #[label] pub phase_span: Span, } + +#[derive(Diagnostic)] +#[diag(passes_eii_impl_not_function)] +pub(crate) struct EiiImplNotFunction { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(passes_eii_impl_requires_unsafe)] +pub(crate) struct EiiImplRequiresUnsafe { + #[primary_span] + pub span: Span, + pub name: Symbol, + #[subdiagnostic] + pub suggestion: EiiImplRequiresUnsafeSuggestion, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + passes_eii_impl_requires_unsafe_suggestion, + applicability = "machine-applicable" +)] +pub(crate) struct EiiImplRequiresUnsafeSuggestion { + #[suggestion_part(code = "unsafe(")] + pub left: Span, + #[suggestion_part(code = ")")] + pub right: Span, +} + +#[derive(Diagnostic)] +#[diag(passes_eii_fn_with_track_caller)] +pub(crate) struct EiiWithTrackCaller { + #[primary_span] + pub attr_span: Span, + pub name: Symbol, + #[label] + pub sig_span: Span, +} + +#[derive(Diagnostic)] +#[diag(passes_eii_without_impl)] +pub(crate) struct EiiWithoutImpl { + #[primary_span] + #[label] + pub span: Span, + pub name: Symbol, + + pub current_crate_name: Symbol, + pub decl_crate_name: Symbol, + #[help] + pub help: (), +} + +#[derive(Diagnostic)] +#[diag(passes_duplicate_eii_impls)] +pub(crate) struct DuplicateEiiImpls { + pub name: Symbol, + + #[primary_span] + #[label(passes_first)] + pub first_span: Span, + pub first_crate: Symbol, + + #[label(passes_second)] + pub second_span: Span, + pub second_crate: Symbol, + + #[note] + pub additional_crates: Option<()>, + + pub num_additional_crates: usize, + pub additional_crate_names: String, + + #[help] + pub help: (), +} diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs index 2ad0b5ff60e84..40eefaa33fa6b 100644 --- a/compiler/rustc_passes/src/lib.rs +++ b/compiler/rustc_passes/src/lib.rs @@ -21,6 +21,7 @@ mod check_export; pub mod dead; mod debugger_visualizer; mod diagnostic_items; +mod eii; pub mod entry; mod errors; #[cfg(debug_assertions)] @@ -50,4 +51,5 @@ pub fn provide(providers: &mut Providers) { stability::provide(providers); upvars::provide(providers); check_export::provide(providers); + providers.check_externally_implementable_items = eii::check_externally_implementable_items; } diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index d1a703fc5d841..ee80deb2d8e6a 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -184,7 +184,13 @@ impl<'tcx> ReachableContext<'tcx> { CodegenFnAttrs::EMPTY }; let is_extern = codegen_attrs.contains_extern_indicator(); - if is_extern { + // Right now, the only way to get "foreign item symbol aliases" is by being an EII-implementation. + // EII implementations will generate under their own name but also under the name of some foreign item + // (hence alias) that may be in another crate. These functions are marked as always-reachable since + // it's very hard to track whether the original foreign item was reachable. It may live in another crate + // and may be reachable from sibling crates. + let has_foreign_aliases_eii = !codegen_attrs.foreign_item_symbol_aliases.is_empty(); + if is_extern || has_foreign_aliases_eii { self.reachable_symbols.insert(search_item); } } else { @@ -431,6 +437,12 @@ fn has_custom_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { // `SymbolExportLevel::Rust` export level but may end up being exported in dylibs. || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) + // Right now, the only way to get "foreign item symbol aliases" is by being an EII-implementation. + // EII implementations will generate under their own name but also under the name of some foreign item + // (hence alias) that may be in another crate. These functions are marked as always-reachable since + // it's very hard to track whether the original foreign item was reachable. It may live in another crate + // and may be reachable from sibling crates. + || !codegen_attrs.foreign_item_symbol_aliases.is_empty() } /// See module-level doc comment above. diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index 95f979a3fed33..736bf9b3bbf15 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -418,6 +418,7 @@ impl Resolver<'_, '_> { && !tcx.is_panic_runtime(cnum) && !tcx.has_global_allocator(cnum) && !tcx.has_panic_handler(cnum) + && tcx.externally_implementable_items(cnum).is_empty() }) { maybe_unused_extern_crates.insert(id, import.span); } diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 35051675fd8dc..4464573dee036 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -296,7 +296,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ribs: &[Rib<'ra>], ignore_binding: Option>, ) -> Option> { - assert!(ns == TypeNS || ns == ValueNS); let orig_ident = ident; let (general_span, normalized_span) = if ident.name == kw::SelfUpper { // FIXME(jseyfried) improve `Self` hygiene diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 4d4acc60ca804..00e1e2d4140f7 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -440,6 +440,8 @@ pub(crate) enum PathSource<'a, 'ast, 'ra> { ReturnTypeNotation, /// Paths from `#[define_opaque]` attributes DefineOpaques, + /// Resolving a macro + Macro, } impl PathSource<'_, '_, '_> { @@ -456,6 +458,7 @@ impl PathSource<'_, '_, '_> { | PathSource::ReturnTypeNotation => ValueNS, PathSource::TraitItem(ns, _) => ns, PathSource::PreciseCapturingArg(ns) => ns, + PathSource::Macro => MacroNS, } } @@ -471,7 +474,8 @@ impl PathSource<'_, '_, '_> { | PathSource::TraitItem(..) | PathSource::DefineOpaques | PathSource::Delegation - | PathSource::PreciseCapturingArg(..) => false, + | PathSource::PreciseCapturingArg(..) + | PathSource::Macro => false, } } @@ -513,6 +517,7 @@ impl PathSource<'_, '_, '_> { }, PathSource::ReturnTypeNotation | PathSource::Delegation => "function", PathSource::PreciseCapturingArg(..) => "type or const parameter", + PathSource::Macro => "macro", } } @@ -607,6 +612,7 @@ impl PathSource<'_, '_, '_> { Res::Def(DefKind::TyParam, _) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } ), PathSource::PreciseCapturingArg(MacroNS) => false, + PathSource::Macro => matches!(res, Res::Def(DefKind::Macro(_), _)), } } @@ -626,6 +632,7 @@ impl PathSource<'_, '_, '_> { (PathSource::TraitItem(..) | PathSource::ReturnTypeNotation, false) => E0576, (PathSource::PreciseCapturingArg(..), true) => E0799, (PathSource::PreciseCapturingArg(..), false) => E0800, + (PathSource::Macro, _) => E0425, } } } @@ -1051,6 +1058,12 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc }; debug!("(resolving function) entering function"); + if let FnKind::Fn(_, _, f) = fn_kind { + for EiiImpl { node_id, eii_macro_path, .. } in &f.eii_impls { + self.smart_resolve_path(*node_id, &None, &eii_macro_path, PathSource::Macro); + } + } + // Create a value rib for the function. self.with_rib(ValueNS, RibKind::FnOrCoroutine, |this| { // Create a label rib for the function. @@ -2005,7 +2018,8 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { | PathSource::TraitItem(..) | PathSource::Type | PathSource::PreciseCapturingArg(..) - | PathSource::ReturnTypeNotation => false, + | PathSource::ReturnTypeNotation + | PathSource::Macro => false, PathSource::Expr(..) | PathSource::Pat | PathSource::Struct(_) @@ -2759,6 +2773,17 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { let def_id = self.r.local_def_id(item.id); self.parent_scope.macro_rules = self.r.macro_rules_scopes[&def_id]; } + + if let Some(EiiExternTarget { extern_item_path, impl_unsafe: _, span: _ }) = + ¯o_def.eii_extern_target + { + self.smart_resolve_path( + item.id, + &None, + extern_item_path, + PathSource::Expr(None), + ); + } } ItemKind::ForeignMod(_) | ItemKind::GlobalAsm(_) => { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index cdb0b5b58da6d..b404855e256f2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -909,6 +909,11 @@ symbols! { effects, eh_catch_typeinfo, eh_personality, + eii, + eii_extern_target, + eii_impl, + eii_internals, + eii_shared_macro, emit, emit_enum, emit_enum_variant, @@ -2318,6 +2323,7 @@ symbols! { unsafe_block_in_unsafe_fn, unsafe_cell, unsafe_cell_raw_get, + unsafe_eii, unsafe_extern_blocks, unsafe_fields, unsafe_no_drop_flag, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index f2f840581cf02..77c63048bab45 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -3781,6 +3781,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { "unsized values must be place expressions and cannot be put in temporaries", ); } + ObligationCauseCode::CompareEii { .. } => { + panic!("trait bounds on EII not yet supported ") + } } } diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 3f58fc448aa68..8bd400980318b 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1782,4 +1782,29 @@ pub(crate) mod builtin { pub macro From($item: item) { /* compiler built-in */ } + + /// Externally Implementable Item: Defines an attribute macro that can override the item + /// this is applied to. + #[unstable(feature = "eii", issue = "125418")] + #[rustc_builtin_macro] + #[allow_internal_unstable(eii_internals, decl_macro, rustc_attrs)] + pub macro eii($item:item) { + /* compiler built-in */ + } + + /// Unsafely Externally Implementable Item: Defines an unsafe attribute macro that can override + /// the item this is applied to. + #[unstable(feature = "eii", issue = "125418")] + #[rustc_builtin_macro] + #[allow_internal_unstable(eii_internals, decl_macro, rustc_attrs)] + pub macro unsafe_eii($item:item) { + /* compiler built-in */ + } + + /// Impl detail of EII + #[unstable(feature = "eii_internals", issue = "none")] + #[rustc_builtin_macro] + pub macro eii_extern_target($item:item) { + /* compiler built-in */ + } } diff --git a/library/core/src/prelude/v1.rs b/library/core/src/prelude/v1.rs index a4be66b90cab3..3446f04a6429c 100644 --- a/library/core/src/prelude/v1.rs +++ b/library/core/src/prelude/v1.rs @@ -117,3 +117,16 @@ pub use crate::macros::builtin::deref; reason = "`type_alias_impl_trait` has open design concerns" )] pub use crate::macros::builtin::define_opaque; + +#[unstable( + feature = "derive_from", + issue = "144889", + reason = "`derive(From)` is unstable" +)] +pub use crate::macros::builtin::From; + +#[unstable(feature = "eii", issue = "125418")] +pub use crate::macros::builtin::{eii, unsafe_eii}; + +#[unstable(feature = "eii_internals", issue = "none")] +pub use crate::macros::builtin::eii_extern_target; diff --git a/library/std/src/prelude/v1.rs b/library/std/src/prelude/v1.rs index 70c1113155656..6c2f5f108f1ae 100644 --- a/library/std/src/prelude/v1.rs +++ b/library/std/src/prelude/v1.rs @@ -110,6 +110,12 @@ pub use core::prelude::v1::deref; )] pub use core::prelude::v1::define_opaque; +#[unstable(feature = "eii", issue = "125418")] +pub use core::prelude::v1::{eii, unsafe_eii}; + +#[unstable(feature = "eii_internals", issue = "none")] +pub use core::prelude::v1::eii_extern_target; + // The file so far is equivalent to core/src/prelude/v1.rs. It is duplicated // rather than glob imported because we want docs to show these re-exports as // pointing to within `std`. diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index ad69e6eb184e1..4a0f103cc9eb3 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -385,6 +385,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { contract: lc, body: lb, define_opaque: _, + eii_impls: _, }), Fn(box ast::Fn { defaultness: rd, @@ -394,6 +395,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { contract: rc, body: rb, define_opaque: _, + eii_impls: _, }), ) => { eq_defaultness(*ld, *rd) @@ -539,6 +541,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { contract: lc, body: lb, define_opaque: _, + eii_impls: _, }), Fn(box ast::Fn { defaultness: rd, @@ -548,6 +551,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { contract: rc, body: rb, define_opaque: _, + eii_impls: _, }), ) => { eq_defaultness(*ld, *rd) @@ -622,6 +626,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { contract: lc, body: lb, define_opaque: _, + eii_impls: _, }), Fn(box ast::Fn { defaultness: rd, @@ -631,6 +636,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { contract: rc, body: rb, define_opaque: _, + eii_impls: _, }), ) => { eq_defaultness(*ld, *rd) diff --git a/tests/ui/eii/auxiliary/codegen1.rs b/tests/ui/eii/auxiliary/codegen1.rs new file mode 100644 index 0000000000000..93c5df2d41c02 --- /dev/null +++ b/tests/ui/eii/auxiliary/codegen1.rs @@ -0,0 +1,18 @@ +//@ no-prefer-dynamic +#![crate_type = "rlib"] +#![feature(eii)] + +#[eii(eii1)] +fn decl1(x: u64); + +mod private { + #[eii(eii2)] + pub fn decl2(x: u64); +} + +pub use private::eii2 as eii3; +pub use private::decl2 as decl3; + +pub fn local_call_decl1(x: u64) { + decl1(x) +} diff --git a/tests/ui/eii/auxiliary/codegen2.rs b/tests/ui/eii/auxiliary/codegen2.rs new file mode 100644 index 0000000000000..9545ad007c57a --- /dev/null +++ b/tests/ui/eii/auxiliary/codegen2.rs @@ -0,0 +1,6 @@ +//@ no-prefer-dynamic +#![crate_type = "rlib"] +#![feature(eii)] + +#[eii(eii1)] +pub fn decl1(x: u64); diff --git a/tests/ui/eii/auxiliary/codegen3.rs b/tests/ui/eii/auxiliary/codegen3.rs new file mode 100644 index 0000000000000..2b882c48d829a --- /dev/null +++ b/tests/ui/eii/auxiliary/codegen3.rs @@ -0,0 +1,22 @@ +//@ no-prefer-dynamic +#![crate_type = "rlib"] +#![feature(eii)] + +// does have an impl but can't be called +#[eii(eii1)] +fn decl1(x: u64); + +#[eii(eii2)] +pub fn decl2(x: u64); + +mod private { + #[eii(eii3)] + pub fn decl3(x: u64); +} + +pub use private::eii3 as eii4; +pub use private::decl3 as decl4; + +pub fn local_call_decl1(x: u64) { + decl1(x) +} diff --git a/tests/ui/eii/auxiliary/cross_crate_eii_declaration.rs b/tests/ui/eii/auxiliary/cross_crate_eii_declaration.rs new file mode 100644 index 0000000000000..3147d91bbee84 --- /dev/null +++ b/tests/ui/eii/auxiliary/cross_crate_eii_declaration.rs @@ -0,0 +1,14 @@ +//@ no-prefer-dynamic +#![crate_type = "rlib"] +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar)] +#[rustc_builtin_macro(eii_shared_macro)] +pub macro foo() {} + +unsafe extern "Rust" { + pub safe fn bar(x: u64) -> u64; +} diff --git a/tests/ui/eii/codegen_cross_crate.rs b/tests/ui/eii/codegen_cross_crate.rs new file mode 100644 index 0000000000000..1d8e72b5a5c50 --- /dev/null +++ b/tests/ui/eii/codegen_cross_crate.rs @@ -0,0 +1,20 @@ +//@ run-pass +//@ check-run-results +//@ aux-build: codegen2.rs +//@ compile-flags: -O +#![feature(eii)] + +extern crate codegen2 as codegen; + +#[codegen::eii1] +fn eii1_impl(x: u64) { + println!("{x:?}") +} + +// what you would write: +fn main() { + // directly + eii1_impl(21); + // through the alias + codegen::decl1(42); +} diff --git a/tests/ui/eii/codegen_cross_crate.run.stdout b/tests/ui/eii/codegen_cross_crate.run.stdout new file mode 100644 index 0000000000000..960b546721002 --- /dev/null +++ b/tests/ui/eii/codegen_cross_crate.run.stdout @@ -0,0 +1,2 @@ +21 +42 diff --git a/tests/ui/eii/codegen_single_crate.rs b/tests/ui/eii/codegen_single_crate.rs new file mode 100644 index 0000000000000..a1710e41aa4ef --- /dev/null +++ b/tests/ui/eii/codegen_single_crate.rs @@ -0,0 +1,19 @@ +//@ run-pass +//@ check-run-results +#![feature(eii)] + +#[eii] +fn hello(x: u64); + +#[hello] +fn hello_impl(x: u64) { + println!("{x:?}") +} + +// what you would write: +fn main() { + // directly + hello_impl(21); + // through the alias + hello(42); +} diff --git a/tests/ui/eii/codegen_single_crate.run.stdout b/tests/ui/eii/codegen_single_crate.run.stdout new file mode 100644 index 0000000000000..960b546721002 --- /dev/null +++ b/tests/ui/eii/codegen_single_crate.run.stdout @@ -0,0 +1,2 @@ +21 +42 diff --git a/tests/ui/eii/cross_crate.rs b/tests/ui/eii/cross_crate.rs new file mode 100644 index 0000000000000..bd5c9d2ad0eac --- /dev/null +++ b/tests/ui/eii/cross_crate.rs @@ -0,0 +1,18 @@ +//@ compile-flags: --crate-type rlib +//@ check-pass +//@ aux-build: cross_crate_eii_declaration.rs +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +extern crate cross_crate_eii_declaration; + +#[unsafe(cross_crate_eii_declaration::foo)] +fn other(x: u64) -> u64 { + x +} + +fn main() { + cross_crate_eii_declaration::bar(0); +} diff --git a/tests/ui/eii/cross_crate_wrong_ty.rs b/tests/ui/eii/cross_crate_wrong_ty.rs new file mode 100644 index 0000000000000..a73f347b359fe --- /dev/null +++ b/tests/ui/eii/cross_crate_wrong_ty.rs @@ -0,0 +1,18 @@ +//@ compile-flags: --crate-type rlib +//@ aux-build: cross_crate_eii_declaration.rs +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +extern crate cross_crate_eii_declaration; + +#[unsafe(cross_crate_eii_declaration::foo)] +fn other() -> u64 { +//~^ ERROR `other` has 0 parameters but #[foo] requires it to have 1 + 0 +} + +fn main() { + cross_crate_eii_declaration::bar(0); +} diff --git a/tests/ui/eii/cross_crate_wrong_ty.stderr b/tests/ui/eii/cross_crate_wrong_ty.stderr new file mode 100644 index 0000000000000..cc3824818ef39 --- /dev/null +++ b/tests/ui/eii/cross_crate_wrong_ty.stderr @@ -0,0 +1,16 @@ +error[E0806]: `other` has 0 parameters but #[foo] requires it to have 1 + --> $DIR/cross_crate_wrong_ty.rs:11:1 + | +LL | #[unsafe(cross_crate_eii_declaration::foo)] + | ------------------------------------------- required because of this attribute +LL | fn other() -> u64 { + | ^^^^^^^^^^^^^^^^^ expected 1 parameter, found 0 + | + ::: $DIR/auxiliary/cross_crate_eii_declaration.rs:13:5 + | +LL | pub safe fn bar(x: u64) -> u64; + | ------------------------------- requires 1 parameter + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0806`. diff --git a/tests/ui/eii/default/auxiliary/decl_with_default.rs b/tests/ui/eii/default/auxiliary/decl_with_default.rs new file mode 100644 index 0000000000000..b1811b07eb17e --- /dev/null +++ b/tests/ui/eii/default/auxiliary/decl_with_default.rs @@ -0,0 +1,8 @@ +//@ no-prefer-dynamic +#![crate_type = "rlib"] +#![feature(eii)] + +#[eii(eii1)] +pub fn decl1(x: u64) { + println!("default {x}"); +} diff --git a/tests/ui/eii/default/auxiliary/impl1.rs b/tests/ui/eii/default/auxiliary/impl1.rs new file mode 100644 index 0000000000000..4d627a5f68a64 --- /dev/null +++ b/tests/ui/eii/default/auxiliary/impl1.rs @@ -0,0 +1,12 @@ +//@ no-prefer-dynamic +//@ aux-build: decl_with_default.rs +#![crate_type = "rlib"] +#![feature(eii)] + +extern crate decl_with_default as decl; + + +#[unsafe(decl::eii1)] //~ ERROR multiple implementations of `#[eii1]` +fn other(x: u64) { + println!("1{x}"); +} diff --git a/tests/ui/eii/default/call_default.rs b/tests/ui/eii/default/call_default.rs new file mode 100644 index 0000000000000..ee799c7d38e76 --- /dev/null +++ b/tests/ui/eii/default/call_default.rs @@ -0,0 +1,11 @@ +//@ no-prefer-dynamic +//@ aux-build: decl_with_default.rs +//@ run-pass +//@ check-run-results +#![feature(eii)] + +extern crate decl_with_default; + +fn main() { + decl_with_default::decl1(10); +} diff --git a/tests/ui/eii/default/call_default.run.stdout b/tests/ui/eii/default/call_default.run.stdout new file mode 100644 index 0000000000000..1e49486f6aaf4 --- /dev/null +++ b/tests/ui/eii/default/call_default.run.stdout @@ -0,0 +1 @@ +default 10 diff --git a/tests/ui/eii/default/call_impl.rs b/tests/ui/eii/default/call_impl.rs new file mode 100644 index 0000000000000..56a26f8b819f5 --- /dev/null +++ b/tests/ui/eii/default/call_impl.rs @@ -0,0 +1,13 @@ +//@ no-prefer-dynamic +//@ aux-build: decl_with_default.rs +//@ aux-build: impl1.rs +//@ run-pass +//@ check-run-results +#![feature(eii)] + +extern crate decl_with_default; +extern crate impl1; + +fn main() { + decl_with_default::decl1(10); +} diff --git a/tests/ui/eii/default/call_impl.run.stdout b/tests/ui/eii/default/call_impl.run.stdout new file mode 100644 index 0000000000000..bc6298e80ad4b --- /dev/null +++ b/tests/ui/eii/default/call_impl.run.stdout @@ -0,0 +1 @@ +110 diff --git a/tests/ui/eii/default/local_crate.rs b/tests/ui/eii/default/local_crate.rs new file mode 100644 index 0000000000000..d35d2a919070e --- /dev/null +++ b/tests/ui/eii/default/local_crate.rs @@ -0,0 +1,12 @@ +//@ run-pass +//@ check-run-results +#![feature(eii)] + +#[eii(eii1)] +pub fn decl1(x: u64) { + println!("default {x}"); +} + +fn main() { + decl1(4); +} diff --git a/tests/ui/eii/default/local_crate.run.stdout b/tests/ui/eii/default/local_crate.run.stdout new file mode 100644 index 0000000000000..032082d92b722 --- /dev/null +++ b/tests/ui/eii/default/local_crate.run.stdout @@ -0,0 +1 @@ +default 4 diff --git a/tests/ui/eii/default/local_crate_explicit.rs b/tests/ui/eii/default/local_crate_explicit.rs new file mode 100644 index 0000000000000..99c8241baad5b --- /dev/null +++ b/tests/ui/eii/default/local_crate_explicit.rs @@ -0,0 +1,18 @@ +//@ run-pass +//@ check-run-results +#![feature(eii)] + +#[eii(eii1)] +pub fn decl1(x: u64) { + //~^ WARN function `decl1` is never used + println!("default {x}"); +} + +#[eii1] +pub fn decl2(x: u64) { + println!("explicit {x}"); +} + +fn main() { + decl1(4); +} diff --git a/tests/ui/eii/default/local_crate_explicit.run.stdout b/tests/ui/eii/default/local_crate_explicit.run.stdout new file mode 100644 index 0000000000000..d7ce3a9fb8bab --- /dev/null +++ b/tests/ui/eii/default/local_crate_explicit.run.stdout @@ -0,0 +1 @@ +explicit 4 diff --git a/tests/ui/eii/default/local_crate_explicit.stderr b/tests/ui/eii/default/local_crate_explicit.stderr new file mode 100644 index 0000000000000..ea40e32a0f7b6 --- /dev/null +++ b/tests/ui/eii/default/local_crate_explicit.stderr @@ -0,0 +1,10 @@ +warning: function `decl1` is never used + --> $DIR/local_crate_explicit.rs:6:8 + | +LL | pub fn decl1(x: u64) { + | ^^^^^ + | + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/eii/duplicate/auxiliary/decl.rs b/tests/ui/eii/duplicate/auxiliary/decl.rs new file mode 100644 index 0000000000000..81557fa6891b8 --- /dev/null +++ b/tests/ui/eii/duplicate/auxiliary/decl.rs @@ -0,0 +1,6 @@ +//@ no-prefer-dynamic +#![crate_type = "rlib"] +#![feature(eii)] + +#[eii(eii1)] +fn decl1(x: u64); diff --git a/tests/ui/eii/duplicate/auxiliary/impl1.rs b/tests/ui/eii/duplicate/auxiliary/impl1.rs new file mode 100644 index 0000000000000..d7c27f4dfde92 --- /dev/null +++ b/tests/ui/eii/duplicate/auxiliary/impl1.rs @@ -0,0 +1,12 @@ +//@ no-prefer-dynamic +//@ aux-build: decl.rs +#![crate_type = "rlib"] +#![feature(eii)] + +extern crate decl; + + +#[unsafe(decl::eii1)] +fn other(x: u64) { + println!("1{x}"); +} diff --git a/tests/ui/eii/duplicate/auxiliary/impl2.rs b/tests/ui/eii/duplicate/auxiliary/impl2.rs new file mode 100644 index 0000000000000..bce6f11b89c48 --- /dev/null +++ b/tests/ui/eii/duplicate/auxiliary/impl2.rs @@ -0,0 +1,12 @@ +//@ no-prefer-dynamic +//@ aux-build: decl.rs +#![crate_type = "rlib"] +#![feature(eii)] + +extern crate decl; + + +#[unsafe(decl::eii1)] +fn other(x: u64) { + println!("2{x}"); +} diff --git a/tests/ui/eii/duplicate/auxiliary/impl3.rs b/tests/ui/eii/duplicate/auxiliary/impl3.rs new file mode 100644 index 0000000000000..82ba5af098635 --- /dev/null +++ b/tests/ui/eii/duplicate/auxiliary/impl3.rs @@ -0,0 +1,12 @@ +//@ no-prefer-dynamic +//@ aux-build: decl.rs +#![crate_type = "rlib"] +#![feature(eii)] + +extern crate decl; + + +#[unsafe(decl::eii1)] +fn other(x: u64) { + println!("3{x}"); +} diff --git a/tests/ui/eii/duplicate/auxiliary/impl4.rs b/tests/ui/eii/duplicate/auxiliary/impl4.rs new file mode 100644 index 0000000000000..5275da1b14333 --- /dev/null +++ b/tests/ui/eii/duplicate/auxiliary/impl4.rs @@ -0,0 +1,12 @@ +//@ no-prefer-dynamic +//@ aux-build: decl.rs +#![crate_type = "rlib"] +#![feature(eii)] + +extern crate decl; + + +#[unsafe(decl::eii1)] +fn other(x: u64) { + println!("4{x}"); +} diff --git a/tests/ui/eii/duplicate/duplicate1.rs b/tests/ui/eii/duplicate/duplicate1.rs new file mode 100644 index 0000000000000..c6336c1166e75 --- /dev/null +++ b/tests/ui/eii/duplicate/duplicate1.rs @@ -0,0 +1,12 @@ +//@ no-prefer-dynamic +//@ aux-build: impl1.rs +//@ aux-build: impl2.rs +#![feature(eii)] + +// has a span but in the other crate +//~? ERROR multiple implementations of `#[eii1]` + +extern crate impl1; +extern crate impl2; + +fn main() {} diff --git a/tests/ui/eii/duplicate/duplicate1.stderr b/tests/ui/eii/duplicate/duplicate1.stderr new file mode 100644 index 0000000000000..54cc141f88694 --- /dev/null +++ b/tests/ui/eii/duplicate/duplicate1.stderr @@ -0,0 +1,15 @@ +error: multiple implementations of `#[eii1]` + --> $DIR/auxiliary/impl1.rs:10:1 + | +LL | fn other(x: u64) { + | ^^^^^^^^^^^^^^^^ first implemented here in crate `impl1` + | + ::: $DIR/auxiliary/impl2.rs:10:1 + | +LL | fn other(x: u64) { + | ---------------- also implemented here in crate `impl2` + | + = help: an "externally implementable item" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict + +error: aborting due to 1 previous error + diff --git a/tests/ui/eii/duplicate/duplicate2.rs b/tests/ui/eii/duplicate/duplicate2.rs new file mode 100644 index 0000000000000..e2b0acdbf30e8 --- /dev/null +++ b/tests/ui/eii/duplicate/duplicate2.rs @@ -0,0 +1,14 @@ +//@ no-prefer-dynamic +//@ aux-build: impl1.rs +//@ aux-build: impl2.rs +//@ aux-build: impl3.rs +#![feature(eii)] + +// has a span but in the other crate +//~? ERROR multiple implementations of `#[eii1]` + +extern crate impl1; +extern crate impl2; +extern crate impl3; + +fn main() {} diff --git a/tests/ui/eii/duplicate/duplicate2.stderr b/tests/ui/eii/duplicate/duplicate2.stderr new file mode 100644 index 0000000000000..033e43c8b2fbd --- /dev/null +++ b/tests/ui/eii/duplicate/duplicate2.stderr @@ -0,0 +1,16 @@ +error: multiple implementations of `#[eii1]` + --> $DIR/auxiliary/impl1.rs:10:1 + | +LL | fn other(x: u64) { + | ^^^^^^^^^^^^^^^^ first implemented here in crate `impl1` + | + ::: $DIR/auxiliary/impl2.rs:10:1 + | +LL | fn other(x: u64) { + | ---------------- also implemented here in crate `impl2` + | + = note: in addition to these two, another implementation was found in crate `impl3` + = help: an "externally implementable item" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict + +error: aborting due to 1 previous error + diff --git a/tests/ui/eii/duplicate/duplicate3.rs b/tests/ui/eii/duplicate/duplicate3.rs new file mode 100644 index 0000000000000..87e6c409b10e8 --- /dev/null +++ b/tests/ui/eii/duplicate/duplicate3.rs @@ -0,0 +1,16 @@ +//@ no-prefer-dynamic +//@ aux-build: impl1.rs +//@ aux-build: impl2.rs +//@ aux-build: impl3.rs +//@ aux-build: impl4.rs +#![feature(eii)] + +// has a span but in the other crate +//~? ERROR multiple implementations of `#[eii1]` + +extern crate impl1; +extern crate impl2; +extern crate impl3; +extern crate impl4; + +fn main() {} diff --git a/tests/ui/eii/duplicate/duplicate3.stderr b/tests/ui/eii/duplicate/duplicate3.stderr new file mode 100644 index 0000000000000..801d40e69c552 --- /dev/null +++ b/tests/ui/eii/duplicate/duplicate3.stderr @@ -0,0 +1,16 @@ +error: multiple implementations of `#[eii1]` + --> $DIR/auxiliary/impl1.rs:10:1 + | +LL | fn other(x: u64) { + | ^^^^^^^^^^^^^^^^ first implemented here in crate `impl1` + | + ::: $DIR/auxiliary/impl2.rs:10:1 + | +LL | fn other(x: u64) { + | ---------------- also implemented here in crate `impl2` + | + = note: in addition to these two, more implementations were also found in the following crates: `impl3`, `impl4` + = help: an "externally implementable item" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict + +error: aborting due to 1 previous error + diff --git a/tests/ui/eii/errors.rs b/tests/ui/eii/errors.rs new file mode 100644 index 0000000000000..e2ce44366a81c --- /dev/null +++ b/tests/ui/eii/errors.rs @@ -0,0 +1,44 @@ +//@ compile-flags: --crate-type rlib +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar)] //~ ERROR `#[eii_extern_target(...)]` is only valid on macros +fn hello() { + #[eii_extern_target(bar)] //~ ERROR `#[eii_extern_target(...)]` is only valid on macros + let x = 3 + 3; +} + +#[eii_extern_target] //~ ERROR `#[eii_extern_target(...)]` expects a list of one or two elements +#[eii_extern_target()] //~ ERROR `#[eii_extern_target(...)]` expects a list of one or two elements +#[eii_extern_target(bar, hello)] //~ ERROR expected this argument to be "unsafe" +#[eii_extern_target(bar, "unsafe", hello)] //~ ERROR `#[eii_extern_target(...)]` expects a list of one or two elements +#[eii_extern_target(bar, hello, "unsafe")] //~ ERROR `#[eii_extern_target(...)]` expects a list of one or two elements +#[eii_extern_target = "unsafe"] //~ ERROR `#[eii_extern_target(...)]` expects a list of one or two elements +#[eii_extern_target(bar)] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar(x: u64) -> u64; +} + +#[foo] //~ ERROR `#[foo]` is only valid on functions +static X: u64 = 4; +#[foo] //~ ERROR `#[foo]` is only valid on functions +const Y: u64 = 4; +#[foo] //~ ERROR `#[foo]` is only valid on functions +macro bar() {} + +#[foo()] +//~^ ERROR `#[foo]` expected no arguments or a single argument: `#[foo(default)]` +#[foo(default, bar)] +//~^ ERROR `#[foo]` expected no arguments or a single argument: `#[foo(default)]` +#[foo("default")] +//~^ ERROR `#[foo]` expected no arguments or a single argument: `#[foo(default)]` +#[foo = "default"] +//~^ ERROR `#[foo]` expected no arguments or a single argument: `#[foo(default)]` +fn other(x: u64) -> u64 { + x +} diff --git a/tests/ui/eii/errors.stderr b/tests/ui/eii/errors.stderr new file mode 100644 index 0000000000000..6cc439a1ecd21 --- /dev/null +++ b/tests/ui/eii/errors.stderr @@ -0,0 +1,98 @@ +error: `#[eii_extern_target(...)]` is only valid on macros + --> $DIR/errors.rs:7:1 + | +LL | #[eii_extern_target(bar)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[eii_extern_target(...)]` is only valid on macros + --> $DIR/errors.rs:9:5 + | +LL | #[eii_extern_target(bar)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[eii_extern_target(...)]` expects a list of one or two elements + --> $DIR/errors.rs:13:1 + | +LL | #[eii_extern_target] + | ^^^^^^^^^^^^^^^^^^^^ + +error: `#[eii_extern_target(...)]` expects a list of one or two elements + --> $DIR/errors.rs:14:1 + | +LL | #[eii_extern_target()] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: expected this argument to be "unsafe" + --> $DIR/errors.rs:15:26 + | +LL | #[eii_extern_target(bar, hello)] + | ^^^^^ + | +note: the second argument is optional + --> $DIR/errors.rs:15:26 + | +LL | #[eii_extern_target(bar, hello)] + | ^^^^^ + +error: `#[eii_extern_target(...)]` expects a list of one or two elements + --> $DIR/errors.rs:16:1 + | +LL | #[eii_extern_target(bar, "unsafe", hello)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[eii_extern_target(...)]` expects a list of one or two elements + --> $DIR/errors.rs:17:1 + | +LL | #[eii_extern_target(bar, hello, "unsafe")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[eii_extern_target(...)]` expects a list of one or two elements + --> $DIR/errors.rs:18:1 + | +LL | #[eii_extern_target = "unsafe"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/errors.rs:27:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/errors.rs:29:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/errors.rs:31:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[foo]` expected no arguments or a single argument: `#[foo(default)]` + --> $DIR/errors.rs:34:1 + | +LL | #[foo()] + | ^^^^^^^^ + +error: `#[foo]` expected no arguments or a single argument: `#[foo(default)]` + --> $DIR/errors.rs:36:1 + | +LL | #[foo(default, bar)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: `#[foo]` expected no arguments or a single argument: `#[foo(default)]` + --> $DIR/errors.rs:38:1 + | +LL | #[foo("default")] + | ^^^^^^^^^^^^^^^^^ + +error: `#[foo]` expected no arguments or a single argument: `#[foo(default)]` + --> $DIR/errors.rs:40:1 + | +LL | #[foo = "default"] + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 15 previous errors + diff --git a/tests/ui/eii/privacy1.rs b/tests/ui/eii/privacy1.rs new file mode 100644 index 0000000000000..b6414e9c28f43 --- /dev/null +++ b/tests/ui/eii/privacy1.rs @@ -0,0 +1,30 @@ +//@ run-pass +//@ check-run-results +//@ aux-build: codegen1.rs +#![feature(eii)] + +extern crate codegen1 as codegen; + +#[codegen::eii1] +fn eii1_impl(x: u64) { + println!("{x:?}") +} + + +#[codegen::eii3] +fn eii3_impl(x: u64) { + println!("{x:?}") +} + +// what you would write: +fn main() { + // directly + eii1_impl(21); + // through the alias + codegen::local_call_decl1(42); + + // directly + eii3_impl(12); + // through the alias + codegen::decl3(24); +} diff --git a/tests/ui/eii/privacy1.run.stdout b/tests/ui/eii/privacy1.run.stdout new file mode 100644 index 0000000000000..c762399185638 --- /dev/null +++ b/tests/ui/eii/privacy1.run.stdout @@ -0,0 +1,4 @@ +21 +42 +12 +24 diff --git a/tests/ui/eii/privacy2.rs b/tests/ui/eii/privacy2.rs new file mode 100644 index 0000000000000..ec6405c11d2a4 --- /dev/null +++ b/tests/ui/eii/privacy2.rs @@ -0,0 +1,26 @@ +//@ aux-build:codegen3.rs +#![feature(eii)] + +extern crate codegen3 as codegen; + +// has a span but in the other crate +//~? ERROR `#[eii2]` required, but not found +//~? ERROR `#[eii3]` required, but not found + +#[codegen::eii1] +fn eii1_impl(x: u64) { + println!("{x:?}") +} + +#[codegen::eii3] //~ ERROR failed to resolve: could not find `eii3` in `codegen` +fn eii3_impl(x: u64) { + println!("{x:?}") +} + +// what you would write: +fn main() { + // directly + eii1_impl(21); + // through the alias + codegen::decl1(42); //~ ERROR function `decl1` is private +} diff --git a/tests/ui/eii/privacy2.stderr b/tests/ui/eii/privacy2.stderr new file mode 100644 index 0000000000000..a2e0977542a40 --- /dev/null +++ b/tests/ui/eii/privacy2.stderr @@ -0,0 +1,38 @@ +error[E0433]: failed to resolve: could not find `eii3` in `codegen` + --> $DIR/privacy2.rs:15:12 + | +LL | #[codegen::eii3] + | ^^^^ could not find `eii3` in `codegen` + +error[E0603]: function `decl1` is private + --> $DIR/privacy2.rs:25:14 + | +LL | codegen::decl1(42); + | ^^^^^ private function + | +note: the function `decl1` is defined here + --> $DIR/auxiliary/codegen3.rs:7:1 + | +LL | fn decl1(x: u64); + | ^^^^^^^^^^^^^^^^^ + +error: `#[eii2]` required, but not found + --> $DIR/auxiliary/codegen3.rs:9:1 + | +LL | #[eii(eii2)] + | ^^^^^^^^^^^^ expected because `#[eii2]` was declared here in crate `codegen3` + | + = help: expected at least one implementation in crate `privacy2` or any of its dependencies + +error: `#[eii3]` required, but not found + --> $DIR/auxiliary/codegen3.rs:13:5 + | +LL | #[eii(eii3)] + | ^^^^^^^^^^^^ expected because `#[eii3]` was declared here in crate `codegen3` + | + = help: expected at least one implementation in crate `privacy2` or any of its dependencies + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0433, E0603. +For more information about an error, try `rustc --explain E0433`. diff --git a/tests/ui/eii/subtype_1.rs b/tests/ui/eii/subtype_1.rs new file mode 100644 index 0000000000000..e391f39e9bc30 --- /dev/null +++ b/tests/ui/eii/subtype_1.rs @@ -0,0 +1,23 @@ +//@ compile-flags: --crate-type rlib +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar)] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar<'a, 'b>(x: &'b u64) -> &'a u64; +} + +#[foo] +fn other<'a, 'b>(x: &'b u64) -> &'b u64 { + //~^ ERROR lifetime parameters or bounds of `other` do not match the declaration + &0 +} + +fn main() { + bar(&0); +} diff --git a/tests/ui/eii/subtype_1.stderr b/tests/ui/eii/subtype_1.stderr new file mode 100644 index 0000000000000..dee544ae3e4eb --- /dev/null +++ b/tests/ui/eii/subtype_1.stderr @@ -0,0 +1,13 @@ +error: lifetime parameters or bounds of `other` do not match the declaration + --> $DIR/subtype_1.rs:16:9 + | +LL | safe fn bar<'a, 'b>(x: &'b u64) -> &'a u64; + | -------- lifetimes in impl do not match this signature +... +LL | #[foo] + | ------ required because of this attribute +LL | fn other<'a, 'b>(x: &'b u64) -> &'b u64 { + | ^^^^^^^^ lifetimes do not match + +error: aborting due to 1 previous error + diff --git a/tests/ui/eii/subtype_2.rs b/tests/ui/eii/subtype_2.rs new file mode 100644 index 0000000000000..227b98642cb13 --- /dev/null +++ b/tests/ui/eii/subtype_2.rs @@ -0,0 +1,23 @@ +//@ compile-flags: --crate-type rlib +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar)] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar<'a>(x: &'static u64) -> &'a u64; +} + +#[foo] +fn other<'a>(x: &'a u64) -> &'static u64 { + //~^ ERROR lifetime parameters or bounds of `other` do not match the declaration + &0 +} + +fn main() { + bar(&0); +} diff --git a/tests/ui/eii/subtype_2.stderr b/tests/ui/eii/subtype_2.stderr new file mode 100644 index 0000000000000..a19422f12d272 --- /dev/null +++ b/tests/ui/eii/subtype_2.stderr @@ -0,0 +1,13 @@ +error: lifetime parameters or bounds of `other` do not match the declaration + --> $DIR/subtype_2.rs:16:9 + | +LL | safe fn bar<'a>(x: &'static u64) -> &'a u64; + | ---- lifetimes in impl do not match this signature +... +LL | #[foo] + | ------ required because of this attribute +LL | fn other<'a>(x: &'a u64) -> &'static u64 { + | ^^^^ lifetimes do not match + +error: aborting due to 1 previous error + diff --git a/tests/ui/eii/subtype_3.rs b/tests/ui/eii/subtype_3.rs new file mode 100644 index 0000000000000..bc7ddc0773eed --- /dev/null +++ b/tests/ui/eii/subtype_3.rs @@ -0,0 +1,23 @@ +//@ compile-flags: --crate-type rlib +//@ check-pass +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar)] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar<'a>(x: &'a u64) -> &'a u64; +} + +#[foo] +fn other<'a>(x: &'a u64) -> &'static u64 { + &0 +} + +fn main() { + bar(&0); +} diff --git a/tests/ui/eii/subtype_4.rs b/tests/ui/eii/subtype_4.rs new file mode 100644 index 0000000000000..610a151d31160 --- /dev/null +++ b/tests/ui/eii/subtype_4.rs @@ -0,0 +1,23 @@ +//@ compile-flags: --crate-type rlib +//@ check-pass +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar)] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar(x: u64) -> u64; +} + +#[foo] +fn other(x: u64) -> u64 { + x +} + +fn main() { + bar(0); +} diff --git a/tests/ui/eii/unsafe_impl_err.rs b/tests/ui/eii/unsafe_impl_err.rs new file mode 100644 index 0000000000000..6d7aca5a4a31e --- /dev/null +++ b/tests/ui/eii/unsafe_impl_err.rs @@ -0,0 +1,22 @@ +//@ compile-flags: --crate-type rlib +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar, "unsafe")] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar(x: u64) -> u64; +} + +#[foo] //~ ERROR `#[foo]` is unsafe to implement +fn other(x: u64) -> u64 { + x +} + +fn main() { + bar(0); +} diff --git a/tests/ui/eii/unsafe_impl_err.stderr b/tests/ui/eii/unsafe_impl_err.stderr new file mode 100644 index 0000000000000..30de526fe2b18 --- /dev/null +++ b/tests/ui/eii/unsafe_impl_err.stderr @@ -0,0 +1,13 @@ +error: `#[foo]` is unsafe to implement + --> $DIR/unsafe_impl_err.rs:15:1 + | +LL | #[foo] + | ^^^^^^ + | +help: wrap the attribute in `unsafe(...)` + | +LL | #[unsafe(foo)] + | +++++++ + + +error: aborting due to 1 previous error + diff --git a/tests/ui/eii/unsafe_impl_ok.rs b/tests/ui/eii/unsafe_impl_ok.rs new file mode 100644 index 0000000000000..79d732f3f5634 --- /dev/null +++ b/tests/ui/eii/unsafe_impl_ok.rs @@ -0,0 +1,23 @@ +//@ compile-flags: --crate-type rlib +//@ check-pass +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar, "unsafe")] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar(x: u64) -> u64; +} + +#[unsafe(foo)] +fn other(x: u64) -> u64 { + x +} + +fn main() { + bar(0); +} diff --git a/tests/ui/eii/wrong_ret_ty.rs b/tests/ui/eii/wrong_ret_ty.rs new file mode 100644 index 0000000000000..85c6fb4316ebd --- /dev/null +++ b/tests/ui/eii/wrong_ret_ty.rs @@ -0,0 +1,22 @@ +//@ compile-flags: --crate-type rlib +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar)] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar(x: u64) -> u64; +} + +#[foo] +fn other(_x: u64) { + //~^ ERROR function `other` has a type that is incompatible with the declaration +} + +fn main() { + bar(0); +} diff --git a/tests/ui/eii/wrong_ret_ty.stderr b/tests/ui/eii/wrong_ret_ty.stderr new file mode 100644 index 0000000000000..23d7dc031b7f8 --- /dev/null +++ b/tests/ui/eii/wrong_ret_ty.stderr @@ -0,0 +1,26 @@ +error[E0806]: function `other` has a type that is incompatible with the declaration of `#[foo]` + --> $DIR/wrong_ret_ty.rs:16:18 + | +LL | fn other(_x: u64) { + | ^ expected `u64`, found `()` + | +note: expected this because of this attribute + --> $DIR/wrong_ret_ty.rs:15:1 + | +LL | #[foo] + | ^^^^^^ +note: type in declaration + --> $DIR/wrong_ret_ty.rs:12:28 + | +LL | safe fn bar(x: u64) -> u64; + | ^^^ + = note: expected signature `fn(_) -> u64` + found signature `fn(_) -> ()` +help: change the output type to match the declaration + | +LL | fn other(_x: u64) -> u64 { + | ++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0806`. diff --git a/tests/ui/eii/wrong_target.rs b/tests/ui/eii/wrong_target.rs new file mode 100644 index 0000000000000..e07ec506526de --- /dev/null +++ b/tests/ui/eii/wrong_target.rs @@ -0,0 +1,62 @@ +#![feature(eii)] + +#[eii] +fn foo() {} + +#[foo] +//~^ ERROR `#[foo]` is only valid on functions +#[eii] +//~^ ERROR `#[eii]` is only valid on functions +const A: usize = 3; + +#[foo] +//~^ ERROR `#[foo]` is only valid on functions +#[eii] +//~^ ERROR `#[eii]` is only valid on functions +macro_rules! foo_impl { + () => {}; +} + +#[foo] +//~^ ERROR `#[foo]` is only valid on functions +#[eii] +//~^ ERROR `#[eii]` is only valid on functions +struct Foo; + +#[foo] +//~^ ERROR `#[foo]` is only valid on functions +#[eii] +//~^ ERROR `#[eii]` is only valid on functions +impl Foo { + #[foo] + //~^ ERROR `#[foo]` is only valid on functions + #[eii] + //~^ ERROR `#[eii]` is only valid on functions + fn foo_impl() {} +} + +#[foo] +//~^ ERROR `#[foo]` is only valid on functions +#[eii] +//~^ ERROR `#[eii]` is only valid on functions +trait Bar { + #[foo] + //~^ ERROR `#[foo]` is only valid on functions + #[eii] + //~^ ERROR `#[eii]` is only valid on functions + fn foo_impl(); +} + +#[foo] +//~^ ERROR `#[foo]` is only valid on functions +#[eii] +//~^ ERROR `#[eii]` is only valid on functions +impl Bar for Foo { + #[foo] + //~^ ERROR `#[foo]` is only valid on functions + #[eii] + //~^ ERROR `#[eii]` is only valid on functions + fn foo_impl() {} +} + +fn main() {} diff --git a/tests/ui/eii/wrong_target.stderr b/tests/ui/eii/wrong_target.stderr new file mode 100644 index 0000000000000..5837f0951448e --- /dev/null +++ b/tests/ui/eii/wrong_target.stderr @@ -0,0 +1,110 @@ +error: `#[foo]` is only valid on functions + --> $DIR/wrong_target.rs:6:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[eii]` is only valid on functions + --> $DIR/wrong_target.rs:8:1 + | +LL | #[eii] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/wrong_target.rs:12:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[eii]` is only valid on functions + --> $DIR/wrong_target.rs:14:1 + | +LL | #[eii] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/wrong_target.rs:20:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[eii]` is only valid on functions + --> $DIR/wrong_target.rs:22:1 + | +LL | #[eii] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/wrong_target.rs:26:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[eii]` is only valid on functions + --> $DIR/wrong_target.rs:28:1 + | +LL | #[eii] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/wrong_target.rs:31:5 + | +LL | #[foo] + | ^^^^^^ + +error: `#[eii]` is only valid on functions + --> $DIR/wrong_target.rs:33:5 + | +LL | #[eii] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/wrong_target.rs:38:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[eii]` is only valid on functions + --> $DIR/wrong_target.rs:40:1 + | +LL | #[eii] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/wrong_target.rs:43:5 + | +LL | #[foo] + | ^^^^^^ + +error: `#[eii]` is only valid on functions + --> $DIR/wrong_target.rs:45:5 + | +LL | #[eii] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/wrong_target.rs:50:1 + | +LL | #[foo] + | ^^^^^^ + +error: `#[eii]` is only valid on functions + --> $DIR/wrong_target.rs:52:1 + | +LL | #[eii] + | ^^^^^^ + +error: `#[foo]` is only valid on functions + --> $DIR/wrong_target.rs:55:5 + | +LL | #[foo] + | ^^^^^^ + +error: `#[eii]` is only valid on functions + --> $DIR/wrong_target.rs:57:5 + | +LL | #[eii] + | ^^^^^^ + +error: aborting due to 18 previous errors + diff --git a/tests/ui/eii/wrong_ty.rs b/tests/ui/eii/wrong_ty.rs new file mode 100644 index 0000000000000..e7417b7928de8 --- /dev/null +++ b/tests/ui/eii/wrong_ty.rs @@ -0,0 +1,23 @@ +//@ compile-flags: --crate-type rlib +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar)] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar(x: u64) -> u64; +} + +#[foo] +fn other(x: usize) -> u64 { + //~^ ERROR function `other` has a type that is incompatible with the declaration + 3 +} + +fn main() { + bar(0); +} diff --git a/tests/ui/eii/wrong_ty.stderr b/tests/ui/eii/wrong_ty.stderr new file mode 100644 index 0000000000000..d9e15a0c804bc --- /dev/null +++ b/tests/ui/eii/wrong_ty.stderr @@ -0,0 +1,27 @@ +error[E0806]: function `other` has a type that is incompatible with the declaration of `#[foo]` + --> $DIR/wrong_ty.rs:16:13 + | +LL | fn other(x: usize) -> u64 { + | ^^^^^ expected `u64`, found `usize` + | +note: expected this because of this attribute + --> $DIR/wrong_ty.rs:15:1 + | +LL | #[foo] + | ^^^^^^ +note: type in declaration + --> $DIR/wrong_ty.rs:12:20 + | +LL | safe fn bar(x: u64) -> u64; + | ^^^ + = note: expected signature `fn(u64) -> _` + found signature `fn(usize) -> _` +help: change the parameter type to match the declaration + | +LL - fn other(x: usize) -> u64 { +LL + fn other(x: u64) -> u64 { + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0806`. diff --git a/tests/ui/eii/wrong_ty_2.rs b/tests/ui/eii/wrong_ty_2.rs new file mode 100644 index 0000000000000..9fe1fd67c2967 --- /dev/null +++ b/tests/ui/eii/wrong_ty_2.rs @@ -0,0 +1,23 @@ +//@ compile-flags: --crate-type rlib +#![feature(eii)] +#![feature(decl_macro)] +#![feature(rustc_attrs)] +#![feature(eii_internals)] + +#[eii_extern_target(bar)] +#[rustc_builtin_macro(eii_shared_macro)] +macro foo() {} + +unsafe extern "Rust" { + safe fn bar(x: u64) -> u64; +} + +#[foo] +fn other() -> u64 { + //~^ ERROR `other` has 0 parameters but #[foo] requires it to have + 3 +} + +fn main() { + bar(0); +} diff --git a/tests/ui/eii/wrong_ty_2.stderr b/tests/ui/eii/wrong_ty_2.stderr new file mode 100644 index 0000000000000..6499e7ac16ca3 --- /dev/null +++ b/tests/ui/eii/wrong_ty_2.stderr @@ -0,0 +1,14 @@ +error[E0806]: `other` has 0 parameters but #[foo] requires it to have 1 + --> $DIR/wrong_ty_2.rs:16:1 + | +LL | safe fn bar(x: u64) -> u64; + | --- requires 1 parameter +... +LL | #[foo] + | ------ required because of this attribute +LL | fn other() -> u64 { + | ^^^^^^^^^^^^^^^^^ expected 1 parameter, found 0 + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0806`. diff --git a/tests/ui/error-codes/E0806.rs b/tests/ui/error-codes/E0806.rs new file mode 100644 index 0000000000000..168c7120fc17d --- /dev/null +++ b/tests/ui/error-codes/E0806.rs @@ -0,0 +1,12 @@ +#![feature(eii)] + +#[eii(foo)] +fn x(); + +#[foo] +fn y(a: u64) -> u64 { + //~^ ERROR E0806 + a +} + +fn main() {} diff --git a/tests/ui/error-codes/E0806.stderr b/tests/ui/error-codes/E0806.stderr new file mode 100644 index 0000000000000..ab6ba45185df3 --- /dev/null +++ b/tests/ui/error-codes/E0806.stderr @@ -0,0 +1,14 @@ +error[E0806]: `y` has 1 parameter but #[foo] requires it to have 0 + --> $DIR/E0806.rs:7:9 + | +LL | fn x(); + | ------- requires 0 parameters +LL | +LL | #[foo] + | ------ required because of this attribute +LL | fn y(a: u64) -> u64 { + | ^^^ expected 0 parameters, found 1 + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0806`. diff --git a/tests/ui/feature-gates/feature-gate-eii-internals.rs b/tests/ui/feature-gates/feature-gate-eii-internals.rs new file mode 100644 index 0000000000000..b70e007ed798b --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-eii-internals.rs @@ -0,0 +1,11 @@ +#![crate_type = "rlib"] +#![feature(decl_macro)] +#![feature(rustc_attrs)] + +#[eii_extern_target(bar)] //~ ERROR use of unstable library feature `eii_internals` +#[rustc_builtin_macro(eii_macro)] +macro foo() {} //~ ERROR: cannot find a built-in macro with name `foo` + +unsafe extern "Rust" { + safe fn bar(x: u64) -> u64; +} diff --git a/tests/ui/feature-gates/feature-gate-eii-internals.stderr b/tests/ui/feature-gates/feature-gate-eii-internals.stderr new file mode 100644 index 0000000000000..f08f0a5e65b3b --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-eii-internals.stderr @@ -0,0 +1,18 @@ +error[E0658]: use of unstable library feature `eii_internals` + --> $DIR/feature-gate-eii-internals.rs:5:3 + | +LL | #[eii_extern_target(bar)] + | ^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(eii_internals)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: cannot find a built-in macro with name `foo` + --> $DIR/feature-gate-eii-internals.rs:7:1 + | +LL | macro foo() {} + | ^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-eii.rs b/tests/ui/feature-gates/feature-gate-eii.rs new file mode 100644 index 0000000000000..3a51c3dd2eaeb --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-eii.rs @@ -0,0 +1,9 @@ +#![crate_type = "rlib"] + +#[eii] //~ ERROR use of unstable library feature `eii` +fn hello(x: u64); + +#[hello] +fn hello_impl(x: u64) { + println!("{x:?}") +} diff --git a/tests/ui/feature-gates/feature-gate-eii.stderr b/tests/ui/feature-gates/feature-gate-eii.stderr new file mode 100644 index 0000000000000..6ec231257ba78 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-eii.stderr @@ -0,0 +1,13 @@ +error[E0658]: use of unstable library feature `eii` + --> $DIR/feature-gate-eii.rs:3:3 + | +LL | #[eii] + | ^^^ + | + = note: see issue #125418 for more information + = help: add `#![feature(eii)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`.