Skip to main content

clippy_config/
types.rs

1use crate::de::{
2    Deserialize, DeserializeOrDefault, DiagCtxt, FromDefault, TomlValue, create_value_list_msg, find_closest_match,
3};
4use clippy_utils::paths::{PathNS, find_crates, lookup_path};
5use core::fmt::{self, Display};
6use itertools::Itertools as _;
7use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8use rustc_errors::{Applicability, Diag};
9use rustc_hir::PrimTy;
10use rustc_hir::def::DefKind;
11use rustc_hir::def_id::DefIdMap;
12use rustc_middle::ty::TyCtxt;
13use rustc_session::Session;
14use rustc_span::{Span, Spanned, Symbol};
15use std::collections::HashMap;
16
17macro_rules! concat_expr {
18    ($($e:expr)*) => {
19        concat!($($e),*)
20    }
21}
22
23macro_rules! name_or_lit {
24    ($name:ident) => {
25        stringify!($name)
26    };
27    ($name:ident $lit:literal) => {
28        $lit
29    };
30}
31
32macro_rules! conf_enum {
33    (
34        $(#[$attrs:meta])*
35        $vis:vis $name:ident {$(
36            $(#[$var_attrs:meta])*
37            $var_name:ident $(($var_lit:literal))?,
38        )*}
39    ) => {
40        $(#[$attrs])*
41        #[derive(Clone, Copy)]
42        $vis enum $name {$(
43            $(#[$var_attrs])*
44            $var_name,
45        )*}
46        impl $name {
47            const NAMES: &[&'static str] = &[$(name_or_lit!($var_name $($var_lit)?)),*];
48            #[allow(dead_code)]
49            const COUNT: usize = {
50                enum __ITEMS__ { $($var_name,)* __COUNT__ }
51                __ITEMS__::__COUNT__ as usize
52            };
53
54            pub fn name(self) -> &'static str {
55                Self::NAMES[self as usize]
56            }
57            #[allow(clippy::should_implement_trait)]
58            pub fn from_str(s: &str) -> Option<Self> {
59                match s {
60                    $(name_or_lit!($var_name $($var_lit)?) => Some(Self::$var_name),)*
61                    _ => None,
62                }
63            }
64        }
65        impl FromDefault<$name> for $name {
66            fn from_default(default: $name) -> Self {
67                default
68            }
69            fn display_default(default: $name) -> impl Display {
70                String::display_default(default.name())
71            }
72        }
73        impl Deserialize for $name {
74            fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
75                let Some(s) = value.get_ref().as_str() else {
76                    dcx.span_err(value.span(), "expected a string");
77                    return None;
78                };
79                let x = Self::from_str(s);
80                if x.is_none() {
81                    let sp = dcx.make_sp(value.span());
82                    let mut diag = dcx.inner.struct_span_err(
83                        sp,
84                        concat_expr!("expected one of: " $("`" name_or_lit!($var_name $($var_lit)?) "`")", "*),
85                    );
86                    if let Some(sugg) = find_closest_match(s, Self::NAMES) {
87                        diag.span_suggestion(sp, "did you mean", sugg, Applicability::MaybeIncorrect);
88                    }
89                    diag.note(create_value_list_msg(dcx, Self::NAMES));
90                    diag.emit();
91                }
92                x
93            }
94        }
95    };
96}
97pub struct Rename {
98    pub path: String,
99    pub rename: String,
100}
101
102impl Deserialize for Rename {
103    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
104        if let Some(table) = value.as_ref().as_table() {
105            deserialize_table!(dcx, table,
106                path("path"): String,
107                rename("rename"): String,
108            );
109            let Some(path) = path else {
110                dcx.span_err(value.span().clone(), "missing required field `path`");
111                return None;
112            };
113            let Some(rename) = rename else {
114                dcx.span_err(value.span().clone(), "missing required field `rename`");
115                return None;
116            };
117            Some(Rename { path, rename })
118        } else {
119            dcx.span_err(value.span(), "expected a table");
120            None
121        }
122    }
123}
124
125pub type DisallowedPathWithoutReplacement = DisallowedPath<false>;
126
127pub struct DisallowedPath<const REPLACEMENT_ALLOWED: bool = true> {
128    path: Spanned<String>,
129    reason: Option<String>,
130    replacement: Option<String>,
131    /// Setting `allow_invalid` to true suppresses a warning if `path` does not refer to an existing
132    /// definition.
133    ///
134    /// This could be useful when conditional compilation is used, or when a clippy.toml file is
135    /// shared among multiple projects.
136    allow_invalid: bool,
137}
138
139impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
140    pub fn path(&self) -> &str {
141        &self.path.node
142    }
143
144    pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) {
145        move |diag| {
146            if let Some(replacement) = &self.replacement {
147                diag.span_suggestion(
148                    span,
149                    self.reason.as_ref().map_or_else(|| String::from("use"), Clone::clone),
150                    replacement,
151                    Applicability::MachineApplicable,
152                );
153            } else if let Some(reason) = &self.reason {
154                diag.note(reason.clone());
155            }
156        }
157    }
158}
159
160impl Deserialize for DisallowedPath<false> {
161    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
162        if let Some(s) = value.as_ref().as_str() {
163            Some(DisallowedPath {
164                path: Spanned {
165                    node: s.into(),
166                    span: dcx.make_sp(value.span()),
167                },
168                reason: None,
169                replacement: None,
170                allow_invalid: false,
171            })
172        } else if let Some(table) = value.as_ref().as_table() {
173            deserialize_table!(dcx, table,
174                path("path"): Spanned<String>,
175                reason("reason"): String,
176                allow_invalid("allow-invalid"): bool,
177            );
178            let Some(path) = path else {
179                dcx.span_err(value.span(), "missing required field `path`");
180                return None;
181            };
182            Some(DisallowedPath {
183                path,
184                reason,
185                replacement: None,
186                allow_invalid: allow_invalid.unwrap_or(false),
187            })
188        } else {
189            dcx.span_err(value.span(), "expected either a string or an inline table");
190            None
191        }
192    }
193}
194
195impl Deserialize for DisallowedPath<true> {
196    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
197        if let Some(s) = value.as_ref().as_str() {
198            Some(DisallowedPath {
199                path: Spanned {
200                    node: s.into(),
201                    span: dcx.make_sp(value.span()),
202                },
203                reason: None,
204                replacement: None,
205                allow_invalid: false,
206            })
207        } else if let Some(table) = value.as_ref().as_table() {
208            deserialize_table!(dcx, table,
209                path("path"): Spanned<String>,
210                reason("reason"): String,
211                replacement("replacement"): String,
212                allow_invalid("allow-invalid"): bool,
213            );
214            let Some(path) = path else {
215                dcx.span_err(value.span(), "missing required field `path`");
216                return None;
217            };
218            Some(DisallowedPath {
219                path,
220                reason,
221                replacement,
222                allow_invalid: allow_invalid.unwrap_or(false),
223            })
224        } else {
225            dcx.span_err(value.span(), "expected either a string or an inline table");
226            None
227        }
228    }
229}
230
231/// Creates a map of disallowed items to the reason they were disallowed.
232#[expect(clippy::type_complexity)]
233pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
234    tcx: TyCtxt<'_>,
235    disallowed_paths: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
236    ns: PathNS,
237    def_kind_predicate: impl Fn(DefKind) -> bool,
238    predicate_description: &str,
239    allow_prim_tys: bool,
240) -> (
241    DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
242    FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
243) {
244    let mut def_ids: DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> = DefIdMap::default();
245    let mut prim_tys: FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> =
246        FxHashMap::default();
247    for disallowed_path in disallowed_paths {
248        let path = &*disallowed_path.path.node;
249        let sym_path: Vec<Symbol> = path.split("::").map(Symbol::intern).collect();
250        let mut resolutions = lookup_path(tcx, ns, &sym_path);
251        resolutions.retain(|&def_id| def_kind_predicate(tcx.def_kind(def_id)));
252
253        let (prim_ty, found_prim_ty) = if let &[name] = sym_path.as_slice()
254            && let Some(prim) = PrimTy::from_name(name)
255        {
256            (allow_prim_tys.then_some(prim), true)
257        } else {
258            (None, false)
259        };
260
261        if resolutions.is_empty()
262            && prim_ty.is_none()
263            && !disallowed_path.allow_invalid
264            // Don't warn about unloaded crates:
265            // https://github.com/rust-lang/rust-clippy/pull/14397#issuecomment-2848328221
266            && (sym_path.len() < 2 || !find_crates(tcx, sym_path[0]).is_empty())
267        {
268            // Relookup the path in an arbitrary namespace to get a good `expected, found` message
269            let found_def_ids = lookup_path(tcx, PathNS::Arbitrary, &sym_path);
270            let message = if let Some(&def_id) = found_def_ids.first() {
271                let (article, description) = tcx.article_and_description(def_id);
272                format!("expected a {predicate_description}, found {article} {description}")
273            } else if found_prim_ty {
274                format!("expected a {predicate_description}, found a primitive type")
275            } else {
276                format!("`{path}` does not refer to a reachable {predicate_description}")
277            };
278            tcx.sess
279                .dcx()
280                .struct_span_warn(disallowed_path.path.span, message)
281                .with_help("add `allow-invalid = true` to the entry to suppress this warning")
282                .emit();
283        }
284
285        for def_id in resolutions {
286            def_ids.insert(def_id, (path, disallowed_path));
287        }
288        if let Some(ty) = prim_ty {
289            prim_tys.insert(ty, (path, disallowed_path));
290        }
291    }
292
293    (def_ids, prim_tys)
294}
295
296conf_enum! {
297    #[derive(PartialEq, Eq)]
298    pub MatchLintBehaviour {
299        AllTypes,
300        WellKnownTypes,
301        Never,
302    }
303}
304
305enum BraceKind {
306    Brace,
307    Bracket,
308    Paren,
309}
310
311impl Deserialize for BraceKind {
312    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
313        let msg = if let Some(s) = value.as_ref().as_str() {
314            match s {
315                "{" | "{}" => return Some(BraceKind::Brace),
316                "[" | "[]" => return Some(BraceKind::Bracket),
317                "(" | "()" => return Some(BraceKind::Paren),
318                _ => "unknown value",
319            }
320        } else {
321            "expected a string"
322        };
323        let mut diag = dcx.inner.struct_span_err(dcx.make_sp(value.span()), msg);
324        diag.note("possible values: `()`, `[]`, `{}`");
325        diag.emit();
326        None
327    }
328}
329
330pub struct MacroMatcher {
331    pub name: String,
332    pub braces: (char, char),
333}
334
335impl Deserialize for MacroMatcher {
336    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
337        if let Some(table) = value.as_ref().as_table() {
338            deserialize_table!(dcx, table,
339                name("name"): String,
340                brace("brace"): BraceKind,
341            );
342            let Some(name) = name else {
343                dcx.span_err(value.span(), "missing required field `name`");
344                return None;
345            };
346            let Some(brace) = brace else {
347                dcx.span_err(value.span(), "missing required field `brace`");
348                return None;
349            };
350            Some(MacroMatcher {
351                name,
352                braces: match brace {
353                    BraceKind::Brace => ('{', '}'),
354                    BraceKind::Bracket => ('[', ']'),
355                    BraceKind::Paren => ('(', ')'),
356                },
357            })
358        } else {
359            dcx.span_err(value.span(), "expected an inline table");
360            None
361        }
362    }
363}
364
365conf_enum! {
366    pub PubUnderscoreFieldsBehaviour {
367        PubliclyExported,
368        AllPubFields,
369    }
370}
371
372conf_enum! {
373    /// Represents the item categories that can be ordered by the source ordering lint.
374    #[derive(Debug, PartialEq, Eq, Hash)]
375    pub SourceItemOrderingCategory {
376        Enum("enum"),
377        Impl("impl"),
378        Module("module"),
379        Struct("struct"),
380        Trait("trait"),
381    }
382}
383
384/// Represents which item categories are enabled for ordering.
385///
386/// The [`Deserialize`] implementation checks that there are no duplicates in
387/// the user configuration.
388pub struct SourceItemOrdering(Vec<SourceItemOrderingCategory>);
389
390impl SourceItemOrdering {
391    pub fn contains(&self, category: SourceItemOrderingCategory) -> bool {
392        self.0.contains(&category)
393    }
394}
395
396impl fmt::Debug for SourceItemOrdering {
397    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398        self.0.fmt(f)
399    }
400}
401
402impl Deserialize for SourceItemOrdering {
403    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
404        let items = Vec::<SourceItemOrderingCategory>::deserialize(dcx, value)?;
405        let mut items_set = FxHashSet::default();
406
407        for item in &items {
408            if items_set.contains(item) {
409                dcx.span_err(
410                    value.span(),
411                    format!(
412                        "The category \"{}\" was enabled more than once in the source ordering configuration.",
413                        item.name()
414                    ),
415                );
416                return None;
417            }
418            items_set.insert(item);
419        }
420        Some(SourceItemOrdering(items))
421    }
422}
423impl FromDefault<()> for SourceItemOrdering {
424    fn from_default((): ()) -> Self {
425        Self(vec![
426            SourceItemOrderingCategory::Enum,
427            SourceItemOrderingCategory::Impl,
428            SourceItemOrderingCategory::Module,
429            SourceItemOrderingCategory::Struct,
430            SourceItemOrderingCategory::Trait,
431        ])
432    }
433    fn display_default((): ()) -> impl Display {
434        r#"["enum", "impl", "module", "struct", "trait"]"#
435    }
436}
437impl DeserializeOrDefault<()> for SourceItemOrdering {
438    fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: ()) -> Self {
439        Self::deserialize(dcx, value).unwrap_or_else(|| Self::from_default(default))
440    }
441}
442
443conf_enum! {
444    #[derive(Debug, PartialEq, Eq, Hash)]
445    pub SourceItemOrderingModuleItemKind {
446        ExternCrate("extern_crate"),
447        Mod("mod"),
448        ForeignMod("foreign_mod"),
449        Use("use"),
450        Macro("macro"),
451        GlobalAsm("global_asm"),
452        Static("static"),
453        Const("const"),
454        TyAlias("ty_alias"),
455        Enum("enum"),
456        Struct("struct"),
457        Union("union"),
458        Trait("trait"),
459        TraitAlias("trait_alias"),
460        Impl("impl"),
461        Fn("fn"),
462        TestBinderConstraints("test_binder_constraints"),
463    }
464}
465
466impl SourceItemOrderingModuleItemKind {
467    pub fn all_variants() -> Vec<Self> {
468        #[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
469        use SourceItemOrderingModuleItemKind::*;
470        vec![
471            ExternCrate,
472            Mod,
473            ForeignMod,
474            Use,
475            Macro,
476            GlobalAsm,
477            Static,
478            Const,
479            TyAlias,
480            Enum,
481            Struct,
482            Union,
483            Trait,
484            TraitAlias,
485            Impl,
486            Fn,
487            TestBinderConstraints,
488        ]
489    }
490}
491
492/// Represents the configured ordering of items within a module.
493///
494/// The [`Deserialize`] implementation checks that no item kinds have been
495/// omitted and that there are no duplicates in the user configuration.
496#[derive(Clone)]
497pub struct SourceItemOrderingModuleItemGroupings {
498    groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)>,
499    lut: HashMap<SourceItemOrderingModuleItemKind, usize>,
500    back_lut: HashMap<SourceItemOrderingModuleItemKind, String>,
501}
502
503impl fmt::Debug for SourceItemOrderingModuleItemGroupings {
504    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505        self.groups.fmt(f)
506    }
507}
508
509impl SourceItemOrderingModuleItemGroupings {
510    fn build_lut(
511        groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
512    ) -> HashMap<SourceItemOrderingModuleItemKind, usize> {
513        let mut lut = HashMap::new();
514        for (group_index, (_, items)) in groups.iter().enumerate() {
515            for &item in items {
516                lut.insert(item, group_index);
517            }
518        }
519        lut
520    }
521
522    fn build_back_lut(
523        groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
524    ) -> HashMap<SourceItemOrderingModuleItemKind, String> {
525        let mut lut = HashMap::new();
526        for (group_name, items) in groups {
527            for &item in items {
528                lut.insert(item, group_name.clone());
529            }
530        }
531        lut
532    }
533
534    pub fn grouping_name_of(&self, item: SourceItemOrderingModuleItemKind) -> Option<&String> {
535        self.back_lut.get(&item)
536    }
537
538    pub fn grouping_names(&self) -> Vec<String> {
539        self.groups.iter().map(|(name, _)| name.clone()).collect()
540    }
541
542    pub fn is_grouping(&self, grouping: &str) -> bool {
543        self.groups.iter().any(|(g, _)| g == grouping)
544    }
545
546    pub fn module_level_order_of(&self, item: SourceItemOrderingModuleItemKind) -> Option<usize> {
547        self.lut.get(&item).copied()
548    }
549}
550
551impl Deserialize for SourceItemOrderingModuleItemGroupings {
552    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
553        let Some(values) = value.as_ref().as_array() else {
554            dcx.span_err(value.span(), "expected an array");
555            return None;
556        };
557        let mut groups = Vec::with_capacity(values.len());
558        for value in values {
559            if let Some(values) = value.as_ref().as_array()
560                && let [value1, value2] = &**values
561            {
562                groups.push((
563                    String::deserialize(dcx, value1)?,
564                    Vec::<SourceItemOrderingModuleItemKind>::deserialize(dcx, value2)?,
565                ));
566            } else {
567                dcx.span_err(value.span(), "expected an array of length two");
568                return None;
569            }
570        }
571
572        let items_total: usize = groups.iter().map(|(_, v)| v.len()).sum();
573        let lut = Self::build_lut(&groups);
574        let back_lut = Self::build_back_lut(&groups);
575
576        let mut expected_items = SourceItemOrderingModuleItemKind::all_variants();
577        for item in lut.keys() {
578            expected_items.retain(|i| i != item);
579        }
580
581        let all_items = SourceItemOrderingModuleItemKind::all_variants();
582        if expected_items.is_empty() && items_total == all_items.len() {
583            let Some(use_group_index) = lut.get(&SourceItemOrderingModuleItemKind::Use) else {
584                dcx.span_err(value.span(), "Error in internal LUT.");
585                return None;
586            };
587            let Some((_, use_group_items)) = groups.get(*use_group_index) else {
588                dcx.span_err(value.span(), "Error in internal LUT.");
589                return None;
590            };
591            if use_group_items.len() > 1 {
592                dcx.span_err(
593                    value.span(),
594                    "The group containing the \"use\" item kind may not contain any other item kinds. \
595                    The \"use\" items will (generally) be sorted by rustfmt already. \
596                    Therefore it makes no sense to implement linting rules that may conflict with rustfmt.",
597                );
598                return None;
599            }
600            Some(Self { groups, lut, back_lut })
601        } else if items_total != all_items.len() {
602            dcx.span_err(value.span(),
603                format!(
604                    "Some module item kinds were configured more than once, or were missing, in the source ordering configuration. \
605                    The module item kinds are: {all_items:?}"
606                )
607            );
608            None
609        } else {
610            dcx.span_err(value.span(),
611                format!(
612                    "Not all module item kinds were part of the configured source ordering rule. \
613                    All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
614                    The module item kinds are: {all_items:?}"
615                )
616            );
617            None
618        }
619    }
620}
621impl FromDefault<()> for SourceItemOrderingModuleItemGroupings {
622    fn from_default((): ()) -> Self {
623        Self {
624            groups: vec![
625                (
626                    "modules".into(),
627                    vec![
628                        SourceItemOrderingModuleItemKind::ExternCrate,
629                        SourceItemOrderingModuleItemKind::Mod,
630                        SourceItemOrderingModuleItemKind::ForeignMod,
631                    ],
632                ),
633                ("use".into(), vec![SourceItemOrderingModuleItemKind::Use]),
634                ("macros".into(), vec![SourceItemOrderingModuleItemKind::Macro]),
635                ("global_asm".into(), vec![SourceItemOrderingModuleItemKind::GlobalAsm]),
636                (
637                    "UPPER_SNAKE_CASE".into(),
638                    vec![
639                        SourceItemOrderingModuleItemKind::Static,
640                        SourceItemOrderingModuleItemKind::Const,
641                    ],
642                ),
643                (
644                    "PascalCase".into(),
645                    vec![
646                        SourceItemOrderingModuleItemKind::TyAlias,
647                        SourceItemOrderingModuleItemKind::Enum,
648                        SourceItemOrderingModuleItemKind::Struct,
649                        SourceItemOrderingModuleItemKind::Union,
650                        SourceItemOrderingModuleItemKind::Trait,
651                        SourceItemOrderingModuleItemKind::TraitAlias,
652                        SourceItemOrderingModuleItemKind::Impl,
653                        SourceItemOrderingModuleItemKind::TestBinderConstraints,
654                    ],
655                ),
656                ("lower_snake_case".into(), vec![SourceItemOrderingModuleItemKind::Fn]),
657            ],
658            lut: HashMap::from_iter([
659                (SourceItemOrderingModuleItemKind::ExternCrate, 0),
660                (SourceItemOrderingModuleItemKind::Mod, 0),
661                (SourceItemOrderingModuleItemKind::ForeignMod, 0),
662                (SourceItemOrderingModuleItemKind::Use, 1),
663                (SourceItemOrderingModuleItemKind::Macro, 2),
664                (SourceItemOrderingModuleItemKind::GlobalAsm, 3),
665                (SourceItemOrderingModuleItemKind::Static, 4),
666                (SourceItemOrderingModuleItemKind::Const, 4),
667                (SourceItemOrderingModuleItemKind::TyAlias, 5),
668                (SourceItemOrderingModuleItemKind::Enum, 5),
669                (SourceItemOrderingModuleItemKind::Struct, 5),
670                (SourceItemOrderingModuleItemKind::Union, 5),
671                (SourceItemOrderingModuleItemKind::Trait, 5),
672                (SourceItemOrderingModuleItemKind::TraitAlias, 5),
673                (SourceItemOrderingModuleItemKind::Impl, 5),
674                (SourceItemOrderingModuleItemKind::TestBinderConstraints, 5),
675                (SourceItemOrderingModuleItemKind::Fn, 6),
676            ]),
677            back_lut: HashMap::from_iter([
678                (SourceItemOrderingModuleItemKind::ExternCrate, "modules".into()),
679                (SourceItemOrderingModuleItemKind::Mod, "modules".into()),
680                (SourceItemOrderingModuleItemKind::ForeignMod, "modules".into()),
681                (SourceItemOrderingModuleItemKind::Use, "use".into()),
682                (SourceItemOrderingModuleItemKind::Macro, "macros".into()),
683                (SourceItemOrderingModuleItemKind::GlobalAsm, "global_asm".into()),
684                (SourceItemOrderingModuleItemKind::Static, "UPPER_SNAKE_CASE".into()),
685                (SourceItemOrderingModuleItemKind::Const, "UPPER_SNAKE_CASE".into()),
686                (SourceItemOrderingModuleItemKind::TyAlias, "PascalCase".into()),
687                (SourceItemOrderingModuleItemKind::Enum, "PascalCase".into()),
688                (SourceItemOrderingModuleItemKind::Struct, "PascalCase".into()),
689                (SourceItemOrderingModuleItemKind::Union, "PascalCase".into()),
690                (SourceItemOrderingModuleItemKind::Trait, "PascalCase".into()),
691                (SourceItemOrderingModuleItemKind::TraitAlias, "PascalCase".into()),
692                (SourceItemOrderingModuleItemKind::Impl, "PascalCase".into()),
693                (
694                    SourceItemOrderingModuleItemKind::TestBinderConstraints,
695                    "PascalCase".into(),
696                ),
697                (SourceItemOrderingModuleItemKind::Fn, "lower_snake_case".into()),
698            ]),
699        }
700    }
701    fn display_default((): ()) -> impl Display {
702        r#"[["modules", ["extern_crate", "mod", "foreign_mod"]], ["use", ["use"]], ["macros", ["macro"]], ["global_asm", ["global_asm"]], ["UPPER_SNAKE_CASE", ["static", "const"]], ["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl", "test_binder_constraints"]], ["lower_snake_case", ["fn"]]]"#
703    }
704}
705impl DeserializeOrDefault<()> for SourceItemOrderingModuleItemGroupings {
706    fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: ()) -> Self {
707        Self::deserialize(dcx, value).unwrap_or_else(|| Self::from_default(default))
708    }
709}
710
711conf_enum! {
712    #[derive(Debug, PartialEq)]
713    pub SourceItemOrderingTraitAssocItemKind {
714        Const("const"),
715        Fn("fn"),
716        Type("type"),
717    }
718}
719
720impl SourceItemOrderingTraitAssocItemKind {
721    pub fn all_variants() -> Vec<Self> {
722        #[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
723        use SourceItemOrderingTraitAssocItemKind::*;
724        vec![Const, Fn, Type]
725    }
726}
727
728/// Represents the order in which associated trait items should be ordered.
729///
730/// The reason to wrap a `Vec` in a newtype is to be able to implement
731/// [`Deserialize`]. Implementing `Deserialize` allows for implementing checks
732/// on configuration completeness at the time of loading the clippy config,
733/// letting the user know if there's any issues with the config (e.g. not
734/// listing all item kinds that should be sorted).
735#[derive(Clone)]
736pub struct SourceItemOrderingTraitAssocItemKinds(Vec<SourceItemOrderingTraitAssocItemKind>);
737
738impl fmt::Debug for SourceItemOrderingTraitAssocItemKinds {
739    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
740        self.0.fmt(f)
741    }
742}
743
744impl SourceItemOrderingTraitAssocItemKinds {
745    pub fn index_of(&self, item: SourceItemOrderingTraitAssocItemKind) -> Option<usize> {
746        self.0.iter().position(|&i| i == item)
747    }
748}
749
750impl Deserialize for SourceItemOrderingTraitAssocItemKinds {
751    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
752        let items = Vec::<SourceItemOrderingTraitAssocItemKind>::deserialize(dcx, value)?;
753
754        let mut expected_items = SourceItemOrderingTraitAssocItemKind::all_variants();
755        for item in &items {
756            expected_items.retain(|i| i != item);
757        }
758
759        let all_items = SourceItemOrderingTraitAssocItemKind::all_variants();
760        if expected_items.is_empty() && items.len() == all_items.len() {
761            Some(Self(items))
762        } else if items.len() != all_items.len() {
763            dcx.span_err(
764                value.span(),
765                format!(
766                    "Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. \
767                    The trait associated item kinds are: {all_items:?}",
768                )
769            );
770            None
771        } else {
772            dcx.span_err(
773                value.span(),
774                format!(
775                    "Not all trait associated item kinds were part of the configured source ordering rule. \
776                    All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
777                    The trait associated item kinds are: {all_items:?}"
778                )
779            );
780            None
781        }
782    }
783}
784impl FromDefault<()> for SourceItemOrderingTraitAssocItemKinds {
785    fn from_default((): ()) -> Self {
786        Self(vec![
787            SourceItemOrderingTraitAssocItemKind::Const,
788            SourceItemOrderingTraitAssocItemKind::Type,
789            SourceItemOrderingTraitAssocItemKind::Fn,
790        ])
791    }
792    fn display_default((): ()) -> impl Display {
793        r#"["const", "type", "fn"]"#
794    }
795}
796impl DeserializeOrDefault<()> for SourceItemOrderingTraitAssocItemKinds {
797    fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: ()) -> Self {
798        Self::deserialize(dcx, value).unwrap_or_else(|| Self::from_default(default))
799    }
800}
801
802/// Describes which specific groupings should have their items ordered
803/// alphabetically.
804///
805/// This is separate from defining and enforcing groupings. For example,
806/// defining enums are grouped before structs still allows for an enum B to be
807/// placed before an enum A. Only when enforcing ordering within the grouping,
808/// will it be checked if A is placed before B.
809#[derive(Clone, Debug)]
810pub enum SourceItemOrderingWithinModuleItemGroupings {
811    /// All groupings should have their items ordered.
812    All,
813
814    /// None of the groupings should have their order checked.
815    None,
816
817    /// Only the specified groupings should have their order checked.
818    Custom(Vec<Spanned<String>>),
819}
820
821impl SourceItemOrderingWithinModuleItemGroupings {
822    pub fn ordered_within(&self, grouping_name: &String) -> bool {
823        match self {
824            SourceItemOrderingWithinModuleItemGroupings::All => true,
825            SourceItemOrderingWithinModuleItemGroupings::None => false,
826            SourceItemOrderingWithinModuleItemGroupings::Custom(groups) => {
827                groups.iter().any(|x| x.node == *grouping_name)
828            },
829        }
830    }
831
832    pub fn check_groupings(&self, sess: &Session, module_item_order_groupings: &SourceItemOrderingModuleItemGroupings) {
833        if let SourceItemOrderingWithinModuleItemGroupings::Custom(groupings) = self {
834            for grouping in groupings {
835                if !module_item_order_groupings.is_grouping(&grouping.node) {
836                    // Since this isn't fixable by rustfix, don't emit a `Suggestion`. This just adds some useful
837                    // info for the user instead.
838                    let names = module_item_order_groupings
839                        .groups
840                        .iter()
841                        .map(|(x, _)| &**x)
842                        .collect::<Vec<_>>();
843                    let suggestion = find_closest_match(&grouping.node, &names)
844                        .map(|s| format!(" perhaps you meant `{s}`?"))
845                        .unwrap_or_default();
846                    let names = names.iter().map(|s| format!("`{s}`")).join(", ");
847                    sess.dcx().span_err(grouping.span, format!(
848                        "unknown ordering group: `{}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}",
849                        grouping.node,
850                    ));
851                }
852            }
853        }
854    }
855}
856
857impl Deserialize for SourceItemOrderingWithinModuleItemGroupings {
858    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
859        match value.as_ref() {
860            toml::de::DeValue::String(str_value) => match &**str_value {
861                "all" => Some(Self::All),
862                "none" => Some(Self::None),
863                _ => {
864                    dcx.span_err(value.span(), "expected: `all`, `none` or a list of category names");
865                    None
866                },
867            },
868            toml::de::DeValue::Array(_) => Vec::deserialize(dcx, value).map(Self::Custom),
869            _ => {
870                dcx.span_err(value.span(), "expected a string or an array of strings");
871                None
872            },
873        }
874    }
875}
876impl FromDefault<()> for SourceItemOrderingWithinModuleItemGroupings {
877    fn from_default((): ()) -> Self {
878        Self::None
879    }
880    fn display_default((): ()) -> impl Display {
881        r#""none""#
882    }
883}
884impl DeserializeOrDefault<()> for SourceItemOrderingWithinModuleItemGroupings {
885    fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: ()) -> Self {
886        Self::deserialize(dcx, value).unwrap_or_else(|| Self::from_default(default))
887    }
888}
889
890conf_enum! {
891    #[derive(Debug, PartialEq, Eq, Hash)]
892    pub InherentImplLintScope {
893        Crate("crate"),
894        File("file"),
895        Module("module"),
896    }
897}
898
899conf_enum! {
900    #[derive(Debug, PartialEq, Eq, Hash)]
901    pub TraitImplItemOrder {
902        Alphabetical("alphabetical"),
903        TraitItemOrdering("trait_item_ordering"),
904        AlphabeticalOrTraitItemOrdering("alphabetical_or_trait_item_ordering"),
905    }
906}
907impl FromDefault<()> for TraitImplItemOrder {
908    fn from_default((): ()) -> Self {
909        Self::Alphabetical
910    }
911    fn display_default((): ()) -> impl Display {
912        r#""alphabetical""#
913    }
914}
915impl DeserializeOrDefault<()> for TraitImplItemOrder {
916    fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: ()) -> Self {
917        Self::deserialize(dcx, value).unwrap_or_else(|| Self::from_default(default))
918    }
919}