Skip to content

Commit bfce0bd

Browse files
committed
Eagerly expand bang macros within stability attributes
1 parent f9bef82 commit bfce0bd

File tree

5 files changed

+192
-29
lines changed

5 files changed

+192
-29
lines changed

compiler/rustc_expand/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ expand_takes_no_arguments =
132132
133133
expand_trace_macro = trace_macro
134134
135+
expand_unsupported_expr_in_key_value =
136+
expression in the value of this attribute must be a literal or macro call
137+
135138
expand_unsupported_key_value =
136139
key-value macro attributes are not supported
137140

compiler/rustc_expand/src/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,13 @@ pub(crate) struct WrongFragmentKind<'a> {
276276
pub name: &'a ast::Path,
277277
}
278278

279+
#[derive(Diagnostic)]
280+
#[diag(expand_unsupported_expr_in_key_value)]
281+
pub(crate) struct UnsupportedExprInKeyValue {
282+
#[primary_span]
283+
pub span: Span,
284+
}
285+
279286
#[derive(Diagnostic)]
280287
#[diag(expand_unsupported_key_value)]
281288
pub(crate) struct UnsupportedKeyValue {

compiler/rustc_expand/src/expand.rs

Lines changed: 162 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::base::*;
22
use crate::config::StripUnconfigured;
33
use crate::errors::{
44
IncompleteParse, RecursionLimitReached, RemoveExprNotSupported, RemoveNodeNotSupported,
5-
UnsupportedKeyValue, WrongFragmentKind,
5+
UnsupportedExprInKeyValue, UnsupportedKeyValue, WrongFragmentKind,
66
};
77
use crate::hygiene::SyntaxContext;
88
use crate::mbe::diagnostics::annotate_err_with_kind;
@@ -12,11 +12,11 @@ use crate::placeholders::{placeholder, PlaceholderExpander};
1212
use rustc_ast as ast;
1313
use rustc_ast::mut_visit::*;
1414
use rustc_ast::ptr::P;
15-
use rustc_ast::token::{self, Delimiter};
16-
use rustc_ast::tokenstream::TokenStream;
15+
use rustc_ast::token::{self, Delimiter, Lit, LitKind, Token, TokenKind};
16+
use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree};
1717
use rustc_ast::visit::{self, AssocCtxt, Visitor};
18-
use rustc_ast::{AssocItemKind, AstNodeWrapper, AttrArgs, AttrStyle, AttrVec, ExprKind};
19-
use rustc_ast::{ForeignItemKind, HasAttrs, HasNodeId};
18+
use rustc_ast::{AssocItemKind, AstNodeWrapper, AttrArgs, AttrKind, AttrStyle};
19+
use rustc_ast::{AttrVec, ExprKind, ForeignItemKind, HasAttrs, HasNodeId};
2020
use rustc_ast::{Inline, ItemKind, MacStmtStyle, MetaItemKind, ModKind};
2121
use rustc_ast::{NestedMetaItem, NodeId, PatKind, StmtKind, TyKind};
2222
use rustc_ast_pretty::pprust;
@@ -32,11 +32,11 @@ use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS};
3232
use rustc_session::lint::BuiltinLintDiagnostics;
3333
use rustc_session::parse::{feature_err, ParseSess};
3434
use rustc_session::Limit;
35-
use rustc_span::symbol::{sym, Ident};
35+
use rustc_span::symbol::{kw, sym, Ident};
3636
use rustc_span::{FileName, LocalExpnId, Span};
3737

3838
use smallvec::SmallVec;
39-
use std::ops::Deref;
39+
use std::ops::{ControlFlow, Deref};
4040
use std::path::PathBuf;
4141
use std::rc::Rc;
4242
use std::{iter, mem};
@@ -772,6 +772,95 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
772772
})
773773
}
774774

