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