diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index e0bdeb30dc84b..5fa7ffd554ef1 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -328,6 +328,10 @@ impl<'a> StripUnconfigured<'a> { }); } + fn process_cfg_attr(&self, attr: Attribute) -> Vec { + 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 /// when the configuration predicate is true, or otherwise expand into an /// empty list of attributes. @@ -335,11 +339,7 @@ impl<'a> StripUnconfigured<'a> { /// 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 /// the attribute is incorrect. - fn process_cfg_attr(&self, attr: Attribute) -> Vec { - if !attr.has_name(sym::cfg_attr) { - return vec![attr]; - } - + crate fn expand_cfg_attr(&self, attr: Attribute, recursive: bool) -> Vec { let (cfg_predicate, expanded_attrs) = match rustc_parse::parse_cfg_attr(&attr, &self.sess.parse_sess) { None => return vec![], @@ -348,95 +348,109 @@ impl<'a> StripUnconfigured<'a> { // Lint on zero attributes in source. 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) { return vec![]; } - // We call `process_cfg_attr` recursively in case there's a - // `cfg_attr` inside of another `cfg_attr`. E.g. - // `#[cfg_attr(false, cfg_attr(true, some_attr))]`. - expanded_attrs - .into_iter() - .flat_map(|(item, span)| { - let orig_tokens = attr.tokens().to_tokenstream(); - - // We are taking an attribute of the form `#[cfg_attr(pred, attr)]` - // and producing an attribute of the form `#[attr]`. We - // have captured tokens for `attr` itself, but we need to - // synthesize tokens for the wrapper `#` and `[]`, which - // we do below. - - // Use the `#` in `#[cfg_attr(pred, attr)]` as the `#` token - // for `attr` when we expand it to `#[attr]` - let mut orig_trees = orig_tokens.trees(); - let pound_token = match orig_trees.next().unwrap() { - TokenTree::Token(token @ Token { kind: TokenKind::Pound, .. }) => token, - _ => panic!("Bad tokens for attribute {:?}", attr), - }; - let pound_span = pound_token.span; - - let mut trees = vec![(AttrAnnotatedTokenTree::Token(pound_token), Spacing::Alone)]; - if attr.style == AttrStyle::Inner { - // For inner attributes, we do the same thing for the `!` in `#![some_attr]` - let bang_token = match orig_trees.next().unwrap() { - TokenTree::Token(token @ Token { kind: TokenKind::Not, .. }) => token, - _ => panic!("Bad tokens for attribute {:?}", attr), - }; - trees.push((AttrAnnotatedTokenTree::Token(bang_token), Spacing::Alone)); - } - // We don't really have a good span to use for the syntheized `[]` - // in `#[attr]`, so just use the span of the `#` token. - let bracket_group = AttrAnnotatedTokenTree::Delimited( - DelimSpan::from_single(pound_span), - DelimToken::Bracket, - item.tokens - .as_ref() - .unwrap_or_else(|| panic!("Missing tokens for {:?}", item)) - .create_token_stream(), - ); - trees.push((bracket_group, Spacing::Alone)); - let tokens = Some(LazyTokenStream::new(AttrAnnotatedTokenStream::new(trees))); - let attr = attr::mk_attr_from_item(item, tokens, attr.style, span); - if attr.has_name(sym::crate_type) { - self.sess.parse_sess.buffer_lint( - rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, - attr.span, - ast::CRATE_NODE_ID, - "`crate_type` within an `#![cfg_attr] attribute is deprecated`", - ); - } - if attr.has_name(sym::crate_name) { - self.sess.parse_sess.buffer_lint( - rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, - attr.span, - ast::CRATE_NODE_ID, - "`crate_name` within an `#![cfg_attr] attribute is deprecated`", - ); - } - self.process_cfg_attr(attr) - }) - .collect() + if recursive { + // We call `process_cfg_attr` recursively in case there's a + // `cfg_attr` inside of another `cfg_attr`. E.g. + // `#[cfg_attr(false, cfg_attr(true, some_attr))]`. + expanded_attrs + .into_iter() + .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(); + + // We are taking an attribute of the form `#[cfg_attr(pred, attr)]` + // and producing an attribute of the form `#[attr]`. We + // have captured tokens for `attr` itself, but we need to + // synthesize tokens for the wrapper `#` and `[]`, which + // we do below. + + // Use the `#` in `#[cfg_attr(pred, attr)]` as the `#` token + // for `attr` when we expand it to `#[attr]` + let mut orig_trees = orig_tokens.trees(); + let pound_token = match orig_trees.next().unwrap() { + TokenTree::Token(token @ Token { kind: TokenKind::Pound, .. }) => token, + _ => panic!("Bad tokens for attribute {:?}", attr), + }; + let pound_span = pound_token.span; + + let mut trees = vec![(AttrAnnotatedTokenTree::Token(pound_token), Spacing::Alone)]; + if attr.style == AttrStyle::Inner { + // For inner attributes, we do the same thing for the `!` in `#![some_attr]` + let bang_token = match orig_trees.next().unwrap() { + TokenTree::Token(token @ Token { kind: TokenKind::Not, .. }) => token, + _ => panic!("Bad tokens for attribute {:?}", attr), + }; + trees.push((AttrAnnotatedTokenTree::Token(bang_token), Spacing::Alone)); + } + // We don't really have a good span to use for the syntheized `[]` + // in `#[attr]`, so just use the span of the `#` token. + let bracket_group = AttrAnnotatedTokenTree::Delimited( + DelimSpan::from_single(pound_span), + DelimToken::Bracket, + item.tokens + .as_ref() + .unwrap_or_else(|| panic!("Missing tokens for {:?}", item)) + .create_token_stream(), + ); + trees.push((bracket_group, Spacing::Alone)); + let tokens = Some(LazyTokenStream::new(AttrAnnotatedTokenStream::new(trees))); + let attr = attr::mk_attr_from_item(item, tokens, attr.style, item_span); + if attr.has_name(sym::crate_type) { + self.sess.parse_sess.buffer_lint( + rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, + attr.span, + ast::CRATE_NODE_ID, + "`crate_type` within an `#![cfg_attr] attribute is deprecated`", + ); + } + if attr.has_name(sym::crate_name) { + self.sess.parse_sess.buffer_lint( + rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, + attr.span, + ast::CRATE_NODE_ID, + "`crate_name` within an `#![cfg_attr] attribute is deprecated`", + ); + } + attr } /// Determines if a node with the given attributes should be included in this configuration. fn in_cfg(&self, attrs: &[Attribute]) -> bool { - attrs.iter().all(|attr| { - if !is_cfg(attr) { + attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr)) + } + + crate fn cfg_true(&self, attr: &Attribute) -> bool { + let meta_item = match validate_attr::parse_meta(&self.sess.parse_sess, attr) { + Ok(meta_item) => meta_item, + Err(mut err) => { + err.emit(); return true; } - let meta_item = match validate_attr::parse_meta(&self.sess.parse_sess, attr) { - Ok(meta_item) => meta_item, - Err(mut err) => { - err.emit(); - return true; - } - }; - parse_cfg(&meta_item, &self.sess).map_or(true, |meta_item| { - attr::cfg_matches(&meta_item, &self.sess.parse_sess, self.features) - }) + }; + parse_cfg(&meta_item, &self.sess).map_or(true, |meta_item| { + attr::cfg_matches(&meta_item, &self.sess.parse_sess, self.features) }) } diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 07ce901fb417a..7604a464be2ff 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -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::{NodeId, PatKind, StmtKind, TyKind}; use rustc_ast_pretty::pprust; -use rustc_attr::is_builtin_attr; use rustc_data_structures::map_in_place::MapInPlace; use rustc_data_structures::sync::Lrc; use rustc_errors::{Applicability, PResult}; @@ -1014,6 +1013,9 @@ trait InvocationCollectorNode: AstLike { fn to_annotatable(self) -> Annotatable; fn fragment_to_output(fragment: AstFragment) -> Self::OutputTy; fn id(&mut self) -> &mut NodeId; + fn descr() -> &'static str { + unreachable!() + } fn noop_flat_map(self, _visitor: &mut V) -> Self::OutputTy { unreachable!() } @@ -1477,6 +1479,9 @@ impl InvocationCollectorNode for P { fn id(&mut self) -> &mut NodeId { &mut self.id } + fn descr() -> &'static str { + "an expression" + } fn noop_visit(&mut self, visitor: &mut V) { noop_visit_expr(self, visitor) } @@ -1576,13 +1581,28 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { ) -> Option<(ast::Attribute, usize, Vec)> { 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| { - attr = attrs - .iter() - .position(|a| !self.cx.expanded_inert_attrs.is_marked(a) && !is_builtin_attr(a)) - .map(|attr_pos| { - let attr = attrs.remove(attr_pos); - let following_derives = attrs[attr_pos..] + attr = Some(match (cfg_pos, attr_pos) { + (Some(pos), _) => (attrs.remove(pos), pos, Vec::new()), + (_, Some(pos)) => { + let attr = attrs.remove(pos); + let following_derives = attrs[pos..] .iter() .filter(|a| a.has_name(sym::derive)) .flat_map(|a| a.meta_item_list().unwrap_or_default()) @@ -1596,17 +1616,15 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { }) .collect(); - (attr, attr_pos, following_derives) - }) + (attr, pos, following_derives) + } + _ => return, + }); }); attr } - fn configure(&mut self, node: T) -> Option { - self.cfg.configure(node) - } - // Detect use of feature-gated or invalid attributes on macro invocations // since they will not be detected after macro expansion. fn check_attributes(&self, attrs: &[ast::Attribute], call: &ast::MacCall) { @@ -1653,28 +1671,71 @@ 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>( &mut self, - node: Node, + mut node: Node, ) -> Node::OutputTy { - let mut node = configure!(self, node); - - if let Some(attr) = self.take_first_attr(&mut node) { - Node::pre_flat_map_node_collect_attr(&self.cfg, &attr.0); - self.collect_attr(attr, node.to_annotatable(), Node::KIND).make_ast::() - } else if node.is_mac_call() { - let (mac, attrs, add_semicolon) = node.take_mac_call(); - self.check_attributes(&attrs, &mac); - let mut res = self.collect_bang(mac, Node::KIND).make_ast::(); - Node::post_flat_map_node_collect_bang(&mut res, add_semicolon); - res - } else { - match Node::wrap_flat_map_node_noop_flat_map(node, self, |mut node, this| { - assign_id!(this, node.id(), || node.noop_flat_map(this)) - }) { - Ok(output) => output, - Err(node) => self.flat_map_node(node), - } + loop { + return match self.take_first_attr(&mut node) { + Some((attr, pos, derives)) => match attr.name_or_empty() { + sym::cfg => { + if self.expand_cfg_true(&mut node, attr, pos) { + 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::() + } + }, + None if node.is_mac_call() => { + let (mac, attrs, add_semicolon) = node.take_mac_call(); + self.check_attributes(&attrs, &mac); + let mut res = self.collect_bang(mac, Node::KIND).make_ast::(); + Node::post_flat_map_node_collect_bang(&mut res, add_semicolon); + res + } + None => { + match Node::wrap_flat_map_node_noop_flat_map(node, self, |mut node, this| { + assign_id!(this, node.id(), || node.noop_flat_map(this)) + }) { + Ok(output) => output, + Err(returned_node) => { + node = returned_node; + continue; + } + } + } + }; } } @@ -1682,19 +1743,40 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { &mut self, node: &mut Node, ) { - if let Some(attr) = self.take_first_attr(node) { - visit_clobber(node, |node| { - self.collect_attr(attr, node.to_annotatable(), Node::KIND).make_ast::() - }) - } else if node.is_mac_call() { - visit_clobber(node, |node| { - // Do not clobber unless it's actually a macro (uncommon case). - let (mac, attrs, _) = node.take_mac_call(); - self.check_attributes(&attrs, &mac); - self.collect_bang(mac, Node::KIND).make_ast::() - }) - } else { - assign_id!(self, node.id(), || node.noop_visit(self)) + loop { + return match self.take_first_attr(node) { + Some((attr, pos, derives)) => match attr.name_or_empty() { + sym::cfg => { + 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::() + }), + }, + None if node.is_mac_call() => { + visit_clobber(node, |node| { + // Do not clobber unless it's actually a macro (uncommon case). + let (mac, attrs, _) = node.take_mac_call(); + self.check_attributes(&attrs, &mac); + self.collect_bang(mac, Node::KIND).make_ast::() + }) + } + None => { + 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) } - 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, // changing that requires some compatibility measures. if node.is_expr() { @@ -1761,7 +1843,6 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> { // `SEMICOLON_IN_EXPRESSIONS_FROM_MACROS` lint if needed. // See #78991 for an investigation of treating macros in this position // as statements, rather than expressions, during parsing. - let mut node = configure!(self, node); return match &node.kind { StmtKind::Expr(expr) 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) { - 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) } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index d7b00699491d4..6e52efe75c19e 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -134,7 +134,6 @@ impl CheckAttrVisitor<'_> { } 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::cfg_attr => self.check_cfg_attr(hir_id, attr), sym::plugin_registrar => self.check_plugin_registrar(hir_id, attr, target), sym::macro_export => self.check_macro_export(hir_id, attr, target), 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) { if target != Target::Fn { self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { diff --git a/src/test/ui/feature-gates/feature-gate-cfg-target-abi.rs b/src/test/ui/feature-gates/feature-gate-cfg-target-abi.rs index f26514939800a..d005dc3ad456d 100644 --- a/src/test/ui/feature-gates/feature-gate-cfg-target-abi.rs +++ b/src/test/ui/feature-gates/feature-gate-cfg-target-abi.rs @@ -1,7 +1,9 @@ #[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); +#[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 fn foo() {} diff --git a/src/test/ui/feature-gates/feature-gate-cfg-target-abi.stderr b/src/test/ui/feature-gates/feature-gate-cfg-target-abi.stderr index ed8cbcbe4f017..013705d4886de 100644 --- a/src/test/ui/feature-gates/feature-gate-cfg-target-abi.stderr +++ b/src/test/ui/feature-gates/feature-gate-cfg-target-abi.stderr @@ -1,23 +1,23 @@ error[E0658]: `cfg(target_abi)` is experimental and subject to change - --> $DIR/feature-gate-cfg-target-abi.rs:2:12 + --> $DIR/feature-gate-cfg-target-abi.rs:1:7 | -LL | #[cfg_attr(target_abi = "x", x)] - | ^^^^^^^^^^^^^^^^ +LL | #[cfg(target_abi = "x")] + | ^^^^^^^^^^^^^^^^ | = note: see issue #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:1:7 + --> $DIR/feature-gate-cfg-target-abi.rs:4:12 | -LL | #[cfg(target_abi = "x")] - | ^^^^^^^^^^^^^^^^ +LL | #[cfg_attr(target_abi = "x", x)] + | ^^^^^^^^^^^^^^^^ | = note: see issue #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:5:19 + --> $DIR/feature-gate-cfg-target-abi.rs:7:19 | 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 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"); | ^^^^^^^^^^^^^^^^ diff --git a/src/test/ui/proc-macro/cfg-eval-fail.rs b/src/test/ui/proc-macro/cfg-eval-fail.rs index 379491f3126b0..a259aa2e6ec0c 100644 --- a/src/test/ui/proc-macro/cfg-eval-fail.rs +++ b/src/test/ui/proc-macro/cfg-eval-fail.rs @@ -4,6 +4,4 @@ fn main() { 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 } diff --git a/src/test/ui/proc-macro/cfg-eval-fail.stderr b/src/test/ui/proc-macro/cfg-eval-fail.stderr index 010ac006b0bee..df8b6d5f382a1 100644 --- a/src/test/ui/proc-macro/cfg-eval-fail.stderr +++ b/src/test/ui/proc-macro/cfg-eval-fail.stderr @@ -4,17 +4,5 @@ error: removing an expression is not supported in this position 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: 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 +error: aborting due to previous error