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