Skip to main content

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}