rustc_attr_parsing/attributes/
cfg.rs1use rustc_ast::{LitKind, NodeId};
2use rustc_feature::{AttributeTemplate, Features, template};
3use rustc_hir::RustcVersion;
4use rustc_hir::attrs::CfgEntry;
5use rustc_session::Session;
6use rustc_session::config::ExpectedValues;
7use rustc_session::lint::BuiltinLintDiag;
8use rustc_session::lint::builtin::UNEXPECTED_CFGS;
9use rustc_session::parse::feature_err;
10use rustc_span::{Span, Symbol, sym};
11use thin_vec::ThinVec;
12
13use crate::context::{AcceptContext, ShouldEmit, Stage};
14use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser};
15use crate::{
16 CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, try_gate_cfg,
17};
18
19pub const CFG_TEMPLATE: AttributeTemplate = template!(
20 List: &["predicate"],
21 "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute"
22);
23
24pub fn parse_cfg_attr<'c, S: Stage>(
25 cx: &'c mut AcceptContext<'_, '_, S>,
26 args: &'c ArgParser<'_>,
27) -> Option<CfgEntry> {
28 let ArgParser::List(list) = args else {
29 cx.expected_list(cx.attr_span);
30 return None;
31 };
32 let Some(single) = list.single() else {
33 cx.expected_single_argument(list.span);
34 return None;
35 };
36 parse_cfg_entry(cx, single)
37}
38
39pub(crate) fn parse_cfg_entry<S: Stage>(
40 cx: &mut AcceptContext<'_, '_, S>,
41 item: &MetaItemOrLitParser<'_>,
42) -> Option<CfgEntry> {
43 Some(match item {
44 MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
45 ArgParser::List(list) => match meta.path().word_sym() {
46 Some(sym::not) => {
47 let Some(single) = list.single() else {
48 cx.expected_single_argument(list.span);
49 return None;
50 };
51 CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span)
52 }
53 Some(sym::any) => CfgEntry::Any(
54 list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
55 list.span,
56 ),
57 Some(sym::all) => CfgEntry::All(
58 list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
59 list.span,
60 ),
61 Some(sym::target) => parse_cfg_entry_target(cx, list, meta.span())?,
62 Some(sym::version) => parse_cfg_entry_version(cx, list, meta.span())?,
63 _ => {
64 cx.emit_err(session_diagnostics::InvalidPredicate {
65 span: meta.span(),
66 predicate: meta.path().to_string(),
67 });
68 return None;
69 }
70 },
71 a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
72 let Some(name) = meta.path().word_sym() else {
73 cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
74 span: meta.path().span(),
75 });
76 return None;
77 };
78 parse_name_value(name, meta.path().span(), a.name_value(), meta.span(), cx)?
79 }
80 },
81 MetaItemOrLitParser::Lit(lit) => match lit.kind {
82 LitKind::Bool(b) => CfgEntry::Bool(b, lit.span),
83 _ => {
84 cx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: lit.span });
85 return None;
86 }
87 },
88 MetaItemOrLitParser::Err(_, _) => return None,
89 })
90}
91
92fn parse_cfg_entry_version<S: Stage>(
93 cx: &mut AcceptContext<'_, '_, S>,
94 list: &MetaItemListParser<'_>,
95 meta_span: Span,
96) -> Option<CfgEntry> {
97 try_gate_cfg(sym::version, meta_span, cx.sess(), cx.features_option());
98 let Some(version) = list.single() else {
99 cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span });
100 return None;
101 };
102 let Some(version_lit) = version.lit() else {
103 cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version.span() });
104 return None;
105 };
106 let Some(version_str) = version_lit.value_str() else {
107 cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version_lit.span });
108 return None;
109 };
110
111 let min_version = parse_version(version_str).or_else(|| {
112 cx.sess()
113 .dcx()
114 .emit_warn(session_diagnostics::UnknownVersionLiteral { span: version_lit.span });
115 None
116 });
117
118 Some(CfgEntry::Version(min_version, list.span))
119}
120
121fn parse_cfg_entry_target<S: Stage>(
122 cx: &mut AcceptContext<'_, '_, S>,
123 list: &MetaItemListParser<'_>,
124 meta_span: Span,
125) -> Option<CfgEntry> {
126 if let Some(features) = cx.features_option()
127 && !features.cfg_target_compact()
128 {
129 feature_err(
130 cx.sess(),
131 sym::cfg_target_compact,
132 meta_span,
133 fluent_generated::attr_parsing_unstable_cfg_target_compact,
134 )
135 .emit();
136 }
137
138 let mut result = ThinVec::new();
139 for sub_item in list.mixed() {
140 let Some(sub_item) = sub_item.meta_item() else {
142 cx.expected_name_value(sub_item.span(), None);
143 continue;
144 };
145 let Some(nv) = sub_item.args().name_value() else {
146 cx.expected_name_value(sub_item.span(), None);
147 continue;
148 };
149
150 let Some(name) = sub_item.path().word_sym() else {
152 cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
153 span: sub_item.path().span(),
154 });
155 return None;
156 };
157 let name = Symbol::intern(&format!("target_{name}"));
158 if let Some(cfg) =
159 parse_name_value(name, sub_item.path().span(), Some(nv), sub_item.span(), cx)
160 {
161 result.push(cfg);
162 }
163 }
164 Some(CfgEntry::All(result, list.span))
165}
166
167fn parse_name_value<S: Stage>(
168 name: Symbol,
169 name_span: Span,
170 value: Option<&NameValueParser>,
171 span: Span,
172 cx: &mut AcceptContext<'_, '_, S>,
173) -> Option<CfgEntry> {
174 try_gate_cfg(name, span, cx.sess(), cx.features_option());
175
176 let value = match value {
177 None => None,
178 Some(value) => {
179 let Some(value_str) = value.value_as_str() else {
180 cx.expected_string_literal(value.value_span, Some(value.value_as_lit()));
181 return None;
182 };
183 Some((value_str, value.value_span))
184 }
185 };
186
187 Some(CfgEntry::NameValue { name, name_span, value, span })
188}
189
190pub fn eval_config_entry(
191 sess: &Session,
192 cfg_entry: &CfgEntry,
193 id: NodeId,
194 features: Option<&Features>,
195 emit_lints: ShouldEmit,
196) -> EvalConfigResult {
197 match cfg_entry {
198 CfgEntry::All(subs, ..) => {
199 let mut all = None;
200 for sub in subs {
201 let res = eval_config_entry(sess, sub, id, features, emit_lints);
202 if !res.as_bool() {
204 all.get_or_insert(res);
205 }
206 }
207 all.unwrap_or_else(|| EvalConfigResult::True)
208 }
209 CfgEntry::Any(subs, span) => {
210 let mut any = None;
211 for sub in subs {
212 let res = eval_config_entry(sess, sub, id, features, emit_lints);
213 if res.as_bool() {
215 any.get_or_insert(res);
216 }
217 }
218 any.unwrap_or_else(|| EvalConfigResult::False {
219 reason: cfg_entry.clone(),
220 reason_span: *span,
221 })
222 }
223 CfgEntry::Not(sub, span) => {
224 if eval_config_entry(sess, sub, id, features, emit_lints).as_bool() {
225 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
226 } else {
227 EvalConfigResult::True
228 }
229 }
230 CfgEntry::Bool(b, span) => {
231 if *b {
232 EvalConfigResult::True
233 } else {
234 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
235 }
236 }
237 CfgEntry::NameValue { name, name_span, value, span } => {
238 if let ShouldEmit::ErrorsAndLints = emit_lints {
239 match sess.psess.check_config.expecteds.get(name) {
240 Some(ExpectedValues::Some(values))
241 if !values.contains(&value.map(|(v, _)| v)) =>
242 {
243 id.emit_span_lint(
244 sess,
245 UNEXPECTED_CFGS,
246 *span,
247 BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value),
248 );
249 }
250 None if sess.psess.check_config.exhaustive_names => {
251 id.emit_span_lint(
252 sess,
253 UNEXPECTED_CFGS,
254 *span,
255 BuiltinLintDiag::UnexpectedCfgName((*name, *name_span), *value),
256 );
257 }
258 _ => { }
259 }
260 }
261
262 if sess.psess.config.contains(&(*name, value.map(|(v, _)| v))) {
263 EvalConfigResult::True
264 } else {
265 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
266 }
267 }
268 CfgEntry::Version(min_version, version_span) => {
269 let Some(min_version) = min_version else {
270 return EvalConfigResult::False {
271 reason: cfg_entry.clone(),
272 reason_span: *version_span,
273 };
274 };
275 let min_version_ok = if sess.psess.assume_incomplete_release {
277 RustcVersion::current_overridable() > *min_version
278 } else {
279 RustcVersion::current_overridable() >= *min_version
280 };
281 if min_version_ok {
282 EvalConfigResult::True
283 } else {
284 EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *version_span }
285 }
286 }
287 }
288}
289
290pub enum EvalConfigResult {
291 True,
292 False { reason: CfgEntry, reason_span: Span },
293}
294
295impl EvalConfigResult {
296 pub fn as_bool(&self) -> bool {
297 match self {
298 EvalConfigResult::True => true,
299 EvalConfigResult::False { .. } => false,
300 }
301 }
302}