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