rustc_lint/early/diagnostics/
check_cfg.rs1use rustc_hir::def_id::LOCAL_CRATE;
2use rustc_middle::bug;
3use rustc_middle::ty::TyCtxt;
4use rustc_session::Session;
5use rustc_session::config::ExpectedValues;
6use rustc_span::edit_distance::find_best_match_for_name;
7use rustc_span::{ExpnKind, Ident, Span, Symbol, sym};
8
9use crate::lints;
10
11const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35;
12
13enum FilterWellKnownNames {
14 Yes,
15 No,
16}
17
18fn sort_and_truncate_possibilities(
19 sess: &Session,
20 mut possibilities: Vec<Symbol>,
21 filter_well_known_names: FilterWellKnownNames,
22) -> (Vec<Symbol>, usize) {
23 let possibilities_len = possibilities.len();
24
25 let n_possibilities = if sess.opts.unstable_opts.check_cfg_all_expected {
26 possibilities.len()
27 } else {
28 match filter_well_known_names {
29 FilterWellKnownNames::Yes => {
30 possibilities.retain(|cfg_name| {
31 !sess.psess.check_config.well_known_names.contains(cfg_name)
32 });
33 }
34 FilterWellKnownNames::No => {}
35 };
36 std::cmp::min(possibilities.len(), MAX_CHECK_CFG_NAMES_OR_VALUES)
37 };
38
39 possibilities.sort_by(|s1, s2| s1.as_str().cmp(s2.as_str()));
40
41 let and_more = possibilities_len.saturating_sub(n_possibilities);
42 possibilities.truncate(n_possibilities);
43 (possibilities, and_more)
44}
45
46enum EscapeQuotes {
47 Yes,
48 No,
49}
50
51fn to_check_cfg_arg(name: Ident, value: Option<Symbol>, quotes: EscapeQuotes) -> String {
52 if let Some(value) = value {
53 let value = str::escape_debug(value.as_str()).to_string();
54 let values = match quotes {
55 EscapeQuotes::Yes => format!("\\\"{}\\\"", value.replace("\"", "\\\\\\\\\"")),
56 EscapeQuotes::No => format!("\"{value}\""),
57 };
58 format!("cfg({name}, values({values}))")
59 } else {
60 format!("cfg({name})")
61 }
62}
63
64fn cargo_help_sub(
65 sess: &Session,
66 inst: &impl Fn(EscapeQuotes) -> String,
67) -> lints::UnexpectedCfgCargoHelp {
68 let unescaped = &inst(EscapeQuotes::No);
73 if matches!(&sess.opts.crate_name, Some(crate_name) if crate_name == "build_script_build") {
74 lints::UnexpectedCfgCargoHelp::lint_cfg(unescaped)
75 } else {
76 lints::UnexpectedCfgCargoHelp::lint_cfg_and_build_rs(unescaped, &inst(EscapeQuotes::Yes))
77 }
78}
79
80fn rustc_macro_help(span: Span) -> Option<lints::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(lints::UnexpectedCfgRustcMacroHelp { macro_kind: macro_kind.descr(), macro_name })
87 } else {
88 None
89 }
90}
91
92fn cargo_macro_help(
93 tcx: Option<TyCtxt<'_>>,
94 span: Span,
95) -> Option<lints::UnexpectedCfgCargoMacroHelp> {
96 let oexpn = span.ctxt().outer_expn_data();
97 if let Some(def_id) = oexpn.macro_def_id
98 && let ExpnKind::Macro(macro_kind, macro_name) = oexpn.kind
99 && def_id.krate != LOCAL_CRATE
100 && let Some(tcx) = tcx
101 {
102 Some(lints::UnexpectedCfgCargoMacroHelp {
103 macro_kind: macro_kind.descr(),
104 macro_name,
105 crate_name: tcx.crate_name(def_id.krate),
106 })
107 } else {
108 None
109 }
110}
111
112pub(super) fn unexpected_cfg_name(
113 sess: &Session,
114 tcx: Option<TyCtxt<'_>>,
115 (name, name_span): (Symbol, Span),
116 value: Option<(Symbol, Span)>,
117) -> lints::UnexpectedCfgName {
118 #[allow(rustc::potential_query_instability)]
119 let possibilities: Vec<Symbol> = sess.psess.check_config.expecteds.keys().copied().collect();
120
121 let mut names_possibilities: Vec<_> = if value.is_none() {
122 #[allow(rustc::potential_query_instability)]
124 sess.psess
125 .check_config
126 .expecteds
127 .iter()
128 .filter_map(|(k, v)| match v {
129 ExpectedValues::Some(v) if v.contains(&Some(name)) => Some(k),
130 _ => None,
131 })
132 .collect()
133 } else {
134 Vec::new()
135 };
136
137 let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
138 let is_from_external_macro = name_span.in_external_macro(sess.source_map());
139 let mut is_feature_cfg = name == sym::feature;
140
141 fn miscapitalized_boolean(name: Symbol) -> Option<bool> {
142 if name.as_str().eq_ignore_ascii_case("false") {
143 Some(false)
144 } else if name.as_str().eq_ignore_ascii_case("true") {
145 Some(true)
146 } else {
147 None
148 }
149 }
150
151 let code_sugg = if is_feature_cfg && is_from_cargo {
152 lints::unexpected_cfg_name::CodeSuggestion::DefineFeatures
153 } else if let Some((_value, value_span)) = value
155 && name == sym::version
156 {
157 lints::unexpected_cfg_name::CodeSuggestion::VersionSyntax {
158 between_name_and_value: name_span.between(value_span),
159 after_value: value_span.shrink_to_hi(),
160 }
161 } else if value.is_none()
164 && let Some(boolean) = miscapitalized_boolean(name)
166 && sess
168 .source_map()
169 .span_to_snippet(name_span)
170 .map_or(true, |snippet| !snippet.contains("r#"))
171 {
172 lints::unexpected_cfg_name::CodeSuggestion::BooleanLiteral {
173 span: name_span,
174 literal: boolean,
175 }
176 } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
178 is_feature_cfg |= best_match == sym::feature;
179
180 if let Some(ExpectedValues::Some(best_match_values)) =
181 sess.psess.check_config.expecteds.get(&best_match)
182 {
183 #[allow(rustc::potential_query_instability)]
185 let mut possibilities = best_match_values.iter().flatten().collect::<Vec<_>>();
186 possibilities.sort_by_key(|s| s.as_str());
187
188 let get_possibilities_sub = || {
189 if !possibilities.is_empty() {
190 let possibilities =
191 possibilities.iter().copied().cloned().collect::<Vec<_>>().into();
192 Some(lints::unexpected_cfg_name::ExpectedValues { best_match, possibilities })
193 } else {
194 None
195 }
196 };
197
198 let best_match = Ident::new(best_match, name_span);
199 if let Some((value, value_span)) = value {
200 if best_match_values.contains(&Some(value)) {
201 lints::unexpected_cfg_name::CodeSuggestion::SimilarNameAndValue {
202 span: name_span,
203 code: best_match.to_string(),
204 }
205 } else if best_match_values.contains(&None) {
206 lints::unexpected_cfg_name::CodeSuggestion::SimilarNameNoValue {
207 span: name_span.to(value_span),
208 code: best_match.to_string(),
209 }
210 } else if let Some(first_value) = possibilities.first() {
211 lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
212 span: name_span.to(value_span),
213 code: format!("{best_match} = \"{first_value}\""),
214 expected: get_possibilities_sub(),
215 }
216 } else {
217 lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
218 span: name_span.to(value_span),
219 code: best_match.to_string(),
220 expected: get_possibilities_sub(),
221 }
222 }
223 } else {
224 lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
225 span: name_span,
226 code: best_match.to_string(),
227 expected: get_possibilities_sub(),
228 }
229 }
230 } else {
231 lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
232 span: name_span,
233 code: best_match.to_string(),
234 expected: None,
235 }
236 }
237 } else {
238 let similar_values = if !names_possibilities.is_empty() && names_possibilities.len() <= 3 {
239 names_possibilities.sort();
240 names_possibilities
241 .iter()
242 .map(|cfg_name| lints::unexpected_cfg_name::FoundWithSimilarValue {
243 span: name_span,
244 code: format!("{cfg_name} = \"{name}\""),
245 })
246 .collect()
247 } else {
248 vec![]
249 };
250
251 let (possibilities, and_more) =
252 sort_and_truncate_possibilities(sess, possibilities, FilterWellKnownNames::Yes);
253 let expected_names = if !possibilities.is_empty() {
254 let possibilities: Vec<_> =
255 possibilities.into_iter().map(|s| Ident::new(s, name_span)).collect();
256 Some(lints::unexpected_cfg_name::ExpectedNames {
257 possibilities: possibilities.into(),
258 and_more,
259 })
260 } else {
261 None
262 };
263 lints::unexpected_cfg_name::CodeSuggestion::SimilarValues {
264 with_similar_values: similar_values,
265 expected_names,
266 }
267 };
268
269 let inst = |escape_quotes| {
270 to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
271 };
272
273 let invocation_help = if is_from_cargo {
274 let help = if !is_feature_cfg && !is_from_external_macro {
275 Some(cargo_help_sub(sess, &inst))
276 } else {
277 None
278 };
279 lints::unexpected_cfg_name::InvocationHelp::Cargo {
280 help,
281 macro_help: cargo_macro_help(tcx, name_span),
282 }
283 } else {
284 let help = lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No));
285 lints::unexpected_cfg_name::InvocationHelp::Rustc {
286 help,
287 macro_help: rustc_macro_help(name_span),
288 }
289 };
290
291 lints::UnexpectedCfgName { code_sugg, invocation_help, name }
292}
293
294pub(super) fn unexpected_cfg_value(
295 sess: &Session,
296 tcx: Option<TyCtxt<'_>>,
297 (name, name_span): (Symbol, Span),
298 value: Option<(Symbol, Span)>,
299) -> lints::UnexpectedCfgValue {
300 let Some(ExpectedValues::Some(values)) = &sess.psess.check_config.expecteds.get(&name) else {
301 bug!(
302 "it shouldn't be possible to have a diagnostic on a value whose name is not in values"
303 );
304 };
305 let mut have_none_possibility = false;
306 #[allow(rustc::potential_query_instability)]
309 let possibilities: Vec<Symbol> = values
310 .iter()
311 .inspect(|a| have_none_possibility |= a.is_none())
312 .copied()
313 .flatten()
314 .collect();
315
316 let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
317 let is_from_external_macro = name_span.in_external_macro(sess.source_map());
318
319 let code_sugg = if !possibilities.is_empty() {
322 let expected_values = {
323 let (possibilities, and_more) = sort_and_truncate_possibilities(
324 sess,
325 possibilities.clone(),
326 FilterWellKnownNames::No,
327 );
328 lints::unexpected_cfg_value::ExpectedValues {
329 name,
330 have_none_possibility,
331 possibilities: possibilities.into(),
332 and_more,
333 }
334 };
335
336 let suggestion = if let Some((value, value_span)) = value {
337 if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
339 Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SimilarName {
340 span: value_span,
341 best_match,
342 })
343 } else {
344 None
345 }
346 } else if let &[first_possibility] = &possibilities[..] {
347 Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue {
348 span: name_span.shrink_to_hi(),
349 first_possibility,
350 })
351 } else {
352 None
353 };
354
355 lints::unexpected_cfg_value::CodeSuggestion::ChangeValue { expected_values, suggestion }
356 } else if have_none_possibility {
357 let suggestion =
358 value.map(|(_value, value_span)| lints::unexpected_cfg_value::RemoveValueSuggestion {
359 span: name_span.shrink_to_hi().to(value_span),
360 });
361 lints::unexpected_cfg_value::CodeSuggestion::RemoveValue { suggestion, name }
362 } else {
363 let span = if let Some((_value, value_span)) = value {
364 name_span.to(value_span)
365 } else {
366 name_span
367 };
368 let suggestion = lints::unexpected_cfg_value::RemoveConditionSuggestion { span };
369 lints::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name }
370 };
371
372 let can_suggest_adding_value = !sess.psess.check_config.well_known_names.contains(&name)
376 || (matches!(sess.psess.unstable_features, rustc_feature::UnstableFeatures::Cheat)
381 && !sess.opts.unstable_opts.ui_testing);
382
383 let inst = |escape_quotes| {
384 to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
385 };
386
387 let invocation_help = if is_from_cargo {
388 let help = if name == sym::feature && !is_from_external_macro {
389 if let Some((value, _value_span)) = value {
390 Some(lints::unexpected_cfg_value::CargoHelp::AddFeature { value })
391 } else {
392 Some(lints::unexpected_cfg_value::CargoHelp::DefineFeatures)
393 }
394 } else if can_suggest_adding_value && !is_from_external_macro {
395 Some(lints::unexpected_cfg_value::CargoHelp::Other(cargo_help_sub(sess, &inst)))
396 } else {
397 None
398 };
399 lints::unexpected_cfg_value::InvocationHelp::Cargo {
400 help,
401 macro_help: cargo_macro_help(tcx, name_span),
402 }
403 } else {
404 let help = if can_suggest_adding_value {
405 Some(lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No)))
406 } else {
407 None
408 };
409 lints::unexpected_cfg_value::InvocationHelp::Rustc {
410 help,
411 macro_help: rustc_macro_help(name_span),
412 }
413 };
414
415 lints::UnexpectedCfgValue {
416 code_sugg,
417 invocation_help,
418 has_value: value.is_some(),
419 value: value.map_or_else(String::new, |(v, _span)| v.to_string()),
420 }
421}