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