1use arrayvec::ArrayVec;
2use core::str::FromStr as _;
3use itertools::Itertools as _;
4use rustc_attr_parsing::parse_version;
5use rustc_errors::{DiagCtxtHandle, DiagMessage};
6use rustc_hir::attrs::RustcVersion;
7use rustc_session::Session;
8use rustc_session::config::ErrorOutputType;
9use rustc_span::edit_distance::edit_distance;
10use rustc_span::{BytePos, Pos as _, Span, Spanned, Symbol};
11use std::collections::{HashMap, HashSet};
12use std::fmt::{self, Display, Write as _};
13use std::hash::{BuildHasher, Hash};
14use std::marker::PhantomData;
15use std::ops::{ControlFlow, Range};
16use toml::de::DeValue;
17
18pub type TomlValue<'a> = toml::Spanned<DeValue<'a>>;
19
20pub struct DiagCtxt<'a> {
21 pub inner: DiagCtxtHandle<'a>,
22 pub width: usize,
23 pub render_pretty: bool,
24 offset: usize,
25}
26impl<'a> DiagCtxt<'a> {
27 pub fn new(sess: &'a Session, offset: usize) -> Self {
28 Self {
29 inner: sess.dcx(),
30 width: sess.diagnostic_width(),
31 render_pretty: match sess.opts.error_format {
32 ErrorOutputType::HumanReadable { .. } => true,
33 ErrorOutputType::Json { pretty, .. } => pretty,
34 },
35 offset,
36 }
37 }
38
39 pub fn make_sp(&self, range: Range<usize>) -> Span {
40 Span::with_root_ctxt(
41 BytePos::from_usize(self.offset + range.start),
42 BytePos::from_usize(self.offset + range.end),
43 )
44 }
45
46 pub fn span_err(&self, range: Range<usize>, msg: impl Into<DiagMessage>) {
47 self.inner.span_err(self.make_sp(range), msg);
48 }
49
50 pub fn span_warn(&self, range: Range<usize>, msg: impl Into<DiagMessage>) {
51 self.inner.span_warn(self.make_sp(range), msg);
52 }
53}
54
55pub fn find_closest_match<'a>(s: &str, options: &[&'a str]) -> Option<&'a str> {
58 let mut s = s.replace('_', "-");
60 s.make_ascii_lowercase();
61 options
62 .iter()
63 .filter_map(|&option| edit_distance(&s, &option.to_ascii_lowercase(), 4).map(|x| (x, option)))
64 .min_by_key(|&(dist, _)| dist)
65 .map(|(_, x)| x)
66}
67
68pub fn create_value_list_msg(dcx: &DiagCtxt<'_>, values: &[&str]) -> String {
70 const NOTE_WITH_MSG_LEN: usize = " = note: possible values: ".len();
71 const NOTE_LEN: usize = " = note: ".len();
72 const TBL_SEP: &str = " ";
73 const INLINE_SEP: &str = ", ";
74
75 if values.len() <= 8
86 && NOTE_WITH_MSG_LEN + values.iter().map(|x| x.len() + INLINE_SEP.len() + 2).sum::<usize>() <= dcx.width
87 {
88 format!(
89 "possible values: {}",
90 values.iter().format_with(INLINE_SEP, |x, f| f(&format_args!("`{x}`"))),
91 )
92 } else if dcx.render_pretty {
93 let min_width = values.iter().map(|x| x.len()).min().unwrap_or(0);
95 let max_col = dcx.width.saturating_sub(NOTE_WITH_MSG_LEN) / (min_width + TBL_SEP.len());
97
98 let start_size = (2..=max_col).try_fold(values.len(), |row_count, col_count| {
100 let needed_rows = values.len().div_ceil(col_count);
101 if needed_rows + 3 <= row_count {
103 ControlFlow::Continue(needed_rows)
104 } else {
105 ControlFlow::Break((row_count, col_count - 1))
106 }
107 });
108 let (mut row_count, init_col_count) = match start_size {
109 ControlFlow::Continue(x) => (x, max_col),
111 ControlFlow::Break(x) => x,
112 };
113
114 let mut total_width = init_col_count * (min_width + TBL_SEP.len()) + NOTE_LEN;
116 let mut col_widths = vec![min_width; init_col_count];
118
119 'outer: loop {
121 if col_widths.len() <= 1 {
123 return format!(
124 "possible values:\n{}",
125 values.iter().format_with("\n", |x, f| f(&format_args!("{x}"))),
126 );
127 }
128 for (col_values, col_width) in values.chunks(row_count).zip(col_widths.iter_mut()) {
129 for value in col_values {
130 if value.len() > *col_width {
131 let delta = value.len() - *col_width;
132 *col_width += delta;
133 total_width += delta;
134 if total_width > dcx.width {
135 col_widths.pop();
137 row_count = values.len().div_ceil(col_widths.len());
138 col_widths.fill(min_width);
139 total_width = col_widths.len() * (min_width + TBL_SEP.len()) + NOTE_LEN;
140 continue 'outer;
141 }
142 }
143 }
144 }
145 break;
146 }
147
148 format!(
149 "possible values:\n{}",
150 (0..row_count).format_with("\n", |row, f| {
151 f(&(row..values.len())
152 .step_by(row_count)
153 .zip(&col_widths)
154 .format_with(TBL_SEP, |(i, &width), f| {
155 let width = if i + row_count < values.len() { width } else { 0 };
157 f(&format_args!("{:width$}", values[i]))
158 }))
159 }),
160 )
161 } else {
162 format!(
163 "possible values:{}",
164 values.iter().format_with("", |x, f| f(&format_args!("\n{x}"))),
165 )
166 }
167}
168
169pub trait FromDefault<T>: Sized {
171 fn from_default(default: T) -> Self;
173 fn display_default(default: T) -> impl Display;
175}
176
177macro_rules! impl_from_default_passthrough {
178 ($($ty:ty),*) => {$(
179 impl FromDefault<$ty> for $ty {
180 #[inline]
181 fn from_default(default: $ty) -> Self {
182 default
183 }
184 #[inline]
185 fn display_default(default: $ty) -> impl Display {
186 default
187 }
188 }
189 )*}
190}
191impl_from_default_passthrough!(bool, u8, i8, u16, i16, u32, i32, u64, i64, f32, f64);
192
193impl<T> FromDefault<()> for Option<T> {
194 fn from_default((): ()) -> Self {
195 None
196 }
197 fn display_default((): ()) -> impl Display {
198 ""
200 }
201}
202
203struct DisplayStr(&'static str);
204impl Display for DisplayStr {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 fmt::Debug::fmt(self.0, f)
207 }
208}
209
210impl FromDefault<&'static str> for String {
211 fn from_default(default: &'static str) -> Self {
212 default.into()
213 }
214 fn display_default(default: &'static str) -> impl Display {
215 DisplayStr(default)
216 }
217}
218
219impl<T> FromDefault<()> for Vec<T> {
220 fn from_default((): ()) -> Self {
221 Vec::new()
222 }
223 fn display_default((): ()) -> impl Display {
224 "[]"
225 }
226}
227
228impl<T, S: Default> FromDefault<()> for HashSet<T, S> {
229 fn from_default((): ()) -> Self {
230 HashSet::default()
231 }
232 fn display_default((): ()) -> impl Display {
233 "[]"
234 }
235}
236
237struct DisplaySlice<T: 'static, U>(&'static [T], PhantomData<U>);
238impl<T, U> Display for DisplaySlice<T, U>
239where
240 T: 'static + Copy,
241 U: FromDefault<T>,
242{
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 f.write_char('[')?;
245 if let Some((head, tail)) = self.0.split_first() {
246 U::display_default(*head).fmt(f)?;
247 for x in tail {
248 write!(f, ", {}", U::display_default(*x))?;
249 }
250 }
251 f.write_char(']')
252 }
253}
254
255impl<T, U> FromDefault<&'static [U]> for Vec<T>
256where
257 T: FromDefault<U>,
258 U: 'static + Copy,
259{
260 fn from_default(default: &'static [U]) -> Self {
261 default.iter().map(|&x| T::from_default(x)).collect()
262 }
263 fn display_default(default: &'static [U]) -> impl Display {
264 DisplaySlice::<_, T>(default, PhantomData)
265 }
266}
267
268impl<T, U, S> FromDefault<&'static [U]> for HashSet<T, S>
269where
270 T: FromDefault<U> + Eq + Hash,
271 U: 'static + Copy,
272 S: Default + BuildHasher,
273{
274 fn from_default(default: &'static [U]) -> Self {
275 default.iter().map(|&x| T::from_default(x)).collect()
276 }
277 fn display_default(default: &'static [U]) -> impl Display {
278 DisplaySlice::<_, T>(default, PhantomData)
279 }
280}
281
282pub trait Deserialize: Sized {
284 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self>;
286}
287
288impl Deserialize for bool {
289 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
290 match value.get_ref().as_bool() {
291 None => {
292 dcx.span_err(value.span(), "expected a boolean");
293 None
294 },
295 x => x,
296 }
297 }
298}
299
300macro_rules! impl_deserialize_int {
301 ($($ty:ident),*) => {$(
302 impl Deserialize for $ty {
303 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
304 match value.get_ref().as_integer() {
305 None => {
306 dcx.span_err(value.span(), "expected an integer");
307 None
308 },
309 Some(x) => match $ty::from_str_radix(x.as_str(), x.radix()) {
310 Ok(x) => Some(x),
311 Err(_) => {
312 dcx.span_err(
313 value.span(),
314 format!("integer is not within the expected range ({}..{})", $ty::MIN, $ty::MAX),
315 );
316 None
317 }
318 }
319 }
320 }
321 }
322 )*}
323}
324impl_deserialize_int!(i8, u8, i16, u16, i32, u32, i64, u64, i128, u128);
325
326macro_rules! impl_deserialize_float {
327 ($($ty:ident),*) => {$(
328 impl Deserialize for $ty {
329 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
330 match value.get_ref().as_float() {
331 None => {
332 dcx.span_err(value.span(), "expected a number");
333 None
334 },
335 Some(x) => match $ty::from_str(x.as_str()) {
336 Ok(x) => Some(x),
337 Err(_) => {
338 dcx.span_err(value.span(), "failed to parse number");
339 None
340 }
341 }
342 }
343 }
344 }
345 )*}
346}
347impl_deserialize_float!(f32, f64);
348
349impl Deserialize for String {
350 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
351 if let Some(x) = value.get_ref().as_str() {
352 Some(x.into())
353 } else {
354 dcx.span_err(value.span(), "expected a string");
355 None
356 }
357 }
358}
359
360impl Deserialize for Symbol {
361 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
362 if let Some(x) = value.get_ref().as_str() {
363 Some(Symbol::intern(x))
364 } else {
365 dcx.span_err(value.span(), "expected a string");
366 None
367 }
368 }
369}
370
371impl Deserialize for RustcVersion {
372 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
373 if let Some(x) = value.get_ref().as_str() {
374 if let Some(x) = parse_version(Symbol::intern(x)) {
375 Some(x)
376 } else {
377 dcx.span_err(value.span(), "failed to parse rust version");
378 None
379 }
380 } else {
381 dcx.span_err(value.span(), "expected a version string");
382 None
383 }
384 }
385}
386
387impl<T: Deserialize, const N: usize> Deserialize for [T; N] {
388 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
389 if let Some(values) = value.get_ref().as_array()
390 && values.len() == N
391 {
392 let values = values
393 .iter()
394 .filter_map(|x| T::deserialize(dcx, x))
395 .collect::<ArrayVec<T, N>>();
396 values.into_inner().ok()
398 } else {
399 dcx.span_err(value.span(), "expected an array of length `{N}`");
400 None
401 }
402 }
403}
404
405impl<T: Deserialize> Deserialize for Vec<T> {
406 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
407 if let Some(values) = value.get_ref().as_array() {
408 Some(values.iter().filter_map(|x| T::deserialize(dcx, x)).collect())
409 } else {
410 dcx.span_err(value.span(), "expected an array");
411 None
412 }
413 }
414}
415
416impl<T, S> Deserialize for HashSet<T, S>
417where
418 T: Deserialize + Eq + Hash,
419 S: Default + BuildHasher,
420{
421 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
422 if let Some(values) = value.as_ref().as_array() {
423 Some(values.iter().filter_map(|x| T::deserialize(dcx, x)).collect())
424 } else {
425 dcx.span_err(value.span(), "expected an array");
426 None
427 }
428 }
429}
430
431impl<T, S> Deserialize for HashMap<T, Span, S>
432where
433 T: Deserialize + Eq + Hash,
434 S: Default + BuildHasher,
435{
436 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
437 if let Some(values) = value.as_ref().as_array() {
438 Some(
439 values
440 .iter()
441 .filter_map(|x| T::deserialize(dcx, x).map(|value| (value, dcx.make_sp(x.span()))))
442 .collect(),
443 )
444 } else {
445 dcx.span_err(value.span(), "expected an array");
446 None
447 }
448 }
449}
450
451impl<T: Deserialize> Deserialize for Spanned<T> {
452 fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
453 T::deserialize(dcx, value).map(|x| Spanned {
454 node: x,
455 span: dcx.make_sp(value.span()),
456 })
457 }
458}
459
460pub trait DeserializeOrDefault<T>: Sized {
462 fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: T) -> Self;
464}
465
466impl<T: Deserialize + FromDefault<T>> DeserializeOrDefault<T> for T {
467 fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: T) -> Self {
468 T::deserialize(dcx, value).unwrap_or_else(|| T::from_default(default))
469 }
470}
471
472impl<T: Deserialize + Default> DeserializeOrDefault<()> for T {
473 fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, (): ()) -> Self {
474 T::deserialize(dcx, value).unwrap_or_default()
475 }
476}
477
478impl<T: Deserialize> DeserializeOrDefault<()> for Option<T> {
479 fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, (): ()) -> Self {
480 T::deserialize(dcx, value)
481 }
482}
483
484pub fn deserialize_array<T, U, C>(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: &'static [U]) -> C
485where
486 T: Deserialize + FromDefault<U>,
487 U: Copy,
488 C: FromIterator<T> + Extend<T>,
489{
490 let default_iter = default.iter().map(|&x| T::from_default(x));
491 if let Some(values) = value.as_ref().as_array() {
492 let mut has_default = false;
493 let mut res: C = values
494 .iter()
495 .filter(|x| {
496 if x.as_ref().as_str().is_some_and(|x| x == "..") {
497 if has_default {
498 dcx.span_warn(value.span(), "duplicate `..` item");
499 }
500 has_default = true;
501 false
502 } else {
503 true
504 }
505 })
506 .filter_map(|x| T::deserialize(dcx, x))
507 .collect();
508 if has_default {
509 res.extend(default_iter);
510 }
511 res
512 } else {
513 dcx.span_err(value.span(), "expected an array");
514 default_iter.collect()
515 }
516}
517
518impl<T, U> DeserializeOrDefault<&'static [U]> for Vec<T>
519where
520 T: Deserialize + FromDefault<U>,
521 U: Copy,
522{
523 fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: &'static [U]) -> Self {
524 deserialize_array(dcx, value, default)
525 }
526}
527
528impl<T, U, S> DeserializeOrDefault<&'static [U]> for HashSet<T, S>
529where
530 T: Deserialize + FromDefault<U> + Eq + Hash,
531 U: Copy,
532 S: Default + BuildHasher,
533{
534 fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: &'static [U]) -> Self {
535 deserialize_array(dcx, value, default)
536 }
537}
538
539macro_rules! deserialize_table {
540 ($dcx:ident, $table:ident, $($name:ident($name_str:literal): $ty:ty,)+) => {
541 $(let mut $name: Option<$ty> = None;)+
542
543 for (key, value) in $table.iter() {
544 match &**key.get_ref() {
545 $($name_str => {
546 $name = <$ty as crate::de::Deserialize>::deserialize($dcx, value.into());
548 },)+
549 _ => {
550 const NAMES: &[&str] = &[$($name_str),*];
551 let sp = $dcx.make_sp(key.span());
552 let mut diag = $dcx.inner.struct_span_err(sp, "unknown key");
553 if let Some(sugg) = crate::de::find_closest_match(key.as_ref(), NAMES) {
554 diag.span_suggestion(sp, "did you mean", sugg, Applicability::MaybeIncorrect);
555 }
556 diag.note(crate::de::create_value_list_msg($dcx, NAMES));
557 diag.emit();
558 },
559 }
560 }
561 }
562}