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
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 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 #[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 } 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 } else if value.is_none()
151 && let Some(boolean) = miscapitalized_boolean(name)
153 && 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 } 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 #[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 #[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 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 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 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 let can_suggest_adding_value = !sess.check_config.well_known_names.contains(&name)
380 || (#[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}