rustc_attr_parsing/attributes/
cfg.rs

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