Auto merge of #150310 - JonathanBrouwer:cfg_trace2, r=jdonszelmann

Port `#[cfg]` and `#[cfg_attr]` trace attributes to the new attribute parsers

This PR converts `cfg` and `cfg_trace` attributes to the new parsed representation.

The primary challenge is that re-parsing these attributes in the HIR is a performance regression, since these attributes were only used in rustdoc and clippy parsing them in the HIR is extra work that was not done in the compiler before. To solve this, we only parse the attributes once and then store their parsed representation in the AST.
This commit is contained in:
bors 2026-01-06 22:59:32 +00:00
commit d9617c8d9a
38 changed files with 445 additions and 521 deletions

View file

@ -34,6 +34,7 @@ use rustc_span::source_map::{Spanned, respan};
use rustc_span::{ByteSymbol, DUMMY_SP, ErrorGuaranteed, Ident, Span, Symbol, kw, sym};
use thin_vec::{ThinVec, thin_vec};
use crate::attr::data_structures::CfgEntry;
pub use crate::format::*;
use crate::token::{self, CommentKind, Delimiter};
use crate::tokenstream::{DelimSpan, LazyAttrTokenStream, TokenStream};
@ -3390,7 +3391,7 @@ impl NormalAttr {
item: AttrItem {
unsafety: Safety::Default,
path: Path::from_ident(ident),
args: AttrArgs::Empty,
args: AttrItemKind::Unparsed(AttrArgs::Empty),
tokens: None,
},
tokens: None,
@ -3402,11 +3403,53 @@ impl NormalAttr {
pub struct AttrItem {
pub unsafety: Safety,
pub path: Path,
pub args: AttrArgs,
pub args: AttrItemKind,
// Tokens for the meta item, e.g. just the `foo` within `#[foo]` or `#![foo]`.
pub tokens: Option<LazyAttrTokenStream>,
}
/// Some attributes are stored in a parsed form, for performance reasons.
/// Their arguments don't have to be reparsed everytime they're used
#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
pub enum AttrItemKind {
Parsed(EarlyParsedAttribute),
Unparsed(AttrArgs),
}
impl AttrItemKind {
pub fn unparsed(self) -> Option<AttrArgs> {
match self {
AttrItemKind::Unparsed(args) => Some(args),
AttrItemKind::Parsed(_) => None,
}
}
pub fn unparsed_ref(&self) -> Option<&AttrArgs> {
match self {
AttrItemKind::Unparsed(args) => Some(args),
AttrItemKind::Parsed(_) => None,
}
}
pub fn span(&self) -> Option<Span> {
match self {
AttrItemKind::Unparsed(args) => args.span(),
AttrItemKind::Parsed(_) => None,
}
}
}
/// Some attributes are stored in parsed form in the AST.
/// This is done for performance reasons, so the attributes don't need to be reparsed on every use.
///
/// Currently all early parsed attributes are excluded from pretty printing at rustc_ast_pretty::pprust::state::print_attribute_inline.
/// When adding new early parsed attributes, consider whether they should be pretty printed.
#[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic)]
pub enum EarlyParsedAttribute {
CfgTrace(CfgEntry),
CfgAttrTrace,
}
impl AttrItem {
pub fn is_valid_for_outer_style(&self) -> bool {
self.path == sym::cfg_attr

View file

@ -0,0 +1,101 @@
use std::fmt;
use rustc_macros::{Decodable, Encodable, HashStable_Generic};
use rustc_span::{Span, Symbol};
use thin_vec::ThinVec;
use crate::attr::version::RustcVersion;
#[derive(Encodable, Decodable, Clone, Debug, PartialEq, Eq, Hash, HashStable_Generic)]
pub enum CfgEntry {
All(ThinVec<CfgEntry>, Span),
Any(ThinVec<CfgEntry>, Span),
Not(Box<CfgEntry>, Span),
Bool(bool, Span),
NameValue { name: Symbol, value: Option<Symbol>, span: Span },
Version(Option<RustcVersion>, Span),
}
impl CfgEntry {
pub fn lower_spans(&mut self, lower_span: impl Copy + Fn(Span) -> Span) {
match self {
CfgEntry::All(subs, span) | CfgEntry::Any(subs, span) => {
*span = lower_span(*span);
subs.iter_mut().for_each(|sub| sub.lower_spans(lower_span));
}
CfgEntry::Not(sub, span) => {
*span = lower_span(*span);
sub.lower_spans(lower_span);
}
CfgEntry::Bool(_, span)
| CfgEntry::NameValue { span, .. }
| CfgEntry::Version(_, span) => {
*span = lower_span(*span);
}
}
}
pub fn span(&self) -> Span {
let (Self::All(_, span)
| Self::Any(_, span)
| Self::Not(_, span)
| Self::Bool(_, span)
| Self::NameValue { span, .. }
| Self::Version(_, span)) = self;
*span
}
/// Same as `PartialEq` but doesn't check spans and ignore order of cfgs.
pub fn is_equivalent_to(&self, other: &Self) -> bool {
match (self, other) {
(Self::All(a, _), Self::All(b, _)) | (Self::Any(a, _), Self::Any(b, _)) => {
a.len() == b.len() && a.iter().all(|a| b.iter().any(|b| a.is_equivalent_to(b)))
}
(Self::Not(a, _), Self::Not(b, _)) => a.is_equivalent_to(b),
(Self::Bool(a, _), Self::Bool(b, _)) => a == b,
(
Self::NameValue { name: name1, value: value1, .. },
Self::NameValue { name: name2, value: value2, .. },
) => name1 == name2 && value1 == value2,
(Self::Version(a, _), Self::Version(b, _)) => a == b,
_ => false,
}
}
}
impl fmt::Display for CfgEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn write_entries(
name: &str,
entries: &[CfgEntry],
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
write!(f, "{name}(")?;
for (nb, entry) in entries.iter().enumerate() {
if nb != 0 {
f.write_str(", ")?;
}
entry.fmt(f)?;
}
f.write_str(")")
}
match self {
Self::All(entries, _) => write_entries("all", entries, f),
Self::Any(entries, _) => write_entries("any", entries, f),
Self::Not(entry, _) => write!(f, "not({entry})"),
Self::Bool(value, _) => write!(f, "{value}"),
Self::NameValue { name, value, .. } => {
match value {
// We use `as_str` and debug display to have characters escaped and `"`
// characters surrounding the string.
Some(value) => write!(f, "{name} = {:?}", value.as_str()),
None => write!(f, "{name}"),
}
}
Self::Version(version, _) => match version {
Some(version) => write!(f, "{version}"),
None => Ok(()),
},
}
}
}

View file

@ -1,5 +1,8 @@
//! Functions dealing with attributes and meta items.
pub mod data_structures;
pub mod version;
use std::fmt::Debug;
use std::sync::atomic::{AtomicU32, Ordering};
@ -8,6 +11,7 @@ use rustc_span::{Ident, Span, Symbol, sym};
use smallvec::{SmallVec, smallvec};
use thin_vec::{ThinVec, thin_vec};
use crate::AttrItemKind;
use crate::ast::{
AttrArgs, AttrId, AttrItem, AttrKind, AttrStyle, AttrVec, Attribute, DUMMY_NODE_ID, DelimArgs,
Expr, ExprKind, LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NormalAttr, Path,
@ -62,6 +66,15 @@ impl Attribute {
}
}
/// Replaces the arguments of this attribute with new arguments `AttrItemKind`.
/// This is useful for making this attribute into a trace attribute, and should otherwise be avoided.
pub fn replace_args(&mut self, new_args: AttrItemKind) {
match &mut self.kind {
AttrKind::Normal(normal) => normal.item.args = new_args,
AttrKind::DocComment(..) => panic!("unexpected doc comment"),
}
}
pub fn unwrap_normal_item(self) -> AttrItem {
match self.kind {
AttrKind::Normal(normal) => normal.item,
@ -77,7 +90,7 @@ impl AttributeExt for Attribute {
fn value_span(&self) -> Option<Span> {
match &self.kind {
AttrKind::Normal(normal) => match &normal.item.args {
AttrKind::Normal(normal) => match &normal.item.args.unparsed_ref()? {
AttrArgs::Eq { expr, .. } => Some(expr.span),
_ => None,
},
@ -147,7 +160,7 @@ impl AttributeExt for Attribute {
fn is_word(&self) -> bool {
if let AttrKind::Normal(normal) = &self.kind {
matches!(normal.item.args, AttrArgs::Empty)
matches!(normal.item.args, AttrItemKind::Unparsed(AttrArgs::Empty))
} else {
false
}
@ -303,7 +316,7 @@ impl AttrItem {
}
pub fn meta_item_list(&self) -> Option<ThinVec<MetaItemInner>> {
match &self.args {
match &self.args.unparsed_ref()? {
AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => {
MetaItemKind::list_from_tokens(args.tokens.clone())
}
@ -324,7 +337,7 @@ impl AttrItem {
/// #[attr("value")]
/// ```
fn value_str(&self) -> Option<Symbol> {
match &self.args {
match &self.args.unparsed_ref()? {
AttrArgs::Eq { expr, .. } => match expr.kind {
ExprKind::Lit(token_lit) => {
LitKind::from_token_lit(token_lit).ok().and_then(|lit| lit.str())
@ -348,7 +361,7 @@ impl AttrItem {
/// #[attr("value")]
/// ```
fn value_span(&self) -> Option<Span> {
match &self.args {
match &self.args.unparsed_ref()? {
AttrArgs::Eq { expr, .. } => Some(expr.span),
AttrArgs::Delimited(_) | AttrArgs::Empty => None,
}
@ -364,7 +377,7 @@ impl AttrItem {
}
pub fn meta_kind(&self) -> Option<MetaItemKind> {
MetaItemKind::from_attr_args(&self.args)
MetaItemKind::from_attr_args(self.args.unparsed_ref()?)
}
}
@ -699,7 +712,13 @@ fn mk_attr(
args: AttrArgs,
span: Span,
) -> Attribute {
mk_attr_from_item(g, AttrItem { unsafety, path, args, tokens: None }, None, style, span)
mk_attr_from_item(
g,
AttrItem { unsafety, path, args: AttrItemKind::Unparsed(args), tokens: None },
None,
style,
span,
)
}
pub fn mk_attr_from_item(

View file

@ -1,16 +1,10 @@
use std::borrow::Cow;
use std::fmt::{self, Display};
use std::sync::OnceLock;
use rustc_error_messages::{DiagArgValue, IntoDiagArg};
use rustc_macros::{
BlobDecodable, Encodable, HashStable_Generic, PrintAttribute, current_rustc_version,
};
use crate::attrs::PrintAttribute;
use rustc_macros::{BlobDecodable, Encodable, HashStable_Generic, current_rustc_version};
#[derive(Encodable, BlobDecodable, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(HashStable_Generic, PrintAttribute)]
#[derive(HashStable_Generic)]
pub struct RustcVersion {
pub major: u16,
pub minor: u16,
@ -47,9 +41,3 @@ impl Display for RustcVersion {
write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl IntoDiagArg for RustcVersion {
fn into_diag_arg(self, _: &mut Option<std::path::PathBuf>) -> DiagArgValue {
DiagArgValue::Str(Cow::Owned(self.to_string()))
}
}

View file

@ -366,6 +366,7 @@ macro_rules! common_visitor_and_walkers {
crate::token::LitKind,
crate::tokenstream::LazyAttrTokenStream,
crate::tokenstream::TokenStream,
EarlyParsedAttribute,
Movability,
Mutability,
Pinnedness,
@ -457,6 +458,7 @@ macro_rules! common_visitor_and_walkers {
ModSpans,
MutTy,
NormalAttr,
AttrItemKind,
Parens,
ParenthesizedArgs,
PatFieldsRest,

View file

@ -694,7 +694,7 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
}
ast::Safety::Default | ast::Safety::Safe(_) => {}
}
match &item.args {
match &item.args.unparsed_ref().expect("Parsed attributes are never printed") {
AttrArgs::Delimited(DelimArgs { dspan: _, delim, tokens }) => self.print_mac_common(
Some(MacHeader::Path(&item.path)),
false,

View file

@ -294,11 +294,9 @@ pub fn parse_cfg_attr(
sess: &Session,
features: Option<&Features>,
) -> Option<(CfgEntry, Vec<(AttrItem, Span)>)> {
match cfg_attr.get_normal_item().args {
ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, ref tokens })
if !tokens.is_empty() =>
{
check_cfg_attr_bad_delim(&sess.psess, dspan, delim);
match cfg_attr.get_normal_item().args.unparsed_ref().unwrap() {
ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, tokens }) if !tokens.is_empty() => {
check_cfg_attr_bad_delim(&sess.psess, *dspan, *delim);
match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| {
parse_cfg_attr_internal(p, sess, features, cfg_attr)
}) {
@ -322,7 +320,7 @@ pub fn parse_cfg_attr(
}
_ => {
let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) =
cfg_attr.get_normal_item().args
cfg_attr.get_normal_item().args.unparsed_ref()?
{
(dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument)
} else {

View file

@ -0,0 +1,53 @@
use rustc_ast::EarlyParsedAttribute;
use rustc_ast::attr::data_structures::CfgEntry;
use rustc_hir::Attribute;
use rustc_hir::attrs::AttributeKind;
use rustc_span::{Span, Symbol, sym};
use thin_vec::ThinVec;
pub(crate) const EARLY_PARSED_ATTRIBUTES: &[&[Symbol]] =
&[&[sym::cfg_trace], &[sym::cfg_attr_trace]];
/// This struct contains the state necessary to convert early parsed attributes to hir attributes
/// The only conversion that really happens here is that multiple early parsed attributes are
/// merged into a single hir attribute, representing their combined state.
/// FIXME: We should make this a nice and extendable system if this is going to be used more often
#[derive(Default)]
pub(crate) struct EarlyParsedState {
/// Attribute state for `#[cfg]` trace attributes
cfg_trace: ThinVec<(CfgEntry, Span)>,
/// Attribute state for `#[cfg_attr]` trace attributes
/// The arguments of these attributes is no longer relevant for any later passes, only their presence.
/// So we discard the arguments here.
cfg_attr_trace: bool,
}
impl EarlyParsedState {
pub(crate) fn accept_early_parsed_attribute(
&mut self,
attr_span: Span,
lower_span: impl Copy + Fn(Span) -> Span,
parsed: &EarlyParsedAttribute,
) {
match parsed {
EarlyParsedAttribute::CfgTrace(cfg) => {
let mut cfg = cfg.clone();
cfg.lower_spans(lower_span);
self.cfg_trace.push((cfg, attr_span));
}
EarlyParsedAttribute::CfgAttrTrace => {
self.cfg_attr_trace = true;
}
}
}
pub(crate) fn finalize_early_parsed_attributes(self, attributes: &mut Vec<Attribute>) {
if !self.cfg_trace.is_empty() {
attributes.push(Attribute::Parsed(AttributeKind::CfgTrace(self.cfg_trace)));
}
if self.cfg_attr_trace {
attributes.push(Attribute::Parsed(AttributeKind::CfgAttrTrace));
}
}
}

View file

@ -2,7 +2,7 @@ use std::convert::identity;
use rustc_ast as ast;
use rustc_ast::token::DocFragmentKind;
use rustc_ast::{AttrStyle, NodeId, Safety};
use rustc_ast::{AttrItemKind, AttrStyle, NodeId, Safety};
use rustc_errors::DiagCtxtHandle;
use rustc_feature::{AttributeTemplate, Features};
use rustc_hir::attrs::AttributeKind;
@ -13,6 +13,7 @@ use rustc_session::lint::BuiltinLintDiag;
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
use crate::context::{AcceptContext, FinalizeContext, SharedContext, Stage};
use crate::early_parsed::{EARLY_PARSED_ATTRIBUTES, EarlyParsedState};
use crate::parser::{ArgParser, PathParser, RefPathParser};
use crate::session_diagnostics::ParsedDescription;
use crate::{Early, Late, OmitDoc, ShouldEmit};
@ -146,8 +147,12 @@ impl<'sess> AttributeParser<'sess, Early> {
normal_attr.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
let path = AttrPath::from_ast(&normal_attr.item.path, identity);
let args =
ArgParser::from_attr_args(&normal_attr.item.args, &parts, &sess.psess, emit_errors)?;
let args = ArgParser::from_attr_args(
&normal_attr.item.args.unparsed_ref().unwrap(),
&parts,
&sess.psess,
emit_errors,
)?;
Self::parse_single_args(
sess,
attr.span,
@ -263,12 +268,12 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
target_id: S::Id,
target: Target,
omit_doc: OmitDoc,
lower_span: impl Copy + Fn(Span) -> Span,
mut emit_lint: impl FnMut(AttributeLint<S::Id>),
) -> Vec<Attribute> {
let mut attributes = Vec::new();
let mut attr_paths: Vec<RefPathParser<'_>> = Vec::new();
let mut early_parsed_state = EarlyParsedState::default();
for attr in attrs {
// If we're only looking for a single attribute, skip all the ones we don't care about.
@ -288,6 +293,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
continue;
}
let attr_span = lower_span(attr.span);
match &attr.kind {
ast::AttrKind::DocComment(comment_kind, symbol) => {
if omit_doc == OmitDoc::Skip {
@ -297,7 +303,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
attributes.push(Attribute::Parsed(AttributeKind::DocComment {
style: attr.style,
kind: DocFragmentKind::Sugared(*comment_kind),
span: lower_span(attr.span),
span: attr_span,
comment: *symbol,
}))
}
@ -305,6 +311,15 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
attr_paths.push(PathParser(&n.item.path));
let attr_path = AttrPath::from_ast(&n.item.path, lower_span);
let args = match &n.item.args {
AttrItemKind::Unparsed(args) => args,
AttrItemKind::Parsed(parsed) => {
early_parsed_state
.accept_early_parsed_attribute(attr_span, lower_span, parsed);
continue;
}
};
self.check_attribute_safety(
&attr_path,
lower_span(n.item.span()),
@ -318,7 +333,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
if let Some(accepts) = S::parsers().accepters.get(parts.as_slice()) {
let Some(args) = ArgParser::from_attr_args(
&n.item.args,
args,
&parts,
&self.sess.psess,
self.stage.should_emit(),
@ -351,7 +366,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
attributes.push(Attribute::Parsed(AttributeKind::DocComment {
style: attr.style,
kind: DocFragmentKind::Raw(nv.value_span),
span: attr.span,
span: attr_span,
comment,
}));
continue;
@ -365,7 +380,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
target_id,
emit_lint: &mut emit_lint,
},
attr_span: lower_span(attr.span),
attr_span,
inner_span: lower_span(n.item.span()),
attr_style: attr.style,
parsed_description: ParsedDescription::Attribute,
@ -396,17 +411,18 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
attributes.push(Attribute::Unparsed(Box::new(AttrItem {
path: attr_path.clone(),
args: self.lower_attr_args(&n.item.args, lower_span),
args: self
.lower_attr_args(n.item.args.unparsed_ref().unwrap(), lower_span),
id: HashIgnoredAttrId { attr_id: attr.id },
style: attr.style,
span: lower_span(attr.span),
span: attr_span,
})));
}
}
}
}
let mut parsed_attributes = Vec::new();
early_parsed_state.finalize_early_parsed_attributes(&mut attributes);
for f in &S::parsers().finalizers {
if let Some(attr) = f(&mut FinalizeContext {
shared: SharedContext {
@ -417,18 +433,16 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
},
all_attrs: &attr_paths,
}) {
parsed_attributes.push(Attribute::Parsed(attr));
attributes.push(Attribute::Parsed(attr));
}
}
attributes.extend(parsed_attributes);
attributes
}
/// Returns whether there is a parser for an attribute with this name
pub fn is_parsed_attribute(path: &[Symbol]) -> bool {
Late::parsers().accepters.contains_key(path)
Late::parsers().accepters.contains_key(path) || EARLY_PARSED_ATTRIBUTES.contains(&path)
}
fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs {

View file

@ -99,6 +99,7 @@ mod interface;
/// like lists or name-value pairs.
pub mod parser;
mod early_parsed;
mod safety;
mod session_diagnostics;
mod target_checking;

View file

@ -122,10 +122,10 @@ impl ArgParser {
}
if args.delim != Delimiter::Parenthesis {
psess.dcx().emit_err(MetaBadDelim {
should_emit.emit_err(psess.dcx().create_err(MetaBadDelim {
span: args.dspan.entire(),
sugg: MetaBadDelimSugg { open: args.dspan.open, close: args.dspan.close },
});
}));
return None;
}

View file

@ -4,7 +4,7 @@ use rustc_hir::AttrPath;
use rustc_hir::lints::{AttributeLint, AttributeLintKind};
use rustc_session::lint::LintId;
use rustc_session::lint::builtin::UNSAFE_ATTR_OUTSIDE_UNSAFE;
use rustc_span::{Span, sym};
use rustc_span::Span;
use crate::context::Stage;
use crate::{AttributeParser, ShouldEmit};
@ -23,11 +23,6 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
}
let name = (attr_path.segments.len() == 1).then_some(attr_path.segments[0]);
if let Some(name) = name
&& [sym::cfg_trace, sym::cfg_attr_trace].contains(&name)
{
return;
}
// FIXME: We should retrieve this information from the attribute parsers instead of from `BUILTIN_ATTRIBUTE_MAP`
let builtin_attr_info = name.and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name));

View file

@ -48,7 +48,7 @@ pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
}
_ => {
let attr_item = attr.get_normal_item();
if let AttrArgs::Eq { .. } = attr_item.args {
if let AttrArgs::Eq { .. } = attr_item.args.unparsed_ref().unwrap() {
// All key-value attributes are restricted to meta-item syntax.
match parse_meta(psess, attr) {
Ok(_) => {}
@ -67,7 +67,7 @@ pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Met
unsafety: item.unsafety,
span: attr.span,
path: item.path.clone(),
kind: match &item.args {
kind: match &item.args.unparsed_ref().unwrap() {
AttrArgs::Empty => MetaItemKind::Word,
AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
check_meta_bad_delim(psess, *dspan, *delim);

View file

@ -358,7 +358,7 @@ mod llvm_enzyme {
let inline_item = ast::AttrItem {
unsafety: ast::Safety::Default,
path: ast::Path::from_ident(Ident::with_dummy_span(sym::inline)),
args: ast::AttrArgs::Delimited(never_arg),
args: rustc_ast::ast::AttrItemKind::Unparsed(ast::AttrArgs::Delimited(never_arg)),
tokens: None,
};
let inline_never_attr = Box::new(ast::NormalAttr { item: inline_item, tokens: None });
@ -421,11 +421,13 @@ mod llvm_enzyme {
}
};
// Now update for d_fn
rustc_ad_attr.item.args = rustc_ast::AttrArgs::Delimited(rustc_ast::DelimArgs {
rustc_ad_attr.item.args = rustc_ast::ast::AttrItemKind::Unparsed(
rustc_ast::AttrArgs::Delimited(rustc_ast::DelimArgs {
dspan: DelimSpan::dummy(),
delim: rustc_ast::token::Delimiter::Parenthesis,
tokens: ts,
});
}),
);
let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id();
let d_attr = outer_normal_attr(&rustc_ad_attr, new_id, span);

View file

@ -807,7 +807,8 @@ impl<'a> TraitDef<'a> {
rustc_ast::AttrItem {
unsafety: Safety::Default,
path: rustc_const_unstable,
args: AttrArgs::Delimited(DelimArgs {
args: rustc_ast::ast::AttrItemKind::Unparsed(AttrArgs::Delimited(
DelimArgs {
dspan: DelimSpan::from_single(self.span),
delim: rustc_ast::token::Delimiter::Parenthesis,
tokens: [
@ -821,10 +822,14 @@ impl<'a> TraitDef<'a> {
]
.into_iter()
.map(|kind| {
TokenTree::Token(Token { kind, span: self.span }, Spacing::Alone)
TokenTree::Token(
Token { kind, span: self.span },
Spacing::Alone,
)
})
.collect(),
}),
},
)),
tokens: None,
},
self.span,

View file

@ -7,8 +7,8 @@ use rustc_ast::tokenstream::{
AttrTokenStream, AttrTokenTree, LazyAttrTokenStream, Spacing, TokenTree,
};
use rustc_ast::{
self as ast, AttrKind, AttrStyle, Attribute, HasAttrs, HasTokens, MetaItem, MetaItemInner,
NodeId, NormalAttr,
self as ast, AttrItemKind, AttrKind, AttrStyle, Attribute, EarlyParsedAttribute, HasAttrs,
HasTokens, MetaItem, MetaItemInner, NodeId, NormalAttr,
};
use rustc_attr_parsing as attr;
use rustc_attr_parsing::{
@ -288,7 +288,9 @@ impl<'a> StripUnconfigured<'a> {
pub(crate) fn expand_cfg_attr(&self, cfg_attr: &Attribute, recursive: bool) -> Vec<Attribute> {
// A trace attribute left in AST in place of the original `cfg_attr` attribute.
// It can later be used by lints or other diagnostics.
let trace_attr = attr_into_trace(cfg_attr.clone(), sym::cfg_attr_trace);
let mut trace_attr = cfg_attr.clone();
trace_attr.replace_args(AttrItemKind::Parsed(EarlyParsedAttribute::CfgAttrTrace));
let trace_attr = attr_into_trace(trace_attr, sym::cfg_attr_trace);
let Some((cfg_predicate, expanded_attrs)) =
rustc_attr_parsing::parse_cfg_attr(cfg_attr, &self.sess, self.features)

View file

@ -7,12 +7,16 @@ use rustc_ast::mut_visit::*;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::visit::{self, AssocCtxt, Visitor, VisitorResult, try_visit, walk_list};
use rustc_ast::{
self as ast, AssocItemKind, AstNodeWrapper, AttrArgs, AttrStyle, AttrVec, DUMMY_NODE_ID,
ExprKind, ForeignItemKind, HasAttrs, HasNodeId, Inline, ItemKind, MacStmtStyle, MetaItemInner,
MetaItemKind, ModKind, NodeId, PatKind, StmtKind, TyKind, token,
self as ast, AssocItemKind, AstNodeWrapper, AttrArgs, AttrItemKind, AttrStyle, AttrVec,
DUMMY_NODE_ID, EarlyParsedAttribute, ExprKind, ForeignItemKind, HasAttrs, HasNodeId, Inline,
ItemKind, MacStmtStyle, MetaItemInner, MetaItemKind, ModKind, NodeId, PatKind, StmtKind,
TyKind, token,
};
use rustc_ast_pretty::pprust;
use rustc_attr_parsing::{AttributeParser, Early, EvalConfigResult, ShouldEmit, validate_attr};
use rustc_attr_parsing::{
AttributeParser, CFG_TEMPLATE, Early, EvalConfigResult, ShouldEmit, eval_config_entry,
parse_cfg, validate_attr,
};
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::PResult;
@ -813,10 +817,10 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
};
let attr_item = attr.get_normal_item();
let safety = attr_item.unsafety;
if let AttrArgs::Eq { .. } = attr_item.args {
if let AttrArgs::Eq { .. } = attr_item.args.unparsed_ref().unwrap() {
self.cx.dcx().emit_err(UnsupportedKeyValue { span });
}
let inner_tokens = attr_item.args.inner_tokens();
let inner_tokens = attr_item.args.unparsed_ref().unwrap().inner_tokens();
match expander.expand_with_safety(self.cx, safety, span, inner_tokens, tokens) {
Ok(tok_result) => {
let fragment = self.parse_ast_fragment(
@ -2188,9 +2192,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
&& !AttributeParser::<Early>::is_parsed_attribute(&attr.path())
{
let attr_name = attr.name().unwrap();
// `#[cfg]` and `#[cfg_attr]` are special - they are
// eagerly evaluated.
if attr_name != sym::cfg_trace && attr_name != sym::cfg_attr_trace {
self.cx.sess.psess.buffer_lint(
UNUSED_ATTRIBUTES,
attr.span,
@ -2205,7 +2206,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
}
}
}
}
fn expand_cfg_true(
&mut self,
@ -2213,11 +2213,26 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
attr: ast::Attribute,
pos: usize,
) -> EvalConfigResult {
let res = self.cfg().cfg_true(&attr, ShouldEmit::ErrorsAndLints);
let Some(cfg) = AttributeParser::parse_single(
self.cfg().sess,
&attr,
attr.span,
self.cfg().lint_node_id,
self.cfg().features,
ShouldEmit::ErrorsAndLints,
parse_cfg,
&CFG_TEMPLATE,
) else {
// Cfg attribute was not parsable, give up
return EvalConfigResult::True;
};
let res = eval_config_entry(self.cfg().sess, &cfg);
if res.as_bool() {
// A trace attribute left in AST in place of the original `cfg` attribute.
// It can later be used by lints or other diagnostics.
let trace_attr = attr_into_trace(attr, sym::cfg_trace);
let mut trace_attr = attr_into_trace(attr, sym::cfg_trace);
trace_attr.replace_args(AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg)));
node.visit_attrs(|attrs| attrs.insert(pos, trace_attr));
}

View file

@ -1,9 +1,9 @@
use std::borrow::Cow;
use std::fmt;
use std::path::PathBuf;
pub use ReprAttr::*;
use rustc_abi::Align;
pub use rustc_ast::attr::data_structures::*;
use rustc_ast::token::DocFragmentKind;
use rustc_ast::{AttrStyle, ast};
use rustc_data_structures::fx::FxIndexMap;
@ -212,83 +212,6 @@ impl<ModId> StrippedCfgItem<ModId> {
}
}
#[derive(Encodable, Decodable, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(HashStable_Generic, PrintAttribute)]
pub enum CfgEntry {
All(ThinVec<CfgEntry>, Span),
Any(ThinVec<CfgEntry>, Span),
Not(Box<CfgEntry>, Span),
Bool(bool, Span),
NameValue { name: Symbol, value: Option<Symbol>, span: Span },
Version(Option<RustcVersion>, Span),
}
impl CfgEntry {
pub fn span(&self) -> Span {
let (Self::All(_, span)
| Self::Any(_, span)
| Self::Not(_, span)
| Self::Bool(_, span)
| Self::NameValue { span, .. }
| Self::Version(_, span)) = self;
*span
}
/// Same as `PartialEq` but doesn't check spans and ignore order of cfgs.
pub fn is_equivalent_to(&self, other: &Self) -> bool {
match (self, other) {
(Self::All(a, _), Self::All(b, _)) | (Self::Any(a, _), Self::Any(b, _)) => {
a.len() == b.len() && a.iter().all(|a| b.iter().any(|b| a.is_equivalent_to(b)))
}
(Self::Not(a, _), Self::Not(b, _)) => a.is_equivalent_to(b),
(Self::Bool(a, _), Self::Bool(b, _)) => a == b,
(
Self::NameValue { name: name1, value: value1, .. },
Self::NameValue { name: name2, value: value2, .. },
) => name1 == name2 && value1 == value2,
(Self::Version(a, _), Self::Version(b, _)) => a == b,
_ => false,
}
}
}
impl fmt::Display for CfgEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn write_entries(
name: &str,
entries: &[CfgEntry],
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
write!(f, "{name}(")?;
for (nb, entry) in entries.iter().enumerate() {
if nb != 0 {
f.write_str(", ")?;
}
entry.fmt(f)?;
}
f.write_str(")")
}
match self {
Self::All(entries, _) => write_entries("all", entries, f),
Self::Any(entries, _) => write_entries("any", entries, f),
Self::Not(entry, _) => write!(f, "not({entry})"),
Self::Bool(value, _) => write!(f, "{value}"),
Self::NameValue { name, value, .. } => {
match value {
// We use `as_str` and debug display to have characters escaped and `"`
// characters surrounding the string.
Some(value) => write!(f, "{name} = {:?}", value.as_str()),
None => write!(f, "{name}"),
}
}
Self::Version(version, _) => match version {
Some(version) => write!(f, "{version}"),
None => Ok(()),
},
}
}
}
/// Possible values for the `#[linkage]` attribute, allowing to specify the
/// linkage type for a `MonoItem`.
///
@ -713,6 +636,12 @@ pub enum AttributeKind {
span: Span,
},
/// Represents the trace attribute of `#[cfg_attr]`
CfgAttrTrace,
/// Represents the trace attribute of `#[cfg]`
CfgTrace(ThinVec<(CfgEntry, Span)>),
/// Represents `#[cfi_encoding]`
CfiEncoding { encoding: Symbol },

View file

@ -26,6 +26,8 @@ impl AttributeKind {
AsPtr(..) => Yes,
AutomaticallyDerived(..) => Yes,
BodyStability { .. } => No,
CfgAttrTrace => Yes,
CfgTrace(..) => Yes,
CfiEncoding { .. } => Yes,
Coinductive(..) => No,
Cold(..) => No,

View file

@ -2,6 +2,8 @@ use std::num::NonZero;
use std::ops::Deref;
use rustc_abi::Align;
use rustc_ast::attr::data_structures::CfgEntry;
use rustc_ast::attr::version::RustcVersion;
use rustc_ast::token::{CommentKind, DocFragmentKind};
use rustc_ast::{AttrStyle, IntTy, UintTy};
use rustc_ast_pretty::pp::Printer;
@ -182,4 +184,6 @@ print_debug!(
Transparency,
SanitizerSet,
DefId,
RustcVersion,
CfgEntry,
);

View file

@ -1371,6 +1371,7 @@ impl AttributeExt for Attribute {
// FIXME: should not be needed anymore when all attrs are parsed
Attribute::Parsed(AttributeKind::DocComment { span, .. }) => *span,
Attribute::Parsed(AttributeKind::Deprecation { span, .. }) => *span,
Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) => cfgs[0].1,
a => panic!("can't get the span of an arbitrary parsed attribute: {a:?}"),
}
}

View file

@ -33,7 +33,6 @@ pub mod pat_util;
mod stability;
mod stable_hash_impls;
mod target;
mod version;
pub mod weak_lang_items;
#[cfg(test)]
@ -42,9 +41,9 @@ mod tests;
#[doc(no_inline)]
pub use hir::*;
pub use lang_items::{LangItem, LanguageItems};
pub use rustc_ast::attr::version::*;
pub use stability::*;
pub use stable_hash_impls::HashStableContext;
pub use target::{MethodKind, Target};
pub use version::*;
arena_types!(rustc_arena::declare_arena);

View file

@ -1,7 +1,7 @@
use rustc_ast as ast;
use rustc_ast::token::{self, MetaVarKind};
use rustc_ast::tokenstream::ParserRange;
use rustc_ast::{Attribute, attr};
use rustc_ast::{AttrItemKind, Attribute, attr};
use rustc_errors::codes::*;
use rustc_errors::{Diag, PResult};
use rustc_span::{BytePos, Span};
@ -313,7 +313,7 @@ impl<'a> Parser<'a> {
this.expect(exp!(CloseParen))?;
}
Ok((
ast::AttrItem { unsafety, path, args, tokens: None },
ast::AttrItem { unsafety, path, args: AttrItemKind::Unparsed(args), tokens: None },
Trailing::No,
UsePreAttrPos::No,
))

View file

@ -229,6 +229,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::BodyStability { .. }
| AttributeKind::ConstStabilityIndirect
| AttributeKind::MacroTransparency(_)
| AttributeKind::CfgTrace(..)
| AttributeKind::Pointee(..)
| AttributeKind::Dummy
| AttributeKind::RustcBuiltinMacro { .. }
@ -302,6 +303,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::RustcPassIndirectlyInNonRusticAbis(..)
| AttributeKind::PinV2(..)
| AttributeKind::WindowsSubsystem(..)
| AttributeKind::CfgAttrTrace
| AttributeKind::ThreadLocal
| AttributeKind::CfiEncoding { .. }
) => { /* do nothing */ }
@ -338,8 +340,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| sym::forbid
| sym::cfg
| sym::cfg_attr
| sym::cfg_trace
| sym::cfg_attr_trace
// need to be fixed
| sym::patchable_function_entry // FIXME(patchable_function_entry)
| sym::deprecated_safe // FIXME(deprecated_safe)
@ -1950,12 +1950,10 @@ impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> {
// only `#[cfg]` and `#[cfg_attr]` are allowed, but it should be removed
// if we allow more attributes (e.g., tool attributes and `allow/deny/warn`)
// in where clauses. After that, only `self.check_attributes` should be enough.
const ATTRS_ALLOWED: &[Symbol] = &[sym::cfg_trace, sym::cfg_attr_trace];
let spans = self
.tcx
.hir_attrs(where_predicate.hir_id)
.iter()
.filter(|attr| !ATTRS_ALLOWED.iter().any(|&sym| attr.has_name(sym)))
// FIXME: We shouldn't need to special-case `doc`!
.filter(|attr| {
matches!(

View file

@ -7,7 +7,6 @@ use std::sync::Arc;
use std::{fmt, mem, ops};
use itertools::Either;
use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::thin_vec::{ThinVec, thin_vec};
use rustc_hir as hir;
@ -29,12 +28,6 @@ mod tests;
#[cfg_attr(test, derive(PartialEq))]
pub(crate) struct Cfg(CfgEntry);
#[derive(PartialEq, Debug)]
pub(crate) struct InvalidCfgError {
pub(crate) msg: &'static str,
pub(crate) span: Span,
}
/// Whether the configuration consists of just `Cfg` or `Not`.
fn is_simple_cfg(cfg: &CfgEntry) -> bool {
match cfg {
@ -105,106 +98,6 @@ fn should_capitalize_first_letter(cfg: &CfgEntry) -> bool {
}
impl Cfg {
/// Parses a `MetaItemInner` into a `Cfg`.
fn parse_nested(
nested_cfg: &MetaItemInner,
exclude: &FxHashSet<NameValueCfg>,
) -> Result<Option<Cfg>, InvalidCfgError> {
match nested_cfg {
MetaItemInner::MetaItem(cfg) => Cfg::parse_without(cfg, exclude),
MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => {
Ok(Some(Cfg(CfgEntry::Bool(*b, DUMMY_SP))))
}
MetaItemInner::Lit(lit) => {
Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
}
}
}
fn parse_without(
cfg: &MetaItem,
exclude: &FxHashSet<NameValueCfg>,
) -> Result<Option<Cfg>, InvalidCfgError> {
let name = match cfg.ident() {
Some(ident) => ident.name,
None => {
return Err(InvalidCfgError {
msg: "expected a single identifier",
span: cfg.span,
});
}
};
match cfg.kind {
MetaItemKind::Word => {
if exclude.contains(&NameValueCfg::new(name)) {
Ok(None)
} else {
Ok(Some(Cfg(CfgEntry::NameValue { name, value: None, span: DUMMY_SP })))
}
}
MetaItemKind::NameValue(ref lit) => match lit.kind {
LitKind::Str(value, _) => {
if exclude.contains(&NameValueCfg::new_value(name, value)) {
Ok(None)
} else {
Ok(Some(Cfg(CfgEntry::NameValue {
name,
value: Some(value),
span: DUMMY_SP,
})))
}
}
_ => Err(InvalidCfgError {
// FIXME: if the main #[cfg] syntax decided to support non-string literals,
// this should be changed as well.
msg: "value of cfg option should be a string literal",
span: lit.span,
}),
},
MetaItemKind::List(ref items) => {
let orig_len = items.len();
let mut sub_cfgs =
items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose());
let ret = match name {
sym::all => {
sub_cfgs.try_fold(Cfg(CfgEntry::Bool(true, DUMMY_SP)), |x, y| Ok(x & y?))
}
sym::any => {
sub_cfgs.try_fold(Cfg(CfgEntry::Bool(false, DUMMY_SP)), |x, y| Ok(x | y?))
}
sym::not => {
if orig_len == 1 {
let mut sub_cfgs = sub_cfgs.collect::<Vec<_>>();
if sub_cfgs.len() == 1 {
Ok(!sub_cfgs.pop().unwrap()?)
} else {
return Ok(None);
}
} else {
Err(InvalidCfgError { msg: "expected 1 cfg-pattern", span: cfg.span })
}
}
_ => Err(InvalidCfgError { msg: "invalid predicate", span: cfg.span }),
};
match ret {
Ok(c) => Ok(Some(c)),
Err(e) => Err(e),
}
}
}
}
/// Parses a `MetaItem` into a `Cfg`.
///
/// The `MetaItem` should be the content of the `#[cfg(...)]`, e.g., `unix` or
/// `target_os = "redox"`.
///
/// If the content is not properly formatted, it will return an error indicating what and where
/// the error is.
pub(crate) fn parse(cfg: &MetaItemInner) -> Result<Cfg, InvalidCfgError> {
Self::parse_nested(cfg, &FxHashSet::default()).map(|ret| ret.unwrap())
}
/// Renders the configuration for human display, as a short HTML description.
pub(crate) fn render_short_html(&self) -> String {
let mut msg = Display(&self.0, Format::ShortHtml).to_string();
@ -644,10 +537,6 @@ impl NameValueCfg {
fn new(name: Symbol) -> Self {
Self { name, value: None }
}
fn new_value(name: Symbol, value: Symbol) -> Self {
Self { name, value: Some(value) }
}
}
impl<'a> From<&'a CfgEntry> for NameValueCfg {
@ -751,15 +640,6 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
tcx: TyCtxt<'_>,
cfg_info: &mut CfgInfo,
) -> Option<Arc<Cfg>> {
fn single<T: IntoIterator>(it: T) -> Option<T::Item> {
let mut iter = it.into_iter();
let item = iter.next()?;
if iter.next().is_some() {
return None;
}
Some(item)
}
fn check_changed_auto_active_status(
changed_auto_active_status: &mut Option<rustc_span::Span>,
attr_span: Span,
@ -859,12 +739,11 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
}
continue;
} else if !cfg_info.parent_is_doc_cfg
&& let Some(name) = attr.name()
&& matches!(name, sym::cfg | sym::cfg_trace)
&& let Some(attr) = single(attr.meta_item_list()?)
&& let Ok(new_cfg) = Cfg::parse(&attr)
&& let hir::Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) = attr
{
cfg_info.current_cfg &= new_cfg;
for (new_cfg, _) in cfgs {
cfg_info.current_cfg &= Cfg(new_cfg.clone());
}
}
}

View file

@ -1,8 +1,5 @@
use rustc_ast::ast::LitIntType;
use rustc_ast::{MetaItemInner, MetaItemLit, Path, Safety, StrStyle};
use rustc_data_structures::thin_vec::thin_vec;
use rustc_hir::attrs::CfgEntry;
use rustc_span::symbol::{Ident, kw};
use rustc_span::{DUMMY_SP, create_default_session_globals_then};
use super::*;
@ -28,10 +25,6 @@ fn name_value_cfg_e(name: &str, value: &str) -> CfgEntry {
}
}
fn dummy_lit(symbol: Symbol, kind: LitKind) -> MetaItemInner {
MetaItemInner::Lit(MetaItemLit { symbol, suffix: None, kind, span: DUMMY_SP })
}
fn cfg_all(v: ThinVec<CfgEntry>) -> Cfg {
Cfg(cfg_all_e(v))
}
@ -52,51 +45,6 @@ fn cfg_not(v: CfgEntry) -> Cfg {
Cfg(CfgEntry::Not(Box::new(v), DUMMY_SP))
}
fn dummy_meta_item_word(name: &str) -> MetaItemInner {
MetaItemInner::MetaItem(MetaItem {
unsafety: Safety::Default,
path: Path::from_ident(Ident::from_str(name)),
kind: MetaItemKind::Word,
span: DUMMY_SP,
})
}
fn dummy_meta_item_name_value(name: &str, symbol: Symbol, kind: LitKind) -> MetaItemInner {
let lit = MetaItemLit { symbol, suffix: None, kind, span: DUMMY_SP };
MetaItemInner::MetaItem(MetaItem {
unsafety: Safety::Default,
path: Path::from_ident(Ident::from_str(name)),
kind: MetaItemKind::NameValue(lit),
span: DUMMY_SP,
})
}
macro_rules! dummy_meta_item_list {
($name:ident, [$($list:ident),* $(,)?]) => {
MetaItemInner::MetaItem(MetaItem {
unsafety: Safety::Default,
path: Path::from_ident(Ident::from_str(stringify!($name))),
kind: MetaItemKind::List(thin_vec![
$(
dummy_meta_item_word(stringify!($list)),
)*
]),
span: DUMMY_SP,
})
};
($name:ident, [$($list:expr),* $(,)?]) => {
MetaItemInner::MetaItem(MetaItem {
unsafety: Safety::Default,
path: Path::from_ident(Ident::from_str(stringify!($name))),
kind: MetaItemKind::List(thin_vec![
$($list,)*
]),
span: DUMMY_SP,
})
};
}
fn cfg_true() -> Cfg {
Cfg(CfgEntry::Bool(true, DUMMY_SP))
}
@ -303,87 +251,6 @@ fn test_cfg_or() {
})
}
#[test]
fn test_parse_ok() {
create_default_session_globals_then(|| {
let r#true = Symbol::intern("true");
let mi = dummy_lit(r#true, LitKind::Bool(true));
assert_eq!(Cfg::parse(&mi), Ok(cfg_true()));
let r#false = Symbol::intern("false");
let mi = dummy_lit(r#false, LitKind::Bool(false));
assert_eq!(Cfg::parse(&mi), Ok(cfg_false()));
let mi = dummy_meta_item_word("all");
assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all")));
let done = Symbol::intern("done");
let mi = dummy_meta_item_name_value("all", done, LitKind::Str(done, StrStyle::Cooked));
assert_eq!(Cfg::parse(&mi), Ok(name_value_cfg("all", "done")));
let mi = dummy_meta_item_list!(all, [a, b]);
assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b")));
let mi = dummy_meta_item_list!(any, [a, b]);
assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") | word_cfg("b")));
let mi = dummy_meta_item_list!(not, [a]);
assert_eq!(Cfg::parse(&mi), Ok(!word_cfg("a")));
let mi = dummy_meta_item_list!(
not,
[dummy_meta_item_list!(
any,
[dummy_meta_item_word("a"), dummy_meta_item_list!(all, [b, c]),]
),]
);
assert_eq!(Cfg::parse(&mi), Ok(!(word_cfg("a") | (word_cfg("b") & word_cfg("c")))));
let mi = dummy_meta_item_list!(all, [a, b, c]);
assert_eq!(Cfg::parse(&mi), Ok(word_cfg("a") & word_cfg("b") & word_cfg("c")));
})
}
#[test]
fn test_parse_err() {
create_default_session_globals_then(|| {
let mi = dummy_meta_item_name_value("foo", kw::False, LitKind::Bool(false));
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(not, [a, b]);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(not, []);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(foo, []);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(
all,
[dummy_meta_item_list!(foo, []), dummy_meta_item_word("b"),]
);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(
any,
[dummy_meta_item_word("a"), dummy_meta_item_list!(foo, []),]
);
assert!(Cfg::parse(&mi).is_err());
let mi = dummy_meta_item_list!(not, [dummy_meta_item_list!(foo, []),]);
assert!(Cfg::parse(&mi).is_err());
let c = Symbol::intern("e");
let mi = dummy_lit(c, LitKind::Char('e'));
assert!(Cfg::parse(&mi).is_err());
let five = Symbol::intern("5");
let mi = dummy_lit(five, LitKind::Int(5.into(), LitIntType::Unsuffixed));
assert!(Cfg::parse(&mi).is_err());
})
}
#[test]
fn test_render_short_html() {
create_default_session_globals_then(|| {

View file

@ -51,7 +51,7 @@ use rustc_middle::ty::{self, AdtKind, GenericArgsRef, Ty, TyCtxt, TypeVisitableE
use rustc_middle::{bug, span_bug};
use rustc_span::ExpnKind;
use rustc_span::hygiene::{AstPass, MacroKind};
use rustc_span::symbol::{Ident, Symbol, kw, sym};
use rustc_span::symbol::{Ident, Symbol, kw};
use rustc_trait_selection::traits::wf::object_region_bounds;
use tracing::{debug, instrument};
use utils::*;
@ -2682,18 +2682,14 @@ fn add_without_unwanted_attributes<'hir>(
import_parent,
));
}
hir::Attribute::Unparsed(normal) if let [name] = &*normal.path.segments => {
if is_inline || *name != sym::cfg_trace {
// If it's not a `cfg()` attribute, we keep it.
// We discard `#[cfg(...)]` attributes unless we're inlining
hir::Attribute::Parsed(AttributeKind::CfgTrace(..)) if !is_inline => {}
// We keep all other attributes
_ => {
attrs.push((Cow::Borrowed(attr), import_parent));
}
}
// FIXME: make sure to exclude `#[cfg_trace]` here when it is ported to the new parsers
hir::Attribute::Parsed(..) => {
attrs.push((Cow::Borrowed(attr), import_parent));
}
_ => {}
}
}
}

View file

@ -2,7 +2,6 @@
use rustc_hir::Attribute;
use rustc_hir::attrs::{AttributeKind, DocAttribute};
use rustc_span::symbol::sym;
use crate::clean::inline::{load_attrs, merge_attrs};
use crate::clean::{CfgInfo, Crate, Item, ItemKind};
@ -39,10 +38,7 @@ fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute])
let mut new_attr = DocAttribute::default();
new_attr.cfg = d.cfg.clone();
attrs.push(Attribute::Parsed(AttributeKind::Doc(Box::new(new_attr))));
} else if let Attribute::Unparsed(normal) = attr
&& let [name] = &*normal.path.segments
&& *name == sym::cfg_trace
{
} else if let Attribute::Parsed(AttributeKind::CfgTrace(..)) = attr {
// If it's a `cfg()` attribute, we keep it.
attrs.push(attr.clone());
}

View file

@ -16,7 +16,7 @@ mod utils;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::msrvs::{self, Msrv, MsrvStack};
use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind};
use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind, AttrItemKind};
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
@ -604,7 +604,7 @@ impl EarlyLintPass for PostExpansionEarlyAttributes {
if attr.has_name(sym::ignore)
&& match &attr.kind {
AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrArgs::Eq { .. }),
AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrItemKind::Unparsed(AttrArgs::Eq { .. })),
AttrKind::DocComment(..) => true,
}
{

View file

@ -2,19 +2,19 @@ use super::{Attribute, SHOULD_PANIC_WITHOUT_EXPECT};
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::token::{Token, TokenKind};
use rustc_ast::tokenstream::TokenTree;
use rustc_ast::{AttrArgs, AttrKind};
use rustc_ast::{AttrArgs, AttrKind, AttrItemKind};
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use rustc_span::sym;
pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) {
if let AttrKind::Normal(normal_attr) = &attr.kind {
if let AttrArgs::Eq { .. } = &normal_attr.item.args {
if let AttrItemKind::Unparsed(AttrArgs::Eq { .. }) = &normal_attr.item.args {
// `#[should_panic = ".."]` found, good
return;
}
if let AttrArgs::Delimited(args) = &normal_attr.item.args
if let AttrItemKind::Unparsed(AttrArgs::Delimited(args)) = &normal_attr.item.args
&& let mut tt_iter = args.tokens.iter()
&& let Some(TokenTree::Token(
Token {

View file

@ -1,7 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::MetaItemInner;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
use rustc_ast::AttrItemKind;
use rustc_ast::EarlyParsedAttribute;
use rustc_span::sym;
use rustc_ast::attr::data_structures::CfgEntry;
declare_clippy_lint! {
/// ### What it does
@ -32,7 +35,12 @@ declare_lint_pass!(CfgNotTest => [CFG_NOT_TEST]);
impl EarlyLintPass for CfgNotTest {
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &rustc_ast::Attribute) {
if attr.has_name(rustc_span::sym::cfg_trace) && contains_not_test(attr.meta_item_list().as_deref(), false) {
if attr.has_name(sym::cfg_trace) {
let AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg)) = &attr.get_normal_item().args else {
unreachable!()
};
if contains_not_test(&cfg, false) {
span_lint_and_then(
cx,
CFG_NOT_TEST,
@ -45,16 +53,16 @@ impl EarlyLintPass for CfgNotTest {
);
}
}
}
}
fn contains_not_test(list: Option<&[MetaItemInner]>, not: bool) -> bool {
list.is_some_and(|list| {
list.iter().any(|item| {
item.ident().is_some_and(|ident| match ident.name {
rustc_span::sym::not => contains_not_test(item.meta_item_list(), !not),
rustc_span::sym::test => not,
_ => contains_not_test(item.meta_item_list(), not),
})
})
})
fn contains_not_test(cfg: &CfgEntry, not: bool) -> bool {
match cfg {
CfgEntry::All(subs, _) | CfgEntry::Any(subs, _) => subs.iter().any(|item| {
contains_not_test(item, not)
}),
CfgEntry::Not(sub, _) => contains_not_test(sub, !not),
CfgEntry::NameValue { name: sym::test, .. } => not,
_ => false
}
}

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use rustc_ast::{AttrArgs, AttrKind, AttrStyle, Attribute};
use rustc_ast::{AttrArgs, AttrKind, AttrStyle, Attribute, AttrItemKind};
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
@ -11,7 +11,7 @@ pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
if !attr.span.from_expansion()
&& let AttrKind::Normal(ref item) = attr.kind
&& attr.doc_str().is_some()
&& let AttrArgs::Eq { expr: meta, .. } = &item.item.args
&& let AttrItemKind::Unparsed(AttrArgs::Eq { expr: meta, .. }) = &item.item.args
&& !attr.span.contains(meta.span)
// Since the `include_str` is already expanded at this point, we can only take the
// whole attribute snippet and then modify for our suggestion.

View file

@ -9,6 +9,8 @@ use rustc_middle::ty::{self, TyCtxt};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::{CrateNum, DefId};
use rustc_span::{ExpnKind, Span};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::find_attr;
declare_clippy_lint! {
/// ### What it does
@ -268,11 +270,6 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
/// attribute.
fn is_under_cfg_attribute(cx: &LateContext<'_>, hir_id: HirId) -> bool {
cx.tcx.hir_parent_id_iter(hir_id).any(|id| {
cx.tcx.hir_attrs(id).iter().any(|attr| {
matches!(
attr.name(),
Some(sym::cfg_trace | sym::cfg_attr_trace)
)
})
find_attr!(cx.tcx.hir_attrs(id), AttributeKind::CfgTrace(..) | AttributeKind::CfgAttrTrace)
})
}

View file

@ -1,3 +1,4 @@
use rustc_ast::AttrItemKind;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call_first_node;
@ -92,7 +93,7 @@ impl EarlyLintPass for LargeIncludeFile {
&& let AttrKind::Normal(ref item) = attr.kind
&& let Some(doc) = attr.doc_str()
&& doc.as_str().len() as u64 > self.max_file_size
&& let AttrArgs::Eq { expr: meta, .. } = &item.item.args
&& let AttrItemKind::Unparsed(AttrArgs::Eq { expr: meta, .. }) = &item.item.args
&& !attr.span.contains(meta.span)
// Since the `include_str` is already expanded at this point, we can only take the
// whole attribute snippet and then modify for our suggestion.

View file

@ -5,7 +5,8 @@ use clippy_utils::res::MaybeResPath;
use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context};
use rustc_hir::{Expr, HirId};
use rustc_lint::{LateContext, LintContext};
use rustc_span::sym;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::find_attr;
use super::CONST_IS_EMPTY;
@ -40,7 +41,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_
fn is_under_cfg(cx: &LateContext<'_>, id: HirId) -> bool {
cx.tcx
.hir_parent_id_iter(id)
.any(|id| cx.tcx.hir_attrs(id).iter().any(|attr| attr.has_name(sym::cfg_trace)))
.any(|id| find_attr!(cx.tcx.hir_attrs(id), AttributeKind::CfgTrace(..)))
}
/// Similar to [`clippy_utils::expr_or_init`], but does not go up the chain if the initialization

View file

@ -9,6 +9,8 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::AssocKind;
use rustc_session::impl_lint_pass;
use rustc_span::sym;
use rustc_hir::Attribute;
use rustc_hir::attrs::AttributeKind;
declare_clippy_lint! {
/// ### What it does
@ -121,7 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
let attrs_sugg = {
let mut sugg = String::new();
for attr in cx.tcx.hir_attrs(assoc_item_hir_id) {
if !attr.has_name(sym::cfg_trace) {
let Attribute::Parsed(AttributeKind::CfgTrace(attrs)) = attr else {
// This might be some other attribute that the `impl Default` ought to inherit.
// But it could also be one of the many attributes that:
// - can't be put on an impl block -- like `#[inline]`
@ -131,10 +133,13 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
// reduce the applicability
app = Applicability::MaybeIncorrect;
continue;
};
for (_, attr_span) in attrs {
sugg.push_str(&snippet_with_applicability(cx.sess(), *attr_span, "_", &mut app));
sugg.push('\n');
}
sugg.push_str(&snippet_with_applicability(cx.sess(), attr.span(), "_", &mut app));
sugg.push('\n');
}
sugg
};

View file

@ -976,7 +976,15 @@ pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
l.style == r.style
&& match (&l.kind, &r.kind) {
(DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2,
(Normal(l), Normal(r)) => eq_path(&l.item.path, &r.item.path) && eq_attr_args(&l.item.args, &r.item.args),
(Normal(l), Normal(r)) => eq_path(&l.item.path, &r.item.path) && eq_attr_item_kind(&l.item.args, &r.item.args),
_ => false,
}
}
pub fn eq_attr_item_kind(l: &AttrItemKind, r: &AttrItemKind) -> bool {
match (l, r) {
(AttrItemKind::Unparsed(l), AttrItemKind::Unparsed(r)) => eq_attr_args(l, r),
(AttrItemKind::Parsed(_l), AttrItemKind::Parsed(_r)) => todo!(),
_ => false,
}
}

View file

@ -121,6 +121,7 @@ use rustc_middle::ty::{
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt,
TypeFlags, TypeVisitableExt, UintTy, UpvarCapture,
};
use rustc_hir::attrs::CfgEntry;
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{Ident, Symbol, kw};
@ -2401,17 +2402,12 @@ pub fn is_test_function(tcx: TyCtxt<'_>, fn_def_id: LocalDefId) -> bool {
/// This only checks directly applied attributes, to see if a node is inside a `#[cfg(test)]` parent
/// use [`is_in_cfg_test`]
pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
tcx.hir_attrs(id).iter().any(|attr| {
if attr.has_name(sym::cfg_trace)
&& let Some(items) = attr.meta_item_list()
&& let [item] = &*items
&& item.has_name(sym::test)
{
if let Some(cfgs) = find_attr!(tcx.hir_attrs(id), AttributeKind::CfgTrace(cfgs) => cfgs)
&& cfgs.iter().any(|(cfg, _)| { matches!(cfg, CfgEntry::NameValue { name: sym::test, ..})}) {
true
} else {
false
}
})
}
/// Checks if any parent node of `HirId` has `#[cfg(test)]` attribute applied
@ -2426,11 +2422,10 @@ pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
/// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied.
pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
tcx.has_attr(def_id, sym::cfg_trace)
|| tcx
find_attr!(tcx.get_all_attrs(def_id), AttributeKind::CfgTrace(..))
|| find_attr!(tcx
.hir_parent_iter(tcx.local_def_id_to_hir_id(def_id))
.flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id))
.any(|attr| attr.has_name(sym::cfg_trace))
.flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id)), AttributeKind::CfgTrace(..))
}
/// Walks up the HIR tree from the given expression in an attempt to find where the value is