1use std::borrow::Cow;
2
3use ahash::HashSet;
4
5use mago_database::file::FileId;
6use mago_interner::StringIdentifier;
7use mago_interner::ThreadedInterner;
8use mago_span::Position;
9use mago_span::Span;
10
11use crate::identifier::method::MethodIdentifier;
12use crate::metadata::CodebaseMetadata;
13use crate::metadata::class_like::ClassLikeMetadata;
14use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
15use crate::metadata::constant::ConstantMetadata;
16use crate::metadata::enum_case::EnumCaseMetadata;
17use crate::metadata::function_like::FunctionLikeMetadata;
18use crate::metadata::property::PropertyMetadata;
19use crate::metadata::ttype::TypeMetadata;
20use crate::symbol::SymbolKind;
21use crate::ttype::atomic::TAtomic;
22use crate::ttype::atomic::object::TObject;
23use crate::ttype::union::TUnion;
24
25pub mod assertion;
26pub mod consts;
27pub mod context;
28pub mod diff;
29pub mod flags;
30pub mod identifier;
31pub mod issue;
32pub mod metadata;
33pub mod misc;
34pub mod populator;
35pub mod reference;
36pub mod scanner;
37pub mod symbol;
38pub mod ttype;
39pub mod visibility;
40
41mod utils;
42
43pub fn function_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
47 let lowered_id = interner.lowered(id);
48 codebase.function_likes.contains_key(&(StringIdentifier::empty(), lowered_id))
49}
50
51pub fn constant_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
56 let lowered_id = lower_constant_name(interner, id);
57 codebase.constants.contains_key(&lowered_id)
58}
59
60pub fn class_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
64 let lowered_id = interner.lowered(id);
65
66 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Class))
67}
68
69pub fn class_or_trait_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
73 let lowered_id = interner.lowered(id);
74
75 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Class | SymbolKind::Trait))
76}
77
78pub fn interface_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
82 let lowered_id = interner.lowered(id);
83
84 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Interface))
85}
86
87pub fn class_or_interface_exists(
91 codebase: &CodebaseMetadata,
92 interner: &ThreadedInterner,
93 id: &StringIdentifier,
94) -> bool {
95 let lowered_id = interner.lowered(id);
96
97 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Class | SymbolKind::Interface))
98}
99
100pub fn enum_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
104 let lowered_id = interner.lowered(id);
105
106 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Enum))
107}
108
109pub fn trait_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
113 let lowered_id = interner.lowered(id);
114
115 matches!(codebase.symbols.get_kind(&lowered_id), Some(SymbolKind::Trait))
116}
117
118pub fn class_like_exists(codebase: &CodebaseMetadata, interner: &ThreadedInterner, id: &StringIdentifier) -> bool {
122 let lowered_id = interner.lowered(id);
123
124 matches!(
125 codebase.symbols.get_kind(&lowered_id),
126 Some(SymbolKind::Class | SymbolKind::Interface | SymbolKind::Enum | SymbolKind::Trait)
127 )
128}
129
130pub fn method_exists(
134 codebase: &CodebaseMetadata,
135 interner: &ThreadedInterner,
136 fqc_id: &StringIdentifier,
137 method_id: &StringIdentifier,
138) -> bool {
139 let lowered_fqc_id = interner.lowered(fqc_id);
140 let lowered_method_id = interner.lowered(method_id);
141
142 codebase
143 .class_likes
144 .get(&lowered_fqc_id)
145 .is_some_and(|meta| meta.appearing_method_ids.contains_key(&lowered_method_id))
146}
147
148pub fn method_id_exists(
149 codebase: &CodebaseMetadata,
150 interner: &ThreadedInterner,
151 method_id: &MethodIdentifier,
152) -> bool {
153 let lowered_fqc_id = interner.lowered(method_id.get_class_name());
154 let lowered_method_id = interner.lowered(method_id.get_method_name());
155
156 codebase.function_likes.contains_key(&(lowered_fqc_id, lowered_method_id))
157}
158
159pub fn is_method_abstract(
160 codebase: &CodebaseMetadata,
161 interner: &ThreadedInterner,
162 fqc_id: &StringIdentifier,
163 method_id: &StringIdentifier,
164) -> bool {
165 let lowered_fqc_id = interner.lowered(fqc_id);
166 let lowered_method_id = interner.lowered(method_id);
167
168 codebase
169 .function_likes
170 .get(&(lowered_fqc_id, lowered_method_id))
171 .and_then(|meta| meta.method_metadata.as_ref())
172 .is_some_and(|method| method.is_abstract)
173}
174
175pub fn is_method_static(
176 codebase: &CodebaseMetadata,
177 interner: &ThreadedInterner,
178 fqc_id: &StringIdentifier,
179 method_id: &StringIdentifier,
180) -> bool {
181 let lowered_fqc_id = interner.lowered(fqc_id);
182 let lowered_method_id = interner.lowered(method_id);
183
184 codebase
185 .function_likes
186 .get(&(lowered_fqc_id, lowered_method_id))
187 .and_then(|meta| meta.method_metadata.as_ref())
188 .is_some_and(|method| method.is_static)
189}
190
191pub fn is_method_final(
192 codebase: &CodebaseMetadata,
193 interner: &ThreadedInterner,
194 fqc_id: &StringIdentifier,
195 method_id: &StringIdentifier,
196) -> bool {
197 let lowered_fqc_id = interner.lowered(fqc_id);
198 let lowered_method_id = interner.lowered(method_id);
199
200 codebase
201 .function_likes
202 .get(&(lowered_fqc_id, lowered_method_id))
203 .and_then(|meta| meta.method_metadata.as_ref())
204 .is_some_and(|method| method.is_final)
205}
206
207pub fn property_exists(
211 codebase: &CodebaseMetadata,
212 interner: &ThreadedInterner,
213 fqc_id: &StringIdentifier,
214 property_id: &StringIdentifier,
215) -> bool {
216 let lowered_fqc_id = interner.lowered(fqc_id);
217
218 codebase.class_likes.get(&lowered_fqc_id).is_some_and(|meta| meta.appearing_property_ids.contains_key(property_id))
219}
220
221pub fn declaring_method_exists(
225 codebase: &CodebaseMetadata,
226 interner: &ThreadedInterner,
227 fqc_id: &StringIdentifier,
228 method_id: &StringIdentifier,
229) -> bool {
230 let lowered_fqc_id = interner.lowered(fqc_id);
231 let lowered_method_id = interner.lowered(method_id);
232
233 codebase
234 .class_likes
235 .get(&lowered_fqc_id)
236 .is_some_and(|meta| meta.declaring_method_ids.contains_key(&lowered_method_id))
237}
238
239pub fn declaring_property_exists(
243 codebase: &CodebaseMetadata,
244 interner: &ThreadedInterner,
245 fqc_id: &StringIdentifier,
246 property_id: &StringIdentifier,
247) -> bool {
248 let lowered_fqc_id = interner.lowered(fqc_id);
249
250 codebase.class_likes.get(&lowered_fqc_id).is_some_and(|meta| meta.properties.contains_key(property_id))
251}
252
253pub fn class_like_constant_or_enum_case_exists(
257 codebase: &CodebaseMetadata,
258 interner: &ThreadedInterner,
259 fqc_id: &StringIdentifier,
260 constant_id: &StringIdentifier,
261) -> bool {
262 let lowered_fqc_id = interner.lowered(fqc_id);
263
264 if let Some(meta) = codebase.class_likes.get(&lowered_fqc_id) {
265 return meta.constants.contains_key(constant_id) || meta.enum_cases.contains_key(constant_id);
266 }
267
268 false
269}
270
271pub fn get_function<'a>(
275 codebase: &'a CodebaseMetadata,
276 interner: &ThreadedInterner,
277 id: &StringIdentifier,
278) -> Option<&'a FunctionLikeMetadata> {
279 let lowered_id = interner.lowered(id);
280
281 codebase.function_likes.get(&(StringIdentifier::empty(), lowered_id))
282}
283
284pub fn get_closure<'a>(
288 codebase: &'a CodebaseMetadata,
289 interner: &ThreadedInterner,
290 file_id: &FileId,
291 position: &Position,
292) -> Option<&'a FunctionLikeMetadata> {
293 let file_id = interner.intern(file_id.to_string());
294 let closure_id = interner.intern(position.to_string());
295
296 codebase.function_likes.get(&(file_id, closure_id))
297}
298
299pub fn get_constant<'a>(
303 codebase: &'a CodebaseMetadata,
304 interner: &ThreadedInterner,
305 id: &StringIdentifier,
306) -> Option<&'a ConstantMetadata> {
307 let lowered_id = lower_constant_name(interner, id);
308
309 codebase.constants.get(&lowered_id)
310}
311
312pub fn get_class<'a>(
316 codebase: &'a CodebaseMetadata,
317 interner: &ThreadedInterner,
318 id: &StringIdentifier,
319) -> Option<&'a ClassLikeMetadata> {
320 let lowered_id = interner.lowered(id);
321
322 if class_exists(codebase, interner, id) { codebase.class_likes.get(&lowered_id) } else { None }
323}
324
325pub fn get_interface<'a>(
329 codebase: &'a CodebaseMetadata,
330 interner: &ThreadedInterner,
331 id: &StringIdentifier,
332) -> Option<&'a ClassLikeMetadata> {
333 let lowered_id = interner.lowered(id);
334
335 if interface_exists(codebase, interner, id) { codebase.class_likes.get(&lowered_id) } else { None }
336}
337
338pub fn get_enum<'a>(
342 codebase: &'a CodebaseMetadata,
343 interner: &ThreadedInterner,
344 id: &StringIdentifier,
345) -> Option<&'a ClassLikeMetadata> {
346 let lowered_id = interner.lowered(id);
347
348 if enum_exists(codebase, interner, id) { codebase.class_likes.get(&lowered_id) } else { None }
349}
350
351pub fn get_trait<'a>(
355 codebase: &'a CodebaseMetadata,
356 interner: &ThreadedInterner,
357 id: &StringIdentifier,
358) -> Option<&'a ClassLikeMetadata> {
359 let lowered_id = interner.lowered(id);
360
361 if trait_exists(codebase, interner, id) { codebase.class_likes.get(&lowered_id) } else { None }
362}
363
364pub fn get_anonymous_class_name(interner: &ThreadedInterner, span: Span) -> StringIdentifier {
365 interner.intern(format!("class@anonymous:{}-{}:{}", span.file_id, span.start.offset, span.end.offset,))
366}
367
368pub fn get_anonymous_class<'a>(
373 codebase: &'a CodebaseMetadata,
374 interner: &ThreadedInterner,
375 span: Span,
376) -> Option<&'a ClassLikeMetadata> {
377 let name = get_anonymous_class_name(interner, span);
378
379 if class_exists(codebase, interner, &name) { codebase.class_likes.get(&name) } else { None }
380}
381
382pub fn get_class_like<'a>(
386 codebase: &'a CodebaseMetadata,
387 interner: &ThreadedInterner,
388 id: &StringIdentifier,
389) -> Option<&'a ClassLikeMetadata> {
390 let lowered_id = interner.lowered(id);
391 codebase.class_likes.get(&lowered_id)
392}
393
394pub fn get_declaring_class_for_property(
395 codebase: &CodebaseMetadata,
396 interner: &ThreadedInterner,
397 fqc_id: &StringIdentifier,
398 property_id: &StringIdentifier,
399) -> Option<StringIdentifier> {
400 let lowered_fqc_id = interner.lowered(fqc_id);
401
402 let class_like = codebase.class_likes.get(&lowered_fqc_id)?;
403
404 class_like.declaring_property_ids.get(property_id).copied()
405}
406
407pub fn get_declaring_property<'a>(
412 codebase: &'a CodebaseMetadata,
413 interner: &ThreadedInterner,
414 fqc_id: &StringIdentifier,
415 property_id: &StringIdentifier,
416) -> Option<&'a PropertyMetadata> {
417 let declaring_fqc_id = get_declaring_class_for_property(codebase, interner, fqc_id, property_id)?;
418 let declaring_class_like = codebase.class_likes.get(&declaring_fqc_id)?;
419
420 declaring_class_like.properties.get(property_id)
421}
422
423pub fn get_method_id(fqc_id: &StringIdentifier, method_name_id: &StringIdentifier) -> MethodIdentifier {
424 MethodIdentifier::new(*fqc_id, *method_name_id)
425}
426
427pub fn get_declaring_method_id(
428 codebase: &CodebaseMetadata,
429 interner: &ThreadedInterner,
430 method_id: &MethodIdentifier,
431) -> MethodIdentifier {
432 let lowered_fqc_id = interner.lowered(method_id.get_class_name());
433 let lowered_method_id = interner.lowered(method_id.get_method_name());
434
435 let Some(class_like_metadata) = codebase.class_likes.get(&lowered_fqc_id) else {
436 return *method_id;
438 };
439
440 if let Some(declaring_fqcn) = class_like_metadata.declaring_method_ids.get(&lowered_method_id)
441 && let Some(declaring_class_metadata) = codebase.class_likes.get(declaring_fqcn)
442 {
443 return MethodIdentifier::new(declaring_class_metadata.original_name, *method_id.get_method_name());
444 };
445
446 if class_like_metadata.flags.is_abstract()
447 && let Some(overridden_classes) = class_like_metadata.overridden_method_ids.get(&lowered_method_id)
448 && let Some(first_class) = overridden_classes.iter().next()
449 && let Some(first_class_metadata) = codebase.class_likes.get(first_class)
450 {
451 return MethodIdentifier::new(first_class_metadata.original_name, *method_id.get_method_name());
452 }
453
454 *method_id
456}
457
458pub fn get_declaring_method<'a>(
465 codebase: &'a CodebaseMetadata,
466 interner: &ThreadedInterner,
467 fqc_id: &StringIdentifier,
468 method_name_id: &StringIdentifier,
469) -> Option<&'a FunctionLikeMetadata> {
470 let method_id = MethodIdentifier::new(interner.lowered(fqc_id), interner.lowered(method_name_id));
471 let declaring_method_id = get_declaring_method_id(codebase, interner, &method_id);
472
473 get_method(codebase, interner, declaring_method_id.get_class_name(), declaring_method_id.get_method_name())
474}
475
476pub fn get_method_by_id<'a>(
477 codebase: &'a CodebaseMetadata,
478 interner: &ThreadedInterner,
479 method_id: &MethodIdentifier,
480) -> Option<&'a FunctionLikeMetadata> {
481 let lowered_fqc_id = interner.lowered(method_id.get_class_name());
482 let lowered_method_id = interner.lowered(method_id.get_method_name());
483
484 codebase.function_likes.get(&(lowered_fqc_id, lowered_method_id))
485}
486
487pub fn get_method<'a>(
488 codebase: &'a CodebaseMetadata,
489 interner: &ThreadedInterner,
490 fqc_id: &StringIdentifier,
491 method_name_id: &StringIdentifier,
492) -> Option<&'a FunctionLikeMetadata> {
493 let lowered_fqc_id = interner.lowered(fqc_id);
494 let lowered_method_id = interner.lowered(method_name_id);
495
496 codebase.function_likes.get(&(lowered_fqc_id, lowered_method_id))
497}
498
499pub fn get_property<'a>(
504 codebase: &'a CodebaseMetadata,
505 interner: &ThreadedInterner,
506 fqc_id: &StringIdentifier,
507 property_id: &StringIdentifier,
508) -> Option<&'a PropertyMetadata> {
509 let lowered_fqc_id = interner.lowered(fqc_id);
510
511 let class_like = codebase.class_likes.get(&lowered_fqc_id)?;
512
513 class_like.properties.get(property_id)
514}
515
516#[derive(Debug, PartialEq)]
518pub enum ClassConstantOrEnumCase<'a> {
519 Constant(&'a ClassLikeConstantMetadata),
520 EnumCase(&'a EnumCaseMetadata),
521}
522
523pub fn get_class_like_constant_or_enum_case<'a>(
527 codebase: &'a CodebaseMetadata,
528 interner: &ThreadedInterner,
529 fqc_id: &StringIdentifier,
530 constant_id: &StringIdentifier,
531) -> Option<ClassConstantOrEnumCase<'a>> {
532 let lowered_fqc_id = interner.lowered(fqc_id);
533 let class_like = codebase.class_likes.get(&lowered_fqc_id)?;
534
535 if let Some(constant_meta) = class_like.constants.get(constant_id) {
536 return Some(ClassConstantOrEnumCase::Constant(constant_meta));
537 }
538
539 if let Some(enum_case_meta) = class_like.enum_cases.get(constant_id) {
540 return Some(ClassConstantOrEnumCase::EnumCase(enum_case_meta));
541 }
542
543 None
544}
545
546pub fn is_instance_of(
551 codebase: &CodebaseMetadata,
552 interner: &ThreadedInterner,
553 child: &StringIdentifier,
554 parent: &StringIdentifier,
555) -> bool {
556 let lowered_child = interner.lowered(child);
557 let lowered_parent = interner.lowered(parent);
558
559 if lowered_child == lowered_parent {
560 return true;
561 }
562
563 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
564 return false;
565 };
566
567 child_meta.has_parent(&lowered_parent)
568}
569
570pub fn inherits_class(
571 codebase: &CodebaseMetadata,
572 interner: &ThreadedInterner,
573 child: &StringIdentifier,
574 parent: &StringIdentifier,
575) -> bool {
576 let lowered_child = interner.lowered(child);
577 let lowered_parent = interner.lowered(parent);
578
579 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
580 return false;
581 };
582
583 child_meta.all_parent_classes.contains(&lowered_parent)
584}
585
586pub fn directly_inherits_class(
587 codebase: &CodebaseMetadata,
588 interner: &ThreadedInterner,
589 child: &StringIdentifier,
590 parent: &StringIdentifier,
591) -> bool {
592 let lowered_child = interner.lowered(child);
593 let lowered_parent = interner.lowered(parent);
594
595 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
596 return false;
597 };
598
599 child_meta.direct_parent_class.as_ref().is_some_and(|parent_class| parent_class == &lowered_parent)
600}
601
602pub fn inherits_interface(
603 codebase: &CodebaseMetadata,
604 interner: &ThreadedInterner,
605 child: &StringIdentifier,
606 parent: &StringIdentifier,
607) -> bool {
608 let lowered_child = interner.lowered(child);
609 let lowered_parent = interner.lowered(parent);
610
611 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
612 return false;
613 };
614
615 child_meta.all_parent_interfaces.contains(&lowered_parent)
616}
617
618pub fn directly_inherits_interface(
619 codebase: &CodebaseMetadata,
620 interner: &ThreadedInterner,
621 child: &StringIdentifier,
622 parent: &StringIdentifier,
623) -> bool {
624 let lowered_child = interner.lowered(child);
625 let lowered_parent = interner.lowered(parent);
626
627 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
628 return false;
629 };
630
631 child_meta.direct_parent_interfaces.contains(&lowered_parent)
632}
633
634pub fn uses_trait(
635 codebase: &CodebaseMetadata,
636 interner: &ThreadedInterner,
637 child: &StringIdentifier,
638 trait_name: &StringIdentifier,
639) -> bool {
640 let lowered_child = interner.lowered(child);
641 let lowered_trait_name = interner.lowered(trait_name);
642
643 let Some(child_meta) = codebase.class_likes.get(&lowered_child) else {
644 return false;
645 };
646
647 child_meta.used_traits.contains(&lowered_trait_name)
648}
649
650#[inline]
654pub fn get_all_descendants(
655 codebase: &CodebaseMetadata,
656 interner: &ThreadedInterner,
657 class_like_name: &StringIdentifier,
658) -> HashSet<StringIdentifier> {
659 let fqc_id = interner.lowered(class_like_name);
660
661 let mut all_descendants = HashSet::default();
663 let mut queue = vec![&fqc_id];
664 let mut visited = HashSet::default();
665 visited.insert(&fqc_id); while let Some(current_name) = queue.pop() {
668 if let Some(direct_descendants) = codebase.direct_classlike_descendants.get(current_name) {
669 for descendant in direct_descendants {
670 if visited.insert(descendant) {
671 all_descendants.insert(*descendant);
673 queue.push(descendant); }
675 }
676 }
677 }
678 all_descendants
679}
680
681pub fn is_method_overriding(
688 codebase: &CodebaseMetadata,
689 interner: &ThreadedInterner,
690 fqc_id: &StringIdentifier,
691 method_name: &StringIdentifier,
692) -> bool {
693 let lowered_method_name = interner.lowered(method_name);
694
695 get_class_like(codebase, interner, fqc_id)
696 .is_some_and(|metadata| metadata.overridden_method_ids.contains_key(&lowered_method_name))
697}
698
699pub fn get_function_like_thrown_types<'a>(
700 codebase: &'a CodebaseMetadata,
701 class_like: Option<&'a ClassLikeMetadata>,
702 function_like: &'a FunctionLikeMetadata,
703) -> &'a [TypeMetadata] {
704 if !function_like.thrown_types.is_empty() {
705 return function_like.thrown_types.as_slice();
706 }
707
708 if !function_like.kind.is_method() {
709 return &[];
710 }
711
712 let Some(class_like) = class_like else {
713 return &[];
714 };
715
716 let Some(method_name) = function_like.name.as_ref() else {
717 return &[];
718 };
719
720 for parent_class_name_id in class_like.overridden_method_ids.get(method_name).into_iter().flatten() {
721 let Some(parent_class) = codebase.class_likes.get(parent_class_name_id) else {
722 continue;
723 };
724
725 let parent_method_id = (*parent_class_name_id, *method_name);
726 if let Some(parent_method) = codebase.function_likes.get(&parent_method_id) {
727 let thrown = get_function_like_thrown_types(codebase, Some(parent_class), parent_method);
728 if !thrown.is_empty() {
729 return thrown;
730 }
731 }
732 }
733
734 &[]
735}
736
737#[inline]
740pub fn get_class_constant_type<'a>(
741 codebase: &'a CodebaseMetadata,
742 interner: &ThreadedInterner,
743 fq_class_name: &StringIdentifier,
744 constant_name: &StringIdentifier,
745) -> Option<Cow<'a, TUnion>> {
746 let class_metadata = get_class_like(codebase, interner, fq_class_name)?;
747
748 if class_metadata.kind.is_enum() && class_metadata.enum_cases.contains_key(constant_name) {
749 return Some(Cow::Owned(TUnion::new(vec![TAtomic::Object(TObject::new_enum_case(
750 class_metadata.original_name,
751 *constant_name,
752 ))])));
753 }
754
755 let constant_metadata = class_metadata.constants.get(constant_name)?;
757
758 if let Some(type_metadata) = constant_metadata.type_metadata.as_ref() {
760 return Some(Cow::Borrowed(&type_metadata.type_union));
764 }
765
766 constant_metadata.inferred_type.as_ref().map(|atomic_type| {
768 Cow::Owned(TUnion::new(vec![atomic_type.clone()]))
770 })
771}
772
773fn lower_constant_name(interner: &ThreadedInterner, name: &StringIdentifier) -> StringIdentifier {
778 let name_str = interner.lookup(name);
779 if !name_str.contains('\\') {
780 return *name;
781 }
782
783 let mut parts: Vec<_> = name_str.split('\\').map(str::to_owned).collect();
784 let total_parts = parts.len();
785 if total_parts > 1 {
786 parts = parts
787 .into_iter()
788 .enumerate()
789 .map(|(i, part)| if i < total_parts - 1 { part.to_ascii_lowercase() } else { part })
790 .collect::<Vec<_>>();
791 }
792
793 interner.intern(parts.join("\\"))
794}