Skip to content

Commit f684bb7

Browse files
committed
Eagerly expand bang macros within stability attributes
1 parent d5b339d commit f684bb7

File tree

5 files changed

+190
-29
lines changed

5 files changed

+190
-29
lines changed

compiler/rustc_expand/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ expand_takes_no_arguments =
133133
134134
expand_trace_macro = trace_macro
135135
136+
expand_unsupported_expr_in_key_value =
137+
expression in the value of this attribute must be a literal or macro call
138+
136139
expand_unsupported_key_value =
137140
key-value macro attributes are not supported
138141

compiler/rustc_expand/src/errors.rs

+7
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,13 @@ pub(crate) struct WrongFragmentKind<'a> {
286286
pub name: &'a ast::Path,
287287
}
288288

289+
#[derive(Diagnostic)]
290+
#[diag(expand_unsupported_expr_in_key_value)]
291+
pub(crate) struct UnsupportedExprInKeyValue {
292+
#[primary_span]
293+
pub span: Span,
294+
}
295+
289296
#[derive(Diagnostic)]
290297
#[diag(expand_unsupported_key_value)]
291298
pub(crate) struct UnsupportedKeyValue {

compiler/rustc_expand/src/expand.rs

+160-26
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,93 @@ 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.emit_err(UnsupportedExprInKeyValue { span: expr_span });
826+
Lit::new(LitKind::Err, kw::Empty, None)
827+
}
828+
}
829+
_ => {
830+
modified = true;
831+
self.cx.emit_err(UnsupportedExprInKeyValue { span: expr_span });
832+
Lit::new(LitKind::Err, kw::Empty, None)
833+
}
834+
};
835+
let token = Token::new(TokenKind::Literal(lit), expr_span);
836+
new_tokens.push(TokenTree::Token(token, Spacing::Alone));
837+
}
838+
Err(err) => {
839+
err.cancel();
840+
return;
841+
}
842+
};
843+
844+
// Comma separators, and optional trailing comma.
845+
if parser.token == token::Eof {
846+
break;
847+
} else if parser.token == token::Comma {
848+
new_tokens.push(TokenTree::Token(parser.token.clone(), parser.token_spacing));
849+
parser.bump();
850+
} else {
851+
return;
852+
}
853+
}
854+
855+
if modified {
856+
delim_args.tokens = TokenStream::new(new_tokens);
857+
normal_attr.tokens = None;
858+
normal_attr.item.tokens = None;
859+
}
860+
}
861+
775862
fn gate_proc_macro_attr_item(&self, span: Span, item: &Annotatable) {
776863
let kind = match item {
777864
Annotatable::Item(_)
@@ -1625,33 +1712,78 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
16251712
/// its position and derives following it. We have to collect the derives in order to resolve
16261713
/// legacy derive helpers (helpers written before derives that introduce them).
16271714
fn take_first_attr(
1628-
&self,
1715+
&mut self,
16291716
item: &mut impl HasAttrs,
16301717
) -> Option<(ast::Attribute, usize, Vec<ast::Path>)> {
1631-
let mut attr = None;
1632-
1633-
let mut cfg_pos = None;
1634-
let mut attr_pos = None;
1635-
for (pos, attr) in item.attrs().iter().enumerate() {
1636-
if !attr.is_doc_comment() && !self.cx.expanded_inert_attrs.is_marked(attr) {
1718+
loop {
1719+
let mut cfg_pos = None;
1720+
let mut attr_pos = None;
1721+
let mut attr_is_builtin = false;
1722+
for (pos, attr) in item.attrs().iter().enumerate() {
1723+
if attr.is_doc_comment() || self.cx.expanded_inert_attrs.is_marked(attr) {
1724+
continue;
1725+
}
16371726
let name = attr.ident().map(|ident| ident.name);
16381727
if name == Some(sym::cfg) || name == Some(sym::cfg_attr) {
16391728
cfg_pos = Some(pos); // a cfg attr found, no need to search anymore
16401729
break;
16411730
} else if attr_pos.is_none()
1642-
&& !name.is_some_and(rustc_feature::is_builtin_attr_name)
1731+
&& match name {
1732+
// User-defined attribute invoked using a single identifier.
1733+
Some(name) if !rustc_feature::is_builtin_attr_name(name) => true,
1734+
// A subset of builtin attributes, like `stable`, which expand
1735+
// nested macro calls within the attribute arguments.
1736+
Some(name) if rustc_feature::expand_nested_meta(name) => {
1737+
attr_is_builtin = true;
1738+
true
1739+
}
1740+
// Built-in inert attribute.
1741+
Some(_) => false,
1742+
// Attribute path longer than one identifier. These are
1743+
// user-defined attribute macros or tool attributes.
1744+
None => true,
1745+
}
16431746
{
16441747
attr_pos = Some(pos); // a non-cfg attr found, still may find a cfg attr
16451748
}
16461749
}
1647-
}
16481750

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

1668-
(attr, pos, following_derives)
1800+
control_flow = ControlFlow::Break(Some((attr, attr_pos, following_derives)));
16691801
}
1670-
_ => return,
1802+
(None, None) => {}
16711803
});
1672-
});
16731804

1674-
attr
1805+
if let ControlFlow::Break(attr) = control_flow {
1806+
return attr;
1807+
}
1808+
}
16751809
}
16761810

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

compiler/rustc_feature/src/builtin_attrs.rs

+17
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,23 @@ pub fn is_valid_for_get_attr(name: Symbol) -> bool {
881881
})
882882
}
883883

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

compiler/rustc_feature/src/lib.rs

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

0 commit comments

Comments
 (0)