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