Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_attr_parsing/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ attr_parsing_as_needed_compatibility =
attr_parsing_bundle_needs_static =
linking modifier `bundle` is only compatible with `static` linking kind

attr_parsing_cfg_attr_bad_delim = wrong `cfg_attr` delimiters

attr_parsing_cfg_predicate_identifier =
`cfg` predicate key must be an identifier

Expand Down
151 changes: 139 additions & 12 deletions compiler/rustc_attr_parsing/src/attributes/cfg.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
use rustc_ast::{LitKind, NodeId};
use rustc_ast::token::Delimiter;
use rustc_ast::tokenstream::DelimSpan;
use rustc_ast::{AttrItem, Attribute, CRATE_NODE_ID, LitKind, NodeId, ast, token};
use rustc_errors::{Applicability, PResult};
use rustc_feature::{AttributeTemplate, Features, template};
use rustc_hir::RustcVersion;
use rustc_hir::attrs::CfgEntry;
use rustc_hir::{AttrPath, RustcVersion};
use rustc_parse::parser::{ForceCollect, Parser};
use rustc_parse::{exp, parse_in};
use rustc_session::Session;
use rustc_session::config::ExpectedValues;
use rustc_session::lint::BuiltinLintDiag;
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
use rustc_session::parse::feature_err;
use rustc_session::parse::{ParseSess, feature_err};
use rustc_span::{Span, Symbol, sym};
use thin_vec::ThinVec;

use crate::context::{AcceptContext, ShouldEmit, Stage};
use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser};
use crate::session_diagnostics::{
AttributeParseError, AttributeParseErrorReason, CfgAttrBadDelim, MetaBadDelimSugg,
};
use crate::{
CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, try_gate_cfg,
AttributeParser, CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics,
try_gate_cfg,
};

pub const CFG_TEMPLATE: AttributeTemplate = template!(
List: &["predicate"],
"https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute"
);

pub fn parse_cfg_attr<'c, S: Stage>(
const CFG_ATTR_TEMPLATE: AttributeTemplate = template!(
List: &["predicate, attr1, attr2, ..."],
"https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute"
);

pub fn parse_cfg<'c, S: Stage>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> Option<CfgEntry> {
Expand Down Expand Up @@ -70,9 +84,7 @@ pub(crate) fn parse_cfg_entry<S: Stage>(
},
a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
let Some(name) = meta.path().word_sym() else {
cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
span: meta.path().span(),
});
cx.expected_identifier(meta.path().span());
return None;
};
parse_name_value(name, meta.path().span(), a.name_value(), meta.span(), cx)?
Expand All @@ -81,7 +93,7 @@ pub(crate) fn parse_cfg_entry<S: Stage>(
MetaItemOrLitParser::Lit(lit) => match lit.kind {
LitKind::Bool(b) => CfgEntry::Bool(b, lit.span),
_ => {
cx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: lit.span });
cx.expected_identifier(lit.span);
return None;
}
},
Expand Down Expand Up @@ -149,9 +161,7 @@ fn parse_cfg_entry_target<S: Stage>(

// Then, parse it as a name-value item
let Some(name) = sub_item.path().word_sym() else {
cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
span: sub_item.path().span(),
});
cx.expected_identifier(sub_item.path().span());
return None;
};
let name = Symbol::intern(&format!("target_{name}"));
Expand Down Expand Up @@ -300,3 +310,120 @@ impl EvalConfigResult {
}
}
}

pub fn parse_cfg_attr(
cfg_attr: &Attribute,
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 parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| {
parse_cfg_attr_internal(p, sess, features, cfg_attr)
}) {
Ok(r) => return Some(r),
Err(e) => {
let suggestions = CFG_ATTR_TEMPLATE.suggestions(cfg_attr.style, sym::cfg_attr);
e.with_span_suggestions(
cfg_attr.span,
"must be of the form",
suggestions,
Applicability::HasPlaceholders,
)
.with_note(format!(
"for more information, visit <{}>",
CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs")
))
.emit();
}
}
}
_ => {
let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) =
cfg_attr.get_normal_item().args
{
(dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument)
} else {
(cfg_attr.span, AttributeParseErrorReason::ExpectedList)
};

sess.dcx().emit_err(AttributeParseError {
span,
attr_span: cfg_attr.span,
template: CFG_ATTR_TEMPLATE,
attribute: AttrPath::from_ast(&cfg_attr.get_normal_item().path),
reason,
attr_style: cfg_attr.style,
});
}
}
None
}

fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
if let Delimiter::Parenthesis = delim {
return;
}
psess.dcx().emit_err(CfgAttrBadDelim {
span: span.entire(),
sugg: MetaBadDelimSugg { open: span.open, close: span.close },
});
}

/// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
fn parse_cfg_attr_internal<'a>(
parser: &mut Parser<'a>,
sess: &'a Session,
features: Option<&Features>,
attribute: &Attribute,
) -> PResult<'a, (CfgEntry, Vec<(ast::AttrItem, Span)>)> {
// Parse cfg predicate
let pred_start = parser.token.span;
let meta = MetaItemOrLitParser::parse_single(parser, ShouldEmit::ErrorsAndLints)?;
let pred_span = pred_start.with_hi(parser.token.span.hi());

