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::diagnostics;
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) -> diagnostics::UnexpectedCfgCargoHelp {
65 let unescaped = &inst(EscapeQuotes::No);
70 if let Some("build_script_build") = sess.opts.crate_name.as_deref() {
71 diagnostics::UnexpectedCfgCargoHelp::lint_cfg(unescaped)
72 } else {
73 diagnostics::UnexpectedCfgCargoHelp::lint_cfg_and_build_rs(
74 unescaped,
75 &inst(EscapeQuotes::Yes),
76 )
77 }
78}
79
80fn rustc_macro_help(span: Span) -> Option<diagnostics::UnexpectedCfgRustcMacroHelp> {
81 let oexpn = span.ctxt().outer_expn_data();
82 if let Some(def_id) = oexpn.macro_def_id
83 && let ExpnKind::Macro(macro_kind, macro_name) = oexpn.kind
84 && def_id.krate != LOCAL_CRATE
85 {
86 Some(diagnostics::UnexpectedCfgRustcMacroHelp {
87 macro_kind: macro_kind.descr(),
88 macro_name,
89 })
90 } else {
91 None
92 }
93}
94
95fn cargo_macro_help(span: Span) -> Option<diagnostics::UnexpectedCfgCargoMacroHelp> {
96 let oexpn = span.ctxt().outer_expn_data();
97 if let Some(def_id) = oexpn.macro_def_id
98 && def_id.krate != LOCAL_CRATE
99 && let ExpnKind::Macro(macro_kind, macro_name) = oexpn.kind
100 {
101 Some(diagnostics::UnexpectedCfgCargoMacroHelp {
102 macro_kind: macro_kind.descr(),
103 macro_name,
104 })
105 } else {
106 None
107 }
108}
109
110pub(crate) fn unexpected_cfg_name(
111 sess: &Session,
112 (name, name_span): (Symbol, Span),
113 value: Option<(Symbol, Span)>,
114) -> diagnostics::UnexpectedCfgName {
115 #[allow(rustc::potential_query_instability)]
116 let possibilities: Vec<Symbol> = sess.check_config.expecteds.keys().copied().collect();
117
118 let mut names_possibilities: Vec<_> = if value.is_none() {
119 #[allow(rustc::potential_query_instability)]
121 sess.check_config
122 .expecteds
123 .iter()
124 .filter_map(|(k, v)| match v {
125 ExpectedValues::Some(v) if v.contains(&Some(name)) => Some(k),
126 _ => None,
127 })
128 .collect()
129 } else {
130 Vec::new()
131 };
132
133 let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
134 let is_from_external_macro = name_span.in_external_macro(sess.source_map());
135 let mut is_feature_cfg = name == sym::feature;
136
137 fn miscapitalized_boolean(name: Symbol) -> Option<bool> {
138 if name.as_str().eq_ignore_ascii_case("false") {
139 Some(false)
140 } else if name.as_str().eq_ignore_ascii_case("true") {
141 Some(true)
142 } else {
143 None
144 }
145 }
146
147 let code_sugg = if is_feature_cfg && is_from_cargo {
148 diagnostics::unexpected_cfg_name::CodeSuggestion::DefineFeatures
149 } else if let Some((_value, value_span)) = value
151 && name == sym::version
152 {
153 diagnostics::unexpected_cfg_name::CodeSuggestion::VersionSyntax {
154 between_name_and_value: name_span.between(value_span),
155 after_value: value_span.shrink_to_hi(),
156 }
157 } else if value.is_none()
160 && let Some(boolean) = miscapitalized_boolean(name)
162 && sess
164 .source_map()
165 .span_to_snippet(name_span)
166 .map_or(true, |snippet| !snippet.contains("r#"))
167 {
168 diagnostics::unexpected_cfg_name::CodeSuggestion::BooleanLiteral {
169 span: name_span,
170 literal: boolean,
171 }
172 } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
174 is_feature_cfg |= best_match == sym::feature;
175
176 if let Some(ExpectedValues::Some(best_match_values)) =
177 sess.check_config.expecteds.get(&best_match)
178 {
179 #[allow(rustc::potential_query_instability)]
181 let mut possibilities = best_match_values.iter().flatten().collect::<Vec<_>>();
182 possibilities.sort_by_key(|s| s.as_str());
183
184 let get_possibilities_sub = || {
185 if possibilities.is_empty() {
186 None
187 } else {
188 let possibilities =
189 possibilities.iter().copied().copied().collect::<Vec<_>>().into();
190 Some(diagnostics::unexpected_cfg_name::ExpectedValues {
191 best_match,
192 possibilities,
193 })
194 }
195 };
196
197 let best_match = Ident::new(best_match, name_span);
198 if let Some((value, value_span)) = value {
199 if best_match_values.contains(&Some(value)) {
200 diagnostics::unexpected_cfg_name::CodeSuggestion::SimilarNameAndValue {
201 span: name_span,
202 code: best_match.to_string(),
203 }
204 } else if best_match_values.contains(&None) {
205 diagnostics::unexpected_cfg_name::CodeSuggestion::SimilarNameNoValue {
206 span: name_span.to(value_span),
207 code: best_match.to_string(),
208 }
209 } else if let Some(first_value) = possibilities.first() {
210 diagnostics::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
211 span: name_span.to(value_span),
212 code: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} = \"{1}\"", best_match,
first_value))
})format!("{best_match} = \"{first_value}\""),
213 expected: get_possibilities_sub(),
214 }
215 } else {
216 diagnostics::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
217 span: name_span.to(value_span),
218 code: best_match.to_string(),
219 expected: get_possibilities_sub(),
220 }
221 }
222 } else {
223 diagnostics::unexpected_cfg_name::CodeSuggestion::SimilarName {
224 span: name_span,
225 code: best_match.to_string(),
226 expected: get_possibilities_sub(),
227 }
228 }
229 } else {
230 diagnostics::unexpected_cfg_name::CodeSuggestion::SimilarName {
231 span: name_span,
232 code: best_match.to_string(),
233 expected: None,
234 }
235 }
236 } else {
237 let similar_values = if !names_possibilities.is_empty() && names_possibilities.len() <= 3 {
238 names_possibilities.sort();
239 names_possibilities
240 .iter()
241 .map(|cfg_name| diagnostics::unexpected_cfg_name::FoundWithSimilarValue {
242 span: name_span,
243 code: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} = \"{1}\"", cfg_name, name))
})format!("{cfg_name} = \"{name}\""),
244 })
245 .collect()
246 } else {
247 ::alloc::vec::Vec::new()vec![]
248 };
249
250 let (possibilities, and_more) =
251 sort_and_truncate_possibilities(sess, possibilities, FilterWellKnownNames::Yes);
252 let expected_names = if possibilities.is_empty() {
253 None
254 } else {
255 let possibilities: Vec<_> =
256 possibilities.into_iter().map(|s| Ident::new(s, name_span)).collect();
257 Some(diagnostics::unexpected_cfg_name::ExpectedNames {
258 possibilities: possibilities.into(),
259 and_more,
260 })
261 };
262 diagnostics::unexpected_cfg_name::CodeSuggestion::SimilarValues {
263 with_similar_values: similar_values,
264 expected_names,
265 }
266 };
267
268 let inst = |escape_quotes| {
269 to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
270 };
271
272 let invocation_help = if is_from_cargo {
273 let help = if !is_feature_cfg && !is_from_external_macro {
274 Some(cargo_help_sub(sess, &inst))
275 } else {
276 None
277 };
278 diagnostics::unexpected_cfg_name::InvocationHelp::Cargo {
279 help,
280 macro_help: cargo_macro_help(name_span),
281 }
282 } else {
283 let help = diagnostics::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No));
284 diagnostics::unexpected_cfg_name::InvocationHelp::Rustc {
285 help,
286 macro_help: rustc_macro_help(name_span),
287 }
288 };
289
290 diagnostics::UnexpectedCfgName { code_sugg, invocation_help, name }
291}
292
293pub(crate) fn unexpected_cfg_value(
294 sess: &Session,
295 (name, name_span): (Symbol, Span),
296 value: Option<(Symbol, Span)>,
297) -> diagnostics::UnexpectedCfgValue {
298 let Some(ExpectedValues::Some(values)) = &sess.check_config.expecteds.get(&name) else {
299 {
::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!(
300 "it shouldn't be possible to have a diagnostic on a value whose name is not in values"
301 );
302 };
303 let mut have_none_possibility = false;
304 #[allow(rustc::potential_query_instability)]
307 let possibilities: Vec<Symbol> = values
308 .iter()
309 .inspect(|a| have_none_possibility |= a.is_none())
310 .copied()
311 .flatten()
312 .collect();
313
314 let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
315 let is_from_external_macro = name_span.in_external_macro(sess.source_map());
316
317 let code_sugg = if let Some((value, _)) = value
318 && sess.check_config.well_known_names.contains(&name)
319 && let valid_names = possible_well_known_names_for_cfg_value(sess, value)
320 && !valid_names.is_empty()
321 {
322 let max_suggestions = 3;
324 let suggestions = valid_names
325 .iter()
326 .take(max_suggestions)
327 .copied()
328 .map(|name| diagnostics::unexpected_cfg_value::ChangeNameSuggestion {
329 span: name_span,
330 name,
331 value,
332 })
333 .collect::<Vec<_>>();
334 diagnostics::unexpected_cfg_value::CodeSuggestion::ChangeName { suggestions }
335 } else if !possibilities.is_empty() {
336 let expected_values = {
339 let (possibilities, and_more) = sort_and_truncate_possibilities(
340 sess,
341 possibilities.clone(),
342 FilterWellKnownNames::No,
343 );
344 diagnostics::unexpected_cfg_value::ExpectedValues {
345 name,
346 have_none_possibility,
347 possibilities: possibilities.into(),
348 and_more,
349 }
350 };
351
352 let suggestion = if let Some((value, value_span)) = value {
353 find_best_match_for_name(&possibilities, value, None).map(|best_match| {
355 diagnostics::unexpected_cfg_value::ChangeValueSuggestion::SimilarName {
356 span: value_span,
357 best_match,
358 }
359 })
360 } else if let &[first_possibility] = &possibilities[..] {
361 Some(diagnostics::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue {
362 span: name_span.shrink_to_hi(),
363 first_possibility,
364 })
365 } else {
366 None
367 };
368
369 diagnostics::unexpected_cfg_value::CodeSuggestion::ChangeValue {
370 expected_values,
371 suggestion,
372 }
373 } else if have_none_possibility {
374 let suggestion = value.map(|(_value, value_span)| {
375 diagnostics::unexpected_cfg_value::RemoveValueSuggestion {
376 span: name_span.shrink_to_hi().to(value_span),
377 }
378 });
379 diagnostics::unexpected_cfg_value::CodeSuggestion::RemoveValue { suggestion, name }
380 } else {
381 let span = if let Some((_value, value_span)) = value {
382 name_span.to(value_span)
383 } else {
384 name_span
385 };
386 let suggestion = diagnostics::unexpected_cfg_value::RemoveConditionSuggestion { span };
387 diagnostics::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name }
388 };
389
390 let can_suggest_adding_value = !sess.check_config.well_known_names.contains(&name)
394 || (#[allow(non_exhaustive_omitted_patterns)] match sess.unstable_features {
rustc_feature::UnstableFeatures::Cheat => true,
_ => false,
}matches!(sess.unstable_features, rustc_feature::UnstableFeatures::Cheat)
399 && !sess.opts.unstable_opts.ui_testing);
400
401 let inst = |escape_quotes| {
402 to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
403 };
404
405 let invocation_help = if is_from_cargo {
406 let help = if name == sym::feature && !is_from_external_macro {
407 if let Some((value, _value_span)) = value {
408 Some(diagnostics::unexpected_cfg_value::CargoHelp::AddFeature { value })
409 } else {
410 Some(diagnostics::unexpected_cfg_value::CargoHelp::DefineFeatures)
411 }
412 } else if can_suggest_adding_value && !is_from_external_macro {
413 Some(diagnostics::unexpected_cfg_value::CargoHelp::Other(cargo_help_sub(sess, &inst)))
414 } else {
415 None
416 };
417 diagnostics::unexpected_cfg_value::InvocationHelp::Cargo {
418 help,
419 macro_help: cargo_macro_help(name_span),
420 }
421 } else {
422 let help = if can_suggest_adding_value {
423 Some(diagnostics::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No)))
424 } else {
425 None
426 };
427 diagnostics::unexpected_cfg_value::InvocationHelp::Rustc {
428 help,
429 macro_help: rustc_macro_help(name_span),
430 }
431 };
432
433 diagnostics::UnexpectedCfgValue {
434 code_sugg,
435 invocation_help,
436 has_value: value.is_some(),
437 value: value.map_or_else(String::new, |(v, _span)| v.to_string()),
438 }
439}
440
441fn possible_well_known_names_for_cfg_value(sess: &Session, value: Symbol) -> Vec<Symbol> {
442 #[allow(rustc::potential_query_instability)]
443 let mut names = sess
444 .check_config
445 .well_known_names
446 .iter()
447 .filter(|name| {
448 sess.check_config
449 .expecteds
450 .get(*name)
451 .is_some_and(|expected_values| expected_values.contains(&Some(value)))
452 })
453 .copied()
454 .collect::<Vec<_>>();
455 names.sort_by(|a, b| a.as_str().cmp(b.as_str()));
456 names
457}