Skip to content

Commit c010fbb

Browse files
committed
Eagerly expand bang macros within stability attributes
1 parent 998d332 commit c010fbb

File tree

5 files changed

+192
-29
lines changed

5 files changed

+192
-29
lines changed

compiler/rustc_expand/messages.ftl

+3
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

+7
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

+162-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;
3434
use rustc_session::{Limit, Session};
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(_)
@@ -1627,33 +1716,78 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
16271716
/// its position and derives following it. We have to collect the derives in order to resolve
16281717
/// legacy derive helpers (helpers written before derives that introduce them).
16291718
fn take_first_attr(
1630-
&self,
1719+
&mut self,
16311720
item: &mut impl HasAttrs,
16321721
) -> Option<(ast::Attribute, usize, Vec<ast::Path>)> {
1633-
let mut attr = None;
1634-
1635-
let mut cfg_pos = None;
1636-
let mut attr_pos = None;
1637-
for (pos, attr) in item.attrs().iter().enumerate() {
1638-
if !attr.is_doc_comment() && !self.cx.expanded_inert_attrs.is_marked(attr) {
1722+
loop {
1723+
let mut cfg_pos = None;
1724+
let mut attr_pos = None;
1725+
let mut attr_is_builtin = false;
1726+
for (pos, attr) in item.attrs().iter().enumerate() {
1727+
if attr.is_doc_comment() || self.cx.expanded_inert_attrs.is_marked(attr) {
1728+
continue;
1729+
}
16391730
let name = attr.ident().map(|ident| ident.name);
16401731
if name == Some(sym::cfg) || name == Some(sym::cfg_attr) {
16411732
cfg_pos = Some(pos); // a cfg attr found, no need to search anymore
16421733
break;
16431734
} else if attr_pos.is_none()
1644-
&& !name.is_some_and(rustc_feature::is_builtin_attr_name)
1735+
&& match name {
1736+
// User-defined attribute invoked using a single identifier.
1737+
Some(name) if !rustc_feature::is_builtin_attr_name(name) => true,
1738+
// A subset of builtin attributes, like `stable`, which expand
1739+
// nested macro calls within the attribute arguments.
1740+
Some(name) if rustc_feature::expand_nested_meta(name) => {
1741+
attr_is_builtin = true;
1742+
true
1743+
}
1744+
// Built-in inert attribute.
1745+
Some(_) => false,
1746+
// Attribute path longer than one identifier. These are
1747+
// user-defined attribute macros or tool attributes.
1748+
None => true,
1749+
}
16451750
{
16461751
attr_pos = Some(pos); // a non-cfg attr found, still may find a cfg attr
16471752
}
16481753
}
1649-
}
16501754

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

1670-
(attr, pos, following_derives)
1804+
control_flow = ControlFlow::Break(Some((attr, attr_pos, following_derives)));
16711805
}
1672-
_ => return,
1806+
(None, None) => {}
16731807
});
1674-
});
16751808

1676-
attr
1809+
if let ControlFlow::Break(attr) = control_flow {
1810+
return attr;
1811+
}
1812+
}
16771813
}
16781814

16791815
// 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
@@ -877,6 +877,23 @@ pub fn is_valid_for_get_attr(name: Symbol) -> bool {
877877
})
878878
}
879879

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

compiler/rustc_feature/src/lib.rs

+3-3
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)