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