From 0a3fd242879bdd53747289db422c11a403333bcb Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Fri, 12 Dec 2025 20:34:54 +0300 Subject: [PATCH] Support attribute inheritance in delegation --- compiler/rustc_ast_lowering/src/delegation.rs | 156 +++++++++++++++--- compiler/rustc_middle/src/ty/mod.rs | 14 +- compiler/rustc_resolve/src/late.rs | 30 +++- tests/pretty/auxiliary/to-reuse-functions.rs | 14 ++ tests/pretty/delegation-inherit-attributes.pp | 61 +++++++ tests/pretty/delegation-inherit-attributes.rs | 56 +++++++ 6 files changed, 305 insertions(+), 26 deletions(-) create mode 100644 tests/pretty/auxiliary/to-reuse-functions.rs create mode 100644 tests/pretty/delegation-inherit-attributes.pp create mode 100644 tests/pretty/delegation-inherit-attributes.rs diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index 0e7db7c9503cb..82bade8829a2f 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -43,13 +43,15 @@ use hir::def::{DefKind, PartialRes, Res}; use hir::{BodyId, HirId}; use rustc_abi::ExternAbi; use rustc_ast::*; +use rustc_attr_parsing::{AttributeParser, ShouldEmit}; use rustc_errors::ErrorGuaranteed; +use rustc_hir::Target; use rustc_hir::attrs::{AttributeKind, InlineAttr}; use rustc_hir::def_id::DefId; use rustc_middle::span_bug; -use rustc_middle::ty::{Asyncness, ResolverAstLowering}; +use rustc_middle::ty::{Asyncness, DelegationFnSigAttrs, ResolverAstLowering}; use rustc_span::symbol::kw; -use rustc_span::{Ident, Span, Symbol}; +use rustc_span::{DUMMY_SP, Ident, Span, Symbol}; use {rustc_ast as ast, rustc_hir as hir}; use super::{GenericArgsMode, ImplTraitContext, LoweringContext, ParamMode}; @@ -62,6 +64,41 @@ pub(crate) struct DelegationResults<'hir> { pub generics: &'hir hir::Generics<'hir>, } +struct AttributeAdditionInfo { + pub equals: fn(&hir::Attribute) -> bool, + pub kind: AttributeAdditionKind, +} + +enum AttributeAdditionKind { + Default { factory: fn(Span) -> hir::Attribute }, + Inherit { flag: DelegationFnSigAttrs, factory: fn(Span, &hir::Attribute) -> hir::Attribute }, +} + +const PARENT_ID: hir::ItemLocalId = hir::ItemLocalId::ZERO; + +static ATTRIBUTES_ADDITIONS: &[AttributeAdditionInfo] = &[ + AttributeAdditionInfo { + equals: |a| matches!(a, hir::Attribute::Parsed(AttributeKind::MustUse { .. })), + kind: AttributeAdditionKind::Inherit { + factory: |span, original_attribute| { + let reason = match original_attribute { + hir::Attribute::Parsed(AttributeKind::MustUse { reason, .. }) => *reason, + _ => None, + }; + + hir::Attribute::Parsed(AttributeKind::MustUse { span, reason }) + }, + flag: DelegationFnSigAttrs::MUST_USE, + }, + }, + AttributeAdditionInfo { + equals: |a| matches!(a, hir::Attribute::Parsed(AttributeKind::Inline(..))), + kind: AttributeAdditionKind::Default { + factory: |span| hir::Attribute::Parsed(AttributeKind::Inline(InlineAttr::Hint, span)), + }, + }, +]; + impl<'hir> LoweringContext<'_, 'hir> { fn is_method(&self, def_id: DefId, span: Span) -> bool { match self.tcx.def_kind(def_id) { @@ -88,7 +125,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let sig_id = self.get_delegation_sig_id(item_id, delegation.id, span, is_in_trait_impl); match sig_id { Ok(sig_id) => { - self.add_inline_attribute_if_needed(span); + self.add_attributes_if_needed(span, sig_id); let is_method = self.is_method(sig_id, span); let (param_count, c_variadic) = self.param_count(sig_id); @@ -103,29 +140,100 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn add_inline_attribute_if_needed(&mut self, span: Span) { - const PARENT_ID: hir::ItemLocalId = hir::ItemLocalId::ZERO; - let create_inline_attr_slice = - || [hir::Attribute::Parsed(AttributeKind::Inline(InlineAttr::Hint, span))]; - - let new_attributes = match self.attrs.get(&PARENT_ID) { - Some(attrs) => { - // Check if reuse already specifies any inline attribute, if so, do nothing - if attrs - .iter() - .any(|a| matches!(a, hir::Attribute::Parsed(AttributeKind::Inline(..)))) + fn add_attributes_if_needed(&mut self, span: Span, sig_id: DefId) { + let new_attributes = self.create_new_attributes( + ATTRIBUTES_ADDITIONS, + span, + sig_id, + self.attrs.get(&PARENT_ID), + ); + + if new_attributes.is_empty() { + return; + } + + let new_arena_allocated_attributes = match self.attrs.get(&PARENT_ID) { + Some(existing_attrs) => self.arena.alloc_from_iter( + existing_attrs.iter().map(|a| a.clone()).chain(new_attributes.into_iter()), + ), + None => self.arena.alloc_from_iter(new_attributes.into_iter()), + }; + + self.attrs.insert(PARENT_ID, new_arena_allocated_attributes); + } + + fn create_new_attributes( + &self, + candidate_additions: &[AttributeAdditionInfo], + span: Span, + sig_id: DefId, + existing_attrs: Option<&&[hir::Attribute]>, + ) -> Vec { + let local_original_attributes = self.parse_local_original_attributes(sig_id); + + candidate_additions + .iter() + .filter_map(|addition_info| { + if let Some(existing_attrs) = existing_attrs + && existing_attrs + .iter() + .any(|existing_attr| (addition_info.equals)(existing_attr)) { - return; + return None; } - self.arena.alloc_from_iter( - attrs.into_iter().map(|a| a.clone()).chain(create_inline_attr_slice()), - ) - } - None => self.arena.alloc_from_iter(create_inline_attr_slice()), - }; + match addition_info.kind { + AttributeAdditionKind::Default { factory } => Some(factory(span)), + AttributeAdditionKind::Inherit { flag, factory } => { + let original_attribute = match sig_id.as_local() { + Some(local_id) => self + .resolver + .delegation_fn_sigs + .get(&local_id) + .is_some_and(|sig| sig.attrs_flags.contains(flag)) + .then(|| { + local_original_attributes + .as_ref() + .map(|attrs| { + attrs + .iter() + .find(|base_attr| (addition_info.equals)(base_attr)) + }) + .flatten() + }) + .flatten(), + None => self + .tcx + .get_all_attrs(sig_id) + .iter() + .find(|base_attr| (addition_info.equals)(base_attr)), + }; - self.attrs.insert(PARENT_ID, new_attributes); + original_attribute.map(|a| factory(span, a)) + } + } + }) + .collect::>() + } + + fn parse_local_original_attributes(&self, sig_id: DefId) -> Option> { + if let Some(local_id) = sig_id.as_local() + && let Some(info) = self.resolver.delegation_fn_sigs.get(&local_id) + && !info.to_inherit_attrs.is_empty() + { + Some(AttributeParser::parse_limited_all( + self.tcx.sess, + info.to_inherit_attrs.as_slice(), + None, + Target::Fn, + DUMMY_SP, + DUMMY_NODE_ID, + Some(self.tcx.features()), + ShouldEmit::Nothing, + )) + } else { + None + } } fn get_delegation_sig_id( @@ -220,7 +328,9 @@ impl<'hir> LoweringContext<'_, 'hir> { // We are not forwarding the attributes, as the delegation fn sigs are collected on the ast, // and here we need the hir attributes. let default_safety = - if sig.target_feature || self.tcx.def_kind(parent) == DefKind::ForeignMod { + if sig.attrs_flags.contains(DelegationFnSigAttrs::TARGET_FEATURE) + || self.tcx.def_kind(parent) == DefKind::ForeignMod + { hir::Safety::Unsafe } else { hir::Safety::Safe diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 6ca4949910f27..418335a893479 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -25,6 +25,7 @@ pub use generic_args::{GenericArgKind, TermKind, *}; pub use generics::*; pub use intrinsic::IntrinsicDef; use rustc_abi::{Align, FieldIdx, Integer, IntegerType, ReprFlags, ReprOptions, VariantIdx}; +use rustc_ast::AttrVec; use rustc_ast::expand::typetree::{FncTree, Kind, Type, TypeTree}; use rustc_ast::node_id::NodeMap; pub use rustc_ast_ir::{Movability, Mutability, try_visit}; @@ -221,13 +222,24 @@ pub struct ResolverAstLowering { pub delegation_fn_sigs: LocalDefIdMap, } +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct DelegationFnSigAttrs: u8 { + const TARGET_FEATURE = 1 << 0; + const MUST_USE = 1 << 1; + } +} + +pub const DELEGATION_INHERIT_ATTRS_START: DelegationFnSigAttrs = DelegationFnSigAttrs::MUST_USE; + #[derive(Debug)] pub struct DelegationFnSig { pub header: ast::FnHeader, pub param_count: usize, pub has_self: bool, pub c_variadic: bool, - pub target_feature: bool, + pub attrs_flags: DelegationFnSigAttrs, + pub to_inherit_attrs: AttrVec, } #[derive(Clone, Copy, Debug, HashStable)] diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index f1a03d5a06109..cc17ca9c026de 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -28,7 +28,9 @@ use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, Par use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::{MissingLifetimeKind, PrimTy, TraitCandidate}; use rustc_middle::middle::resolve_bound_vars::Set1; -use rustc_middle::ty::{AssocTag, DelegationFnSig, Visibility}; +use rustc_middle::ty::{ + AssocTag, DELEGATION_INHERIT_ATTRS_START, DelegationFnSig, DelegationFnSigAttrs, Visibility, +}; use rustc_middle::{bug, span_bug}; use rustc_session::config::{CrateType, ResolveDocLinks}; use rustc_session::lint; @@ -5294,13 +5296,37 @@ impl ItemInfoCollector<'_, '_, '_> { id: NodeId, attrs: &[Attribute], ) { + static NAMES_TO_FLAGS: &[(Symbol, DelegationFnSigAttrs)] = &[ + (sym::target_feature, DelegationFnSigAttrs::TARGET_FEATURE), + (sym::must_use, DelegationFnSigAttrs::MUST_USE), + ]; + + let mut to_inherit_attrs = AttrVec::new(); + let mut attrs_flags = DelegationFnSigAttrs::empty(); + + 'attrs_loop: for attr in attrs { + for &(name, flag) in NAMES_TO_FLAGS { + if attr.has_name(name) { + attrs_flags.set(flag, true); + + if flag.bits() >= DELEGATION_INHERIT_ATTRS_START.bits() { + to_inherit_attrs.push(attr.clone()); + } + + continue 'attrs_loop; + } + } + } + let sig = DelegationFnSig { header, param_count: decl.inputs.len(), has_self: decl.has_self(), c_variadic: decl.c_variadic(), - target_feature: attrs.iter().any(|attr| attr.has_name(sym::target_feature)), + attrs_flags, + to_inherit_attrs, }; + self.r.delegation_fn_sigs.insert(self.r.local_def_id(id), sig); } } diff --git a/tests/pretty/auxiliary/to-reuse-functions.rs b/tests/pretty/auxiliary/to-reuse-functions.rs new file mode 100644 index 0000000000000..55d6ecf035e34 --- /dev/null +++ b/tests/pretty/auxiliary/to-reuse-functions.rs @@ -0,0 +1,14 @@ +//@ edition:2021 + +#[must_use] +#[cold] +pub unsafe fn unsafe_fn_extern() -> usize { 1 } + +#[must_use = "extern_fn_extern: some reason"] +#[deprecated] +pub extern "C" fn extern_fn_extern() -> usize { 1 } + +pub const fn const_fn_extern() -> usize { 1 } + +#[must_use] +pub async fn async_fn_extern() { } diff --git a/tests/pretty/delegation-inherit-attributes.pp b/tests/pretty/delegation-inherit-attributes.pp new file mode 100644 index 0000000000000..772e177b88835 --- /dev/null +++ b/tests/pretty/delegation-inherit-attributes.pp @@ -0,0 +1,61 @@ +//@ edition:2021 +//@ aux-crate:to_reuse_functions=to-reuse-functions.rs +//@ pretty-mode:hir +//@ pretty-compare-only +//@ pp-exact:delegation-inherit-attributes.pp + +#![allow(incomplete_features)] +#![feature(fn_delegation)] +#[attr = MacroUse {arguments: UseAll}] +extern crate std; +#[prelude_import] +use std::prelude::rust_2021::*; + +extern crate to_reuse_functions; + +mod to_reuse { + #[attr = MustUse {reason: "foo: some reason"}] + #[attr = Cold] + fn foo(x: usize) -> usize { x } + + #[attr = MustUse] + #[attr = Cold] + fn foo_no_reason(x: usize) -> usize { x } + + #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = Cold] + fn bar(x: usize) -> usize { x } +} + +#[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] +#[attr = MustUse {reason: "foo: some reason"}] +#[attr = Inline(Hint)] +fn foo1(arg0: _) -> _ { to_reuse::foo(self + 1) } + +#[attr = MustUse] +#[attr = Inline(Hint)] +fn foo_no_reason(arg0: _) -> _ { to_reuse::foo_no_reason(self + 1) } + +#[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] +#[attr = MustUse {reason: "some reason"}] +#[attr = Inline(Hint)] +fn foo2(arg0: _) -> _ { to_reuse::foo(self + 1) } + +#[attr = Inline(Hint)] +fn bar(arg0: _) -> _ { to_reuse::bar(arg0) } + +#[attr = MustUse] +#[attr = Inline(Hint)] +unsafe fn unsafe_fn_extern() -> _ { to_reuse_functions::unsafe_fn_extern() } +#[attr = MustUse {reason: "extern_fn_extern: some reason"}] +#[attr = Inline(Hint)] +extern "C" fn extern_fn_extern() + -> _ { to_reuse_functions::extern_fn_extern() } +#[attr = Inline(Hint)] +const fn const_fn_extern() -> _ { to_reuse_functions::const_fn_extern() } +#[attr = MustUse {reason: "some reason"}] +#[attr = Inline(Hint)] +async fn async_fn_extern() -> _ { to_reuse_functions::async_fn_extern() } + + +fn main() { } diff --git a/tests/pretty/delegation-inherit-attributes.rs b/tests/pretty/delegation-inherit-attributes.rs new file mode 100644 index 0000000000000..fe74b9a55a7d7 --- /dev/null +++ b/tests/pretty/delegation-inherit-attributes.rs @@ -0,0 +1,56 @@ +//@ edition:2021 +//@ aux-crate:to_reuse_functions=to-reuse-functions.rs +//@ pretty-mode:hir +//@ pretty-compare-only +//@ pp-exact:delegation-inherit-attributes.pp + +#![allow(incomplete_features)] +#![feature(fn_delegation)] + +extern crate to_reuse_functions; + +mod to_reuse { + #[must_use = "foo: some reason"] + #[cold] + pub fn foo(x: usize) -> usize { + x + } + + #[must_use] + #[cold] + pub fn foo_no_reason(x: usize) -> usize { + x + } + + #[cold] + #[deprecated] + pub fn bar(x: usize) -> usize { + x + } +} + +#[deprecated] +reuse to_reuse::foo as foo1 { + self + 1 +} + +reuse to_reuse::foo_no_reason { + self + 1 +} + +#[deprecated] +#[must_use = "some reason"] +reuse to_reuse::foo as foo2 { + self + 1 +} + +reuse to_reuse::bar; + +reuse to_reuse_functions::unsafe_fn_extern; +reuse to_reuse_functions::extern_fn_extern; +reuse to_reuse_functions::const_fn_extern; +#[must_use = "some reason"] +reuse to_reuse_functions::async_fn_extern; + + +fn main() {}