Skip to main content

clippy_config/
de.rs

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
55/// Attempts to find the closest matching string from the list. Returns `None`
56/// if the edit distance is too large.
57pub fn find_closest_match<'a>(s: &str, options: &[&'a str]) -> Option<&'a str> {
58    // Don't treat `_` to `-` and case conversions as an edit.
59    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
68/// Creates a message listing all possible values suitable for use in `Diag::note`.
69pub 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    // Print everything on one line if it will fit and there aren't too many values.
76    // e.g. "note: possible values: `value1`, `value2`, `value3`"
77    //
78    // If there are too many values and the note would exceed the terminal width lay the
79    // values out into columns. e.g.
80    //    possible values:
81    //    value1    value5
82    //    value2    value6
83    //    value3    value7
84    //    value4
85    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        // The minimum width a column could possibly have.
94        let min_width = values.iter().map(|x| x.len()).min().unwrap_or(0);
95        // The number of columns that fit using the minimum width.
96        let max_col = dcx.width.saturating_sub(NOTE_WITH_MSG_LEN) / (min_width + TBL_SEP.len());
97
98        // Determine the starting dimensions of the search.
99        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            // Only add a new column if it will remove several rows.
102            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            // Also handles the case where `max_col < 2`
110            ControlFlow::Continue(x) => (x, max_col),
111            ControlFlow::Break(x) => x,
112        };
113
114        // The current total width required.
115        let mut total_width = init_col_count * (min_width + TBL_SEP.len()) + NOTE_LEN;
116        // The current width of each column without the prefix.
117        let mut col_widths = vec![min_width; init_col_count];
118
119        // Determine the required width of each column.
120        'outer: loop {
121            // Also handles the case where `max_col` is zero.
122            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                            // Remove a column and reset the metrics then retry.
136                            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                        // Don't print trailing whitespace on the right edge.
156                        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
169/// A type which can be constructed from a default value.
170pub trait FromDefault<T>: Sized {
171    /// Creates this value from a default value.
172    fn from_default(default: T) -> Self;
173    /// Writes the default value to a string.
174    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        // will cause an error in the metadata collector
199        ""
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
282/// A type which can be deserialized from a toml value.
283pub trait Deserialize: Sized {
284    /// Attempt to deserialize the value. Returns `None` and raises an error on failure.
285    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            // A value's deserialize impl will have already given an error
397            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
460/// A type which can be deserialized from a toml value with a fallback to a default value.
461pub trait DeserializeOrDefault<T>: Sized {
462    /// Attempt to deserialize the value. Returns the default value and raises an error on failure.
463    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                    // Duplicate keys are handled by the toml parser
547                    $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}