rustc_attr_parsing/attributes/
cfg.rs1use rustc_ast::token::Delimiter;
2use rustc_ast::tokenstream::DelimSpan;
3use rustc_ast::{AttrItem, Attribute, CRATE_NODE_ID, LitKind, NodeId, ast, token};
4use rustc_errors::{Applicability, PResult};
5use rustc_feature::{AttrSuggestionStyle, AttributeTemplate, Features, template};
6use rustc_hir::attrs::CfgEntry;
7use rustc_hir::{AttrPath, RustcVersion};
8use rustc_parse::parser::{ForceCollect, Parser};
9use rustc_parse::{exp, parse_in};
10use rustc_session::Session;
11use rustc_session::config::ExpectedValues;
12use rustc_session::lint::BuiltinLintDiag;
13use rustc_session::lint::builtin::UNEXPECTED_CFGS;
14use rustc_session::parse::{ParseSess, feature_err};
15use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};
16use thin_vec::ThinVec;
17
18use crate::context::{AcceptContext, ShouldEmit, Stage};
19use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser};
20use crate::session_diagnostics::{
21 AttributeParseError, AttributeParseErrorReason, CfgAttrBadDelim, MetaBadDelimSugg,
22 ParsedDescription,
23};
24use crate::{
25 AttributeParser, CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics,
26 try_gate_cfg,
27};
28
29pub const CFG_TEMPLATE: AttributeTemplate = template!(
30 List: &["predicate"],
31 "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute"
32);
33
34const CFG_ATTR_TEMPLATE: AttributeTemplate = template!(
35 List: &["predicate, attr1, attr2, ..."],
36 "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute"
37);
38
39pub fn parse_cfg<'c, S: Stage>(
40 cx: &'c mut AcceptContext<'_, '_, S>,
41 args: &'c ArgParser<'_>,
42) -> Option<CfgEntry> {
43 let ArgParser::List(list) = args else {
44 cx.expected_list(cx.attr_span);
45 return None;
46 };
47 let Some(single) = list.single() else {
48 cx.expected_single_argument(list.span);
49 return None;
50 };
51 parse_cfg_entry(cx, single).ok()
52}
53
54pub fn parse_cfg_entry<S: Stage>(
55 cx: &mut AcceptContext<'_, '_, S>,
56 item: &MetaItemOrLitParser<'_>,
57) -> Result<CfgEntry, ErrorGuaranteed> {
58 Ok(match item {
59 MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
60 ArgParser::List(list) => match meta.path().word_sym() {
61 Some(sym::not) => {
62 let Some(single) = list.single() else {
63 return Err(cx.expected_single_argument(list.span));
64 };
65 CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span)
66 }
67 Some(sym::any) => CfgEntry::Any(
68 list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
69 list.span,
70 ),
71 Some(sym::all) => CfgEntry::All(
72 list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
73 list.span,
74 ),
75 Some(sym::target) => parse_cfg_entry_target(cx, list, meta.span())?,
76 Some(sym::version) => parse_cfg_entry_version(cx, list, meta.span())?,
77 _ => {
78 return Err(cx.emit_err(session_diagnostics::InvalidPredicate {
79 span: meta.span(),
80 predicate: meta.path().to_string(),
81 }));
82 }
83 },
84 a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
85 let Some(name) = meta.path().word_sym().filter(|s| !s.is_path_segment_keyword())
86 else {
87 return Err(cx.expected_identifier(meta.path().span()));
88 };
89 parse_name_value(name, meta.path().span(), a.name_value(), meta.span(), cx)?
90 }
91 },
92 MetaItemOrLitParser::Lit(lit) => match lit.kind {
93 LitKind::Bool(b) => CfgEntry::Bool(b, lit.span),
94 _ => return Err(cx.expected_identifier(lit.span)),
95 },
96 MetaItemOrLitParser::Err(_, err) => return Err(*err),
97 })
98}
99
100fn parse_cfg_entry_version<S: Stage>(
101 cx: &mut AcceptContext<'_, '_, S>,
102 list: &MetaItemListParser<'_>,
103 meta_span: Span,
104) -> Result<CfgEntry, ErrorGuaranteed> {
105 try_gate_cfg(sym::version, meta_span, cx.sess(), cx.features_option());
106 let Some(version) = list.single() else {
107 return Err(
108 cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span })
109 );
110 };
111 let Some(version_lit) = version.lit() else {
112 return Err(
113 cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version.span() })
114 );
115 };
116 let Some(version_str) = version_lit.value_str() else {
117 return Err(
118 cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version_lit.span })
119 );
120 };
121
122 let min_version = parse_version(version_str).or_else(|| {
123 cx.sess()
124 .dcx()
125 .emit_warn(session_diagnostics::UnknownVersionLiteral { span: version_lit.span });
126 None
127 });
128
129 Ok(CfgEntry::Version(min_version, list.span))
130}
131
132fn parse_cfg_entry_target<S: Stage>(
133 cx: &mut AcceptContext<'_, '_, S>,
134 list: &MetaItemListParser<'_>,
135 meta_span: Span,
136) -> Result<CfgEntry, ErrorGuaranteed> {
137 if let Some(features) = cx.features_option()
138 && !features.cfg_target_compact()
139 {
140 feature_err(
141 cx.sess(),
142 sym::cfg_target_compact,
143 meta_span,
144 fluent_generated::attr_parsing_unstable_cfg_target_compact,
145 )
146 .emit();
147 }
148
149 let mut result = ThinVec::new();
150 for sub_item in list.mixed() {
151 let Some(sub_item) = sub_item.meta_item() else {
153 cx.expected_name_value(sub_item.span(), None);
154 continue;
155 };
156 let Some(nv) = sub_item.args().name_value() else {
157 cx.expected_name_value(sub_item.span(), None);
158 continue;
159 };
160
161 let Some(name) = sub_item.path().word_sym().filter(|s| !s.is_path_segment_keyword()) else {
163 return Err(cx.expected_identifier(sub_item.path().span()));
164 };
165 let name = Symbol::intern(&format!("target_{name}"));
166 if let Ok(cfg) =
167 parse_name_value(name, sub_item.path().span(), Some(nv), sub_item.span(), cx)
168 {
169 result.push(cfg);
170 }
171 }
172 Ok(CfgEntry::All(result, list.span))
173}
174
175fn parse_name_value<S: Stage>(
176 name: Symbol,
177 name_span: Span,
178 value: Option<&NameValueParser>,
179 span: Span,
180 cx: &mut AcceptContext<'_, '_, S>,
181) -> Result<CfgEntry, ErrorGuaranteed> {
182 try_gate_cfg(name, span, cx.sess(), cx.features_option());
183
184 let value = match value {
185 None => None,
186 Some(value) => {
187 let Some(value_str) = value.value_as_str() else {
188 return Err(
189 cx.expected_string_literal(value.value_span, Some(value.value_as_lit()))
190 );
191 };
192 Some((value_str, value.value_span))
193 }
194 };
195
196 Ok(CfgEntry::NameValue { name, name_span, value, span })
197}
198
199pub fn eval_config_entry(
200 sess: &Session,
201 cfg_entry: &CfgEntry,
202 id: NodeId,
203 emit_lints: ShouldEmit,
204) -> EvalConfigResult {
205 match cfg_entry {
206 CfgEntry::All(subs, ..) => {
207 let mut all = None;
208 for sub in subs {
209 let res = eval_config_entry(sess, sub, id, emit_lints);
210 if !res.as_bool() {
212 all.get_or_insert(res);
213 }
214 }
215 all.unwrap_or_else(|| EvalConfigResult::True)
216 }
217 CfgEntry::Any(subs, span) => {
218 let mut any = None;
219 for sub in subs {
220 let res = eval_config_entry(sess, sub, id, emit_lints);
221 if res.as_bool() {
223 any.get_or_insert(res);
224 }
225 }
226 any.unwrap_or_else(|| EvalConfigResult::False {
227 reason: cfg_entry.clone(),
228 reason_span: *span,
229 })
230 }
231 CfgEntry::Not(sub, span) => {
232 if eval_config_entry(sess, sub, id, emit_lints).as_bool() {
233 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
234 } else {
235 EvalConfigResult::True
236 }
237 }
238 CfgEntry::Bool(b, span) => {
239 if *b {
240 EvalConfigResult::True
241 } else {
242 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
243 }
244 }
245 CfgEntry::NameValue { name, name_span, value, span } => {
246 if let ShouldEmit::ErrorsAndLints = emit_lints {
247 match sess.psess.check_config.expecteds.get(name) {
248 Some(ExpectedValues::Some(values))
249 if !values.contains(&value.map(|(v, _)| v)) =>
250 {
251 id.emit_span_lint(
252 sess,
253 UNEXPECTED_CFGS,
254 *span,
255 BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value),
256 );
257 }
258 None if sess.psess.check_config.exhaustive_names => {
259 id.emit_span_lint(
260 sess,
261 UNEXPECTED_CFGS,
262 *span,
263 BuiltinLintDiag::UnexpectedCfgName((*name, *name_span), *value),
264 );
265 }
266 _ => { }
267 }
268 }
269
270 if sess.psess.config.contains(&(*name, value.map(|(v, _)| v))) {
271 EvalConfigResult::True
272 } else {
273 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
274 }
275 }
276 CfgEntry::Version(min_version, version_span) => {
277 let Some(min_version) = min_version else {
278 return EvalConfigResult::False {
279 reason: cfg_entry.clone(),
280 reason_span: *version_span,
281 };
282 };
283 let min_version_ok = if sess.psess.assume_incomplete_release {
285 RustcVersion::current_overridable() > *min_version
286 } else {
287 RustcVersion::current_overridable() >= *min_version
288 };
289 if min_version_ok {
290 EvalConfigResult::True
291 } else {
292 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *version_span }
293 }
294 }
295 }
296}
297
298pub enum EvalConfigResult {
299 True,
300 False { reason: CfgEntry, reason_span: Span },
301}
302
303impl EvalConfigResult {
304 pub fn as_bool(&self) -> bool {
305 match self {
306 EvalConfigResult::True => true,
307 EvalConfigResult::False { .. } => false,
308 }
309 }
310}
311
312pub fn parse_cfg_attr(
313 cfg_attr: &Attribute,
314 sess: &Session,
315 features: Option<&Features>,
316) -> Option<(CfgEntry, Vec<(AttrItem, Span)>)> {
317 match cfg_attr.get_normal_item().args {
318 ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, ref tokens })
319 if !tokens.is_empty() =>
320 {
321 check_cfg_attr_bad_delim(&sess.psess, dspan, delim);
322 match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| {
323 parse_cfg_attr_internal(p, sess, features, cfg_attr)
324 }) {
325 Ok(r) => return Some(r),
326 Err(e) => {
327 let suggestions = CFG_ATTR_TEMPLATE
328 .suggestions(AttrSuggestionStyle::Attribute(cfg_attr.style), sym::cfg_attr);
329 e.with_span_suggestions(
330 cfg_attr.span,
331 "must be of the form",
332 suggestions,
333 Applicability::HasPlaceholders,
334 )
335 .with_note(format!(
336 "for more information, visit <{}>",
337 CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs")
338 ))
339 .emit();
340 }
341 }
342 }
343 _ => {
344 let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) =
345 cfg_attr.get_normal_item().args
346 {
347 (dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument)
348 } else {
349 (cfg_attr.span, AttributeParseErrorReason::ExpectedList)
350 };
351
352 sess.dcx().emit_err(AttributeParseError {
353 span,
354 attr_span: cfg_attr.span,
355 template: CFG_ATTR_TEMPLATE,
356 path: AttrPath::from_ast(&cfg_attr.get_normal_item().path),
357 description: ParsedDescription::Attribute,
358 reason,
359 suggestions: CFG_ATTR_TEMPLATE
360 .suggestions(AttrSuggestionStyle::Attribute(cfg_attr.style), sym::cfg_attr),
361 });
362 }
363 }
364 None
365}
366
367fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
368 if let Delimiter::Parenthesis = delim {
369 return;
370 }
371 psess.dcx().emit_err(CfgAttrBadDelim {
372 span: span.entire(),
373 sugg: MetaBadDelimSugg { open: span.open, close: span.close },
374 });
375}
376
377fn parse_cfg_attr_internal<'a>(
379 parser: &mut Parser<'a>,
380 sess: &'a Session,
381 features: Option<&Features>,
382 attribute: &Attribute,
383) -> PResult<'a, (CfgEntry, Vec<(ast::AttrItem, Span)>)> {
384 let pred_start = parser.token.span;
386 let meta = MetaItemOrLitParser::parse_single(parser, ShouldEmit::ErrorsAndLints)?;
387 let pred_span = pred_start.with_hi(parser.token.span.hi());
388
389 let cfg_predicate = AttributeParser::parse_single_args(
390 sess,
391 attribute.span,
392 attribute.get_normal_item().span(),
393 attribute.style,
394 AttrPath {
395 segments: attribute
396 .ident_path()
397 .expect("cfg_attr is not a doc comment")
398 .into_boxed_slice(),
399 span: attribute.span,
400 },
401 ParsedDescription::Attribute,
402 pred_span,
403 CRATE_NODE_ID,
404 features,
405 ShouldEmit::ErrorsAndLints,
406 &meta,
407 parse_cfg_entry,
408 &CFG_ATTR_TEMPLATE,
409 )
410 .map_err(|_err: ErrorGuaranteed| {
411 let mut diag = sess.dcx().struct_err(
413 "cfg_entry parsing failing with `ShouldEmit::ErrorsAndLints` should emit a error.",
414 );
415 diag.downgrade_to_delayed_bug();
416 diag
417 })?;
418
419 parser.expect(exp!(Comma))?;
420
421 let mut expanded_attrs = Vec::with_capacity(1);
423 while parser.token != token::Eof {
424 let lo = parser.token.span;
425 let item = parser.parse_attr_item(ForceCollect::Yes)?;
426 expanded_attrs.push((item, lo.to(parser.prev_token.span)));
427 if !parser.eat(exp!(Comma)) {
428 break;
429 }
430 }
431
432 Ok((cfg_predicate, expanded_attrs))
433}