775+
/// Expand the macros in the values of an attribute such as:
776+
/// `#[stable(feature = get_feature_name!($signedness))]`
777+
fn expand_nested_meta(&mut self, attr: &mut ast::Attribute) {
778+
let AttrKind::Normal(normal_attr) = &mut attr.kind else { return };
779+
let AttrArgs::Delimited(delim_args) = &mut normal_attr.item.args else { return };
780+
781+
let tokens = delim_args.tokens.clone();
782+
let mut new_tokens = Vec::with_capacity(tokens.len());
783+
let subparser_name = Some("built-in attribute");
784+
let mut parser = Parser::new(self.cx.parse_sess(), tokens, subparser_name);
785+
786+
// Have any expansions occurred.
787+
let mut modified = false;
788+
789+
// If the attribute contains unrecognized syntax, just return early
790+
// without modifying `delim_args.tokens`. Whatever tries to parse it to
791+
// ast::MetaItem later will report its own error.
792+
while parser.token != token::Eof {
793+
// Parse name of a NameValue meta item.
794+
if parser.token.is_ident() {
795+
new_tokens.push(TokenTree::Token(parser.token.clone(), parser.token_spacing));
796+
parser.bump();
797+
} else {
798+
return;
799+
}
800+
801+
// Parse `=` between name and value.
802+
if parser.token == token::Eq {
803+
new_tokens.push(TokenTree::Token(parser.token.clone(), parser.token_spacing));
804+
parser.bump();
805+
} else {
806+
return;
807+
}
808+
809+
// Parse value expr, and if it's a macro call, then fully expand it
810+
// to a literal.
811+
match parser.parse_expr().map(P::into_inner) {
812+
Ok(mut expr) => {
813+
let expr_span = expr.span;
814+
let lit = match expr.kind {
815+
ExprKind::Lit(lit) => lit,
816+
ExprKind::MacCall(mac) => {
817+
modified = true;
818+
expr.kind = ExprKind::MacCall(mac);
819+
if let AstFragment::Expr(expr) =
820+
self.fully_expand_fragment(AstFragment::Expr(P(expr)))
821+
&& let ExprKind::Lit(lit) = expr.kind
822+
{
823+
lit
824+
} else {
825+
self.cx
826+
.dcx()
827+
.emit_err(UnsupportedExprInKeyValue { span: expr_span });
828+
Lit::new(LitKind::Err, kw::Empty, None)
829+
}
830+
}
831+
_ => {
832+
modified = true;
833+
self.cx.dcx().emit_err(UnsupportedExprInKeyValue { span: expr_span });
834+
Lit::new(LitKind::Err, kw::Empty, None)
835+
}
836+
};
837+
let token = Token::new(TokenKind::Literal(lit), expr_span);
838+
new_tokens.push(TokenTree::Token(token, Spacing::Alone));
839+
}
840+
Err(err) => {
841+
err.cancel();
842+
return;
843+
}
844+
};
845+
846+
// Comma separators, and optional trailing comma.
847+
if parser.token == token::Eof {
848+
break;
849+
} else if parser.token == token::Comma {
850+
new_tokens.push(TokenTree::Token(parser.token.clone(), parser.token_spacing));
851+
parser.bump();
852+
} else {
853+
return;
854+
}
855+
}
856+
857+
if modified {
858+
delim_args.tokens = TokenStream::new(new_tokens);
859+
normal_attr.tokens = None;
860+
normal_attr.item.tokens = None;
861+
}
862+
}
863+
775864
fn gate_proc_macro_attr_item(&self, span: Span, item: &Annotatable) {
776865
let kind = match item {
777866
Annotatable::Item(_)
@@ -1628,33 +1717,78 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
16281717
/// its position and derives following it. We have to collect the derives in order to resolve
16291718
/// legacy derive helpers (helpers written before derives that introduce them).
16301719
fn take_first_attr(
1631-
&self,
1720+
&mut self,
16321721
item: &mut impl HasAttrs,
16331722
) -> Option<(ast::Attribute, usize, Vec<ast::Path>)> {
1634-
let mut attr = None;
1635-
1636-
let mut cfg_pos = None;
1637-
let mut attr_pos = None;
1638-
for (pos, attr) in item.attrs().iter().enumerate() {
1639-
if !attr.is_doc_comment() && !self.cx.expanded_inert_attrs.is_marked(attr) {
1723+
loop {
1724+
let mut cfg_pos = None;
1725+
let mut attr_pos = None;
1726+
let mut attr_is_builtin = false;
1727+
for (pos, attr) in item.attrs().iter().enumerate() {
1728+
if attr.is_doc_comment() || self.cx.expanded_inert_attrs.is_marked(attr) {
1729+
continue;
1730+
}
16401731
let name = attr.ident().map(|ident| ident.name);
16411732
if name == Some(sym::cfg) || name == Some(sym::cfg_attr) {
16421733
cfg_pos = Some(pos); // a cfg attr found, no need to search anymore
16431734
break;
16441735
} else if attr_pos.is_none()
1645-
&& !name.is_some_and(rustc_feature::is_builtin_attr_name)
1736+
&& match name {
1737+
// User-defined attribute invoked using a single identifier.
1738+
Some(name) if !rustc_feature::is_builtin_attr_name(name) => true,
1739+
// A subset of builtin attributes, like `stable`, which expand
1740+
// nested macro calls within the attribute arguments.
1741+
Some(name) if rustc_feature::expand_nested_meta(name) => {
1742+
attr_is_builtin = true;
1743+
true
1744+
}
1745+
// Built-in inert attribute.
1746+
Some(_) => false,
1747+
// Attribute path longer than one identifier. These are
1748+
// user-defined attribute macros or tool attributes.
1749+
None => true,
1750+
}
16461751
{
16471752
attr_pos = Some(pos); // a non-cfg attr found, still may find a cfg attr
16481753
}
16491754
}
1650-
}
16511755

1652-
item.visit_attrs(|attrs| {
1653-
attr = Some(match (cfg_pos, attr_pos) {
1654-
(Some(pos), _) => (attrs.remove(pos), pos, Vec::new()),
1655-
(_, Some(pos)) => {
1656-
let attr = attrs.remove(pos);
1657-
let following_derives = attrs[pos..]
1756+
let mut control_flow = ControlFlow::Break(None);
1757+
item.visit_attrs(|attrs| match (cfg_pos, attr_pos) {
1758+
(Some(cfg_pos), _) => {
1759+
let cfg = attrs.remove(cfg_pos);
1760+
let following_derives = Vec::new();
1761+
control_flow = ControlFlow::Break(Some((cfg, cfg_pos, following_derives)));
1762+
}
1763+
(None, Some(attr_pos)) if attr_is_builtin => {
1764+
// A built-in attribute such as #[stable(feature = f!($x))].
1765+
// Eagerly expand its arguments here and now.
1766+
//
1767+
// This does not get a LocalExpnId because nothing else in
1768+
// `item` is affected by this expansion, unlike attribute
1769+
// macros which replace `item` with their own output. If a
1770+
// subsequent expansion within `item` fails, there is no
1771+
// need to show `stable` in that diagnostic's macro
1772+
// backtrace.
1773+
//
1774+
// Also, this expansion does not go through the placeholder
1775+
// system and PlaceholderExpander because there is no
1776+
// reliance on the Resolver to look up the name of this
1777+
// attribute. Since we know here it's a built-in attribute,
1778+
// there is no possibility that name resolution would be
1779+
// indeterminate and we'd need to defer the expansion until
1780+
// after some other one.
1781+
let attr = &mut attrs[attr_pos];
1782+
MacroExpander::new(self.cx, self.monotonic).expand_nested_meta(attr);
1783+
self.cx.expanded_inert_attrs.mark(attr);
1784+
1785+
// Now loop back to the top of `take_first_attr` in search
1786+
// of a more interesting attribute to return to the caller.
1787+
control_flow = ControlFlow::Continue(());
1788+
}
1789+
(None, Some(attr_pos)) => {
1790+
let attr = attrs.remove(attr_pos);
1791+
let following_derives = attrs[attr_pos..]
16581792
.iter()
16591793
.filter(|a| a.has_name(sym::derive))
16601794
.flat_map(|a| a.meta_item_list().unwrap_or_default())
@@ -1668,13 +1802,15 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
16681802
})
16691803
.collect();
16701804

1671-
(attr, pos, following_derives)
1805+
control_flow = ControlFlow::Break(Some((attr, attr_pos, following_derives)));
16721806
}
1673-
_ => return,
1807+
(None, None) => {}
16741808
});
1675-
});
16761809

1677-
attr
1810+
if let ControlFlow::Break(attr) = control_flow {
1811+
return attr;
1812+
}
1813+
}
16781814
}
16791815

16801816
// Detect use of feature-gated or invalid attributes on macro invocations

compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,23 @@ pub fn is_valid_for_get_attr(name: Symbol) -> bool {
875875
})
876876
}
877877

878+
/// If true, bang macros are allowed and eagerly expanded within the value part
879+
/// of nested NameValue meta items. All such expansions must produce a literal.
880+
///
881+
/// `#[stable(feature = get_feature_name!($signedness))]`
882+
pub fn expand_nested_meta(name: Symbol) -> bool {
883+
// Just these few for now. We can roll this out more broadly if it would be
884+
// useful.
885+
//
886+
// WARNING: While it's all right to add nightly-only builtin attributes
887+
// here, adding something that is available to stable (such as `deprecated`)
888+
// would require the addition of a feature gate.
889+
matches!(
890+
name,
891+
sym::stable | sym::unstable | sym::rustc_const_stable | sym::rustc_const_unstable
892+
)
893+
}
894+
878895
pub static BUILTIN_ATTRIBUTE_MAP: LazyLock<FxHashMap<Symbol, &BuiltinAttribute>> =
879896
LazyLock::new(|| {
880897
let mut map = FxHashMap::default();

compiler/rustc_feature/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option<NonZeroU3
125125
pub use accepted::ACCEPTED_FEATURES;
126126
pub use builtin_attrs::AttributeDuplicates;
127127
pub use builtin_attrs::{
128-
deprecated_attributes, find_gated_cfg, is_builtin_attr_name, is_builtin_only_local,
129-
is_valid_for_get_attr, AttributeGate, AttributeTemplate, AttributeType, BuiltinAttribute,
130-
GatedCfg, BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
128+
deprecated_attributes, expand_nested_meta, find_gated_cfg, is_builtin_attr_name,
129+
is_builtin_only_local, is_valid_for_get_attr, AttributeGate, AttributeTemplate, AttributeType,
130+
BuiltinAttribute, GatedCfg, BUILTIN_ATTRIBUTES, BUILTIN_ATTRIBUTE_MAP,
131131
};
132132
pub use removed::REMOVED_FEATURES;
133133
pub use unstable::{Features, INCOMPATIBLE_FEATURES, UNSTABLE_FEATURES};

0 commit comments

Comments
 (0)