rustc_attr_parsing/attributes/
cfg.rs1use 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};
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 = template!(
31 List: &["predicate"],
32 "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute"
33);
34
35const CFG_ATTR_TEMPLATE: AttributeTemplate = 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 MetaItemOrLitParser::Err(_, err) => return Err(*err),
98 })
99}
100
101fn parse_cfg_entry_version<S: Stage>(
102 cx: &mut AcceptContext<'_, '_, S>,
103 list: &MetaItemListParser,
104 meta_span: Span,
105) -> Result<CfgEntry, ErrorGuaranteed> {
106 try_gate_cfg(sym::version, meta_span, cx.sess(), cx.features_option());
107 let Some(version) = list.single() else {
108 return Err(
109 cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span })
110 );
111 };
112 let Some(version_lit) = version.lit() else {
113 return Err(
114 cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version.span() })
115 );
116 };
117 let Some(version_str) = version_lit.value_str() else {
118 return Err(
119 cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version_lit.span })
120 );
121 };
122
123 let min_version = parse_version(version_str).or_else(|| {
124 cx.sess()
125 .dcx()
126 .emit_warn(session_diagnostics::UnknownVersionLiteral { span: version_lit.span });
127 None
128 });
129
130 Ok(CfgEntry::Version(min_version, list.span))
131}
132
133fn parse_cfg_entry_target<S: Stage>(
134 cx: &mut AcceptContext<'_, '_, S>,
135 list: &MetaItemListParser,
136 meta_span: Span,
137) -> Result<CfgEntry, ErrorGuaranteed> {
138 if let Some(features) = cx.features_option()
139 && !features.cfg_target_compact()
140 {
141 feature_err(
142 cx.sess(),
143 sym::cfg_target_compact,
144 meta_span,
145 fluent_generated::attr_parsing_unstable_cfg_target_compact,
146 )
147 .emit();
148 }
149
150 let mut result = ThinVec::new();
151 for sub_item in list.mixed() {
152 let Some(sub_item) = sub_item.meta_item() else {
154 cx.expected_name_value(sub_item.span(), None);
155 continue;
156 };
157 let Some(nv) = sub_item.args().name_value() else {
158 cx.expected_name_value(sub_item.span(), None);
159 continue;
160 };
161
162 let Some(name) = sub_item.path().word_sym().filter(|s| !s.is_path_segment_keyword()) else {
164 return Err(cx.expected_identifier(sub_item.path().span()));
165 };
166 let name = Symbol::intern(&format!("target_{name}"));
167 if let Ok(cfg) =
168 parse_name_value(name, sub_item.path().span(), Some(nv), sub_item.span(), cx)
169 {
170 result.push(cfg);
171 }
172 }
173 Ok(CfgEntry::All(result, list.span))
174}
175
176pub(crate) fn parse_name_value<S: Stage>(
177 name: Symbol,
178 name_span: Span,
179 value: Option<&NameValueParser>,
180 span: Span,
181 cx: &mut AcceptContext<'_, '_, S>,
182) -> Result<CfgEntry, ErrorGuaranteed> {
183 try_gate_cfg(name, span, cx.sess(), cx.features_option());
184
185 let value = match value {
186 None => None,
187 Some(value) => {
188 let Some(value_str) = value.value_as_str() else {
189 return Err(
190 cx.expected_string_literal(value.value_span, Some(value.value_as_lit()))
191 );
192 };
193 Some((value_str, value.value_span))
194 }
195 };
196
197 match cx.sess.psess.check_config.expecteds.get(&name) {
198 Some(ExpectedValues::Some(values)) if !values.contains(&value.map(|(v, _)| v)) => cx
199 .emit_lint(
200 UNEXPECTED_CFGS,
201 AttributeLintKind::UnexpectedCfgValue((name, name_span), value),
202 span,
203 ),
204 None if cx.sess.psess.check_config.exhaustive_names => cx.emit_lint(
205 UNEXPECTED_CFGS,
206 AttributeLintKind::UnexpectedCfgName((name, name_span), value),
207 span,
208 ),
209 _ => { }
210 }
211
212 Ok(CfgEntry::NameValue { name, value: value.map(|(v, _)| v), span })
213}
214
215pub fn eval_config_entry(sess: &Session, cfg_entry: &CfgEntry) -> EvalConfigResult {
216 match cfg_entry {
217 CfgEntry::All(subs, ..) => {
218 for sub in subs {
219 let res = eval_config_entry(sess, sub);
220 if !res.as_bool() {
221 return res;
222 }
223 }
224 EvalConfigResult::True
225 }
226 CfgEntry::Any(subs, span) => {
227 for sub in subs {
228 let res = eval_config_entry(sess, sub);
229 if res.as_bool() {
230 return res;
231 }
232 }
233 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
234 }
235 CfgEntry::Not(sub, span) => {
236 if eval_config_entry(sess, sub).as_bool() {
237 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
238 } else {
239 EvalConfigResult::True
240 }
241 }
242 CfgEntry::Bool(b, span) => {
243 if *b {
244 EvalConfigResult::True
245 } else {
246 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
247 }
248 }
249 CfgEntry::NameValue { name, value, span } => {
250 if sess.psess.config.contains(&(*name, *value)) {
251 EvalConfigResult::True
252 } else {
253 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
254 }
255 }
256 CfgEntry::Version(min_version, version_span) => {
257 let Some(min_version) = min_version else {
258 return EvalConfigResult::False {
259 reason: cfg_entry.clone(),
260 reason_span: *version_span,
261 };
262 };
263 let min_version_ok = if sess.psess.assume_incomplete_release {
265 RustcVersion::current_overridable() > *min_version
266 } else {
267 RustcVersion::current_overridable() >= *min_version
268 };
269 if min_version_ok {
270 EvalConfigResult::True
271 } else {
272 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *version_span }
273 }
274 }
275 }
276}
277
278pub enum EvalConfigResult {
279 True,
280 False { reason: CfgEntry, reason_span: Span },
281}
282
283impl EvalConfigResult {
284 pub fn as_bool(&self) -> bool {
285 match self {
286 EvalConfigResult::True => true,
287 EvalConfigResult::False { .. } => false,
288 }
289 }
290}
291
292pub fn parse_cfg_attr(
293 cfg_attr: &Attribute,
294 sess: &Session,
295 features: Option<&Features>,
296) -> Option<(CfgEntry, Vec<(AttrItem, Span)>)> {
297 match cfg_attr.get_normal_item().args {
298 ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, ref tokens })
299 if !tokens.is_empty() =>
300 {
301 check_cfg_attr_bad_delim(&sess.psess, dspan, delim);
302 match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| {
303 parse_cfg_attr_internal(p, sess, features, cfg_attr)
304 }) {
305 Ok(r) => return Some(r),
306 Err(e) => {
307 let suggestions = CFG_ATTR_TEMPLATE
308 .suggestions(AttrSuggestionStyle::Attribute(cfg_attr.style), sym::cfg_attr);
309 e.with_span_suggestions(
310 cfg_attr.span,
311 "must be of the form",
312 suggestions,
313 Applicability::HasPlaceholders,
314 )
315 .with_note(format!(
316 "for more information, visit <{}>",
317 CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs")
318 ))
319 .emit();
320 }
321 }
322 }
323 _ => {
324 let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) =
325 cfg_attr.get_normal_item().args
326 {
327 (dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument)
328 } else {
329 (cfg_attr.span, AttributeParseErrorReason::ExpectedList)
330 };
331
332 sess.dcx().emit_err(AttributeParseError {
333 span,
334 attr_span: cfg_attr.span,
335 template: CFG_ATTR_TEMPLATE,
336 path: AttrPath::from_ast(&cfg_attr.get_normal_item().path, identity),
337 description: ParsedDescription::Attribute,
338 reason,
339 suggestions: CFG_ATTR_TEMPLATE
340 .suggestions(AttrSuggestionStyle::Attribute(cfg_attr.style), sym::cfg_attr),
341 });
342 }
343 }
344 None
345}
346
347fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
348 if let Delimiter::Parenthesis = delim {
349 return;
350 }
351 psess.dcx().emit_err(CfgAttrBadDelim {
352 span: span.entire(),
353 sugg: MetaBadDelimSugg { open: span.open, close: span.close },
354 });
355}
356
357fn parse_cfg_attr_internal<'a>(
359 parser: &mut Parser<'a>,
360 sess: &'a Session,
361 features: Option<&Features>,
362 attribute: &Attribute,
363) -> PResult<'a, (CfgEntry, Vec<(ast::AttrItem, Span)>)> {
364 let pred_start = parser.token.span;
366 let meta = MetaItemOrLitParser::parse_single(parser, ShouldEmit::ErrorsAndLints)?;
367 let pred_span = pred_start.with_hi(parser.token.span.hi());
368
369 let cfg_predicate = AttributeParser::parse_single_args(
370 sess,
371 attribute.span,
372 attribute.get_normal_item().span(),
373 attribute.style,
374 AttrPath {
375 segments: attribute
376 .ident_path()
377 .expect("cfg_attr is not a doc comment")
378 .into_boxed_slice(),
379 span: attribute.span,
380 },
381 Some(attribute.get_normal_item().unsafety),
382 ParsedDescription::Attribute,
383 pred_span,
384 CRATE_NODE_ID,
385 features,
386 ShouldEmit::ErrorsAndLints,
387 &meta,
388 parse_cfg_entry,
389 &CFG_ATTR_TEMPLATE,
390 )
391 .map_err(|_err: ErrorGuaranteed| {
392 let mut diag = sess.dcx().struct_err(
394 "cfg_entry parsing failing with `ShouldEmit::ErrorsAndLints` should emit a error.",
395 );
396 diag.downgrade_to_delayed_bug();
397 diag
398 })?;
399
400 parser.expect(exp!(Comma))?;
401
402 let mut expanded_attrs = Vec::with_capacity(1);
404 while parser.token != token::Eof {
405 let lo = parser.token.span;
406 let item = parser.parse_attr_item(ForceCollect::Yes)?;
407 expanded_attrs.push((item, lo.to(parser.prev_token.span)));
408 if !parser.eat(exp!(Comma)) {
409 break;
410 }
411 }
412
413 Ok((cfg_predicate, expanded_attrs))
414}
415
416fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
417 let gate = find_gated_cfg(|sym| sym == name);
418 if let (Some(feats), Some(gated_cfg)) = (features, gate) {
419 gate_cfg(gated_cfg, span, sess, feats);
420 }
421}
422
423#[allow(rustc::untranslatable_diagnostic)] fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
425 let (cfg, feature, has_feature) = gated_cfg;
426 if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
427 let explain = format!("`cfg({cfg})` is experimental and subject to change");
428 feature_err(sess, *feature, cfg_span, explain).emit();
429 }
430}