1
Fork 0

expand: Pick cfgs and cfg_attrs one by one, like other attributes

This commit is contained in:
Vadim Petrochenkov 2021-12-29 18:47:19 +08:00
parent d63a8d965e
commit e87ce7ad74
7 changed files with 240 additions and 165 deletions

View file

@ -328,6 +328,10 @@ impl<'a> StripUnconfigured<'a> {
}); });
} }
fn process_cfg_attr(&self, attr: Attribute) -> Vec<Attribute> {
if attr.has_name(sym::cfg_attr) { self.expand_cfg_attr(attr, true) } else { vec![attr] }
}
/// Parse and expand a single `cfg_attr` attribute into a list of attributes /// Parse and expand a single `cfg_attr` attribute into a list of attributes
/// when the configuration predicate is true, or otherwise expand into an /// when the configuration predicate is true, or otherwise expand into an
/// empty list of attributes. /// empty list of attributes.
@ -335,11 +339,7 @@ impl<'a> StripUnconfigured<'a> {
/// Gives a compiler warning when the `cfg_attr` contains no attributes and /// Gives a compiler warning when the `cfg_attr` contains no attributes and
/// is in the original source file. Gives a compiler error if the syntax of /// is in the original source file. Gives a compiler error if the syntax of
/// the attribute is incorrect. /// the attribute is incorrect.
fn process_cfg_attr(&self, attr: Attribute) -> Vec<Attribute> { crate fn expand_cfg_attr(&self, attr: Attribute, recursive: bool) -> Vec<Attribute> {
if !attr.has_name(sym::cfg_attr) {
return vec![attr];
}
let (cfg_predicate, expanded_attrs) = let (cfg_predicate, expanded_attrs) =
match rustc_parse::parse_cfg_attr(&attr, &self.sess.parse_sess) { match rustc_parse::parse_cfg_attr(&attr, &self.sess.parse_sess) {
None => return vec![], None => return vec![],
@ -348,19 +348,36 @@ impl<'a> StripUnconfigured<'a> {
// Lint on zero attributes in source. // Lint on zero attributes in source.
if expanded_attrs.is_empty() { if expanded_attrs.is_empty() {
return vec![attr]; self.sess.parse_sess.buffer_lint(
rustc_lint_defs::builtin::UNUSED_ATTRIBUTES,
attr.span,
ast::CRATE_NODE_ID,
"`#[cfg_attr]` does not expand to any attributes",
);
} }
if !attr::cfg_matches(&cfg_predicate, &self.sess.parse_sess, self.features) { if !attr::cfg_matches(&cfg_predicate, &self.sess.parse_sess, self.features) {
return vec![]; return vec![];
} }
if recursive {
// We call `process_cfg_attr` recursively in case there's a // We call `process_cfg_attr` recursively in case there's a
// `cfg_attr` inside of another `cfg_attr`. E.g. // `cfg_attr` inside of another `cfg_attr`. E.g.
// `#[cfg_attr(false, cfg_attr(true, some_attr))]`. // `#[cfg_attr(false, cfg_attr(true, some_attr))]`.
expanded_attrs expanded_attrs
.into_iter() .into_iter()
.flat_map(|(item, span)| { .flat_map(|item| self.process_cfg_attr(self.expand_cfg_attr_item(&attr, item)))
.collect()
} else {
expanded_attrs.into_iter().map(|item| self.expand_cfg_attr_item(&attr, item)).collect()
}
}
fn expand_cfg_attr_item(
&self,
attr: &Attribute,
(item, item_span): (ast::AttrItem, Span),
) -> Attribute {
let orig_tokens = attr.tokens().to_tokenstream(); let orig_tokens = attr.tokens().to_tokenstream();
// We are taking an attribute of the form `#[cfg_attr(pred, attr)]` // We are taking an attribute of the form `#[cfg_attr(pred, attr)]`
@ -399,7 +416,7 @@ impl<'a> StripUnconfigured<'a> {
); );
trees.push((bracket_group, Spacing::Alone)); trees.push((bracket_group, Spacing::Alone));
let tokens = Some(LazyTokenStream::new(AttrAnnotatedTokenStream::new(trees))); let tokens = Some(LazyTokenStream::new(AttrAnnotatedTokenStream::new(trees)));
let attr = attr::mk_attr_from_item(item, tokens, attr.style, span); let attr = attr::mk_attr_from_item(item, tokens, attr.style, item_span);
if attr.has_name(sym::crate_type) { if attr.has_name(sym::crate_type) {
self.sess.parse_sess.buffer_lint( self.sess.parse_sess.buffer_lint(
rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
@ -416,17 +433,15 @@ impl<'a> StripUnconfigured<'a> {
"`crate_name` within an `#![cfg_attr] attribute is deprecated`", "`crate_name` within an `#![cfg_attr] attribute is deprecated`",
); );
} }
self.process_cfg_attr(attr) attr
})
.collect()
} }
/// Determines if a node with the given attributes should be included in this configuration. /// Determines if a node with the given attributes should be included in this configuration.
fn in_cfg(&self, attrs: &[Attribute]) -> bool { fn in_cfg(&self, attrs: &[Attribute]) -> bool {
attrs.iter().all(|attr| { attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr))
if !is_cfg(attr) {
return true;
} }
crate fn cfg_true(&self, attr: &Attribute) -> bool {
let meta_item = match validate_attr::parse_meta(&self.sess.parse_sess, attr) { let meta_item = match validate_attr::parse_meta(&self.sess.parse_sess, attr) {
Ok(meta_item) => meta_item, Ok(meta_item) => meta_item,
Err(mut err) => { Err(mut err) => {
@ -437,7 +452,6 @@ impl<'a> StripUnconfigured<'a> {
parse_cfg(&meta_item, &self.sess).map_or(true, |meta_item| { parse_cfg(&meta_item, &self.sess).map_or(true, |meta_item| {
attr::cfg_matches(&meta_item, &self.sess.parse_sess, self.features) attr::cfg_matches(&meta_item, &self.sess.parse_sess, self.features)
}) })
})
} }
/// If attributes are not allowed on expressions, emit an error for `attr` /// If attributes are not allowed on expressions, emit an error for `attr`

View file

@ -15,7 +15,6 @@ use rustc_ast::{AssocItemKind, AstLike, AstLikeWrapper, AttrStyle, ExprKind, For
use rustc_ast::{Inline, ItemKind, MacArgs, MacStmtStyle, MetaItemKind, ModKind, NestedMetaItem}; use rustc_ast::{Inline, ItemKind, MacArgs, MacStmtStyle, MetaItemKind, ModKind, NestedMetaItem};
use rustc_ast::{NodeId, PatKind, StmtKind, TyKind}; use rustc_ast::{NodeId, PatKind, StmtKind, TyKind};
use rustc_ast_pretty::pprust; use rustc_ast_pretty::pprust;
use rustc_attr::is_builtin_attr;
use rustc_data_structures::map_in_place::MapInPlace; use rustc_data_structures::map_in_place::MapInPlace;
use rustc_data_structures::sync::Lrc; use rustc_data_structures::sync::Lrc;
use rustc_errors::{Applicability, PResult}; use rustc_errors::{Applicability, PResult};
@ -1014,6 +1013,9 @@ trait InvocationCollectorNode: AstLike {
fn to_annotatable(self) -> Annotatable; fn to_annotatable(self) -> Annotatable;
fn fragment_to_output(fragment: AstFragment) -> Self::OutputTy; fn fragment_to_output(fragment: AstFragment) -> Self::OutputTy;
fn id(&mut self) -> &mut NodeId; fn id(&mut self) -> &mut NodeId;
fn descr() -> &'static str {
unreachable!()
}
fn noop_flat_map<V: MutVisitor>(self, _visitor: &mut V) -> Self::OutputTy { fn noop_flat_map<V: MutVisitor>(self, _visitor: &mut V) -> Self::OutputTy {
unreachable!() unreachable!()
} }
@ -1477,6 +1479,9 @@ impl InvocationCollectorNode for P<ast::Expr> {
fn id(&mut self) -> &mut NodeId { fn id(&mut self) -> &mut NodeId {
&mut self.id &mut self.id
} }
fn descr() -> &'static str {
"an expression"
}
fn noop_visit<V: MutVisitor>(&mut self, visitor: &mut V) { fn noop_visit<V: MutVisitor>(&mut self, visitor: &mut V) {
noop_visit_expr(self, visitor) noop_visit_expr(self, visitor)
} }
@ -1576,13 +1581,28 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
) -> Option<(ast::Attribute, usize, Vec<ast::Path>)> { ) -> Option<(ast::Attribute, usize, Vec<ast::Path>)> {
let mut attr = None; let mut attr = None;
let mut cfg_pos = None;
let mut attr_pos = None;
for (pos, attr) in item.attrs().iter().enumerate() {
if !attr.is_doc_comment() && !self.cx.expanded_inert_attrs.is_marked(attr) {
let name = attr.ident().map(|ident| ident.name);
if name == Some(sym::cfg) || name == Some(sym::cfg_attr) {
cfg_pos = Some(pos); // a cfg attr found, no need to search anymore
break;
} else if attr_pos.is_none()
&& !name.map_or(false, rustc_feature::is_builtin_attr_name)
{
attr_pos = Some(pos); // a non-cfg attr found, still may find a cfg attr
}
}
}
item.visit_attrs(|attrs| { item.visit_attrs(|attrs| {
attr = attrs attr = Some(match (cfg_pos, attr_pos) {
.iter() (Some(pos), _) => (attrs.remove(pos), pos, Vec::new()),
.position(|a| !self.cx.expanded_inert_attrs.is_marked(a) && !is_builtin_attr(a)) (_, Some(pos)) => {
.map(|attr_pos| { let attr = attrs.remove(pos);
let attr = attrs.remove(attr_pos); let following_derives = attrs[pos..]
let following_derives = attrs[attr_pos..]
.iter() .iter()
.filter(|a| a.has_name(sym::derive)) .filter(|a| a.has_name(sym::derive))
.flat_map(|a| a.meta_item_list().unwrap_or_default()) .flat_map(|a| a.meta_item_list().unwrap_or_default())
@ -1596,17 +1616,15 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
}) })
.collect(); .collect();
(attr, attr_pos, following_derives) (attr, pos, following_derives)
}) }
_ => return,
});
}); });
attr attr
} }
fn configure<T: AstLike>(&mut self, node: T) -> Option<T> {
self.cfg.configure(node)
}
// Detect use of feature-gated or invalid attributes on macro invocations // Detect use of feature-gated or invalid attributes on macro invocations
// since they will not be detected after macro expansion. // since they will not be detected after macro expansion.
fn check_attributes(&self, attrs: &[ast::Attribute], call: &ast::MacCall) { fn check_attributes(&self, attrs: &[ast::Attribute], call: &ast::MacCall) {
@ -1653,49 +1671,113 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
} }
} }
fn expand_cfg_true(
&mut self,
node: &mut impl AstLike,
attr: ast::Attribute,
pos: usize,
) -> bool {
let res = self.cfg.cfg_true(&attr);
if res {
// FIXME: `cfg(TRUE)` attributes do not currently remove themselves during expansion,
// and some tools like rustdoc and clippy rely on that. Find a way to remove them
// while keeping the tools working.
self.cx.expanded_inert_attrs.mark(&attr);
node.visit_attrs(|attrs| attrs.insert(pos, attr));
}
res
}
fn expand_cfg_attr(&self, node: &mut impl AstLike, attr: ast::Attribute, pos: usize) {
node.visit_attrs(|attrs| {
attrs.splice(pos..pos, self.cfg.expand_cfg_attr(attr, false));
});
}
fn flat_map_node<Node: InvocationCollectorNode<OutputTy: Default>>( fn flat_map_node<Node: InvocationCollectorNode<OutputTy: Default>>(
&mut self, &mut self,
node: Node, mut node: Node,
) -> Node::OutputTy { ) -> Node::OutputTy {
let mut node = configure!(self, node); loop {
return match self.take_first_attr(&mut node) {
if let Some(attr) = self.take_first_attr(&mut node) { Some((attr, pos, derives)) => match attr.name_or_empty() {
Node::pre_flat_map_node_collect_attr(&self.cfg, &attr.0); sym::cfg => {
self.collect_attr(attr, node.to_annotatable(), Node::KIND).make_ast::<Node>() if self.expand_cfg_true(&mut node, attr, pos) {
} else if node.is_mac_call() { continue;
}
Default::default()
}
sym::cfg_attr => {
self.expand_cfg_attr(&mut node, attr, pos);
continue;
}
_ => {
Node::pre_flat_map_node_collect_attr(&self.cfg, &attr);
self.collect_attr((attr, pos, derives), node.to_annotatable(), Node::KIND)
.make_ast::<Node>()
}
},
None if node.is_mac_call() => {
let (mac, attrs, add_semicolon) = node.take_mac_call(); let (mac, attrs, add_semicolon) = node.take_mac_call();
self.check_attributes(&attrs, &mac); self.check_attributes(&attrs, &mac);
let mut res = self.collect_bang(mac, Node::KIND).make_ast::<Node>(); let mut res = self.collect_bang(mac, Node::KIND).make_ast::<Node>();
Node::post_flat_map_node_collect_bang(&mut res, add_semicolon); Node::post_flat_map_node_collect_bang(&mut res, add_semicolon);
res res
} else { }
None => {
match Node::wrap_flat_map_node_noop_flat_map(node, self, |mut node, this| { match Node::wrap_flat_map_node_noop_flat_map(node, self, |mut node, this| {
assign_id!(this, node.id(), || node.noop_flat_map(this)) assign_id!(this, node.id(), || node.noop_flat_map(this))
}) { }) {
Ok(output) => output, Ok(output) => output,
Err(node) => self.flat_map_node(node), Err(returned_node) => {
node = returned_node;
continue;
} }
} }
} }
};
}
}
fn visit_node<Node: InvocationCollectorNode<OutputTy = Node> + DummyAstNode>( fn visit_node<Node: InvocationCollectorNode<OutputTy = Node> + DummyAstNode>(
&mut self, &mut self,
node: &mut Node, node: &mut Node,
) { ) {
if let Some(attr) = self.take_first_attr(node) { loop {
visit_clobber(node, |node| { return match self.take_first_attr(node) {
self.collect_attr(attr, node.to_annotatable(), Node::KIND).make_ast::<Node>() Some((attr, pos, derives)) => match attr.name_or_empty() {
}) sym::cfg => {
} else if node.is_mac_call() { let span = attr.span;
if self.expand_cfg_true(node, attr, pos) {
continue;
}
let msg =
format!("removing {} is not supported in this position", Node::descr());
self.cx.span_err(span, &msg);
continue;
}
sym::cfg_attr => {
self.expand_cfg_attr(node, attr, pos);
continue;
}
_ => visit_clobber(node, |node| {
self.collect_attr((attr, pos, derives), node.to_annotatable(), Node::KIND)
.make_ast::<Node>()
}),
},
None if node.is_mac_call() => {
visit_clobber(node, |node| { visit_clobber(node, |node| {
// Do not clobber unless it's actually a macro (uncommon case). // Do not clobber unless it's actually a macro (uncommon case).
let (mac, attrs, _) = node.take_mac_call(); let (mac, attrs, _) = node.take_mac_call();
self.check_attributes(&attrs, &mac); self.check_attributes(&attrs, &mac);
self.collect_bang(mac, Node::KIND).make_ast::<Node>() self.collect_bang(mac, Node::KIND).make_ast::<Node>()
}) })
} else { }
None => {
assign_id!(self, node.id(), || node.noop_visit(self)) assign_id!(self, node.id(), || node.noop_visit(self))
} }
};
}
} }
} }
@ -1750,7 +1832,7 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
self.flat_map_node(node) self.flat_map_node(node)
} }
fn flat_map_stmt(&mut self, node: ast::Stmt) -> SmallVec<[ast::Stmt; 1]> { fn flat_map_stmt(&mut self, mut node: ast::Stmt) -> SmallVec<[ast::Stmt; 1]> {
// FIXME: invocations in semicolon-less expressions positions are expanded as expressions, // FIXME: invocations in semicolon-less expressions positions are expanded as expressions,
// changing that requires some compatibility measures. // changing that requires some compatibility measures.
if node.is_expr() { if node.is_expr() {
@ -1761,7 +1843,6 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
// `SEMICOLON_IN_EXPRESSIONS_FROM_MACROS` lint if needed. // `SEMICOLON_IN_EXPRESSIONS_FROM_MACROS` lint if needed.
// See #78991 for an investigation of treating macros in this position // See #78991 for an investigation of treating macros in this position
// as statements, rather than expressions, during parsing. // as statements, rather than expressions, during parsing.
let mut node = configure!(self, node);
return match &node.kind { return match &node.kind {
StmtKind::Expr(expr) StmtKind::Expr(expr)
if matches!(**expr, ast::Expr { kind: ExprKind::MacCall(..), .. }) => if matches!(**expr, ast::Expr { kind: ExprKind::MacCall(..), .. }) =>
@ -1793,7 +1874,10 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
} }
fn visit_expr(&mut self, node: &mut P<ast::Expr>) { fn visit_expr(&mut self, node: &mut P<ast::Expr>) {
self.cfg.configure_expr(node); // FIXME: Feature gating is performed inconsistently between `Expr` and `OptExpr`.
if let Some(attr) = node.attrs.first() {
self.cfg.maybe_emit_expr_attr_err(attr);
}
self.visit_node(node) self.visit_node(node)
} }

View file

@ -134,7 +134,6 @@ impl CheckAttrVisitor<'_> {
} }
sym::macro_use | sym::macro_escape => self.check_macro_use(hir_id, attr, target), sym::macro_use | sym::macro_escape => self.check_macro_use(hir_id, attr, target),
sym::path => self.check_generic_attr(hir_id, attr, target, &[Target::Mod]), sym::path => self.check_generic_attr(hir_id, attr, target, &[Target::Mod]),
sym::cfg_attr => self.check_cfg_attr(hir_id, attr),
sym::plugin_registrar => self.check_plugin_registrar(hir_id, attr, target), sym::plugin_registrar => self.check_plugin_registrar(hir_id, attr, target),
sym::macro_export => self.check_macro_export(hir_id, attr, target), sym::macro_export => self.check_macro_export(hir_id, attr, target),
sym::ignore | sym::should_panic | sym::proc_macro_derive => { sym::ignore | sym::should_panic | sym::proc_macro_derive => {
@ -1823,16 +1822,6 @@ impl CheckAttrVisitor<'_> {
} }
} }
fn check_cfg_attr(&self, hir_id: HirId, attr: &Attribute) {
if let Some((_, attrs)) = rustc_parse::parse_cfg_attr(&attr, &self.tcx.sess.parse_sess) {
if attrs.is_empty() {
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
lint.build("`#[cfg_attr]` does not expand to any attributes").emit();
});
}
}
}
fn check_plugin_registrar(&self, hir_id: HirId, attr: &Attribute, target: Target) { fn check_plugin_registrar(&self, hir_id: HirId, attr: &Attribute, target: Target) {
if target != Target::Fn { if target != Target::Fn {
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {

View file

@ -1,7 +1,9 @@
#[cfg(target_abi = "x")] //~ ERROR `cfg(target_abi)` is experimental #[cfg(target_abi = "x")] //~ ERROR `cfg(target_abi)` is experimental
#[cfg_attr(target_abi = "x", x)] //~ ERROR `cfg(target_abi)` is experimental
struct Foo(u64, u64); struct Foo(u64, u64);
#[cfg_attr(target_abi = "x", x)] //~ ERROR `cfg(target_abi)` is experimental
struct Bar(u64, u64);
#[cfg(not(any(all(target_abi = "x"))))] //~ ERROR `cfg(target_abi)` is experimental #[cfg(not(any(all(target_abi = "x"))))] //~ ERROR `cfg(target_abi)` is experimental
fn foo() {} fn foo() {}

View file

@ -1,12 +1,3 @@
error[E0658]: `cfg(target_abi)` is experimental and subject to change
--> $DIR/feature-gate-cfg-target-abi.rs:2:12
|
LL | #[cfg_attr(target_abi = "x", x)]
| ^^^^^^^^^^^^^^^^
|
= note: see issue #80970 <https://github.com/rust-lang/rust/issues/80970> for more information
= help: add `#![feature(cfg_target_abi)]` to the crate attributes to enable
error[E0658]: `cfg(target_abi)` is experimental and subject to change error[E0658]: `cfg(target_abi)` is experimental and subject to change
--> $DIR/feature-gate-cfg-target-abi.rs:1:7 --> $DIR/feature-gate-cfg-target-abi.rs:1:7
| |
@ -17,7 +8,16 @@ LL | #[cfg(target_abi = "x")]
= help: add `#![feature(cfg_target_abi)]` to the crate attributes to enable = help: add `#![feature(cfg_target_abi)]` to the crate attributes to enable
error[E0658]: `cfg(target_abi)` is experimental and subject to change error[E0658]: `cfg(target_abi)` is experimental and subject to change
--> $DIR/feature-gate-cfg-target-abi.rs:5:19 --> $DIR/feature-gate-cfg-target-abi.rs:4:12
|
LL | #[cfg_attr(target_abi = "x", x)]
| ^^^^^^^^^^^^^^^^
|
= note: see issue #80970 <https://github.com/rust-lang/rust/issues/80970> for more information
= help: add `#![feature(cfg_target_abi)]` to the crate attributes to enable
error[E0658]: `cfg(target_abi)` is experimental and subject to change
--> $DIR/feature-gate-cfg-target-abi.rs:7:19
| |
LL | #[cfg(not(any(all(target_abi = "x"))))] LL | #[cfg(not(any(all(target_abi = "x"))))]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
@ -26,7 +26,7 @@ LL | #[cfg(not(any(all(target_abi = "x"))))]
= help: add `#![feature(cfg_target_abi)]` to the crate attributes to enable = help: add `#![feature(cfg_target_abi)]` to the crate attributes to enable
error[E0658]: `cfg(target_abi)` is experimental and subject to change error[E0658]: `cfg(target_abi)` is experimental and subject to change
--> $DIR/feature-gate-cfg-target-abi.rs:9:10 --> $DIR/feature-gate-cfg-target-abi.rs:11:10
| |
LL | cfg!(target_abi = "x"); LL | cfg!(target_abi = "x");
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^

View file

@ -4,6 +4,4 @@
fn main() { fn main() {
let _ = #[cfg_eval] #[cfg(FALSE)] 0; let _ = #[cfg_eval] #[cfg(FALSE)] 0;
//~^ ERROR removing an expression is not supported in this position //~^ ERROR removing an expression is not supported in this position
//~| ERROR removing an expression is not supported in this position
//~| ERROR removing an expression is not supported in this position
} }

View file

@ -4,17 +4,5 @@ error: removing an expression is not supported in this position
LL | let _ = #[cfg_eval] #[cfg(FALSE)] 0; LL | let _ = #[cfg_eval] #[cfg(FALSE)] 0;
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^
error: removing an expression is not supported in this position error: aborting due to previous error
--> $DIR/cfg-eval-fail.rs:5:25
|
LL | let _ = #[cfg_eval] #[cfg(FALSE)] 0;
| ^^^^^^^^^^^^^
error: removing an expression is not supported in this position
--> $DIR/cfg-eval-fail.rs:5:25
|
LL | let _ = #[cfg_eval] #[cfg(FALSE)] 0;
| ^^^^^^^^^^^^^
error: aborting due to 3 previous errors