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