Skip to main content

rustc_attr_parsing/attributes/diagnostic/
check_cfg.rs

1use rustc_session::Session;
2use rustc_session::config::ExpectedValues;
3use rustc_span::def_id::LOCAL_CRATE;
4use rustc_span::edit_distance::find_best_match_for_name;
5use rustc_span::{ExpnKind, Ident, Span, Symbol, sym};
6
7use crate::errors;
8
9const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35;
10
11enum FilterWellKnownNames {
12    Yes,
13    No,
14}
15
16fn sort_and_truncate_possibilities(
17    sess: &Session,
18    mut possibilities: Vec<Symbol>,
19    filter_well_known_names: FilterWellKnownNames,
20) -> (Vec<Symbol>, usize) {
21    let possibilities_len = possibilities.len();
22
23    let n_possibilities = if sess.opts.unstable_opts.check_cfg_all_expected {
24        possibilities.len()
25    } else {
26        match filter_well_known_names {
27            FilterWellKnownNames::Yes => {
28                possibilities.retain(|cfg_name| {
29                    !sess.psess.check_config.well_known_names.contains(cfg_name)
30                });
31            }
32            FilterWellKnownNames::No => {}
33        };
34        std::cmp::min(possibilities.len(), MAX_CHECK_CFG_NAMES_OR_VALUES)
35    };
36
37    possibilities.sort_by(|s1, s2| s1.as_str().cmp(s2.as_str()));
38
39    let and_more = possibilities_len.saturating_sub(n_possibilities);
40    possibilities.truncate(n_possibilities);
41    (possibilities, and_more)
42}
43
44enum EscapeQuotes {
45    Yes,
46    No,
47}
48
49fn to_check_cfg_arg(name: Ident, value: Option<Symbol>, quotes: EscapeQuotes) -> String {
50    if let Some(value) = value {
51        let value = str::escape_debug(value.as_str()).to_string();
52        let values = match quotes {
53            EscapeQuotes::Yes => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\\\"{0}\\\"",
                value.replace("\"", "\\\\\\\\\"")))
    })format!("\\\"{}\\\"", value.replace("\"", "\\\\\\\\\"")),
54            EscapeQuotes::No => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\"{0}\"", value))
    })format!("\"{value}\""),
55        };
56        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cfg({0}, values({1}))", name,
                values))
    })format!("cfg({name}, values({values}))")
57    } else {
58        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("cfg({0})", name))
    })format!("cfg({name})")
