Skip to content

Commit 7113d58

Browse files
Port #[cfg_attr] to the new attribute parsing infrastructure
Signed-off-by: Jonathan Brouwer <jonathantbrouwer@gmail.com>
1 parent e0c190f commit 7113d58

File tree

6 files changed

+171
-82
lines changed

6 files changed

+171
-82
lines changed

compiler/rustc_attr_parsing/messages.ftl

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ attr_parsing_as_needed_compatibility =
44
attr_parsing_bundle_needs_static =
55
linking modifier `bundle` is only compatible with `static` linking kind
66
7+
attr_parsing_cfg_attr_bad_delim = wrong `cfg_attr` delimiters
8+
79
attr_parsing_cfg_predicate_identifier =
810
`cfg` predicate key must be an identifier
911
@@ -264,13 +266,3 @@ attr_parsing_unused_multiple =
264266
265267
attr_parsing_whole_archive_needs_static =
266268
linking modifier `whole-archive` is only compatible with `static` linking kind
267-
268-
attr_parsing_limit_invalid =
269-
`limit` must be a non-negative integer
270-
.label = {$error_str}
271-
272-
attr_parsing_cfg_attr_bad_delim = wrong `cfg_attr` delimiters
273-
274-
attr_parsing_malformed_cfg_attr = malformed `cfg_attr` attribute input
275-
.suggestion = missing condition and attribute
276-
.note = for more information, visit <https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute>

compiler/rustc_attr_parsing/src/attributes/cfg.rs

Lines changed: 86 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use rustc_ast::token::Delimiter;
22
use rustc_ast::tokenstream::DelimSpan;
3-
use rustc_ast::{AttrItem, Attribute, LitKind, MetaItemInner, NodeId, ast, token};
4-
use rustc_errors::PResult;
3+
use rustc_ast::{AttrItem, Attribute, CRATE_NODE_ID, LitKind, NodeId, ast, token};
4+
use rustc_errors::{Applicability, PResult};
55
use rustc_feature::{AttributeTemplate, Features, template};
6-
use rustc_hir::RustcVersion;
76
use rustc_hir::attrs::CfgEntry;
7+
use rustc_hir::{AttrPath, RustcVersion};
88
use rustc_parse::parser::{ForceCollect, Parser};
99
use rustc_parse::{exp, parse_in};
1010
use rustc_session::Session;
@@ -17,16 +17,24 @@ use thin_vec::ThinVec;
1717

1818
use crate::context::{AcceptContext, ShouldEmit, Stage};
1919
use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser};
20-
use crate::session_diagnostics::{CfgAttrBadDelim, MalformedCfgAttr, MetaBadDelimSugg};
20+
use crate::session_diagnostics::{
21+
AttributeParseError, AttributeParseErrorReason, CfgAttrBadDelim, MetaBadDelimSugg,
22+
};
2123
use crate::{
22-
CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, try_gate_cfg,
24+
AttributeParser, CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics,
25+
try_gate_cfg,
2326
};
2427

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

