rustc_attr_parsing/attributes/diagnostic/
check_cfg.rs1use 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 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 #[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 } 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 } else if value.is_none()
153 && let Some(boolean) = miscapitalized_boolean(name)
155 && 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 } 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 #[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 #[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 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 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 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 let can_suggest_adding_value = !sess.psess.check_config.well_known_names.contains(&name)
382 || (#[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}