rustc_attr_parsing/attributes/
cfg.rs

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