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    }
463}
464
465impl SourceItemOrderingModuleItemKind {
466    pub fn all_variants() -> Vec<Self> {
467        #[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
468        use SourceItemOrderingModuleItemKind::*;
469        vec![
470            ExternCrate,
471            Mod,
472            ForeignMod,
473            Use,
474            Macro,
475            GlobalAsm,
476            Static,
477            Const,
478            TyAlias,
479            Enum,
480            Struct,
481            Union,
482            Trait,
483            TraitAlias,
484            Impl,
485            Fn,
486        ]
487    }
488}
489
490/// Represents the configured ordering of items within a module.
491///
492/// The [`Deserialize`] implementation checks that no item kinds have been
493/// omitted and that there are no duplicates in the user configuration.
494#[derive(Clone)]
495pub struct SourceItemOrderingModuleItemGroupings {
496    groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)>,
497    lut: HashMap<SourceItemOrderingModuleItemKind, usize>,
498    back_lut: HashMap<SourceItemOrderingModuleItemKind, String>,
499}
500
501impl fmt::Debug for SourceItemOrderingModuleItemGroupings {
502    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503        self.groups.fmt(f)
504    }
505}
506
507impl SourceItemOrderingModuleItemGroupings {
508    fn build_lut(
509        groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
510    ) -> HashMap<SourceItemOrderingModuleItemKind, usize> {
511        let mut lut = HashMap::new();
512        for (group_index, (_, items)) in groups.iter().enumerate() {
513            for &item in items {
514                lut.insert(item, group_index);
515            }
516        }
517        lut
518    }
519
520    fn build_back_lut(
521        groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
522    ) -> HashMap<SourceItemOrderingModuleItemKind, String> {
523        let mut lut = HashMap::new();
524        for (group_name, items) in groups {
525            for &item in items {
526                lut.insert(item, group_name.clone());
527            }
528        }
529        lut
530    }
531
532    pub fn grouping_name_of(&self, item: SourceItemOrderingModuleItemKind) -> Option<&String> {
533        self.back_lut.get(&item)
534    }
535
536    pub fn grouping_names(&self) -> Vec<String> {
537        self.groups.iter().map(|(name, _)| name.clone()).collect()
538    }
539
540    pub fn is_grouping(&self, grouping: &str) -> bool {
541        self.groups.iter().any(|(g, _)| g == grouping)
542    }
543
544    pub fn module_level_order_of(&self, item: SourceItemOrderingModuleItemKind) -> Option<usize> {
545        self.lut.get(&item).copied()
546    }
547}
548
549impl Deserialize for SourceItemOrderingModuleItemGroupings {
550    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
551        let Some(values) = value.as_ref().as_array() else {
552            dcx.span_err(value.span(), "expected an array");
553            return None;
554        };
555        let mut groups = Vec::with_capacity(values.len());
556        for value in values {
557            if let Some(values) = value.as_ref().as_array()
558                && let [value1, value2] = &**values
559            {
560                groups.push((
561                    String::deserialize(dcx, value1)?,
562                    Vec::<SourceItemOrderingModuleItemKind>::deserialize(dcx, value2)?,
563                ));
564            } else {
565                dcx.span_err(value.span(), "expected an array of length two");
566                return None;
567            }
568        }
569
570        let items_total: usize = groups.iter().map(|(_, v)| v.len()).sum();
571        let lut = Self::build_lut(&groups);
572        let back_lut = Self::build_back_lut(&groups);
573
574        let mut expected_items = SourceItemOrderingModuleItemKind::all_variants();
575        for item in lut.keys() {
576            expected_items.retain(|i| i != item);
577        }
578
579        let all_items = SourceItemOrderingModuleItemKind::all_variants();
580        if expected_items.is_empty() && items_total == all_items.len() {
581            let Some(use_group_index) = lut.get(&SourceItemOrderingModuleItemKind::Use) else {
582                dcx.span_err(value.span(), "Error in internal LUT.");
583                return None;
584            };
585            let Some((_, use_group_items)) = groups.get(*use_group_index) else {
586                dcx.span_err(value.span(), "Error in internal LUT.");
587                return None;
588            };
589            if use_group_items.len() > 1 {
590                dcx.span_err(
591                    value.span(),
592                    "The group containing the \"use\" item kind may not contain any other item kinds. \
593                    The \"use\" items will (generally) be sorted by rustfmt already. \
594                    Therefore it makes no sense to implement linting rules that may conflict with rustfmt.",
595                );
596                return None;
597            }
598            Some(Self { groups, lut, back_lut })
599        } else if items_total != all_items.len() {
600            dcx.span_err(value.span(),
601                format!(
602                    "Some module item kinds were configured more than once, or were missing, in the source ordering configuration. \
603                    The module item kinds are: {all_items:?}"
604                )
605            );
606            None
607        } else {
608            dcx.span_err(value.span(),
609                format!(
610                    "Not all module item kinds were part of the configured source ordering rule. \
611                    All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
612                    The module item kinds are: {all_items:?}"
613                )
614            );
615            None
616        }
617    }
618}
619impl FromDefault<()> for SourceItemOrderingModuleItemGroupings {
620    fn from_default((): ()) -> Self {
621        Self {
622            groups: vec![
623                (
624                    "modules".into(),
625                    vec![
626                        SourceItemOrderingModuleItemKind::ExternCrate,
627                        SourceItemOrderingModuleItemKind::Mod,
628                        SourceItemOrderingModuleItemKind::ForeignMod,
629                    ],
630                ),
631                ("use".into(), vec![SourceItemOrderingModuleItemKind::Use]),
632                ("macros".into(), vec![SourceItemOrderingModuleItemKind::Macro]),
633                ("global_asm".into(), vec![SourceItemOrderingModuleItemKind::GlobalAsm]),
634                (
635                    "UPPER_SNAKE_CASE".into(),
636                    vec![
637                        SourceItemOrderingModuleItemKind::Static,
638                        SourceItemOrderingModuleItemKind::Const,
639                    ],
640                ),
641                (
642                    "PascalCase".into(),
643                    vec![
644                        SourceItemOrderingModuleItemKind::TyAlias,
645                        SourceItemOrderingModuleItemKind::Enum,
646                        SourceItemOrderingModuleItemKind::Struct,
647                        SourceItemOrderingModuleItemKind::Union,
648                        SourceItemOrderingModuleItemKind::Trait,
649                        SourceItemOrderingModuleItemKind::TraitAlias,
650                        SourceItemOrderingModuleItemKind::Impl,
651                    ],
652                ),
653                ("lower_snake_case".into(), vec![SourceItemOrderingModuleItemKind::Fn]),
654            ],
655            lut: HashMap::from_iter([
656                (SourceItemOrderingModuleItemKind::ExternCrate, 0),
657                (SourceItemOrderingModuleItemKind::Mod, 0),
658                (SourceItemOrderingModuleItemKind::ForeignMod, 0),
659                (SourceItemOrderingModuleItemKind::Use, 1),
660                (SourceItemOrderingModuleItemKind::Macro, 2),
661                (SourceItemOrderingModuleItemKind::GlobalAsm, 3),
662                (SourceItemOrderingModuleItemKind::Static, 4),
663                (SourceItemOrderingModuleItemKind::Const, 4),
664                (SourceItemOrderingModuleItemKind::TyAlias, 5),
665                (SourceItemOrderingModuleItemKind::Enum, 5),
666                (SourceItemOrderingModuleItemKind::Struct, 5),
667                (SourceItemOrderingModuleItemKind::Union, 5),
668                (SourceItemOrderingModuleItemKind::Trait, 5),
669                (SourceItemOrderingModuleItemKind::TraitAlias, 5),
670                (SourceItemOrderingModuleItemKind::Impl, 5),
671                (SourceItemOrderingModuleItemKind::Fn, 6),
672            ]),
673            back_lut: HashMap::from_iter([
674                (SourceItemOrderingModuleItemKind::ExternCrate, "modules".into()),
675                (SourceItemOrderingModuleItemKind::Mod, "modules".into()),
676                (SourceItemOrderingModuleItemKind::ForeignMod, "modules".into()),
677                (SourceItemOrderingModuleItemKind::Use, "use".into()),
678                (SourceItemOrderingModuleItemKind::Macro, "macros".into()),
679                (SourceItemOrderingModuleItemKind::GlobalAsm, "global_asm".into()),
680                (SourceItemOrderingModuleItemKind::Static, "UPPER_SNAKE_CASE".into()),
681                (SourceItemOrderingModuleItemKind::Const, "UPPER_SNAKE_CASE".into()),
682                (SourceItemOrderingModuleItemKind::TyAlias, "PascalCase".into()),
683                (SourceItemOrderingModuleItemKind::Enum, "PascalCase".into()),
684                (SourceItemOrderingModuleItemKind::Struct, "PascalCase".into()),
685                (SourceItemOrderingModuleItemKind::Union, "PascalCase".into()),
686                (SourceItemOrderingModuleItemKind::Trait, "PascalCase".into()),
687                (SourceItemOrderingModuleItemKind::TraitAlias, "PascalCase".into()),
688                (SourceItemOrderingModuleItemKind::Impl, "PascalCase".into()),
689                (SourceItemOrderingModuleItemKind::Fn, "lower_snake_case".into()),
690            ]),
691        }
692    }
693    fn display_default((): ()) -> impl Display {
694        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"]], ["lower_snake_case", ["fn"]]]"#
695    }
696}
697impl DeserializeOrDefault<()> for SourceItemOrderingModuleItemGroupings {
698    fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: ()) -> Self {
699        Self::deserialize(dcx, value).unwrap_or_else(|| Self::from_default(default))
700    }
701}
702
703conf_enum! {
704    #[derive(Debug, PartialEq)]
705    pub SourceItemOrderingTraitAssocItemKind {
706        Const("const"),
707        Fn("fn"),
708        Type("type"),
709    }
710}
711
712impl SourceItemOrderingTraitAssocItemKind {
713    pub fn all_variants() -> Vec<Self> {
714        #[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
715        use SourceItemOrderingTraitAssocItemKind::*;
716        vec![Const, Fn, Type]
717    }
718}
719
720/// Represents the order in which associated trait items should be ordered.
721///
722/// The reason to wrap a `Vec` in a newtype is to be able to implement
723/// [`Deserialize`]. Implementing `Deserialize` allows for implementing checks
724/// on configuration completeness at the time of loading the clippy config,
725/// letting the user know if there's any issues with the config (e.g. not
726/// listing all item kinds that should be sorted).
727#[derive(Clone)]
728pub struct SourceItemOrderingTraitAssocItemKinds(Vec<SourceItemOrderingTraitAssocItemKind>);
729
730impl fmt::Debug for SourceItemOrderingTraitAssocItemKinds {
731    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732        self.0.fmt(f)
733    }
734}
735
736impl SourceItemOrderingTraitAssocItemKinds {
737    pub fn index_of(&self, item: SourceItemOrderingTraitAssocItemKind) -> Option<usize> {
738        self.0.iter().position(|&i| i == item)
739    }
740}
741
742impl Deserialize for SourceItemOrderingTraitAssocItemKinds {
743    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
744        let items = Vec::<SourceItemOrderingTraitAssocItemKind>::deserialize(dcx, value)?;
745
746        let mut expected_items = SourceItemOrderingTraitAssocItemKind::all_variants();
747        for item in &items {
748            expected_items.retain(|i| i != item);
749        }
750
751        let all_items = SourceItemOrderingTraitAssocItemKind::all_variants();
752        if expected_items.is_empty() && items.len() == all_items.len() {
753            Some(Self(items))
754        } else if items.len() != all_items.len() {
755            dcx.span_err(
756                value.span(),
757                format!(
758                    "Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. \
759                    The trait associated item kinds are: {all_items:?}",
760                )
761            );
762            None
763        } else {
764            dcx.span_err(
765                value.span(),
766                format!(
767                    "Not all trait associated item kinds were part of the configured source ordering rule. \
768                    All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
769                    The trait associated item kinds are: {all_items:?}"
770                )
771            );
772            None
773        }
774    }
775}
776impl FromDefault<()> for SourceItemOrderingTraitAssocItemKinds {
777    fn from_default((): ()) -> Self {
778        Self(vec![
779            SourceItemOrderingTraitAssocItemKind::Const,
780            SourceItemOrderingTraitAssocItemKind::Type,
781            SourceItemOrderingTraitAssocItemKind::Fn,
782        ])
783    }
784    fn display_default((): ()) -> impl Display {
785        r#"["const", "type", "fn"]"#
786    }
787}
788impl DeserializeOrDefault<()> for SourceItemOrderingTraitAssocItemKinds {
789    fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: ()) -> Self {
790        Self::deserialize(dcx, value).unwrap_or_else(|| Self::from_default(default))
791    }
792}
793
794/// Describes which specific groupings should have their items ordered
795/// alphabetically.
796///
797/// This is separate from defining and enforcing groupings. For example,
798/// defining enums are grouped before structs still allows for an enum B to be
799/// placed before an enum A. Only when enforcing ordering within the grouping,
800/// will it be checked if A is placed before B.
801#[derive(Clone, Debug)]
802pub enum SourceItemOrderingWithinModuleItemGroupings {
803    /// All groupings should have their items ordered.
804    All,
805
806    /// None of the groupings should have their order checked.
807    None,
808
809    /// Only the specified groupings should have their order checked.
810    Custom(Vec<Spanned<String>>),
811}
812
813impl SourceItemOrderingWithinModuleItemGroupings {
814    pub fn ordered_within(&self, grouping_name: &String) -> bool {
815        match self {
816            SourceItemOrderingWithinModuleItemGroupings::All => true,
817            SourceItemOrderingWithinModuleItemGroupings::None => false,
818            SourceItemOrderingWithinModuleItemGroupings::Custom(groups) => {
819                groups.iter().any(|x| x.node == *grouping_name)
820            },
821        }
822    }
823
824    pub fn check_groupings(&self, sess: &Session, module_item_order_groupings: &SourceItemOrderingModuleItemGroupings) {
825        if let SourceItemOrderingWithinModuleItemGroupings::Custom(groupings) = self {
826            for grouping in groupings {
827                if !module_item_order_groupings.is_grouping(&grouping.node) {
828                    // Since this isn't fixable by rustfix, don't emit a `Suggestion`. This just adds some useful
829                    // info for the user instead.
830                    let names = module_item_order_groupings
831                        .groups
832                        .iter()
833                        .map(|(x, _)| &**x)
834                        .collect::<Vec<_>>();
835                    let suggestion = find_closest_match(&grouping.node, &names)
836                        .map(|s| format!(" perhaps you meant `{s}`?"))
837                        .unwrap_or_default();
838                    let names = names.iter().map(|s| format!("`{s}`")).join(", ");
839                    sess.dcx().span_err(grouping.span, format!(
840                        "unknown ordering group: `{}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}",
841                        grouping.node,
842                    ));
843                }
844            }
845        }
846    }
847}
848
849impl Deserialize for SourceItemOrderingWithinModuleItemGroupings {
850    fn deserialize(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>) -> Option<Self> {
851        match value.as_ref() {
852            toml::de::DeValue::String(str_value) => match &**str_value {
853                "all" => Some(Self::All),
854                "none" => Some(Self::None),
855                _ => {
856                    dcx.span_err(value.span(), "expected: `all`, `none` or a list of category names");
857                    None
858                },
859            },
860            toml::de::DeValue::Array(_) => Vec::deserialize(dcx, value).map(Self::Custom),
861            _ => {
862                dcx.span_err(value.span(), "expected a string or an array of strings");
863                None
864            },
865        }
866    }
867}
868impl FromDefault<()> for SourceItemOrderingWithinModuleItemGroupings {
869    fn from_default((): ()) -> Self {
870        Self::None
871    }
872    fn display_default((): ()) -> impl Display {
873        r#""none""#
874    }
875}
876impl DeserializeOrDefault<()> for SourceItemOrderingWithinModuleItemGroupings {
877    fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: ()) -> Self {
878        Self::deserialize(dcx, value).unwrap_or_else(|| Self::from_default(default))
879    }
880}
881
882conf_enum! {
883    #[derive(Debug, PartialEq, Eq, Hash)]
884    pub InherentImplLintScope {
885        Crate("crate"),
886        File("file"),
887        Module("module"),
888    }
889}
890
891conf_enum! {
892    #[derive(Debug, PartialEq, Eq, Hash)]
893    pub TraitImplItemOrder {
894        Alphabetical("alphabetical"),
895        TraitItemOrdering("trait_item_ordering"),
896        AlphabeticalOrTraitItemOrdering("alphabetical_or_trait_item_ordering"),
897    }
898}
899impl FromDefault<()> for TraitImplItemOrder {
900    fn from_default((): ()) -> Self {
901        Self::Alphabetical
902    }
903    fn display_default((): ()) -> impl Display {
904        r#""alphabetical""#
905    }
906}
907impl DeserializeOrDefault<()> for TraitImplItemOrder {
908    fn deserialize_or_default(dcx: &DiagCtxt<'_>, value: &TomlValue<'_>, default: ()) -> Self {
909        Self::deserialize(dcx, value).unwrap_or_else(|| Self::from_default(default))
910    }
911}