rustc_attr_parsing/attributes/
cfg.rs

1use 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        // First, validate that this is a NameValue item
137        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        // Then, parse it as a name-value item
147        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                // We cannot short-circuit because `eval_config_entry` emits some lints
199                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                // We cannot short-circuit because `eval_config_entry` emits some lints
210                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                    _ => { /* not unexpected */ }
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            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
272            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}