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