let cfg_predicate = AttributeParser::parse_single_args(
sess,
attribute.span,
attribute.style,
AttrPath {
segments: attribute
.ident_path()
.expect("cfg_attr is not a doc comment")
.into_boxed_slice(),
span: attribute.span,
},
pred_span,
CRATE_NODE_ID,
features,
ShouldEmit::ErrorsAndLints,
&meta,
parse_cfg_entry,
&CFG_ATTR_TEMPLATE,
)
.ok_or_else(|| {
let mut diag = sess.dcx().struct_err(
"cfg_entry parsing failing with `ShouldEmit::ErrorsAndLints` should emit a error.",
);
diag.downgrade_to_delayed_bug();
diag
})?;

parser.expect(exp!(Comma))?;

// Presumably, the majority of the time there will only be one attr.
let mut expanded_attrs = Vec::with_capacity(1);
while parser.token != token::Eof {
let lo = parser.token.span;
let item = parser.parse_attr_item(ForceCollect::Yes)?;
expanded_attrs.push((item, lo.to(parser.prev_token.span)));
if !parser.eat(exp!(Comma)) {
break;
}
}

Ok((cfg_predicate, expanded_attrs))
}
64 changes: 52 additions & 12 deletions compiler/rustc_attr_parsing/src/interface.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::borrow::Cow;

use rustc_ast as ast;
use rustc_ast::NodeId;
use rustc_ast::{AttrStyle, NodeId};
use rustc_errors::DiagCtxtHandle;
use rustc_feature::{AttributeTemplate, Features};
use rustc_hir::attrs::AttributeKind;
Expand Down Expand Up @@ -62,7 +62,8 @@ impl<'sess> AttributeParser<'sess, Early> {
)
}

/// Usually you want `parse_limited`, which defaults to no errors.
/// This does the same as `parse_limited`, except it has a `should_emit` parameter which allows it to emit errors.
/// Usually you want `parse_limited`, which emits no errors.
pub fn parse_limited_should_emit(
sess: &'sess Session,
attrs: &[ast::Attribute],
Expand All @@ -86,6 +87,13 @@ impl<'sess> AttributeParser<'sess, Early> {
parsed.pop()
}

/// This method allows you to parse a list of attributes *before* `rustc_ast_lowering`.
/// This can be used for attributes that would be removed before `rustc_ast_lowering`, such as attributes on macro calls.
///
/// Try to use this as little as possible. Attributes *should* be lowered during
/// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
/// crash if you tried to do so through [`parse_limited_all`](Self::parse_limited_all).
/// Therefore, if `parse_only` is None, then features *must* be provided.
pub fn parse_limited_all(
sess: &'sess Session,
attrs: &[ast::Attribute],
Expand All @@ -111,6 +119,8 @@ impl<'sess> AttributeParser<'sess, Early> {
)
}

/// This method parses a single attribute, using `parse_fn`.
/// This is useful if you already know what exact attribute this is, and want to parse it.
pub fn parse_single<T>(
sess: &'sess Session,
attr: &ast::Attribute,
Expand All @@ -121,13 +131,6 @@ impl<'sess> AttributeParser<'sess, Early> {
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser<'_>) -> Option<T>,
template: &AttributeTemplate,
) -> Option<T> {
let mut parser = Self {
features,
tools: Vec::new(),
parse_only: None,
sess,
stage: Early { emit_errors },
};
let ast::AttrKind::Normal(normal_attr) = &attr.kind else {
panic!("parse_single called on a doc attr")
};
Expand All @@ -136,6 +139,43 @@ impl<'sess> AttributeParser<'sess, Early> {
let meta_parser = MetaItemParser::from_attr(normal_attr, &parts, &sess.psess, emit_errors)?;
let path = meta_parser.path();
let args = meta_parser.args();
Self::parse_single_args(
sess,
attr.span,
attr.style,
path.get_attribute_path(),
target_span,
target_node_id,
features,
emit_errors,
args,
parse_fn,
template,
)
}

/// This method is equivalent to `parse_single`, but parses arguments using `parse_fn` using manually created `args`.
/// This is useful when you want to parse other things than attributes using attribute parsers.
pub fn parse_single_args<T, I>(
sess: &'sess Session,
attr_span: Span,
attr_style: AttrStyle,
attr_path: AttrPath,
target_span: Span,
target_node_id: NodeId,
features: Option<&'sess Features>,
emit_errors: ShouldEmit,
args: &I,
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &I) -> Option<T>,
template: &AttributeTemplate,
) -> Option<T> {
let mut parser = Self {
features,
tools: Vec::new(),
parse_only: None,
sess,
stage: Early { emit_errors },
};
let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext {
shared: SharedContext {
cx: &mut parser,
Expand All @@ -145,10 +185,10 @@ impl<'sess> AttributeParser<'sess, Early> {
crate::lints::emit_attribute_lint(&lint, sess);
},
},
attr_span: attr.span,
attr_style: attr.style,
attr_span,
attr_style,
template,
attr_path: path.get_attribute_path(),
attr_path,
};
parse_fn(&mut cx, args)
}
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_attr_parsing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ mod session_diagnostics;
mod target_checking;
pub mod validate_attr;

pub use attributes::cfg::{CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg_attr};
pub use attributes::cfg::{
CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg, parse_cfg_attr,
};
pub use attributes::cfg_old::*;
pub use attributes::util::{is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version};
pub use context::{Early, Late, OmitDoc, ShouldEmit};
Expand Down
Loading
Loading