zelkova_compiler/name.rs
1//! Home to the `Name` and `QualName` primitives
2//!
3//! `Name` is a generic identifier, which can be qualified (refers to a value/type and its
4//! module) or not. `QualName` is an identifier which have a reference to its module.
5//!
6//! While `Name` is pretty useful during parsing (it let us have a generic, simple
7//! to manipulate, type), `QualName` is actually the identifier we want for every
8//! subsequent phases as we have to refer to the actual property of that identifier,
9//! and each identifier's name is only unique within its own module.
10//!
11//! Having a dedicated type let us enforce such distinction at compile time (as well
12//! as having a potential performance boost by not having to parse the underlying
13//! `String` on each access, although at the cost of more memory usage).
14//!
15//! In the future, if performance requires it, this module will probably also host
16//! the interner for qualified and unqualified names.
17
18pub use zelkova_syntax::name::Name;
19
20use super::PackageName;
21
22/// Qualified name: a declaration, named by the package and the module that declare it.
23///
24/// The module half can be thought of as a non-empty vector: `My.Module.function` is a vec of
25/// `My` and `Module`, plus the always non-empty `function`.
26///
27/// # Why the package is part of it
28///
29/// A module's name is unique within its package and not across a build: a package may
30/// hold its own `Size` beside a dependency's, which it reaches as `AcmeWidgets.Size`
31/// ([*What a package boundary cannot
32/// rename*](../../docs/spec/packages.md#what-a-package-boundary-cannot-rename)). Both
33/// declare `Size.Size`, and only the package tells the two apart. With it, every
34/// `QualName` names one declaration on its own: once we are given a `QualName`, no further
35/// resolution is necessary, and two `QualName`s are equal exactly when they name the same
36/// declaration. Every map the phases after canonicalization key by one — the typer's
37/// unions and constructors, a scalar's identity ([`Scalar::declares`]) — relies on that.
38///
39/// The package is part of the identity and of nothing a source file writes:
40/// [`to_name`](QualName::to_name) leaves it out, since `acme-widgets` is a spelling no
41/// Zelkova source contains.
42///
43/// [`Scalar::declares`]: super::scalars::Scalar::declares
44#[derive(Debug, PartialEq, Clone, Eq, Hash)]
45pub struct QualName {
46 package: PackageName,
47 module: Vec<String>,
48 name: String,
49}
50
51impl QualName {
52 /// The name `s` spells, declared in `package`: `Basics.Int` is the module `Basics`
53 /// and the name `Int`. `None` when `s` has no module half.
54 pub fn parse<S: Into<String>>(package: PackageName, s: S) -> Option<QualName> {
55 let name = s.into();
56 let mut segments: Vec<_> = name.split('.').map(String::from).collect();
57
58 match segments.pop() {
59 Some(name) if !segments.is_empty() => Some(QualName {
60 package,
61 name,
62 module: segments,
63 }),
64 _ => None,
65 }
66 }
67
68 /// The name `name`, declared in `package` by the module whose segments `prefix`
69 /// yields. `None` when `prefix` yields nothing.
70 // TODO Write tests
71 pub fn from_strs<S1, S2, I>(package: PackageName, name: S1, prefix: I) -> Option<QualName>
72 where
73 S1: Into<String>,
74 S2: Into<String>,
75 I: Iterator<Item = S2>,
76 {
77 let name = name.into();
78 let module: Vec<String> = prefix.map(|s| s.into()).collect();
79
80 match module.len() {
81 0 => None,
82 _ => Some(QualName {
83 package,
84 name,
85 module,
86 }),
87 }
88 }
89
90 /// The name `name`, as declared by `module` of `package`.
91 ///
92 /// `module` is written out with its dots, the way a `module` header writes it, and
93 /// is split back into segments here; `name` is kept whole. It is how
94 /// [`ModuleName::qualify_name`](super::ModuleName::qualify_name) names a declaration
95 /// of a module it holds, and how a name the compiler knows without reading it from a
96 /// source file — a [scalar](super::scalars) — becomes a `QualName`.
97 ///
98 /// **`module` must be a non-empty module path.** That is a precondition on the
99 /// caller, not a check: this returns a `QualName` rather than an `Option` because
100 /// the two halves arrive separately and there is no text to go looking for a module
101 /// in, which is what keeps the scalar names off any `unwrap` path. It is *not* a
102 /// claim that every input is well formed — `in_module(package, "", "Bool")` builds a
103 /// name that renders as `.Bool` and that
104 /// [`Scalar::declares`](super::scalars::Scalar::declares) recognises for nothing,
105 /// where [`QualName::parse`] and [`QualName::from_strs`] would both have returned
106 /// `None`. Every caller passes a module path it holds.
107 pub fn in_module(package: PackageName, module: &str, name: &str) -> QualName {
108 QualName {
109 package,
110 module: module.split('.').map(String::from).collect(),
111 name: name.to_string(),
112 }
113 }
114
115 /// The name `name`, declared by the same module of the same package as this one.
116 ///
117 /// It is how a union's constructors are named from the union: a constructor is
118 /// declared by the module that declares the type it builds.
119 pub fn sibling(&self, name: &Name) -> QualName {
120 QualName {
121 package: self.package.clone(),
122 module: self.module.clone(),
123 name: name.as_str().to_string(),
124 }
125 }
126
127 /// The package that declares this name.
128 pub fn package(&self) -> &PackageName {
129 &self.package
130 }
131
132 /// The module and the name, written out with dots — `Size.Size` — and without the
133 /// package, which no source spells.
134 // TODO Write tests
135 pub fn to_name(&self) -> Name {
136 if !self.module.is_empty() {
137 Name::new(format!("{}.{}", self.module.join("."), self.name))
138 } else {
139 Name::new(self.name.clone())
140 }
141 }
142
143 pub fn unqualified_name(&self) -> Name {
144 Name::new(self.name.clone())
145 }
146
147 /// The module half, written out with its dots — `My.App` of `My.App.function`.
148 ///
149 /// The counterpart of [`QualName::unqualified_name`]; the two halves rejoined are
150 /// [`QualName::to_name`].
151 pub fn module_name(&self) -> Name {
152 Name::new(self.module.join("."))
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn package() -> PackageName {
161 PackageName::new("test-project").unwrap()
162 }
163
164 #[test]
165 fn qual_name_from_str() {
166 assert_eq!(QualName::parse(package(), "Int"), None);
167
168 assert_eq!(
169 QualName::parse(package(), "Basics.Int"),
170 Some(QualName {
171 package: package(),
172 module: vec!["Basics".into()],
173 name: "Int".into()
174 })
175 );
176
177 assert_eq!(
178 QualName::parse(package(), "My.App.Module.function"),
179 Some(QualName {
180 package: package(),
181 module: vec!["My".into(), "App".into(), "Module".into(),],
182 name: "function".into()
183 })
184 );
185 }
186
187 /// The package is part of the identity: the same spelling declared by two packages
188 /// is two names, and neither spells its package.
189 #[test]
190 fn a_package_tells_two_same_spelled_names_apart() {
191 let widgets = PackageName::new("acme-widgets").unwrap();
192 let mine = QualName::in_module(package(), "Size", "Size");
193 let theirs = QualName::in_module(widgets.clone(), "Size", "Size");
194
195 assert_ne!(mine, theirs);
196 assert_eq!(mine.to_name(), theirs.to_name());
197 assert_eq!(theirs.package(), &widgets);
198 }
199
200 #[test]
201 fn a_sibling_shares_the_package_and_the_module() {
202 let widgets = PackageName::new("acme-widgets").unwrap();
203 let union = QualName::in_module(widgets.clone(), "Acme.Size", "Size");
204
205 assert_eq!(
206 union.sibling(&"Small".into()),
207 QualName::in_module(widgets, "Acme.Size", "Small")
208 );
209 }
210}