rustc_attr_parsing/attributes/
cfg.rs

1use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NodeId};
2use rustc_ast_pretty::pprust;
3use rustc_attr_data_structures::RustcVersion;
4use rustc_feature::{Features, GatedCfg, find_gated_cfg};
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};
11
12use crate::session_diagnostics::{self, UnsupportedLiteralReason};
13use crate::{fluent_generated, parse_version};
14
15#[derive(Clone, Debug)]
16pub struct Condition {
17    pub name: Symbol,
18    pub name_span: Span,
19    pub value: Option<Symbol>,
20    pub value_span: Option<Span>,
21    pub span: Span,
22}
23
24/// Tests if a cfg-pattern matches the cfg set
25pub fn cfg_matches(
26    cfg: &MetaItemInner,
27    sess: &Session,
28    lint_node_id: NodeId,
29    features: Option<&Features>,
30) -> bool {
31    eval_condition(cfg, sess, features, &mut |cfg| {
32        try_gate_cfg(cfg.name, cfg.span, sess, features);
33        match sess.psess.check_config.expecteds.get(&cfg.name) {
34            Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
35                sess.psess.buffer_lint(
36                    UNEXPECTED_CFGS,
37                    cfg.span,
38                    lint_node_id,
39                    BuiltinLintDiag::UnexpectedCfgValue(
40                        (cfg.name, cfg.name_span),
41                        cfg.value.map(|v| (v, cfg.value_span.unwrap())),
42                    ),
43                );
44            }
45            None if sess.psess.check_config.exhaustive_names => {
46                sess.psess.buffer_lint(
47                    UNEXPECTED_CFGS,
48                    cfg.span,
49                    lint_node_id,
50                    BuiltinLintDiag::UnexpectedCfgName(
51                        (cfg.name, cfg.name_span),
52                        cfg.value.map(|v| (v, cfg.value_span.unwrap())),
53                    ),
54                );
55            }
56            _ => { /* not unexpected */ }
57        }
58        sess.psess.config.contains(&(cfg.name, cfg.value))
59    })
60}
61
62fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
63    let gate = find_gated_cfg(|sym| sym == name);
64    if let (Some(feats), Some(gated_cfg)) = (features, gate) {
65        gate_cfg(gated_cfg, span, sess, feats);
66    }
67}
68
69#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
70fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
71    let (cfg, feature, has_feature) = gated_cfg;
72    if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
73        let explain = format!("`cfg({cfg})` is experimental and subject to change");
74        feature_err(sess, *feature, cfg_span, explain).emit();
75    }
76}
77
78/// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
79/// evaluate individual items.
80pub fn eval_condition(
81    cfg: &MetaItemInner,
82    sess: &Session,
83    features: Option<&Features>,
84    eval: &mut impl FnMut(Condition) -> bool,
85) -> bool {
86    let dcx = sess.dcx();
87
88    let cfg = match cfg {
89        MetaItemInner::MetaItem(meta_item) => meta_item,
90        MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => {
91            return *b;
92        }
93        _ => {
94            dcx.emit_err(session_diagnostics::UnsupportedLiteral {
95                span: cfg.span(),
96                reason: UnsupportedLiteralReason::CfgBoolean,
97                is_bytestr: false,
98                start_point_span: sess.source_map().start_point(cfg.span()),
99            });
100            return false;
101        }
102    };
103
104    match &cfg.kind {
105        MetaItemKind::List(mis) if cfg.has_name(sym::version) => {
106            try_gate_cfg(sym::version, cfg.span, sess, features);
107            let (min_version, span) = match &mis[..] {
108                [MetaItemInner::Lit(MetaItemLit { kind: LitKind::Str(sym, ..), span, .. })] => {
109                    (sym, span)
110                }
111                [
112                    MetaItemInner::Lit(MetaItemLit { span, .. })
113                    | MetaItemInner::MetaItem(MetaItem { span, .. }),
114                ] => {
115                    dcx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: *span });
116                    return false;
117                }
118                [..] => {
119                    dcx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral {
120                        span: cfg.span,
121                    });
122                    return false;
123                }
124            };
125            let Some(min_version) = parse_version(*min_version) else {
126                dcx.emit_warn(session_diagnostics::UnknownVersionLiteral { span: *span });
127                return false;
128            };
129
130            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
131            if sess.psess.assume_incomplete_release {
132                RustcVersion::CURRENT > min_version
133            } else {
134                RustcVersion::CURRENT >= min_version
135            }
136        }
137        MetaItemKind::List(mis) => {
138            for mi in mis.iter() {
139                if mi.meta_item_or_bool().is_none() {
140                    dcx.emit_err(session_diagnostics::UnsupportedLiteral {
141                        span: mi.span(),
142                        reason: UnsupportedLiteralReason::Generic,
143                        is_bytestr: false,
144                        start_point_span: sess.source_map().start_point(mi.span()),
145                    });
146                    return false;
147                }
148            }
149
150            // The unwraps below may look dangerous, but we've already asserted
151            // that they won't fail with the loop above.
152            match cfg.name() {
153                Some(sym::any) => mis
154                    .iter()
155                    // We don't use any() here, because we want to evaluate all cfg condition
156                    // as eval_condition can (and does) extra checks
157                    .fold(false, |res, mi| res | eval_condition(mi, sess, features, eval)),
158                Some(sym::all) => mis
159                    .iter()
160                    // We don't use all() here, because we want to evaluate all cfg condition
161                    // as eval_condition can (and does) extra checks
162                    .fold(true, |res, mi| res & eval_condition(mi, sess, features, eval)),
163                Some(sym::not) => {
164                    let [mi] = mis.as_slice() else {
165                        dcx.emit_err(session_diagnostics::ExpectedOneCfgPattern { span: cfg.span });
166                        return false;
167                    };
168
169                    !eval_condition(mi, sess, features, eval)
170                }
171                Some(sym::target) => {
172                    if let Some(features) = features
173                        && !features.cfg_target_compact()
174                    {
175                        feature_err(
176                            sess,
177                            sym::cfg_target_compact,
178                            cfg.span,
179                            fluent_generated::attr_parsing_unstable_cfg_target_compact,
180                        )
181                        .emit();
182                    }
183
184                    mis.iter().fold(true, |res, mi| {
185                        let Some(mut mi) = mi.meta_item().cloned() else {
186                            dcx.emit_err(session_diagnostics::CfgPredicateIdentifier {
187                                span: mi.span(),
188                            });
189                            return false;
190                        };
191
192                        if let [seg, ..] = &mut mi.path.segments[..] {
193                            seg.ident.name = Symbol::intern(&format!("target_{}", seg.ident.name));
194                        }
195
196                        res & eval_condition(&MetaItemInner::MetaItem(mi), sess, features, eval)
197                    })
198                }
199                _ => {
200                    dcx.emit_err(session_diagnostics::InvalidPredicate {
201                        span: cfg.span,
202                        predicate: pprust::path_to_string(&cfg.path),
203                    });
204                    false
205                }
206            }
207        }
208        MetaItemKind::Word | MetaItemKind::NameValue(..) if cfg.path.segments.len() != 1 => {
209            dcx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: cfg.path.span });
210            true
211        }
212        MetaItemKind::NameValue(lit) if !lit.kind.is_str() => {
213            dcx.emit_err(session_diagnostics::UnsupportedLiteral {
214                span: lit.span,
215                reason: UnsupportedLiteralReason::CfgString,
216                is_bytestr: lit.kind.is_bytestr(),
217                start_point_span: sess.source_map().start_point(lit.span),
218            });
219            true
220        }
221        MetaItemKind::Word | MetaItemKind::NameValue(..) => {
222            let ident = cfg.ident().expect("multi-segment cfg predicate");
223            eval(Condition {
224                name: ident.name,
225                name_span: ident.span,
226                value: cfg.value_str(),
227                value_span: cfg.name_value_literal_span(),
228                span: cfg.span,
229            })
230        }
231    }
232}