rustc_lint/early/diagnostics/
check_cfg.rs
1use 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 let code_sugg = if is_feature_cfg && is_from_cargo {
142 lints::unexpected_cfg_name::CodeSuggestion::DefineFeatures
143 } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
145 is_feature_cfg |= best_match == sym::feature;
146
147 if let Some(ExpectedValues::Some(best_match_values)) =
148 sess.psess.check_config.expecteds.get(&best_match)
149 {
150 #[allow(rustc::potential_query_instability)]
152 let mut possibilities = best_match_values.iter().flatten().collect::<Vec<_>>();
153 possibilities.sort_by_key(|s| s.as_str());
154
155 let get_possibilities_sub = || {
156 if !possibilities.is_empty() {
157 let possibilities =
158 possibilities.iter().copied().cloned().collect::<Vec<_>>().into();
159 Some(lints::unexpected_cfg_name::ExpectedValues { best_match, possibilities })
160 } else {
161 None
162 }
163 };
164
165 let best_match = Ident::new(best_match, name_span);
166 if let Some((value, value_span)) = value {
167 if best_match_values.contains(&Some(value)) {
168 lints::unexpected_cfg_name::CodeSuggestion::SimilarNameAndValue {
169 span: name_span,
170 code: best_match.to_string(),
171 }
172 } else if best_match_values.contains(&None) {
173 lints::unexpected_cfg_name::CodeSuggestion::SimilarNameNoValue {
174 span: name_span.to(value_span),
175 code: best_match.to_string(),
176 }
177 } else if let Some(first_value) = possibilities.first() {
178 lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
179 span: name_span.to(value_span),
180 code: format!("{best_match} = \"{first_value}\""),
181 expected: get_possibilities_sub(),
182 }
183 } else {
184 lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues {
185 span: name_span.to(value_span),
186 code: best_match.to_string(),
187 expected: get_possibilities_sub(),
188 }
189 }
190 } else {
191 lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
192 span: name_span,
193 code: best_match.to_string(),
194 expected: get_possibilities_sub(),
195 }
196 }
197 } else {
198 lints::unexpected_cfg_name::CodeSuggestion::SimilarName {
199 span: name_span,
200 code: best_match.to_string(),
201 expected: None,
202 }
203 }
204 } else {
205 let similar_values = if !names_possibilities.is_empty() && names_possibilities.len() <= 3 {
206 names_possibilities.sort();
207 names_possibilities
208 .iter()
209 .map(|cfg_name| lints::unexpected_cfg_name::FoundWithSimilarValue {
210 span: name_span,
211 code: format!("{cfg_name} = \"{name}\""),
212 })
213 .collect()
214 } else {
215 vec![]
216 };
217
218 let (possibilities, and_more) =
219 sort_and_truncate_possibilities(sess, possibilities, FilterWellKnownNames::Yes);
220 let expected_names = if !possibilities.is_empty() {
221 let possibilities: Vec<_> =
222 possibilities.into_iter().map(|s| Ident::new(s, name_span)).collect();
223 Some(lints::unexpected_cfg_name::ExpectedNames {
224 possibilities: possibilities.into(),
225 and_more,
226 })
227 } else {
228 None
229 };
230 lints::unexpected_cfg_name::CodeSuggestion::SimilarValues {
231 with_similar_values: similar_values,
232 expected_names,
233 }
234 };
235
236 let inst = |escape_quotes| {
237 to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
238 };
239
240 let invocation_help = if is_from_cargo {
241 let help = if !is_feature_cfg && !is_from_external_macro {
242 Some(cargo_help_sub(sess, &inst))
243 } else {
244 None
245 };
246 lints::unexpected_cfg_name::InvocationHelp::Cargo {
247 help,
248 macro_help: cargo_macro_help(tcx, name_span),
249 }
250 } else {
251 let help = lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No));
252 lints::unexpected_cfg_name::InvocationHelp::Rustc {
253 help,
254 macro_help: rustc_macro_help(name_span),
255 }
256 };
257
258 lints::UnexpectedCfgName { code_sugg, invocation_help, name }
259}
260
261pub(super) fn unexpected_cfg_value(
262 sess: &Session,
263 tcx: Option<TyCtxt<'_>>,
264 (name, name_span): (Symbol, Span),
265 value: Option<(Symbol, Span)>,
266) -> lints::UnexpectedCfgValue {
267 let Some(ExpectedValues::Some(values)) = &sess.psess.check_config.expecteds.get(&name) else {
268 bug!(
269 "it shouldn't be possible to have a diagnostic on a value whose name is not in values"
270 );
271 };
272 let mut have_none_possibility = false;
273 #[allow(rustc::potential_query_instability)]
276 let possibilities: Vec<Symbol> = values
277 .iter()
278 .inspect(|a| have_none_possibility |= a.is_none())
279 .copied()
280 .flatten()
281 .collect();
282
283 let is_from_cargo = rustc_session::utils::was_invoked_from_cargo();
284 let is_from_external_macro = name_span.in_external_macro(sess.source_map());
285
286 let code_sugg = if !possibilities.is_empty() {
289 let expected_values = {
290 let (possibilities, and_more) = sort_and_truncate_possibilities(
291 sess,
292 possibilities.clone(),
293 FilterWellKnownNames::No,
294 );
295 lints::unexpected_cfg_value::ExpectedValues {
296 name,
297 have_none_possibility,
298 possibilities: possibilities.into(),
299 and_more,
300 }
301 };
302
303 let suggestion = if let Some((value, value_span)) = value {
304 if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
306 Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SimilarName {
307 span: value_span,
308 best_match,
309 })
310 } else {
311 None
312 }
313 } else if let &[first_possibility] = &possibilities[..] {
314 Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue {
315 span: name_span.shrink_to_hi(),
316 first_possibility,
317 })
318 } else {
319 None
320 };
321
322 lints::unexpected_cfg_value::CodeSuggestion::ChangeValue { expected_values, suggestion }
323 } else if have_none_possibility {
324 let suggestion =
325 value.map(|(_value, value_span)| lints::unexpected_cfg_value::RemoveValueSuggestion {
326 span: name_span.shrink_to_hi().to(value_span),
327 });
328 lints::unexpected_cfg_value::CodeSuggestion::RemoveValue { suggestion, name }
329 } else {
330 let span = if let Some((_value, value_span)) = value {
331 name_span.to(value_span)
332 } else {
333 name_span
334 };
335 let suggestion = lints::unexpected_cfg_value::RemoveConditionSuggestion { span };
336 lints::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name }
337 };
338
339 let can_suggest_adding_value = !sess.psess.check_config.well_known_names.contains(&name)
343 || (matches!(sess.psess.unstable_features, rustc_feature::UnstableFeatures::Cheat)
348 && !sess.opts.unstable_opts.ui_testing);
349
350 let inst = |escape_quotes| {
351 to_check_cfg_arg(Ident::new(name, name_span), value.map(|(v, _s)| v), escape_quotes)
352 };
353
354 let invocation_help = if is_from_cargo {
355 let help = if name == sym::feature && !is_from_external_macro {
356 if let Some((value, _value_span)) = value {
357 Some(lints::unexpected_cfg_value::CargoHelp::AddFeature { value })
358 } else {
359 Some(lints::unexpected_cfg_value::CargoHelp::DefineFeatures)
360 }
361 } else if can_suggest_adding_value && !is_from_external_macro {
362 Some(lints::unexpected_cfg_value::CargoHelp::Other(cargo_help_sub(sess, &inst)))
363 } else {
364 None
365 };
366 lints::unexpected_cfg_value::InvocationHelp::Cargo {
367 help,
368 macro_help: cargo_macro_help(tcx, name_span),
369 }
370 } else {
371 let help = if can_suggest_adding_value {
372 Some(lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No)))
373 } else {
374 None
375 };
376 lints::unexpected_cfg_value::InvocationHelp::Rustc {
377 help,
378 macro_help: rustc_macro_help(name_span),
379 }
380 };
381
382 lints::UnexpectedCfgValue {
383 code_sugg,
384 invocation_help,
385 has_value: value.is_some(),
386 value: value.map_or_else(String::new, |(v, _span)| v.to_string()),
387 }
388}