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