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_lint_defs::builtin::UNEXPECTED_CFGS;
11use rustc_parse::parser::{ForceCollect, Parser, Recovery};
12use rustc_parse::{exp, parse_in};
13use rustc_session::Session;
14use rustc_session::config::ExpectedValues;
15use rustc_session::diagnostics::feature_err;
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, _) => {
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() }
248 }
249 CfgEntry::Not(sub, _) => {
250 if eval_config_entry(sess, sub).as_bool() {
251 EvalConfigResult::False { reason: cfg_entry.clone() }
252 } else {
253 EvalConfigResult::True
254 }
255 }
256 CfgEntry::Bool(b, _) => {
257 if *b {
258 EvalConfigResult::True
259 } else {
260 EvalConfigResult::False { reason: cfg_entry.clone() }
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() }
268 }
269 }
270 CfgEntry::Version(min_version, _) => {
271 let Some(min_version) = min_version else {
272 return EvalConfigResult::False { reason: cfg_entry.clone() };
273 };
274 let min_version_ok = if sess.opts.unstable_opts.assume_incomplete_release {
276 RustcVersion::current_overridable() > *min_version
277 } else {
278 RustcVersion::current_overridable() >= *min_version
279 };
280 if min_version_ok {
281 EvalConfigResult::True
282 } else {
283 EvalConfigResult::False { reason: cfg_entry.clone() }
284 }
285 }
286 }
287}
288
289pub enum EvalConfigResult {
290 True,
291 False { reason: CfgEntry },
292}
293
294impl EvalConfigResult {
295 pub fn as_bool(&self) -> bool {
296 match self {
297 EvalConfigResult::True => true,
298 EvalConfigResult::False { .. } => false,
299 }
300 }
301}
302
303pub fn parse_cfg_attr(
304 cfg_attr: &Attribute,
305 sess: &Session,
306 features: Option<&Features>,
307 lint_node_id: ast::NodeId,
308) -> Option<(CfgEntry, Vec<(WithTokens<AttrItem>, Span)>)> {
309 match &cfg_attr.get_normal_item().args {
310 ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, tokens }) if !tokens.is_empty() => {
311 check_cfg_attr_bad_delim(&sess.psess, *dspan, *delim);
312 match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| {
313 parse_cfg_attr_internal(p, sess, features, lint_node_id, cfg_attr)
314 }) {
315 Ok(r) => return Some(r),
316 Err(e) => {
317 let suggestions = CFG_ATTR_TEMPLATE.suggestions(
318 ParsedDescription::Attribute,
319 cfg_attr.get_normal_item().unsafety,
320 sym::cfg_attr,
321 );
322 e.with_span_suggestions(
323 cfg_attr.get_normal_item().span,
324 "must be of the form",
325 suggestions,
326 Applicability::HasPlaceholders,
327 )
328 .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!(
329 "for more information, visit <{}>",
330 CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs")
331 ))
332 .emit();
333 }
334 }
335 }
336 _ => {
337 let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) =
338 cfg_attr.get_normal_item().args
339 {
340 (dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument)
341 } else {
342 (cfg_attr.get_normal_item().span, AttributeParseErrorReason::ExpectedList)
343 };
344
345 sess.dcx().emit_err(AttributeParseError {
346 span,
347 inner_span: cfg_attr.get_normal_item().span,
348 template: CFG_ATTR_TEMPLATE,
349 path: AttrPath::from_ast(&cfg_attr.get_normal_item().path, identity),
350 description: ParsedDescription::Attribute,
351 reason,
352 suggestions: diagnostics::AttributeParseErrorSuggestions::CreatedByTemplate(
353 CFG_ATTR_TEMPLATE.suggestions(
354 ParsedDescription::Attribute,
355 cfg_attr.get_normal_item().unsafety,
356 sym::cfg_attr,
357 ),
358 ),
359 });
360 }
361 }
362 None
363}
364
365fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
366 if let Delimiter::Parenthesis = delim {
367 return;
368 }
369 psess.dcx().emit_err(CfgAttrBadDelim {
370 span: span.entire(),
371 sugg: MetaBadDelimSugg { open: span.open, close: span.close },
372 });
373}
374
375fn parse_cfg_attr_internal<'a>(
377 parser: &mut Parser<'a>,
378 sess: &'a Session,
379 features: Option<&Features>,
380 lint_node_id: ast::NodeId,
381 attribute: &Attribute,
382) -> PResult<'a, (CfgEntry, Vec<(WithTokens<ast::AttrItem>, Span)>)> {
383 let pred_start = parser.token.span;
385 let meta = MetaItemOrLitParser::parse_single(
386 parser,
387 ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
388 AllowExprMetavar::Yes,
389 )?;
390 let pred_span = pred_start.with_hi(parser.token.span.hi());
391
392 let cfg_predicate = AttributeParser::parse_single_args(
393 sess,
394 attribute.span,
395 attribute.get_normal_item().span,
396 attribute.style,
397 AttrPath { segments: attribute.path().into_boxed_slice(), span: attribute.span },
398 Some(attribute.get_normal_item().unsafety),
399 AttributeSafety::Normal,
400 ParsedDescription::Attribute,
401 pred_span,
402 lint_node_id,
403 Target::Crate,
404 features,
405 ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
406 &meta,
407 parse_cfg_entry,
408 &CFG_ATTR_TEMPLATE,
409 )
410 .map_err(|_err: ErrorGuaranteed| {
411 let mut diag = sess.dcx().struct_err(
413 "cfg_entry parsing failing with `ShouldEmit::ErrorsAndLints` should emit a error.",
414 );
415 diag.downgrade_to_delayed_bug();
416 diag
417 })?;
418
419 parser.expect(::rustc_parse::parser::token_type::ExpTokenPair {
tok: rustc_ast::token::Comma,
token_type: ::rustc_parse::parser::token_type::TokenType::Comma,
}exp!(Comma))?;
420
421 let mut expanded_attrs = Vec::with_capacity(1);
423 while parser.token != token::Eof {
424 let lo = parser.token.span;
425 let item = parser.parse_attr_item(ForceCollect::Yes)?;
426 expanded_attrs.push((item, lo.to(parser.prev_token.span)));
427 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)) {
428 break;
429 }
430 }
431
432 Ok((cfg_predicate, expanded_attrs))
433}
434
435fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
436 let gate = find_gated_cfg(name);
437 if let (Some(feats), Some(gated_cfg)) = (features, gate) {
438 gate_cfg(gated_cfg, span, sess, feats);
439 }
440}
441
442fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
443 let (cfg, feature, has_feature) = gated_cfg;
444 if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
445 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");
446 feature_err(sess, *feature, cfg_span, explain).emit();
447 }
448}