59    }
60}
61
62fn cargo_help_sub(
63    sess: &Session,
64    inst: &impl Fn(EscapeQuotes) -> String,
65) -> errors::UnexpectedCfgCargoHelp {
66    // We don't want to suggest the `build.rs` way to expected cfgs if we are already in a
67    // `build.rs`. We therefor do a best effort check (looking if the `--crate-name` is
68    // `build_script_build`) to try to figure out if we are building a Cargo build script
69
70    let unescaped = &inst(EscapeQuotes::No);
71    if let Some("build_script_build") = sess.opts.crate_name.as_deref() {
72        errors::UnexpectedCfgCargoHelp::lint_cfg(unescaped)
73    } else {
74        errors::UnexpectedCfgCargoHelp::lint_cfg_and_build_rs(unescaped, &inst(EscapeQuotes::Yes))
75    }
76}
77
78fn rustc_macro_help(span: Span) -> Option<errors::UnexpectedCfgRustcMacroHelp> {
79    let oexpn = span.ctxt().outer_expn_data();
80    if let Some(def_id) = oexpn.macro_def_id
81        && let ExpnKind::Macro(macro_kind, macro_name) = oexpn.kind
82        && def_id.krate != LOCAL_CRATE
83    {
84        Some(errors::UnexpectedCfgRustcMacroHelp { macro_kind: macro_kind.descr(), macro_name })
85    } else {
86        None
87    }
88}
89
90fn cargo_macro_help(span: Span) -> Option<errors::UnexpectedCfgCargoMacroHelp> {
91    let oexpn = span.ctxt().outer_expn_data();
92    if let Some(def_id) = oexpn.macro_def_id
93        && def_id.krate != LOCAL_CRATE
94        && let ExpnKind::Macro(macro_kind, macro_name) = oexpn.kind
95    {
96        Some(errors::UnexpectedCfgCargoMacroHelp { macro_kind: macro_kind.descr(), macro_name })
97    } else {
98        None
99    }
100}
101
102pub(crate) fn unexpected_cfg_name(
103    sess: &Session,
104    (name, name_span): (Symbol, Span),
105    value: Option<(Symbol, Span)>,
106) -> errors::UnexpectedCfgName {
107    #[allow(rustc::potential_query_instability)]
108    let possibilities: Vec<Symbol> = sess.psess.check_config.expecteds.keys().copied().collect();
109
110    let mut names_possibilities: Vec<_> = if value.is_none() {
111        // We later sort and display all the possibilities, so the order here does not matter.
112        #[allow(rustc::potential_query_instability)]
113        sess.psess
114            .check_config
115            .expecteds
116            .iter()
117            .filter_map(|(k, v)| match v {
118                ExpectedValues::Some(v) if v.contains(&Some(name)) => Some(k),
119                _ => None,
120            })
121            .collect()
122    } else {
123        Vec::new()
124    };
125
126    let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
127    let is_from_external_macro = name_span.in_external_macro(sess.source_map());
128    let mut is_feature_cfg = name == sym::feature;
129
130    fn miscapitalized_boolean(name: Symbol) -> Option<bool> {
131        if name.as_str().eq_ignore_ascii_case("false") {
132            Some(false)
133        } else if name.as_str().eq_ignore_ascii_case("true") {
134            Some(true)
135        } else {
136            None
137        }
138    }
139
140    let code_sugg = if is_feature_cfg && is_from_cargo {
141        errors::unexpected_cfg_name::CodeSuggestion::DefineFeatures
142    // Suggest correct `version("..")` predicate syntax
143    } else if let Some((_value, value_span)) = value
144        && name == sym::version
145    {
146        errors::unexpected_cfg_name::CodeSuggestion::VersionSyntax {
147            between_name_and_value: name_span.between(value_span),
148            after_value: value_span.shrink_to_hi(),
149        }
150    // Suggest a literal `false` instead
151    // Detect miscapitalized `False`/`FALSE` etc, ensuring that this isn't `r#false`
152    } else if value.is_none()
153        // If this is a miscapitalized False/FALSE, suggest the boolean literal instead
154        && let Some(boolean) = miscapitalized_boolean(name)
155        // Check this isn't a raw identifier
156        && sess
157            .source_map()
158            .span_to_snippet(name_span)
159            .map_or(true, |snippet| !snippet.contains("r#"))
160    {
161        errors::unexpected_cfg_name::CodeSuggestion::BooleanLiteral {
162            span: name_span,
163            literal: boolean,
164        }
165    // Suggest the most probable if we found one
166    } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
167        is_feature_cfg |= best_match == sym::feature;
168
169        if let Some(ExpectedValues::Some(best_match_values)) =
170            sess.psess.check_config.expecteds.get(&best_match)
171        {
172            // We will soon sort, so the initial order does not matter.
173            #[allow(rustc::potential_query_instability)]
174            let mut possibilities = best_match_values.iter().flatten().collect::<Vec<_>>();
175            possibilities.sort_by_key(|s| s.as_str());
176
177            let get_possibilities_sub = || {
178                if !possibilities.is_empty() {
179                    let possibilities =
180                        possibilities.iter().copied().cloned().collect::<Vec<_>>().into();
181                    Some(errors::unexpected_cfg_name::ExpectedValues { best_match, possibilities })
182                } else {
183                    None
184                }
185            };
186
187            let best_match = Ident::new(best_match, name_span);
188            if let Some((value, value_span)) = value {
189                if best_match_values.contains(&Some(value)) {
190                    errors::unexpected_cfg_name::CodeSuggestion::SimilarNameAndValue {
191                        span: name_span,
192                        code: best_match.to_string(),
193                    }
194                } else if best_match_values.contains(&None) {
195                    errors::unexpected_cfg_name::CodeSuggestion::SimilarNameNoValue {
196                        span: name_span.to(value_span),
197                        code: best_match.to_string(),
198                    }
199                } else if let Some(first_value) = possibilities.first() {
200                    errors::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
201                        span: name_span.to(value_span),
202                        code: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} = \"{1}\"", best_match,
                first_value))
    })format!("{best_match} = \"{first_value}\""),
