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