Skip to main content

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}