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