zelkova_compiler/scalars.rs
1//! The five type names the compiler knows as scalars, and the one it knows beside them.
2//!
3//! `Int`, `Float`, `Bool`, `Char` and `String` are **scalar**: each is declared in
4//! Zelkova like any other type, and the compiler additionally knows the
5//! representation each target gives it. [*Scalar
6//! types*](../../docs/spec/types.md#scalar-types) is the rule; this module is the
7//! list it names.
8//!
9//! A scalar is known by the **qualified name of its declaration** — package, module and
10//! name — and never by its spelling ([`DEC-15` decision
11//! 1](../../docs/decisions/dec-15.md#1--a-scalar-type-is-known-by-its-qualified-name)).
12//! A module declaring its own `Int` declares an ordinary type that shares three letters
13//! with a scalar, and every phase treats it as one — which is why [`Scalar::declares`]
14//! takes a [`QualName`] and there is no entry point taking a bare
15//! [`Name`](super::name::Name). The same goes for a whole package: a dependency's own
16//! `Basics` declaring `type Int = Int` declares `Basics.Int` of *that* package, and the
17//! five scalars are declared in [`zelkova-core`](super::resolve::CORE_PACKAGE) only.
18//!
19//! # Why the list lives here and not in the typer
20//!
21//! The typer is the only reader today: `typer::canonical_type_to_typer_type` maps
22//! four of the five onto its own literal types, and `BOOL` names the union an `if`
23//! condition is checked against. The list is nonetheless a fact about
24//! the language rather than about type inference, and the same five names answer
25//! questions the typer never asks — which names a module underneath `Basics` receives
26//! without an import, and which declarations may name nothing but themselves. It sits
27//! beside the phases for the same reason
28//! [`default_imports`](super::default_imports) does.
29
30use super::name::QualName;
31use super::resolve::CORE_PACKAGE;
32use super::PackageName;
33
34/// One scalar type: the module of `zelkova-core` its declaration lives in, and the name
35/// that declaration writes.
36///
37/// The module is written out with its dots, as a `module` header writes it, so a
38/// nested module name is spelled the one way everywhere. The package is not a field:
39/// it is [`CORE_PACKAGE`] for all five.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct Scalar {
42 pub module: &'static str,
43 pub name: &'static str,
44}
45
46impl Scalar {
47 /// Whether `name` is this scalar's declaration.
48 ///
49 /// All three parts have to match: `Example.Int` is not [`INT`], and neither is a
50 /// `Basics.Int` declared by any package but [`CORE_PACKAGE`]. An `Int` that
51 /// resolves to nothing never reaches here at all — canonicalization rejects
52 /// the name rather than building a type out of it.
53 pub fn declares(&self, name: &QualName) -> bool {
54 name.package().as_str() == CORE_PACKAGE
55 && name.unqualified_name().as_str() == self.name
56 && name.module_name().as_str() == self.module
57 }
58
59 /// This scalar's declaration, as the qualified name every phase after
60 /// canonicalization spells a type with.
61 ///
62 /// The inverse of [`Scalar::declares`], and what a phase reaches for when it needs
63 /// to *name* a scalar rather than recognise one — the typer builds the type of an
64 /// `if` condition out of [`BOOL`] this way.
65 pub fn qual_name(&self) -> QualName {
66 QualName::in_module(PackageName::core(), self.module, self.name)
67 }
68}
69
70/// `Basics.Int`.
71pub const INT: Scalar = Scalar {
72 module: "Basics",
73 name: "Int",
74};
75
76/// `Basics.Float`.
77pub const FLOAT: Scalar = Scalar {
78 module: "Basics",
79 name: "Float",
80};
81
82/// `Basics.Bool`, which is [a scalar and an ordinary union at
83/// once](../../docs/spec/types.md#scalar-types): the compiler knows its
84/// representation and nothing about its structure, so `True` and `False` stay
85/// constructors like any others.
86pub const BOOL: Scalar = Scalar {
87 module: "Basics",
88 name: "Bool",
89};
90
91/// `Char.Char`.
92pub const CHAR: Scalar = Scalar {
93 module: "Char",
94 name: "Char",
95};
96
97/// `String.String`.
98pub const STRING: Scalar = Scalar {
99 module: "String",
100 name: "String",
101};
102
103/// `Basics.Position`: the type a derivation hands `differed` and `atConstructor` for a
104/// constructor's place in its declaration.
105///
106/// Known by name for the reason a scalar is — the compiler has to give those parameters a
107/// type before any class has been read — and **not a scalar**: it is declared in Zelkova
108/// like any other type, has no representation the compiler knows, and is in neither
109/// [`SCALARS`] nor [`OPAQUE_SCALARS`]. Its one constructor, `Position`, holds the
110/// zero-based place as an `Int` and is exposed to no module, so a derived instance names
111/// it by this declaration ([`Scalar::qual_name`]) and never through scope.
112pub const POSITION: Scalar = Scalar {
113 module: "Basics",
114 name: "Position",
115};
116
117/// The five scalar types, in the order [*Scalar
118/// types*](../../docs/spec/types.md#scalar-types) lists them.
119pub const SCALARS: &[Scalar] = &[INT, FLOAT, BOOL, CHAR, STRING];
120
121/// The scalar `name` declares, if it declares one.
122pub fn scalar_of(name: &QualName) -> Option<Scalar> {
123 SCALARS.iter().copied().find(|s| s.declares(name))
124}
125
126/// The four scalars whose declaration is **opaque** ([`DEC-15` decision
127/// 2](../../docs/decisions/dec-15.md#2--a-scalar-type-is-declared-in-zelkova-and-an-opaque-ones-declaration-names-itself)):
128/// nothing in the language constructs or inspects a value of one, so each declares
129/// only its own name and contributes no constructor.
130///
131/// [`BOOL`] is deliberately absent. It is a scalar — the compiler knows its
132/// representation — but its declaration is `Basics`' genuine `type Bool = True |
133/// False`, and the self-naming rule does not reach it ([`DEC-15` decision
134/// 5](../../docs/decisions/dec-15.md#5--bool-is-a-scalar-and-an-ordinary-union-and-both-at-once)).
135pub const OPAQUE_SCALARS: &[Scalar] = &[INT, FLOAT, CHAR, STRING];
136
137/// The opaque scalar `name` declares, if it declares one — `None` for [`BOOL`] even
138/// though [`scalar_of`] would answer it, since `BOOL` is not in [`OPAQUE_SCALARS`].
139pub fn opaque_scalar_of(name: &QualName) -> Option<Scalar> {
140 OPAQUE_SCALARS.iter().copied().find(|s| s.declares(name))
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 fn qual(s: &str) -> QualName {
148 QualName::parse(PackageName::core(), s).unwrap()
149 }
150
151 /// `Position` is known by name and is not a scalar: no list of scalars holds it, and
152 /// `scalar_of` does not answer it.
153 #[test]
154 fn position_is_known_by_name_and_is_not_a_scalar() {
155 assert!(POSITION.declares(&qual("Basics.Position")));
156 assert_eq!(POSITION.qual_name(), qual("Basics.Position"));
157 assert!(!SCALARS.contains(&POSITION));
158 assert!(!OPAQUE_SCALARS.contains(&POSITION));
159 assert_eq!(scalar_of(&qual("Basics.Position")), None);
160 }
161
162 #[test]
163 fn a_scalar_is_its_declaration() {
164 assert_eq!(scalar_of(&qual("Basics.Int")), Some(INT));
165 assert_eq!(scalar_of(&qual("Basics.Float")), Some(FLOAT));
166 assert_eq!(scalar_of(&qual("Basics.Bool")), Some(BOOL));
167 assert_eq!(scalar_of(&qual("Char.Char")), Some(CHAR));
168 assert_eq!(scalar_of(&qual("String.String")), Some(STRING));
169 }
170
171 /// The two directions agree: the name a scalar writes is the one it recognises.
172 ///
173 /// Mutation-checked by giving [`Scalar::qual_name`] the wrong module
174 /// (`QualName::in_module(PackageName::core(), "Example", self.name)`): `declares`
175 /// then rejects every scalar's own name and the loop goes red.
176 #[test]
177 fn a_scalar_recognises_the_name_it_writes() {
178 for scalar in SCALARS {
179 assert!(
180 scalar.declares(&scalar.qual_name()),
181 "{:?} does not recognise its own qualified name",
182 scalar
183 );
184 }
185
186 assert_eq!(BOOL.qual_name(), qual("Basics.Bool"));
187 assert_eq!(CHAR.qual_name(), qual("Char.Char"));
188 }
189
190 /// The spelling alone decides nothing: the same four letters declared elsewhere
191 /// is an ordinary type.
192 #[test]
193 fn another_modules_int_is_not_the_scalar() {
194 assert_eq!(scalar_of(&qual("Example.Int")), None);
195 assert_eq!(scalar_of(&qual("Js.Basics.Int")), None);
196 assert_eq!(scalar_of(&qual("Basics.Char")), None);
197 assert_eq!(scalar_of(&qual("Char.String")), None);
198 }
199
200 /// The package is part of the identity: `Basics.Int` declared by a package other
201 /// than `zelkova-core` is an ordinary type, however it is spelled.
202 ///
203 /// Mutation-checked by dropping the package comparison from [`Scalar::declares`]:
204 /// every assertion below then answers the scalar and goes red.
205 #[test]
206 fn another_packages_basics_int_is_not_the_scalar() {
207 let acme = PackageName::new("acme-basics").unwrap();
208
209 for scalar in SCALARS {
210 let elsewhere = QualName::in_module(acme.clone(), scalar.module, scalar.name);
211 assert_eq!(scalar_of(&elsewhere), None, "{:?} of acme-basics", scalar);
212 }
213 }
214
215 /// The four opaque scalars answer `opaque_scalar_of`; `Bool` is a scalar and
216 /// does not.
217 #[test]
218 fn bool_is_a_scalar_but_not_an_opaque_one() {
219 assert_eq!(opaque_scalar_of(&qual("Basics.Int")), Some(INT));
220 assert_eq!(opaque_scalar_of(&qual("Basics.Float")), Some(FLOAT));
221 assert_eq!(opaque_scalar_of(&qual("Char.Char")), Some(CHAR));
222 assert_eq!(opaque_scalar_of(&qual("String.String")), Some(STRING));
223
224 assert_eq!(scalar_of(&qual("Basics.Bool")), Some(BOOL));
225 assert_eq!(opaque_scalar_of(&qual("Basics.Bool")), None);
226 }
227}