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 edition = self.edition(enum_id);
511        let mut enum_variants_replacements = data
512            .variants
513            .keys()
514            .filter_map(|name| {
515                to_camel_case(&name.display_no_db(edition).to_smolstr()).map(|new_name| {
516                    Replacement {
517                        current_name: name.clone(),
518                        suggested_text: new_name,
519                        expected_case: CaseType::UpperCamelCase,
520                    }
521                })
522            })
523            .peekable();
524
525        // XXX: only look at sources if we do have incorrect names
526        if enum_variants_replacements.peek().is_none() {
527            return;
528        }
529
530        let enum_loc = enum_id.lookup(self.db);
531        let enum_src = enum_loc.source(self.db);
532
533        let Some(enum_variants_list) = enum_src.value.variant_list() else {
534            always!(
535                enum_variants_replacements.peek().is_none(),
536                "Replacements ({:?}) were generated for enum variants \
537                which had no fields list: {:?}",
538                enum_variants_replacements,
539                enum_src
540            );
541            return;
542        };
543        let mut enum_variants_iter = enum_variants_list.variants();
544        for variant_replacement in enum_variants_replacements {
545            // We assume that parameters in replacement are in the same order as in the
546            // actual params list, but just some of them (ones that named correctly) are skipped.
547            let variant = loop {
548                if let Some(variant) = enum_variants_iter.next() {
549                    let Some(variant_name) = variant.name() else {
550                        continue;
551                    };
552                    if variant_name.as_name() == variant_replacement.current_name {
553                        break variant;
554                    }
555                } else {
556                    never!(
557                        "Replacement ({:?}) was generated for an enum variant \
558                        which was not found: {:?}",
559                        variant_replacement,
560                        enum_src
561                    );
562                    return;
563                }
564            };
565
566            self.create_incorrect_case_diagnostic_for_ast_node(
567                variant_replacement,
568                enum_src.file_id,
569                &variant,
570                IdentType::Variant,
571            );
572        }
573    }
574
575    /// Check incorrect names for fields of enum variant.
576    fn validate_enum_variant_fields(&mut self, variant_id: EnumVariantId) {
577        let variant_data = variant_id.fields(self.db);
578        if variant_data.shape != FieldsShape::Record {
579            return;
580        };
581        let edition = self.edition(variant_id);
582        let mut variant_field_replacements = variant_data
583            .fields()
584            .iter()
585            .filter_map(|(_, field)| {
586                to_lower_snake_case(&field.name.display_no_db(edition).to_smolstr()).map(
587                    |new_name| Replacement {
588                        current_name: field.name.clone(),
589                        suggested_text: new_name,
590                        expected_case: CaseType::LowerSnakeCase,
591                    },
592                )
593            })
594            .peekable();
595
596        // XXX: only look at sources if we do have incorrect names
597        if variant_field_replacements.peek().is_none() {
598            return;
599        }
600
601        let variant_loc = variant_id.lookup(self.db);
602        let variant_src = variant_loc.source(self.db);
603
604        let Some(ast::FieldList::RecordFieldList(variant_fields_list)) =
605            variant_src.value.field_list()
606        else {
607            always!(
608                variant_field_replacements.peek().is_none(),
609                "Replacements ({:?}) were generated for an enum variant \
610                which had no fields list: {:?}",
611                variant_field_replacements.collect::<Vec<_>>(),
612                variant_src
613            );
614            return;
615        };
616        let mut variant_variants_iter = variant_fields_list.fields();
617        for field_replacement in variant_field_replacements {
618            // We assume that parameters in replacement are in the same order as in the
619            // actual params list, but just some of them (ones that named correctly) are skipped.
620            let field = loop {
621                if let Some(field) = variant_variants_iter.next() {
622                    let Some(field_name) = field.name() else {
623                        continue;
624                    };
625                    if field_name.as_name() == field_replacement.current_name {
626                        break field;
627                    }
628                } else {
629                    never!(
630                        "Replacement ({:?}) was generated for an enum variant field \
631                        which was not found: {:?}",
632                        field_replacement,
633                        variant_src
634                    );
635                    return;
636                }
637            };
638
639            self.create_incorrect_case_diagnostic_for_ast_node(
640                field_replacement,
641                variant_src.file_id,
642                &field,
643                IdentType::Field,
644            );
645        }
646    }
647
648    fn validate_const(&mut self, const_id: ConstId) {
649        let container = const_id.lookup(self.db).container;
650        if self.is_trait_impl_container(container) {
651            cov_mark::hit!(trait_impl_assoc_const_incorrect_case_ignored);
652            return;
653        }
654
655        let data = ConstSignature::of(self.db, const_id);
656        let Some(name) = &data.name else {
657            return;
658        };
659        self.create_incorrect_case_diagnostic_for_item_name(
660            const_id,
661            name,
662            CaseType::UpperSnakeCase,
663            IdentType::Constant,
664        );
665    }
666
667    fn validate_static(&mut self, static_id: StaticId) {
668        let data = StaticSignature::of(self.db, static_id);
669        if data.flags.contains(StaticFlags::EXTERN) {
670            cov_mark::hit!(extern_static_incorrect_case_ignored);
671            return;
672        }
673        if AttrFlags::query(self.db, static_id.into()).contains(AttrFlags::NO_MANGLE) {
674            cov_mark::hit!(no_mangle_static_incorrect_case_ignored);
675            return;
676        }
677
678        self.create_incorrect_case_diagnostic_for_item_name(
679            static_id,
680            &data.name,
681            CaseType::UpperSnakeCase,
682            IdentType::StaticVariable,
683        );
684    }
685
686    fn validate_type_alias(&mut self, type_alias_id: TypeAliasId) {
687        let container = type_alias_id.lookup(self.db).container;
688        if self.is_trait_impl_container(container) {
689            cov_mark::hit!(trait_impl_assoc_type_incorrect_case_ignored);
690            return;
691        }
692
693        // Check the type alias name.
694        let data = TypeAliasSignature::of(self.db, type_alias_id);
695        self.create_incorrect_case_diagnostic_for_item_name(
696            type_alias_id,
697            &data.name,
698            CaseType::UpperCamelCase,
699            IdentType::TypeAlias,
700        );
701    }
702
703    fn create_incorrect_case_diagnostic_for_item_name<N, S, L>(
704        &mut self,
705        item_id: L,
706        name: &Name,
707        expected_case: CaseType,
708        ident_type: IdentType,
709    ) where
710        N: AstNode + HasName + fmt::Debug,
711        S: HasSource<Value = N>,
712        L: Lookup<Data = S> + HasModule + Copy,
713    {
714        let to_expected_case_type = match expected_case {
715            CaseType::LowerSnakeCase => to_lower_snake_case,
716            CaseType::UpperSnakeCase => to_upper_snake_case,
717            CaseType::UpperCamelCase => to_camel_case,
718        };
719        let edition = self.edition(item_id);
720        let Some(replacement) =
721            to_expected_case_type(&name.display(self.db, edition).to_smolstr()).map(|new_name| {
722                Replacement { current_name: name.clone(), suggested_text: new_name, expected_case }
723            })
724        else {
725            return;
726        };
727
728        let item_loc = item_id.lookup(self.db);
729        let item_src = item_loc.source(self.db);
730        self.create_incorrect_case_diagnostic_for_ast_node(
731            replacement,
732            item_src.file_id,
733            &item_src.value,
734            ident_type,
735        );
736    }
737
738    fn create_incorrect_case_diagnostic_for_ast_node<T>(
739        &mut self,
740        replacement: Replacement,
741        file_id: HirFileId,
742        node: &T,
743        ident_type: IdentType,
744    ) where
745        T: AstNode + HasName + fmt::Debug,
746    {
747        let Some(name_ast) = node.name() else {
748            never!(
749                "Replacement ({:?}) was generated for a {:?} without a name: {:?}",
750                replacement,
751                ident_type,
752                node
753            );
754            return;
755        };
756
757        let edition = file_id.original_file(self.db).edition(self.db);
758        let diagnostic = IncorrectCase {
759            file: file_id,
760            ident_type,
761            ident: AstPtr::new(&name_ast),
762            expected_case: replacement.expected_case,
763            ident_text: replacement.current_name.display(self.db, edition).to_string(),
764            suggested_text: replacement.suggested_text,
765        };
766
767        self.sink.push(diagnostic);
768    }
769
770    fn is_trait_impl_container(&self, container_id: ItemContainerId) -> bool {
771        if let ItemContainerId::ImplId(impl_id) = container_id
772            && self.db.impl_trait(impl_id).is_some()
773        {
774            return true;
775        }
776        false
777    }
778}