Skip to main content

hir_ty/diagnostics/
decl_check.rs

1//! Provides validators for names of declarations.
2//!
3//! This includes the following items:
4//!
5//! - variable bindings (e.g. `let x = foo();`)
6//! - struct fields (e.g. `struct Foo { field: u8 }`)
7//! - enum variants (e.g. `enum Foo { Variant { field: u8 } }`)
8//! - function/method arguments (e.g. `fn foo(arg: u8)`)
9//! - constants (e.g. `const FOO: u8 = 10;`)
10//! - static items (e.g. `static FOO: u8 = 10;`)
11//! - match arm bindings (e.g. `foo @ Some(_)`)
12//! - modules (e.g. `mod foo { ... }` or `mod foo;`)
13
14mod case_conv;
15
16use std::fmt;
17
18use hir_def::{
19    AdtId, ConstId, EnumId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup,
20    ModuleDefId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, UnionId,
21    attrs::AttrFlags,
22    expr_store::Body,
23    hir::Pat,
24    item_tree::FieldsShape,
25    signatures::{
26        ConstSignature, EnumSignature, FunctionSignature, StaticFlags, StaticSignature,
27        StructSignature, TraitSignature, TypeAliasSignature, UnionSignature,
28    },
29    src::HasSource,
30};
31use hir_expand::{
32    HirFileId,
33    name::{AsName, Name},
34};
35use rustc_abi::ExternAbi;
36use stdx::{always, never};
37use syntax::{
38    AstNode, AstPtr, ToSmolStr,
39    ast::{self, HasName},
40    utils::is_raw_identifier,
41};
42
43use crate::db::HirDatabase;
44
45use self::case_conv::{to_camel_case, to_lower_snake_case, to_upper_snake_case};
46
47pub fn incorrect_case(db: &dyn HirDatabase, owner: ModuleDefId) -> Vec<IncorrectCase> {
48    let _p = tracing::info_span!("incorrect_case").entered();
49    let mut validator = DeclValidator::new(db);
50    validator.validate_item(owner);
51    validator.sink
52}
53
54#[derive(Debug)]
55pub enum CaseType {
56    /// `some_var`
57    LowerSnakeCase,
58    /// `SOME_CONST`
59    UpperSnakeCase,
60    /// `SomeStruct`
61    UpperCamelCase,
62}
63
64impl fmt::Display for CaseType {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        let repr = match self {
67            CaseType::LowerSnakeCase => "snake_case",
68            CaseType::UpperSnakeCase => "UPPER_SNAKE_CASE",
69            CaseType::UpperCamelCase => "UpperCamelCase",
70        };
71
72        repr.fmt(f)
73    }
74}
75
76#[derive(Debug)]
77pub enum IdentType {
78    Constant,
79    Enum,
80    Field,
81    Function,
82    Module,
83    Parameter,
84    StaticVariable,
85    Structure,
86    Trait,
87    TypeAlias,
88    Union,
89    Variable,
90    Variant,
91}
92
93impl fmt::Display for IdentType {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        let repr = match self {
96            IdentType::Constant => "Constant",
97            IdentType::Enum => "Enum",
98            IdentType::Field => "Field",
99            IdentType::Function => "Function",
100            IdentType::Module => "Module",
101            IdentType::Parameter => "Parameter",
102            IdentType::StaticVariable => "Static variable",
103            IdentType::Structure => "Structure",
104            IdentType::Trait => "Trait",
105            IdentType::TypeAlias => "Type alias",
106            IdentType::Union => "Union",
107            IdentType::Variable => "Variable",
108            IdentType::Variant => "Variant",
109        };
110
111        repr.fmt(f)
112    }
113}
114
115#[derive(Debug)]
116pub struct IncorrectCase {
117    pub file: HirFileId,
118    pub ident: AstPtr<ast::Name>,
119    pub expected_case: CaseType,
120    pub ident_type: IdentType,
121    pub ident_text: String,
122    pub suggested_text: String,
123}
124
125pub(super) struct DeclValidator<'a> {
126    db: &'a dyn HirDatabase,
127    pub(super) sink: Vec<IncorrectCase>,
128}
129
130#[derive(Debug)]
131struct Replacement {
132    current_name: Name,
133    suggested_text: String,
134    expected_case: CaseType,
135}
136
137impl<'a> DeclValidator<'a> {
138    pub(super) fn new(db: &'a dyn HirDatabase) -> DeclValidator<'a> {
139        DeclValidator { db, sink: Vec::new() }
140    }
141
142    pub(super) fn validate_item(&mut self, item: ModuleDefId) {
143        match item {
144            ModuleDefId::ModuleId(module_id) => self.validate_module(module_id),
145            ModuleDefId::TraitId(trait_id) => self.validate_trait(trait_id),
146            ModuleDefId::FunctionId(func) => self.validate_func(func),
147            ModuleDefId::AdtId(adt) => self.validate_adt(adt),
148            ModuleDefId::ConstId(const_id) => self.validate_const(const_id),
149            ModuleDefId::StaticId(static_id) => self.validate_static(static_id),
150            ModuleDefId::TypeAliasId(type_alias_id) => self.validate_type_alias(type_alias_id),
151            _ => (),
152        }
153    }
154
155    fn validate_adt(&mut self, adt: AdtId) {
156        match adt {
157            AdtId::StructId(struct_id) => self.validate_struct(struct_id),
158            AdtId::EnumId(enum_id) => self.validate_enum(enum_id),
159            AdtId::UnionId(union_id) => self.validate_union(union_id),
160        }
161    }
162
163    fn validate_module(&mut self, module_id: ModuleId) {
164        // Check the module name.
165        let Some(module_name) = module_id.name(self.db) else { return };
166        let Some(module_name_replacement) =
167            to_lower_snake_case(module_name.as_str()).map(|new_name| Replacement {
168                current_name: module_name,
169                suggested_text: new_name,
170                expected_case: CaseType::LowerSnakeCase,
171            })
172        else {
173            return;
174        };
175        let module_data = &module_id.def_map(self.db)[module_id];
176        let Some(module_src) = module_data.declaration_source(self.db) else {
177            return;
178        };
179        self.create_incorrect_case_diagnostic_for_ast_node(
180            module_name_replacement,
181            module_src.file_id,
182            &module_src.value,
183            IdentType::Module,
184        );
185    }
186
187    fn validate_trait(&mut self, trait_id: TraitId) {
188        // Check the trait name.
189        let data = TraitSignature::of(self.db, trait_id);
190        self.create_incorrect_case_diagnostic_for_item_name(
191            trait_id,
192            &data.name,
193            CaseType::UpperCamelCase,
194            IdentType::Trait,
195        );
196    }
197
198    fn validate_func(&mut self, func: FunctionId) {
199        let container = func.lookup(self.db).container;
200        if matches!(container, ItemContainerId::ExternBlockId(_)) {
201            cov_mark::hit!(extern_func_incorrect_case_ignored);
202            return;
203        }
204
205        // Check the function name.
206        // Skipped if function is an associated item of a trait implementation.
207        if !self.is_trait_impl_container(container) {
208            let data = FunctionSignature::of(self.db, func);
209
210            // Don't run the lint on extern "[not Rust]" fn items with the
211            // #[no_mangle] attribute.
212            let no_mangle = AttrFlags::query(self.db, func.into()).contains(AttrFlags::NO_MANGLE);
213            if no_mangle && data.abi != ExternAbi::Rust {
214                cov_mark::hit!(extern_func_no_mangle_ignored);
215            } else {
216                self.create_incorrect_case_diagnostic_for_item_name(
217                    func,
218                    &data.name,
219                    CaseType::LowerSnakeCase,
220                    IdentType::Function,
221                );
222            }
223        } else {
224            cov_mark::hit!(trait_impl_assoc_func_name_incorrect_case_ignored);
225        }
226
227        // Check the patterns inside the function body.
228        self.validate_func_body(func);
229    }
230
231    /// Check incorrect names for patterns inside the function body.
232    /// This includes function parameters except for trait implementation associated functions.
233    fn validate_func_body(&mut self, func: FunctionId) {
234        let body = Body::of(self.db, func.into());
235        let edition = self.edition(func);
236        let mut pats_replacements = body
237            .pats()
238            .filter_map(|(pat_id, pat)| match pat {
239                Pat::Bind { id, .. } => {
240                    let bind_name = &body[*id].name;
241                    let mut suggested_text = to_lower_snake_case(bind_name.as_str())?;
242                    if is_raw_identifier(&suggested_text, edition) {
243                        suggested_text.insert_str(0, "r#");
244                    }
245                    let replacement = Replacement {
246                        current_name: bind_name.clone(),
247                        suggested_text,
248                        expected_case: CaseType::LowerSnakeCase,
249                    };
250                    Some((pat_id, replacement))
251                }
252                _ => None,
253            })
254            .peekable();
255
256        // XXX: only look at source_map if we do have missing fields
257        if pats_replacements.peek().is_none() {
258            return;
259        }
260
261        let source_map = &Body::with_source_map(self.db, func.into()).1;
262        for (id, replacement) in pats_replacements {
263            let Ok(source_ptr) = source_map.pat_syntax(id) else {
264                continue;
265            };
266            let Some(ptr) = source_ptr.value.cast::<ast::IdentPat>() else {
267                continue;
268            };
269            let root = source_ptr.file_syntax(self.db);
270            let ident_pat = ptr.to_node(&root);
271            let Some(parent) = ident_pat.syntax().parent() else {
272                continue;
273            };
274
275            let is_shorthand = ast::RecordPatField::cast(parent.clone())
276                .map(|parent| parent.name_ref().is_none())
277                .unwrap_or_default();
278            if is_shorthand {
279                // We don't check shorthand field patterns, such as 'field' in `Thing { field }`,
280                // since the shorthand isn't the declaration.
281                continue;
282            }
283
284            let is_param = ast::Param::can_cast(parent.kind());
285            let ident_type = if is_param { IdentType::Parameter } else { IdentType::Variable };
286
287            self.create_incorrect_case_diagnostic_for_ast_node(
288                replacement,
289                source_ptr.file_id,
290                &ident_pat,
291                ident_type,
292            );
293        }
294    }
295
296    fn edition(&self, id: impl HasModule) -> span::Edition {
297        let krate = id.krate(self.db);
298        krate.data(self.db).edition
299    }
300
301    fn validate_struct(&mut self, struct_id: StructId) {
302        // Check the structure name.
303        let data = StructSignature::of(self.db, struct_id);
304
305        // rustc implementation excuses repr(C) since C structs predominantly don't
306        // use camel case.
307        let has_repr_c = data.repr(self.db, struct_id).is_some_and(|repr| repr.c());
308        if !has_repr_c {
309            self.create_incorrect_case_diagnostic_for_item_name(
310                struct_id,
311                &data.name,
312                CaseType::UpperCamelCase,
313                IdentType::Structure,
314            );
315        }
316
317        // Check the field names.
318        self.validate_struct_fields(struct_id);
319    }
320
321    /// Check incorrect names for struct fields.
322    fn validate_struct_fields(&mut self, struct_id: StructId) {
323        let data = struct_id.fields(self.db);
324        if data.shape != FieldsShape::Record {
325            return;
326        };
327        let edition = self.edition(struct_id);
328        let mut struct_fields_replacements = data
329            .fields()
330            .iter()
331            .filter_map(|(_, field)| {
332                to_lower_snake_case(&field.name.display_no_db(edition).to_smolstr()).map(
333                    |new_name| Replacement {
334                        current_name: field.name.clone(),
335                        suggested_text: new_name,
336                        expected_case: CaseType::LowerSnakeCase,
337                    },
338                )
339            })
340            .peekable();
341
342        // XXX: Only look at sources if we do have incorrect names.
343        if struct_fields_replacements.peek().is_none() {
344            return;
345        }
346
347        let struct_loc = struct_id.lookup(self.db);
348        let struct_src = struct_loc.source(self.db);
349
350        let Some(ast::FieldList::RecordFieldList(struct_fields_list)) =
351            struct_src.value.field_list()
352        else {
353            always!(
354                struct_fields_replacements.peek().is_none(),
355                "Replacements ({:?}) were generated for a structure fields \
356                which had no fields list: {:?}",
357                struct_fields_replacements.collect::<Vec<_>>(),
358                struct_src
359            );
360            return;
361        };
362        let mut struct_fields_iter = struct_fields_list.fields();
363        for field_replacement in struct_fields_replacements {
364            // We assume that parameters in replacement are in the same order as in the
365            // actual params list, but just some of them (ones that named correctly) are skipped.
366            let field = loop {
367                if let Some(field) = struct_fields_iter.next() {
368                    let Some(field_name) = field.name() else {
369                        continue;
370                    };
371                    if field_name.as_name() == field_replacement.current_name {
372                        break field;
373                    }
374                } else {
375                    never!(
376                        "Replacement ({:?}) was generated for a structure field \
377                        which was not found: {:?}",
378                        field_replacement,
379                        struct_src
380                    );
381                    return;
382                }
383            };
384
385            self.create_incorrect_case_diagnostic_for_ast_node(
386                field_replacement,
387                struct_src.file_id,
388                &field,
389                IdentType::Field,
390            );
391        }
392    }
393
394    fn validate_union(&mut self, union_id: UnionId) {
395        // Check the union name.
396        let data = UnionSignature::of(self.db, union_id);
397
398        // rustc implementation excuses repr(C) since C unions predominantly don't
399        // use camel case.
400        let has_repr_c = AttrFlags::repr(self.db, union_id.into()).is_some_and(|repr| repr.c());
401        if !has_repr_c {
402            self.create_incorrect_case_diagnostic_for_item_name(
403                union_id,
404                &data.name,
405                CaseType::UpperCamelCase,
406                IdentType::Union,
407            );
408        }
409
410        // Check the field names.
411        self.validate_union_fields(union_id);
412    }
413
414    /// Check incorrect names for union fields.
415    fn validate_union_fields(&mut self, union_id: UnionId) {
416        let data = union_id.fields(self.db);
417        let edition = self.edition(union_id);
418        let mut union_fields_replacements = data
419            .fields()
420            .iter()
421            .filter_map(|(_, field)| {
422                to_lower_snake_case(&field.name.display_no_db(edition).to_smolstr()).map(
423                    |new_name| Replacement {
424                        current_name: field.name.clone(),
425                        suggested_text: new_name,
426                        expected_case: CaseType::LowerSnakeCase,
427                    },
428                )
429            })
430            .peekable();
431
432        // XXX: Only look at sources if we do have incorrect names.
433        if union_fields_replacements.peek().is_none() {
434            return;
435        }
436
437        let union_loc = union_id.lookup(self.db);
438        let union_src = union_loc.source(self.db);
439
440        let Some(union_fields_list) = union_src.value.record_field_list() else {
441            always!(
442                union_fields_replacements.peek().is_none(),
443                "Replacements ({:?}) were generated for a union fields \
444                which had no fields list: {:?}",
445                union_fields_replacements.collect::<Vec<_>>(),
446                union_src
447            );
448            return;
449        };
450        let mut union_fields_iter = union_fields_list.fields();
451        for field_replacement in union_fields_replacements {
452            // We assume that parameters in replacement are in the same order as in the
453            // actual params list, but just some of them (ones that named correctly) are skipped.
454            let field = loop {
455                if let Some(field) = union_fields_iter.next() {
456                    let Some(field_name) = field.name() else {
457                        continue;
458                    };
459                    if field_name.as_name() == field_replacement.current_name {
460                        break field;
461                    }
462                } else {
463                    never!(
464                        "Replacement ({:?}) was generated for a union field \
465                        which was not found: {:?}",
466                        field_replacement,
467                        union_src
468                    );
469                    return;
470                }
471            };
472
473            self.create_incorrect_case_diagnostic_for_ast_node(
474                field_replacement,
475                union_src.file_id,
476                &field,
477                IdentType::Field,
478            );
479        }
480    }
481
482    fn validate_enum(&mut self, enum_id: EnumId) {
483        // Check the enum name.
484        let data = EnumSignature::of(self.db, enum_id);
485
486        // rustc implementation excuses repr(C) since C structs predominantly don't
487        // use camel case.
488        let has_repr_c = data.repr(self.db, enum_id).is_some_and(|repr| repr.c());
489        if !has_repr_c {
490            self.create_incorrect_case_diagnostic_for_item_name(
491                enum_id,
492                &data.name,
493                CaseType::UpperCamelCase,
494                IdentType::Enum,
495            );
496        }
497
498        // Check the variant names.
499        self.validate_enum_variants(enum_id)
500    }
501
502    /// Check incorrect names for enum variants.
503    fn validate_enum_variants(&mut self, enum_id: EnumId) {
504        let data = enum_id.enum_variants(self.db);
505
506        for (variant_id, _) in data.variants.values() {
507            self.validate_enum_variant_fields(*variant_id);
508        }
509
510        let mut enum_variants_replacements = data
511            .variants
512            .keys()
513            .filter_map(|name| {
514                to_camel_case(name.as_str()).map(|new_name| Replacement {
515                    current_name: name.clone(),
516                    suggested_text: new_name,
517                    expected_case: CaseType::UpperCamelCase,
518                })
519            })
520            .peekable();
521
522        // XXX: only look at sources if we do have incorrect names
523        if enum_variants_replacements.peek().is_none() {
524            return;
525        }
526
527        let enum_loc = enum_id.lookup(self.db);
528        let enum_src = enum_loc.source(self.db);
529
530        let Some(enum_variants_list) = enum_src.value.variant_list() else {
531            always!(
532                enum_variants_replacements.peek().is_none(),
533                "Replacements ({:?}) were generated for enum variants \
534                which had no fields list: {:?}",
535                enum_variants_replacements,
536                enum_src
537            );
538            return;
539        };
540        let mut enum_variants_iter = enum_variants_list.variants();
541        for variant_replacement in enum_variants_replacements {
542            // We assume that parameters in replacement are in the same order as in the
543            // actual params list, but just some of them (ones that named correctly) are skipped.
544            let variant = loop {
545                if let Some(variant) = enum_variants_iter.next() {
546                    let Some(variant_name) = variant.name() else {
547                        continue;
548                    };
549                    if variant_name.as_name() == variant_replacement.current_name {
550                        break variant;
551                    }
552                } else {
553                    never!(
554                        "Replacement ({:?}) was generated for an enum variant \
555                        which was not found: {:?}",
556                        variant_replacement,
557                        enum_src
558                    );
559                    return;
560                }
561            };
562
563            self.create_incorrect_case_diagnostic_for_ast_node(
564                variant_replacement,
565                enum_src.file_id,
566                &variant,
567                IdentType::Variant,
568            );
569        }
570    }
571
572    /// Check incorrect names for fields of enum variant.
573    fn validate_enum_variant_fields(&mut self, variant_id: EnumVariantId) {
574        let variant_data = variant_id.fields(self.db);
575        if variant_data.shape != FieldsShape::Record {
576            return;
577        };
578        let edition = self.edition(variant_id);
579        let mut variant_field_replacements = variant_data
580            .fields()
581            .iter()
582            .filter_map(|(_, field)| {
583                to_lower_snake_case(&field.name.display_no_db(edition).to_smolstr()).map(
584                    |new_name| Replacement {
585                        current_name: field.name.clone(),
586                        suggested_text: new_name,
587                        expected_case: CaseType::LowerSnakeCase,
588                    },
589                )
590            })
591            .peekable();
592
593        // XXX: only look at sources if we do have incorrect names
594        if variant_field_replacements.peek().is_none() {
595            return;
596        }
597
598        let variant_loc = variant_id.lookup(self.db);
599        let variant_src = variant_loc.source(self.db);
600
601        let Some(ast::FieldList::RecordFieldList(variant_fields_list)) =
602            variant_src.value.field_list()
603        else {
604            always!(
605                variant_field_replacements.peek().is_none(),
606                "Replacements ({:?}) were generated for an enum variant \
607                which had no fields list: {:?}",
608                variant_field_replacements.collect::<Vec<_>>(),
609                variant_src
610            );
611            return;
612        };
613        let mut variant_variants_iter = variant_fields_list.fields();
614        for field_replacement in variant_field_replacements {
615            // We assume that parameters in replacement are in the same order as in the
616            // actual params list, but just some of them (ones that named correctly) are skipped.
617            let field = loop {
618                if let Some(field) = variant_variants_iter.next() {
619                    let Some(field_name) = field.name() else {
620                        continue;
621                    };
622                    if field_name.as_name() == field_replacement.current_name {
623                        break field;
624                    }
625                } else {
626                    never!(
627                        "Replacement ({:?}) was generated for an enum variant field \
628                        which was not found: {:?}",
629                        field_replacement,
630                        variant_src
631                    );
632                    return;
633                }
634            };
635
636            self.create_incorrect_case_diagnostic_for_ast_node(
637                field_replacement,
638                variant_src.file_id,
639                &field,
640                IdentType::Field,
641            );
642        }
643    }
644
645    fn validate_const(&mut self, const_id: ConstId) {
646        let container = const_id.lookup(self.db).container;
647        if self.is_trait_impl_container(container) {
648            cov_mark::hit!(trait_impl_assoc_const_incorrect_case_ignored);
649            return;
650        }
651
652        let data = ConstSignature::of(self.db, const_id);
653        let Some(name) = &data.name else {
654            return;
655        };
656        self.create_incorrect_case_diagnostic_for_item_name(
657            const_id,
658            name,
659            CaseType::UpperSnakeCase,
660            IdentType::Constant,
661        );
662    }
663
664    fn validate_static(&mut self, static_id: StaticId) {
665        let data = StaticSignature::of(self.db, static_id);
666        if data.flags.contains(StaticFlags::EXTERN) {
667            cov_mark::hit!(extern_static_incorrect_case_ignored);
668            return;
669        }
670        if AttrFlags::query(self.db, static_id.into()).contains(AttrFlags::NO_MANGLE) {
671            cov_mark::hit!(no_mangle_static_incorrect_case_ignored);
672            return;
673        }
674
675        self.create_incorrect_case_diagnostic_for_item_name(
676            static_id,
677            &data.name,
678            CaseType::UpperSnakeCase,
679            IdentType::StaticVariable,
680        );
681    }
682
683    fn validate_type_alias(&mut self, type_alias_id: TypeAliasId) {
684        let container = type_alias_id.lookup(self.db).container;
685        if self.is_trait_impl_container(container) {
686            cov_mark::hit!(trait_impl_assoc_type_incorrect_case_ignored);
687            return;
688        }
689
690        // Check the type alias name.
691        let data = TypeAliasSignature::of(self.db, type_alias_id);
692        self.create_incorrect_case_diagnostic_for_item_name(
693            type_alias_id,
694            &data.name,
695            CaseType::UpperCamelCase,
696            IdentType::TypeAlias,
697        );
698    }
699
700    fn create_incorrect_case_diagnostic_for_item_name<N, S, L>(
701        &mut self,
702        item_id: L,
703        name: &Name,
704        expected_case: CaseType,
705        ident_type: IdentType,
706    ) where
707        N: AstNode + HasName + fmt::Debug,
708        S: HasSource<Value = N>,
709        L: Lookup<Data = S> + HasModule + Copy,
710    {
711        let to_expected_case_type = match expected_case {
712            CaseType::LowerSnakeCase => to_lower_snake_case,
713            CaseType::UpperSnakeCase => to_upper_snake_case,
714            CaseType::UpperCamelCase => to_camel_case,
715        };
716        let edition = self.edition(item_id);
717        let Some(replacement) = to_expected_case_type(name.as_str()).map(|mut new_name| {
718            if is_raw_identifier(&new_name, edition) {
719                new_name.insert_str(0, "r#");
720            }
721            Replacement { current_name: name.clone(), suggested_text: new_name, expected_case }
722        }) else {
723            return;
724        };
725
726        let item_loc = item_id.lookup(self.db);
727        let item_src = item_loc.source(self.db);
728        self.create_incorrect_case_diagnostic_for_ast_node(
729            replacement,
730            item_src.file_id,
731            &item_src.value,
732            ident_type,
733        );
734    }
735
736    fn create_incorrect_case_diagnostic_for_ast_node<T>(
737        &mut self,
738        replacement: Replacement,
739        file_id: HirFileId,
740        node: &T,
741        ident_type: IdentType,
742    ) where
743        T: AstNode + HasName + fmt::Debug,
744    {
745        let Some(name_ast) = node.name() else {
746            never!(
747                "Replacement ({:?}) was generated for a {:?} without a name: {:?}",
748                replacement,
749                ident_type,
750                node
751            );
752            return;
753        };
754
755        let edition = file_id.original_file(self.db).edition(self.db);
756        let diagnostic = IncorrectCase {
757            file: file_id,
758            ident_type,
759            ident: AstPtr::new(&name_ast),
760            expected_case: replacement.expected_case,
761            ident_text: replacement.current_name.display(self.db, edition).to_string(),
762            suggested_text: replacement.suggested_text,
763        };
764
765        self.sink.push(diagnostic);
766    }
767
768    fn is_trait_impl_container(&self, container_id: ItemContainerId) -> bool {
769        if let ItemContainerId::ImplId(impl_id) = container_id
770            && self.db.impl_trait(impl_id).is_some()
771        {
772            return true;
773        }
774        false
775    }
776}