1#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
10pub enum NameAtomError {
11 #[error("name atom cannot be empty")]
13 Empty,
14 #[error("name atom cannot contain `.`")]
16 ContainsDot,
17}
18
19#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub struct NameAtom(String);
27
28impl NameAtom {
29 pub fn parse(s: impl Into<String>) -> Result<Self, NameAtomError> {
36 let s = s.into();
37 if s.is_empty() {
38 return Err(NameAtomError::Empty);
39 }
40 if s.contains('.') {
41 return Err(NameAtomError::ContainsDot);
42 }
43 Ok(Self(s))
44 }
45
46 #[must_use]
52 pub(crate) fn new_unchecked_for_parser(s: String) -> Self {
53 debug_assert!(Self::parse(s.as_str()).is_ok());
54 Self(s)
55 }
56
57 #[must_use]
59 pub fn as_str(&self) -> &str {
60 &self.0
61 }
62
63 #[must_use]
65 pub fn into_inner(self) -> String {
66 self.0
67 }
68}
69
70impl std::fmt::Debug for NameAtom {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 std::fmt::Debug::fmt(&self.0, f)
73 }
74}
75
76impl std::fmt::Display for NameAtom {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 f.write_str(&self.0)
79 }
80}
81
82impl std::ops::Deref for NameAtom {
83 type Target = str;
84
85 fn deref(&self) -> &Self::Target {
86 self.as_str()
87 }
88}
89
90impl PartialEq<str> for NameAtom {
91 fn eq(&self, other: &str) -> bool {
92 self.as_str() == other
93 }
94}
95
96impl PartialEq<&str> for NameAtom {
97 fn eq(&self, other: &&str) -> bool {
98 self.as_str() == *other
99 }
100}
101
102impl PartialEq<String> for NameAtom {
103 fn eq(&self, other: &String) -> bool {
104 self.as_str() == other
105 }
106}
107
108impl PartialEq<NameAtom> for str {
109 fn eq(&self, other: &NameAtom) -> bool {
110 self == other.as_str()
111 }
112}
113
114impl PartialEq<NameAtom> for &str {
115 fn eq(&self, other: &NameAtom) -> bool {
116 *self == other.as_str()
117 }
118}
119
120impl AsRef<str> for NameAtom {
121 fn as_ref(&self) -> &str {
122 self.as_str()
123 }
124}
125
126impl std::borrow::Borrow<str> for NameAtom {
127 fn borrow(&self) -> &str {
128 self.as_str()
129 }
130}
131
132impl From<NameAtom> for String {
133 fn from(atom: NameAtom) -> Self {
134 atom.into_inner()
135 }
136}
137
138impl From<&NameAtom> for String {
139 fn from(atom: &NameAtom) -> Self {
140 atom.as_str().to_string()
141 }
142}
143
144impl From<NameAtom> for std::borrow::Cow<'_, str> {
145 fn from(atom: NameAtom) -> Self {
146 Self::Owned(atom.into_inner())
147 }
148}
149
150impl TryFrom<String> for NameAtom {
151 type Error = NameAtomError;
152
153 fn try_from(value: String) -> Result<Self, Self::Error> {
154 Self::parse(value)
155 }
156}
157
158impl TryFrom<&str> for NameAtom {
159 type Error = NameAtomError;
160
161 fn try_from(value: &str) -> Result<Self, Self::Error> {
162 Self::parse(value)
163 }
164}
165
166use std::marker::PhantomData;
167
168pub trait NameNamespace:
175 std::fmt::Debug + Clone + Copy + PartialEq + Eq + std::hash::Hash + PartialOrd + Ord + 'static
176{
177 const DISPLAY_NAME: &'static str;
179}
180
181pub mod namespace {
185 use super::NameNamespace;
186
187 macro_rules! define_namespace {
188 ($($(#[$meta:meta])* $Name:ident => $display:literal;)+) => {
189 $(
190 $(#[$meta])*
191 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
192 pub enum $Name {}
193
194 impl NameNamespace for $Name {
195 const DISPLAY_NAME: &'static str = $display;
196 }
197 )+
198 };
199 }
200
201 define_namespace! {
202 Decl => "DeclName";
204 Dim => "DimName";
206 Unit => "UnitName";
208 StructType => "StructTypeName";
210 Index => "IndexName";
212 Fn => "FnName";
214 Field => "FieldName";
216 IndexVariant => "IndexVariantName";
218 Constructor => "ConstructorName";
220 GenericParam => "GenericParamName";
222 DimVar => "DimVarName";
224 Local => "LocalName";
226 ModuleAlias => "ModuleAliasName";
228 PlotProperty => "PlotPropertyName";
230 }
231}
232
233#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
242pub struct NameDef<Ns: NameNamespace> {
243 atom: NameAtom,
244 _ns: PhantomData<Ns>,
245}
246
247impl<Ns: NameNamespace> std::fmt::Debug for NameDef<Ns> {
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 std::fmt::Debug::fmt(&self.atom, f)
252 }
253}
254
255impl<Ns: NameNamespace> NameDef<Ns> {
256 #[must_use]
263 #[expect(
264 clippy::panic,
265 reason = "infallible constructor documents invalid input panic"
266 )]
267 pub fn new(s: impl Into<String>) -> Self {
268 Self::try_new(s).unwrap_or_else(|err| {
269 panic!("invalid {} leaf name: {err}", Ns::DISPLAY_NAME);
270 })
271 }
272
273 pub fn try_new(s: impl Into<String>) -> Result<Self, NameAtomError> {
280 NameAtom::parse(s).map(Self::from_atom)
281 }
282
283 #[must_use]
285 pub const fn from_atom(atom: NameAtom) -> Self {
286 Self {
287 atom,
288 _ns: PhantomData,
289 }
290 }
291
292 #[must_use]
294 pub const fn atom(&self) -> &NameAtom {
295 &self.atom
296 }
297
298 #[must_use]
300 pub fn as_str(&self) -> &str {
301 self.atom.as_str()
302 }
303
304 #[must_use]
306 pub fn into_atom(self) -> NameAtom {
307 self.atom
308 }
309
310 #[must_use]
312 pub fn into_inner(self) -> String {
313 self.atom.into_inner()
314 }
315}
316
317impl<Ns: NameNamespace> std::fmt::Display for NameDef<Ns> {
318 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319 f.write_str(self.as_str())
320 }
321}
322
323impl<Ns: NameNamespace> PartialEq<str> for NameDef<Ns> {
324 fn eq(&self, other: &str) -> bool {
325 self.as_str() == other
326 }
327}
328
329impl<Ns: NameNamespace> PartialEq<&str> for NameDef<Ns> {
330 fn eq(&self, other: &&str) -> bool {
331 self.as_str() == *other
332 }
333}
334
335impl<Ns: NameNamespace> AsRef<str> for NameDef<Ns> {
336 fn as_ref(&self) -> &str {
337 self.as_str()
338 }
339}
340
341impl<Ns: NameNamespace> std::borrow::Borrow<str> for NameDef<Ns> {
342 fn borrow(&self) -> &str {
343 self.as_str()
344 }
345}
346
347impl<Ns: NameNamespace> From<NameAtom> for NameDef<Ns> {
348 fn from(atom: NameAtom) -> Self {
349 Self::from_atom(atom)
350 }
351}
352
353impl<Ns: NameNamespace> From<String> for NameDef<Ns> {
354 fn from(s: String) -> Self {
355 Self::new(s)
356 }
357}
358
359impl<Ns: NameNamespace> From<&str> for NameDef<Ns> {
360 fn from(s: &str) -> Self {
361 Self::new(s)
362 }
363}
364
365#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
371pub struct ResolvedName<Ns: NameNamespace> {
372 owner: crate::dag_id::DagId,
373 name: NameAtom,
374 _ns: PhantomData<Ns>,
375}
376
377impl<Ns: NameNamespace> std::fmt::Debug for ResolvedName<Ns> {
378 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
379 f.debug_struct("ResolvedName")
380 .field("namespace", &Ns::DISPLAY_NAME)
381 .field("owner", &self.owner)
382 .field("name", &self.name)
383 .finish()
384 }
385}
386
387impl<Ns: NameNamespace> ResolvedName<Ns> {
388 #[must_use]
390 pub const fn new(owner: crate::dag_id::DagId, name: NameAtom) -> Self {
391 Self {
392 owner,
393 name,
394 _ns: PhantomData,
395 }
396 }
397
398 #[must_use]
400 pub fn from_def(owner: crate::dag_id::DagId, name: NameDef<Ns>) -> Self {
401 Self::new(owner, name.into_atom())
402 }
403
404 #[must_use]
406 pub const fn owner(&self) -> &crate::dag_id::DagId {
407 &self.owner
408 }
409
410 #[must_use]
412 pub const fn atom(&self) -> &NameAtom {
413 &self.name
414 }
415
416 #[must_use]
418 pub fn as_str(&self) -> &str {
419 self.name.as_str()
420 }
421
422 #[must_use]
428 pub fn to_unowned_def_name(&self) -> NameDef<Ns> {
429 NameDef::from_atom(self.name.clone())
430 }
431
432 #[must_use]
434 pub fn into_parts(self) -> (crate::dag_id::DagId, NameAtom) {
435 (self.owner, self.name)
436 }
437}
438
439impl<Ns: NameNamespace> std::fmt::Display for ResolvedName<Ns> {
440 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
441 write!(f, "{}.{}", self.owner, self.name)
442 }
443}
444
445pub type DeclName = NameDef<namespace::Decl>;
447
448pub type DimName = NameDef<namespace::Dim>;
450
451pub type UnitName = NameDef<namespace::Unit>;
453
454pub type StructTypeName = NameDef<namespace::StructType>;
456
457pub type IndexName = NameDef<namespace::Index>;
459
460pub type FnName = NameDef<namespace::Fn>;
462
463pub type FieldName = NameDef<namespace::Field>;
465
466pub type IndexVariantName = NameDef<namespace::IndexVariant>;
468
469pub type ConstructorName = NameDef<namespace::Constructor>;
476
477pub type GenericParamName = NameDef<namespace::GenericParam>;
479
480pub type DimVarName = NameDef<namespace::DimVar>;
485
486pub type LocalName = NameDef<namespace::Local>;
488
489pub type ModuleAliasName = NameDef<namespace::ModuleAlias>;
491
492pub type PlotPropertyName = NameDef<namespace::PlotProperty>;
494
495impl IndexVariantName {
496 #[must_use]
500 pub fn range_step(n: impl std::fmt::Display) -> Self {
501 Self::new(format!("#{n}"))
502 }
503
504 #[must_use]
506 pub fn qualified_by(&self, index: &IndexName) -> QualifiedIndexVariantName {
507 QualifiedIndexVariantName::new(index.clone(), self.clone())
508 }
509}
510
511#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
513pub struct QualifiedIndexVariantName {
514 index: IndexName,
515 variant: IndexVariantName,
516}
517
518impl QualifiedIndexVariantName {
519 #[must_use]
521 pub const fn new(index: IndexName, variant: IndexVariantName) -> Self {
522 Self { index, variant }
523 }
524
525 #[must_use]
527 pub const fn index(&self) -> &IndexName {
528 &self.index
529 }
530
531 #[must_use]
533 pub const fn variant(&self) -> &IndexVariantName {
534 &self.variant
535 }
536}
537
538impl std::fmt::Display for QualifiedIndexVariantName {
539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540 write!(f, "{}.{}", self.index, self.variant)
541 }
542}
543
544#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
550pub struct ResolvedIndexVariant {
551 index: ResolvedName<namespace::Index>,
552 variant: IndexVariantName,
553}
554
555impl ResolvedIndexVariant {
556 #[must_use]
559 pub const fn new(index: ResolvedName<namespace::Index>, variant: IndexVariantName) -> Self {
560 Self { index, variant }
561 }
562
563 #[must_use]
565 pub const fn index(&self) -> &ResolvedName<namespace::Index> {
566 &self.index
567 }
568
569 #[must_use]
571 pub const fn variant(&self) -> &IndexVariantName {
572 &self.variant
573 }
574
575 #[must_use]
577 pub fn into_parts(self) -> (ResolvedName<namespace::Index>, IndexVariantName) {
578 (self.index, self.variant)
579 }
580}
581
582impl std::fmt::Debug for ResolvedIndexVariant {
583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584 f.debug_struct("ResolvedIndexVariant")
585 .field("index", &self.index)
586 .field("variant", &self.variant)
587 .finish()
588 }
589}
590
591impl std::fmt::Display for ResolvedIndexVariant {
592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593 write!(f, "{}.{}", self.index, self.variant)
594 }
595}
596
597#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
599pub struct TimeScaleName(crate::registry::time_scale::TimeScale);
600
601impl TimeScaleName {
602 #[must_use]
604 pub const fn new(scale: crate::registry::time_scale::TimeScale) -> Self {
605 Self(scale)
606 }
607
608 #[must_use]
610 pub const fn scale(self) -> crate::registry::time_scale::TimeScale {
611 self.0
612 }
613
614 #[must_use]
616 pub const fn as_str(self) -> &'static str {
617 self.0.name()
618 }
619}
620
621impl std::fmt::Display for TimeScaleName {
622 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
623 f.write_str(self.as_str())
624 }
625}
626
627impl AsRef<str> for TimeScaleName {
628 fn as_ref(&self) -> &str {
629 self.as_str()
630 }
631}
632
633use std::sync::Arc;
636
637#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
649pub struct ScopedName {
650 qualifier: Arc<[Arc<str>]>,
652 member: Arc<str>,
654}
655
656impl ScopedName {
657 #[must_use]
659 pub fn local(member: impl Into<Arc<str>>) -> Self {
660 Self {
661 qualifier: Arc::from([] as [Arc<str>; 0]),
662 member: member.into(),
663 }
664 }
665
666 #[must_use]
668 pub fn qualified(module: impl Into<Arc<str>>, member: impl Into<Arc<str>>) -> Self {
669 Self::qualified_path([module], member)
670 }
671
672 #[must_use]
674 pub fn qualified_path(
675 qualifier: impl IntoIterator<Item = impl Into<Arc<str>>>,
676 member: impl Into<Arc<str>>,
677 ) -> Self {
678 Self {
679 qualifier: qualifier.into_iter().map(Into::into).collect(),
680 member: member.into(),
681 }
682 }
683
684 #[must_use]
689 pub fn member(&self) -> &str {
690 &self.member
691 }
692
693 #[must_use]
695 pub fn qualifier(&self) -> &[Arc<str>] {
696 &self.qualifier
697 }
698
699 #[must_use]
701 pub fn is_qualified(&self) -> bool {
702 !self.qualifier.is_empty()
703 }
704
705 #[must_use]
711 pub fn with_prefix(&self, prefix: &str) -> Self {
712 Self::qualified(prefix, Arc::clone(&self.member))
713 }
714}
715
716impl std::fmt::Display for ScopedName {
717 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718 for segment in self.qualifier.iter() {
719 f.write_str(segment)?;
720 f.write_str(".")?;
721 }
722 f.write_str(&self.member)
723 }
724}
725
726impl From<NameAtom> for ScopedName {
727 fn from(atom: NameAtom) -> Self {
732 Self::local(atom.into_inner())
733 }
734}
735
736impl From<String> for ScopedName {
737 fn from(s: String) -> Self {
739 Self::local(s)
740 }
741}
742
743impl From<DeclName> for ScopedName {
744 fn from(name: DeclName) -> Self {
748 Self::local(name.into_inner())
749 }
750}
751
752#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
763pub struct UnitRef {
764 qualifier: Option<ModuleAliasName>,
766 name: UnitName,
768}
769
770impl UnitRef {
771 #[must_use]
773 pub fn local(name: impl Into<UnitName>) -> Self {
774 Self {
775 qualifier: None,
776 name: name.into(),
777 }
778 }
779
780 #[must_use]
782 pub const fn qualified(qualifier: ModuleAliasName, name: UnitName) -> Self {
783 Self {
784 qualifier: Some(qualifier),
785 name,
786 }
787 }
788
789 #[must_use]
791 pub const fn qualifier(&self) -> Option<&ModuleAliasName> {
792 self.qualifier.as_ref()
793 }
794
795 #[must_use]
797 pub const fn name(&self) -> &UnitName {
798 &self.name
799 }
800
801 #[must_use]
803 pub const fn is_qualified(&self) -> bool {
804 self.qualifier.is_some()
805 }
806}
807
808impl From<UnitName> for UnitRef {
809 fn from(name: UnitName) -> Self {
813 Self::local(name)
814 }
815}
816
817impl From<NameAtom> for UnitRef {
818 fn from(atom: NameAtom) -> Self {
822 Self::local(UnitName::from_atom(atom))
823 }
824}
825
826impl std::fmt::Display for UnitRef {
827 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
828 if let Some(qualifier) = &self.qualifier {
829 write!(f, "{qualifier}.")?;
830 }
831 write!(f, "{}", self.name)
832 }
833}
834
835#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
843pub struct NamePath {
844 segments: crate::syntax::non_empty::NonEmpty<NameAtom>,
845}
846
847impl NamePath {
848 #[must_use]
850 pub const fn new(segments: crate::syntax::non_empty::NonEmpty<NameAtom>) -> Self {
851 Self { segments }
852 }
853
854 #[must_use]
856 pub fn local(atom: NameAtom) -> Self {
857 Self::new(crate::syntax::non_empty::NonEmpty::singleton(atom))
858 }
859
860 #[must_use]
862 pub fn qualified_path(qualifier: impl IntoIterator<Item = NameAtom>, leaf: NameAtom) -> Self {
863 let mut segments: Vec<NameAtom> = qualifier.into_iter().collect();
864 segments.push(leaf);
865 let first = segments.remove(0);
866 Self::new(crate::syntax::non_empty::NonEmpty::new(first, segments))
867 }
868
869 #[must_use]
871 pub fn segments(&self) -> &[NameAtom] {
872 self.segments.as_slice()
873 }
874
875 #[must_use]
877 pub fn into_segments(self) -> crate::syntax::non_empty::NonEmpty<NameAtom> {
878 self.segments
879 }
880
881 #[must_use]
883 pub const fn len(&self) -> usize {
884 self.segments.len()
885 }
886
887 #[must_use]
889 pub const fn is_empty(&self) -> bool {
890 false
891 }
892
893 #[must_use]
895 pub const fn is_bare(&self) -> bool {
896 self.segments.len() == 1
897 }
898
899 #[must_use]
901 pub fn leaf(&self) -> &NameAtom {
902 self.segments.last()
903 }
904
905 #[must_use]
907 pub fn as_bare(&self) -> Option<&NameAtom> {
908 match self.segments.as_slice() {
909 [atom] => Some(atom),
910 _ => None,
911 }
912 }
913
914 #[must_use]
918 pub fn split_last(&self) -> (&[NameAtom], &NameAtom) {
919 let (leaf, qualifier) = self.segments.split_last();
920 (qualifier, leaf)
921 }
922
923 #[must_use]
925 pub fn qualifier_segments(&self) -> &[NameAtom] {
926 self.split_last().0
927 }
928
929 #[must_use]
931 pub fn qualifier_and_leaf(&self) -> Option<(&[NameAtom], &NameAtom)> {
932 let (qualifier, leaf) = self.split_last();
933 (!qualifier.is_empty()).then_some((qualifier, leaf))
934 }
935
936 #[must_use]
938 pub fn display_path(&self) -> String {
939 self.segments
940 .iter()
941 .map(NameAtom::as_str)
942 .collect::<Vec<_>>()
943 .join(".")
944 }
945}
946
947impl From<NameAtom> for NamePath {
948 fn from(atom: NameAtom) -> Self {
949 Self::local(atom)
950 }
951}
952
953impl From<IndexName> for NamePath {
954 fn from(name: IndexName) -> Self {
955 Self::local(name.into_atom())
956 }
957}
958
959impl From<String> for NamePath {
960 #[expect(
961 clippy::panic,
962 reason = "From<String> is a convenience for trusted leaf names"
963 )]
964 fn from(s: String) -> Self {
965 Self::local(NameAtom::parse(s).unwrap_or_else(|err| {
966 panic!("invalid NamePath leaf name: {err}");
967 }))
968 }
969}
970
971impl From<&str> for NamePath {
972 fn from(s: &str) -> Self {
973 Self::from(s.to_string())
974 }
975}
976
977impl std::fmt::Display for NamePath {
978 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
979 for (idx, segment) in self.segments.iter().enumerate() {
980 if idx > 0 {
981 f.write_str(".")?;
982 }
983 f.write_str(segment.as_str())?;
984 }
985 Ok(())
986 }
987}
988
989#[cfg(test)]
990mod tests {
991 use super::*;
992 use std::collections::HashMap;
993
994 #[test]
995 fn name_atom_rejects_dotted_paths() {
996 assert_eq!(
997 NameAtom::parse("module.Value"),
998 Err(NameAtomError::ContainsDot)
999 );
1000 assert_eq!(
1001 DeclName::try_new("module.Value"),
1002 Err(NameAtomError::ContainsDot)
1003 );
1004 }
1005
1006 #[test]
1007 fn name_atom_accepts_internal_leaf_names() {
1008 let atom = NameAtom::parse("#0").unwrap();
1009 assert_eq!(atom.as_str(), "#0");
1010 }
1011
1012 #[test]
1013 fn newtype_display() {
1014 let name = DeclName::new("dry_mass");
1015 assert_eq!(format!("{name}"), "dry_mass");
1016 }
1017
1018 #[test]
1019 fn newtype_as_str() {
1020 let name = DimName::new("Length");
1021 assert_eq!(name.as_str(), "Length");
1022 }
1023
1024 #[test]
1025 fn newtype_into_inner() {
1026 let name = UnitName::new("km");
1027 assert_eq!(name.into_inner(), "km");
1028 }
1029
1030 #[test]
1031 fn newtype_hash_map_borrow_lookup() {
1032 let mut map = HashMap::new();
1033 map.insert(DeclName::new("x"), 42);
1034 assert_eq!(map.get("x"), Some(&42));
1036 }
1037
1038 #[test]
1039 fn newtype_from_string() {
1040 let name: FieldName = "dv1".to_string().into();
1041 assert_eq!(name.as_str(), "dv1");
1042 }
1043
1044 #[test]
1045 fn newtype_from_str() {
1046 let name: IndexVariantName = "Departure".into();
1047 assert_eq!(name.as_str(), "Departure");
1048 }
1049
1050 #[test]
1051 fn newtype_equality() {
1052 assert_eq!(IndexName::new("Maneuver"), IndexName::new("Maneuver"));
1053 assert_ne!(IndexName::new("Maneuver"), IndexName::new("Phase"));
1054 }
1055
1056 #[test]
1057 fn newtype_ord() {
1058 let a = FnName::new("alpha");
1059 let b = FnName::new("beta");
1060 assert!(a < b);
1061 }
1062
1063 #[test]
1064 fn name_path_preserves_qualifier_and_leaf() {
1065 let path = NamePath::qualified_path(
1066 [NameAtom::parse("module").unwrap()],
1067 NameAtom::parse("Index").unwrap(),
1068 );
1069 assert_eq!(path.display_path(), "module.Index");
1070 assert_eq!(path.leaf().as_str(), "Index");
1071 assert_eq!(
1072 path.qualifier_segments()
1073 .iter()
1074 .map(NameAtom::as_str)
1075 .collect::<Vec<_>>(),
1076 ["module"]
1077 );
1078 }
1079
1080 #[test]
1081 fn name_def_aliases_keep_namespace_and_leaf_invariant() {
1082 let decl = DeclName::new("x");
1083 let index = IndexName::new("x");
1084
1085 assert_eq!(decl.as_str(), index.as_str());
1086 assert_eq!(
1087 DeclName::try_new("module.x"),
1088 Err(NameAtomError::ContainsDot)
1089 );
1090 assert_eq!(
1091 IndexName::try_new("module.x"),
1092 Err(NameAtomError::ContainsDot)
1093 );
1094 }
1095
1096 #[test]
1097 fn resolved_name_carries_canonical_owner_and_leaf() {
1098 let name = DeclName::new("dry_mass");
1099 let resolved = ResolvedName::<namespace::Decl>::from_def(
1100 crate::dag_id::DagId::new("helpers", ["mass"]),
1101 name,
1102 );
1103
1104 assert_eq!(resolved.owner().to_string(), "helpers.mass");
1105 assert_eq!(resolved.as_str(), "dry_mass");
1106 assert_eq!(resolved.to_string(), "helpers.mass.dry_mass");
1107 assert_eq!(resolved.to_unowned_def_name(), DeclName::new("dry_mass"));
1108 }
1109
1110 #[test]
1111 fn resolved_index_variant_carries_resolved_index_owner() {
1112 let index = ResolvedName::<namespace::Index>::from_def(
1113 crate::dag_id::DagId::root("mission"),
1114 IndexName::new("Phase"),
1115 );
1116 let variant = ResolvedIndexVariant::new(index, IndexVariantName::new("Burn"));
1117
1118 assert_eq!(variant.index().owner().to_string(), "mission");
1119 assert_eq!(variant.index().as_str(), "Phase");
1120 assert_eq!(variant.variant().as_str(), "Burn");
1121 assert_eq!(variant.to_string(), "mission.Phase.Burn");
1122 }
1123
1124 #[test]
1125 fn scoped_name_qualified_display_uses_dot() {
1126 let name = ScopedName::qualified("module", "x");
1127 assert_eq!(format!("{name}"), "module.x");
1128 assert_eq!(name.member(), "x");
1129 assert_eq!(
1130 name.qualifier().iter().map(|s| &**s).collect::<Vec<_>>(),
1131 ["module"]
1132 );
1133 }
1134
1135 #[test]
1136 fn scoped_name_supports_nested_qualifier_path() {
1137 let name = ScopedName::qualified_path(["helpers", "math"], "G0");
1138 assert_eq!(format!("{name}"), "helpers.math.G0");
1139 assert_eq!(name.member(), "G0");
1140 assert_eq!(
1141 name.qualifier().iter().map(|s| &**s).collect::<Vec<_>>(),
1142 ["helpers", "math"]
1143 );
1144 }
1145}