203                        expected: get_possibilities_sub(),
204                    }
205                } else {
206                    errors::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
207                        span: name_span.to(value_span),
208                        code: best_match.to_string(),
209                        expected: get_possibilities_sub(),
210                    }
211                }
212            } else {
213                errors::unexpected_cfg_name::CodeSuggestion::SimilarName {
214                    span: name_span,
215                    code: best_match.to_string(),
216                    expected: get_possibilities_sub(),
217                }
218            }
219        } else {
220            errors::unexpected_cfg_name::CodeSuggestion::SimilarName {
221                span: name_span,
222                code: best_match.to_string(),
223                expected: None,
224            }
225        }
226    } else {
227        let similar_values = if !names_possibilities.is_empty() && names_possibilities.len() <= 3 {
228            names_possibilities.sort();
229            names_possibilities
230                .iter()
231                .map(|cfg_name| errors::unexpected_cfg_name::FoundWithSimilarValue {
232                    span: name_span,
233                    code: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} = \"{1}\"", cfg_name, name))
    })format!("{cfg_name} = \"{name}\""),
234                })
235                .collect()
236        } else {
237            ::alloc::vec::Vec::new()vec![]
238        };
239
240        let (possibilities, and_more) =
241            sort_and_truncate_possibilities(sess, possibilities, FilterWellKnownNames::Yes);
242        let expected_names = if !possibilities.is_empty() {
243            let possibilities: Vec<_> =
244                possibilities.into_iter().map(|s| Ident::new(s, name_span)).collect();
245            Some(errors::unexpected_cfg_name::ExpectedNames {
246                possibilities: possibilities.into(),
247                and_more,
248            })
249        } else {
250            None
251        };
252        errors::unexpected_cfg_name::CodeSuggestion::SimilarValues {
253            with_similar_values: similar_values,
254            expected_names,
255        }
256    };
257
258    let inst = |escape_quotes| {
259        to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
260    };
261
262    let invocation_help = if is_from_cargo {
263        let help = if !is_feature_cfg && !is_from_external_macro {
264            Some(cargo_help_sub(sess, &inst))
265        } else {
266            None
267        };
268        errors::unexpected_cfg_name::InvocationHelp::Cargo {
269            help,
270            macro_help: cargo_macro_help(name_span),
271        }
272    } else {
273        let help = errors::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No));
274        errors::unexpected_cfg_name::InvocationHelp::Rustc {
275            help,
276            macro_help: rustc_macro_help(name_span),
277        }
278    };
279
280    errors::UnexpectedCfgName { code_sugg, invocation_help, name }
281}
282
283pub(crate) fn unexpected_cfg_value(
284    sess: &Session,
285    (name, name_span): (Symbol, Span),
286    value: Option<(Symbol, Span)>,
287) -> errors::UnexpectedCfgValue {
288    let Some(ExpectedValues::Some(values)) = &sess.psess.check_config.expecteds.get(&name) else {
289        {
    ::core::panicking::panic_fmt(format_args!("it shouldn\'t be possible to have a diagnostic on a value whose name is not in values"));
};panic!(
290            "it shouldn't be possible to have a diagnostic on a value whose name is not in values"
291        );
292    };
293    let mut have_none_possibility = false;
294    // We later sort possibilities if it is not empty, so the
295    // order here does not matter.
296    #[allow(rustc::potential_query_instability)]
297    let possibilities: Vec<Symbol> = values
298        .iter()
299        .inspect(|a| have_none_possibility |= a.is_none())
300        .copied()
301        .flatten()
302        .collect();
303
304    let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
305    let is_from_external_macro = name_span.in_external_macro(sess.source_map());
306
307    let code_sugg = if let Some((value, _)) = value
308        && sess.psess.check_config.well_known_names.contains(&name)
309        && let valid_names = possible_well_known_names_for_cfg_value(sess, value)
310        && !valid_names.is_empty()
311    {
312        // Suggest changing the name to something for which `value` is an expected value.
313        let max_suggestions = 3;
314        let suggestions = valid_names
315            .iter()
316            .take(max_suggestions)
317            .copied()
318            .map(|name| errors::unexpected_cfg_value::ChangeNameSuggestion {
319                span: name_span,
320                name,
321                value,
322            })
323            .collect::<Vec<_>>();
324        errors::unexpected_cfg_value::CodeSuggestion::ChangeName { suggestions }
325    } else if !possibilities.is_empty() {
326        // Show the full list if all possible values for a given name, but don't do it
327        // for names as the possibilities could be very long
328        let expected_values = {
329            let (possibilities, and_more) = sort_and_truncate_possibilities(
330                sess,
331                possibilities.clone(),
332                FilterWellKnownNames::No,
333            );
334            errors::unexpected_cfg_value::ExpectedValues {
335                name,
336                have_none_possibility,
337                possibilities: possibilities.into(),
338                and_more,
339            }
340        };
341
342        let suggestion = if let Some((value, value_span)) = value {
343            // Suggest the most probable if we found one
344            if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
345                Some(errors::unexpected_cfg_value::ChangeValueSuggestion::SimilarName {
346                    span: value_span,
347                    best_match,
348                })
349            } else {
350                None
351            }
352        } else if let &[first_possibility] = &possibilities[..] {
353            Some(errors::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue {
354                span: name_span.shrink_to_hi(),
355                first_possibility,
356            })
357        } else {
358            None
359        };
360
361        errors::unexpected_cfg_value::CodeSuggestion::ChangeValue { expected_values, suggestion }
362    } else if have_none_possibility {
363        let suggestion =
364            value.map(|(_value, value_span)| errors::unexpected_cfg_value::RemoveValueSuggestion {
365                span: name_span.shrink_to_hi().to(value_span),
366            });
367        errors::unexpected_cfg_value::CodeSuggestion::RemoveValue { suggestion, name }
368    } else {
369        let span = if let Some((_value, value_span)) = value {
370            name_span.to(value_span)
371        } else {
372            name_span
373        };
374        let suggestion = errors::unexpected_cfg_value::RemoveConditionSuggestion { span };
375        errors::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name }
376    };
377
378    // We don't want to encourage people to add values to a well-known names, as these are
379    // defined by rustc/Rust itself. Users can still do this if they wish, but should not be
380    // encouraged to do so.
381    let can_suggest_adding_value = !sess.psess.check_config.well_known_names.contains(&name)
382        // Except when working on rustc or the standard library itself, in which case we want to
383        // suggest adding these cfgs to the "normal" place because of bootstrapping reasons. As a
384        // basic heuristic, we use the "cheat" unstable feature enable method and the
385        // non-ui-testing enabled option.
386        || (#[allow(non_exhaustive_omitted_patterns)] match sess.psess.unstable_features {
    rustc_feature::UnstableFeatures::Cheat => true,
    _ => false,
}matches!(sess.psess.unstable_features, rustc_feature::UnstableFeatures::Cheat)
387            && !sess.opts.unstable_opts.ui_testing);
388
389    let inst = |escape_quotes| {
390        to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
391    };
392
393    let invocation_help = if is_from_cargo {
394        let help = if name == sym::feature && !is_from_external_macro {
395            if let Some((value, _value_span)) = value {
396                Some(errors::unexpected_cfg_value::CargoHelp::AddFeature { value })
397            } else {
398                Some(errors::unexpected_cfg_value::CargoHelp::DefineFeatures)
399            }
400        } else if can_suggest_adding_value && !is_from_external_macro {
401            Some(errors::unexpected_cfg_value::CargoHelp::Other(cargo_help_sub(sess, &inst)))
402        } else {
403            None
404        };
405        errors::unexpected_cfg_value::InvocationHelp::Cargo {
406            help,
407            macro_help: cargo_macro_help(name_span),
408        }
409    } else {
410        let help = if can_suggest_adding_value {
411            Some(errors::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No)))
412        } else {
413            None
414        };
415        errors::unexpected_cfg_value::InvocationHelp::Rustc {
416            help,
417            macro_help: rustc_macro_help(name_span),
418        }
419    };
420
421    errors::UnexpectedCfgValue {
422        code_sugg,
423        invocation_help,
424        has_value: value.is_some(),
425        value: value.map_or_else(String::new, |(v, _span)| v.to_string()),
426    }
427}
428
429fn possible_well_known_names_for_cfg_value(sess: &Session, value: Symbol) -> Vec<Symbol> {
430    #[allow(rustc::potential_query_instability)]
431    let mut names = sess
432        .psess
433        .check_config
434        .well_known_names
435        .iter()
436        .filter(|name| {
437            sess.psess
438                .check_config
439                .expecteds
440                .get(*name)
441                .map(|expected_values| expected_values.contains(&Some(value)))
442                .unwrap_or_default()
443        })
444        .copied()
445        .collect::<Vec<_>>();
446    names.sort_by(|a, b| a.as_str().cmp(b.as_str()));
447    names
448}