Rollup merge of #149843 - aerooneqq:inherit-attributes-in-delegation, r=petrochenkov

Inherit attributes in delegation

This PR adds support for inheriting attributes of the original function of the delegation and is a part of rust-lang/rust#118212, for now we inherit only #[must_use] attribute, but we can add other attributes to inherit. The list of processed attributes can be found [here](https://github.com/aerooneqq/public-docs/blob/master/rust/delegation/processed_attributes.md).

r? ``@petrochenkov``
This commit is contained in:
Jacob Pratt 2025-12-13 00:55:56 -05:00 committed by GitHub
commit e7ba4a5e6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 305 additions and 26 deletions

View file

@ -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))];
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),
);
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(..))))
{
return;
}
if new_attributes.is_empty() {
return;
}
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()),
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_attributes);
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<hir::Attribute> {
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 None;
}
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)),
};
original_attribute.map(|a| factory(span, a))
}
}
})
.collect::<Vec<_>>()
}
fn parse_local_original_attributes(&self, sig_id: DefId) -> Option<Vec<hir::Attribute>> {
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

View file

@ -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<DelegationFnSig>,
}
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)]

View file

@ -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;
@ -5297,13 +5299,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);
}
}

View file

@ -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() { }

View file

@ -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() { }

View file

@ -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() {}