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