33+
const CFG_ATTR_TEMPLATE: AttributeTemplate = template!(
34+
List: &["predicate, attr1, attr2, ..."],
35+
"https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute"
36+
);
37+
3038
pub fn parse_cfg<'c, S: Stage>(
3139
cx: &'c mut AcceptContext<'_, '_, S>,
3240
args: &'c ArgParser<'_>,
@@ -76,9 +84,7 @@ pub(crate) fn parse_cfg_entry<S: Stage>(
7684
},
7785
a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
7886
let Some(name) = meta.path().word_sym() else {
79-
cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
80-
span: meta.path().span(),
81-
});
87+
cx.expected_identifier(meta.path().span());
8288
return None;
8389
};
8490
parse_name_value(name, meta.path().span(), a.name_value(), meta.span(), cx)?
@@ -87,7 +93,7 @@ pub(crate) fn parse_cfg_entry<S: Stage>(
8793
MetaItemOrLitParser::Lit(lit) => match lit.kind {
8894
LitKind::Bool(b) => CfgEntry::Bool(b, lit.span),
8995
_ => {
90-
cx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: lit.span });
96+
cx.expected_identifier(lit.span);
9197
return None;
9298
}
9399
},
@@ -155,9 +161,7 @@ fn parse_cfg_entry_target<S: Stage>(
155161

156162
// Then, parse it as a name-value item
157163
let Some(name) = sub_item.path().word_sym() else {
158-
cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
159-
span: sub_item.path().span(),
160-
});
164+
cx.expected_identifier(sub_item.path().span());
161165
return None;
162166
};
163167
let name = Symbol::intern(&format!("target_{name}"));
@@ -309,32 +313,51 @@ impl EvalConfigResult {
309313

310314
pub fn parse_cfg_attr(
311315
cfg_attr: &Attribute,
312-
psess: &ParseSess,
313-
) -> Option<(MetaItemInner, Vec<(AttrItem, Span)>)> {
314-
const CFG_ATTR_GRAMMAR_HELP: &str = "#[cfg_attr(condition, attribute, other_attribute, ...)]";
315-
const CFG_ATTR_NOTE_REF: &str = "for more information, visit \
316-
<https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute>";
317-
316+
sess: &Session,
317+
features: Option<&Features>,
318+
) -> Option<(CfgEntry, Vec<(AttrItem, Span)>)> {
318319
match cfg_attr.get_normal_item().args {
319320
ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, ref tokens })
320321
if !tokens.is_empty() =>
321322
{
322-
check_cfg_attr_bad_delim(psess, dspan, delim);
323-
match parse_in(psess, tokens.clone(), "`cfg_attr` input", |p| {
324-
parse_cfg_attr_internal(p)
323+
check_cfg_attr_bad_delim(&sess.psess, dspan, delim);
324+
match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| {
325+
parse_cfg_attr_internal(p, sess, features, cfg_attr)
325326
}) {
326327
Ok(r) => return Some(r),
327328
Err(e) => {
328-
e.with_help(format!("the valid syntax is `{CFG_ATTR_GRAMMAR_HELP}`"))
329-
.with_note(CFG_ATTR_NOTE_REF)
330-
.emit();
329+
let suggestions = CFG_ATTR_TEMPLATE.suggestions(cfg_attr.style, sym::cfg_attr);
330+
e.with_span_suggestions(
331+
cfg_attr.span,
332+
"must be of the form",
333+
suggestions,
334+
Applicability::HasPlaceholders,
335+
)
336+
.with_note(format!(
337+
"for more information, visit <{}>",
338+
CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs")
339+
))
340+
.emit();
331341
}
332342
}
333343
}
334344
_ => {
335-
psess
336-
.dcx()
337-
.emit_err(MalformedCfgAttr { span: cfg_attr.span, sugg: CFG_ATTR_GRAMMAR_HELP });
345+
let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) =
346+
cfg_attr.get_normal_item().args
347+
{
348+
(dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument)
349+
} else {
350+
(cfg_attr.span, AttributeParseErrorReason::ExpectedList)
351+
};
352+
353+
sess.dcx().emit_err(AttributeParseError {
354+
span,
355+
attr_span: cfg_attr.span,
356+
template: CFG_ATTR_TEMPLATE,
357+
attribute: AttrPath::from_ast(&cfg_attr.get_normal_item().path),
358+
reason,
359+
attr_style: cfg_attr.style,
360+
});
338361
}
339362
}
340363
None
@@ -353,8 +376,42 @@ fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter
353376
/// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
354377
fn parse_cfg_attr_internal<'a>(
355378
parser: &mut Parser<'a>,
356-
) -> PResult<'a, (ast::MetaItemInner, Vec<(ast::AttrItem, Span)>)> {
357-
let cfg_predicate = parser.parse_meta_item_inner()?;
379+
sess: &'a Session,
380+
features: Option<&Features>,
381+
attribute: &Attribute,
382+
) -> PResult<'a, (CfgEntry, Vec<(ast::AttrItem, Span)>)> {
383+
// Parse cfg predicate
384+
let pred_start = parser.token.span;
385+
let meta = MetaItemOrLitParser::parse_single(parser, ShouldEmit::ErrorsAndLints)?;
386+
let pred_span = pred_start.with_hi(parser.token.span.hi());
387+
388+
let cfg_predicate = AttributeParser::parse_single_args(
389+
sess,
390+
attribute.span,
391+
attribute.style,
392+
AttrPath {
393+
segments: attribute
394+
.ident_path()
395+
.expect("cfg_attr is not a doc comment")
396+
.into_boxed_slice(),
397+
span: attribute.span,
398+
},
399+
pred_span,
400+
CRATE_NODE_ID,
401+
features,
402+
ShouldEmit::ErrorsAndLints,
403+
&meta,
404+
parse_cfg_entry,
405+
&CFG_ATTR_TEMPLATE,
406+
)
407+
.ok_or_else(|| {
408+
let mut diag = sess.dcx().struct_err(
409+
"cfg_entry parsing failing with `ShouldEmit::ErrorsAndLints` should emit a error.",
410+
);
411+
diag.downgrade_to_delayed_bug();
412+
diag
413+
})?;
414+
358415
parser.expect(exp!(Comma))?;
359416

