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