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