360417
// Presumably, the majority of the time there will only be one attr.

compiler/rustc_attr_parsing/src/interface.rs

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::borrow::Cow;
22

33
use rustc_ast as ast;
4-
use rustc_ast::NodeId;
4+
use rustc_ast::{AttrStyle, NodeId};
55
use rustc_errors::DiagCtxtHandle;
66
use rustc_feature::{AttributeTemplate, Features};
77
use rustc_hir::attrs::AttributeKind;
@@ -62,7 +62,8 @@ impl<'sess> AttributeParser<'sess, Early> {
6262
)
6363
}
6464

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

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

122+
/// This method parses a single attribute, using `parse_fn`.
123+
/// This is useful if you already know what exact attribute this is, and want to parse it.
114124
pub fn parse_single<T>(
115125
sess: &'sess Session,
116126
attr: &ast::Attribute,
@@ -121,13 +131,6 @@ impl<'sess> AttributeParser<'sess, Early> {
121131
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser<'_>) -> Option<T>,
122132
template: &AttributeTemplate,
123133
) -> Option<T> {
124-
let mut parser = Self {
125-
features,
126-
tools: Vec::new(),
127-
parse_only: None,
128-
sess,
129-
stage: Early { emit_errors },
130-
};
131134
let ast::AttrKind::Normal(normal_attr) = &attr.kind else {
132135
panic!("parse_single called on a doc attr")
133136
};
@@ -136,6 +139,43 @@ impl<'sess> AttributeParser<'sess, Early> {
136139
let meta_parser = MetaItemParser::from_attr(normal_attr, &parts, &sess.psess, emit_errors)?;
137140
let path = meta_parser.path();
138141
let args = meta_parser.args();
142+
Self::parse_single_args(
143+
sess,
144+
attr.span,
145+
attr.style,
146+
path.get_attribute_path(),
147+
target_span,
148+
target_node_id,
149+
features,
150+
emit_errors,
151+
args,
152+
parse_fn,
153+
template,
154+
)
155+
}
156+
157+
/// This method is equivalent to `parse_single`, but parses arguments using `parse_fn` using manually created `args`.
158+
/// This is useful when you want to parse other things than attributes using attribute parsers.
159+
pub fn parse_single_args<T, I>(
160+
sess: &'sess Session,
161+
attr_span: Span,
162+
attr_style: AttrStyle,
163+
attr_path: AttrPath,
164+
target_span: Span,
165+
target_node_id: NodeId,
166+
features: Option<&'sess Features>,
167+
emit_errors: ShouldEmit,
168+
args: &I,
169+
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &I) -> Option<T>,
170+
template: &AttributeTemplate,
171+
) -> Option<T> {
172+
let mut parser = Self {
173+
features,
174+
tools: Vec::new(),
175+
parse_only: None,
176+
sess,
177+
stage: Early { emit_errors },
178+
};
139179
let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext {
140180
shared: SharedContext {
141181
cx: &mut parser,
@@ -145,10 +185,10 @@ impl<'sess> AttributeParser<'sess, Early> {
145185
crate::lints::emit_attribute_lint(&lint, sess);
146186
},
147187
},
148-
attr_span: attr.span,
149-
attr_style: attr.style,
188+
attr_span,
189+
attr_style,
150190
template,
151-
attr_path: path.get_attribute_path(),
191+
attr_path,
152192
};
153193
parse_fn(&mut cx, args)
154194
}

compiler/rustc_attr_parsing/src/parser.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::fmt::{Debug, Display};
88

99
use rustc_ast::token::{self, Delimiter, MetaVarKind};
1010
use rustc_ast::tokenstream::TokenStream;
11-
use rustc_ast::{AttrArgs, DelimArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path};
11+
use rustc_ast::{AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path};
1212
use rustc_ast_pretty::pprust;
1313
use rustc_errors::{Diag, PResult};
1414
use rustc_hir::{self as hir, AttrPath};
@@ -124,7 +124,11 @@ impl<'a> ArgParser<'a> {
124124
return None;
125125
}
126126

127-
Self::List(MetaItemListParser::new(args, psess, should_emit)?)
127+
Self::List(
128+
MetaItemListParser::new(&args.tokens, args.dspan.entire(), psess, should_emit)
129+
.map_err(|e| should_emit.emit_err(e))
130+
.ok()?,
131+
)
128132
}
129133
AttrArgs::Eq { eq_span, expr } => Self::NameValue(NameValueParser {
130134
eq_span: *eq_span,
@@ -186,7 +190,15 @@ pub enum MetaItemOrLitParser<'a> {
186190
Err(Span, ErrorGuaranteed),
187191
}
188192

189-
impl<'a> MetaItemOrLitParser<'a> {
193+
impl<'sess> MetaItemOrLitParser<'sess> {
194+
pub fn parse_single(
195+
parser: &mut Parser<'sess>,
196+
should_emit: ShouldEmit,
197+
) -> PResult<'sess, MetaItemOrLitParser<'static>> {
198+
let mut this = MetaItemListParserContext { parser, should_emit };
199+
this.parse_meta_item_inner()
200+
}
201+
190202
pub fn span(&self) -> Span {
191203
match self {
192204
MetaItemOrLitParser::MetaItemParser(generic_meta_item_parser) => {
@@ -204,7 +216,7 @@ impl<'a> MetaItemOrLitParser<'a> {
204216
}
205217
}
206218

207-
pub fn meta_item(&self) -> Option<&MetaItemParser<'a>> {
219+
pub fn meta_item(&self) -> Option<&MetaItemParser<'sess>> {
208220
match self {
209221
MetaItemOrLitParser::MetaItemParser(parser) => Some(parser),
210222
_ => None,
@@ -542,23 +554,13 @@ pub struct MetaItemListParser<'a> {
542554
}
543555

544556
impl<'a> MetaItemListParser<'a> {
545-
fn new<'sess>(
546-
delim: &'a DelimArgs,
557+
pub(crate) fn new<'sess>(
558+
tokens: &'a TokenStream,
559+
span: Span,
547560
psess: &'sess ParseSess,
548561
should_emit: ShouldEmit,
549-
) -> Option<Self> {
550-
match MetaItemListParserContext::parse(
551-
delim.tokens.clone(),
552-
psess,
553-
delim.dspan.entire(),
554-
should_emit,
555-
) {
556-
Ok(s) => Some(s),
557-
Err(e) => {
558-
should_emit.emit_err(e);
559-
None
560-
}
561-
}
562+
) -> Result<Self, Diag<'sess>> {
563+
MetaItemListParserContext::parse(tokens.clone(), psess, span, should_emit)
562564
}
563565

564566
/// Lets you pick and choose as what you want to parse each element in the list

0 commit comments

Comments
 (0)