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 let possibilities =
187 possibilities.iter().copied().cloned().collect::<Vec<_>>().into();
188 Some(diagnostics::unexpected_cfg_name::ExpectedValues {
189 best_match,
190 possibilities,
191 })
192 } else {
193 None
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 let possibilities: Vec<_> =
254 possibilities.into_iter().map(|s| Ident::new(s, name_span)).collect();
255 Some(diagnostics::unexpected_cfg_name::ExpectedNames {
256 possibilities: possibilities.into(),
257 and_more,
258 })
259 } else {
260 None
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 if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
355 Some(diagnostics::unexpected_cfg_value::ChangeValueSuggestion::SimilarName {
356 span: value_span,
357 best_match,
358 })
359 } else {
360 None
361 }
362 } else if let &[first_possibility] = &possibilities[..] {
363 Some(diagnostics::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue {
364 span: name_span.shrink_to_hi(),
365 first_possibility,
366 })
367 } else {
368 None
369 };
370
371 diagnostics::unexpected_cfg_value::CodeSuggestion::ChangeValue {
372 expected_values,
373 suggestion,
374 }
375 } else if have_none_possibility {
376 let suggestion = value.map(|(_value, value_span)| {
377 diagnostics::unexpected_cfg_value::RemoveValueSuggestion {
378 span: name_span.shrink_to_hi().to(value_span),
379 }
380 });
381 diagnostics::unexpected_cfg_value::CodeSuggestion::RemoveValue { suggestion, name }
382 } else {
383 let span = if let Some((_value, value_span)) = value {
384 name_span.to(value_span)
385 } else {
386 name_span
387 };
388 let suggestion = diagnostics::unexpected_cfg_value::RemoveConditionSuggestion { span };
389 diagnostics::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name }
390 };
391
392 let can_suggest_adding_value = !sess.check_config.well_known_names.contains(&name)
396 || (#[allow(non_exhaustive_omitted_patterns)] match sess.unstable_features {
rustc_feature::UnstableFeatures::Cheat => true,
_ => false,
}matches!(sess.unstable_features, rustc_feature::UnstableFeatures::Cheat)
401 && !sess.opts.unstable_opts.ui_testing);
402
403 let inst = |escape_quotes| {
404 to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
405 };
406
407 let invocation_help = if is_from_cargo {
408 let help = if name == sym::feature && !is_from_external_macro {
409 if let Some((value, _value_span)) = value {
410 Some(diagnostics::unexpected_cfg_value::CargoHelp::AddFeature { value })
411 } else {
412 Some(diagnostics::unexpected_cfg_value::CargoHelp::DefineFeatures)
413 }
414 } else if can_suggest_adding_value && !is_from_external_macro {
415 Some(diagnostics::unexpected_cfg_value::CargoHelp::Other(cargo_help_sub(sess, &inst)))
416 } else {
417 None
418 };
419 diagnostics::unexpected_cfg_value::InvocationHelp::Cargo {
420 help,
421 macro_help: cargo_macro_help(name_span),
422 }
423 } else {
424 let help = if can_suggest_adding_value {
425 Some(diagnostics::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No)))
426 } else {
427 None
428 };
429 diagnostics::unexpected_cfg_value::InvocationHelp::Rustc {
430 help,
431 macro_help: rustc_macro_help(name_span),
432 }
433 };
434
435 diagnostics::UnexpectedCfgValue {
436 code_sugg,
437 invocation_help,
438 has_value: value.is_some(),
439 value: value.map_or_else(String::new, |(v, _span)| v.to_string()),
440 }
441}
442
443fn possible_well_known_names_for_cfg_value(sess: &Session, value: Symbol) -> Vec<Symbol> {
444 #[allow(rustc::potential_query_instability)]
445 let mut names = sess
446 .check_config
447 .well_known_names
448 .iter()
449 .filter(|name| {
450 sess.check_config
451 .expecteds
452 .get(*name)
453 .map(|expected_values| expected_values.contains(&Some(value)))
454 .unwrap_or_default()
455 })
456 .copied()
457 .collect::<Vec<_>>();
458 names.sort_by(|a, b| a.as_str().cmp(b.as_str()));
459 names
460}