Skip to main content

submilli_engine/runtime/prelude/
declaration.rs

1//! The prelude's declaration: the full typed interface/type surface plus the
2//! cached package assembly codegen and the typechecker consume.
3
4use std::collections::{BTreeMap, BTreeSet};
5use std::sync::OnceLock;
6
7use crate::{
8    Dispatch, DocComment, FileId, MethodSig, PackageDeclaration, Param, PropertySig, Span, Type,
9    TypeKind, TypeSymbol, ValueKind, ValueSymbol,
10};
11
12/// All prelude builtins document themselves; their docs are attributed to the
13/// prelude's reserved [`FileId`]. Shadows [`crate::doc`] so call sites stay terse.
14pub(crate) fn doc(literal: &str) -> Option<DocComment> {
15    crate::doc(FileId::PRELUDE, literal)
16}
17
18/// A non-generic `Temporal.<local>` interface reference whose nominal identity
19/// matches the `TypeSymbol` the namespace registers (`extend(prelude("Temporal"),
20/// local)`, `#`-segmented — distinct from the dotted registry/display name).
21pub(crate) fn temporal_ref(local: &str) -> Type {
22    Type::interface_ref(
23        crate::Package::prelude(),
24        format!("Temporal.{local}"),
25        crate::mangle::extend(&crate::mangle::prelude("Temporal"), local),
26        Vec::new(),
27    )
28}
29
30pub const MODULE_NAME: &str = "submilli:prelude";
31
32// Mutable: these back exported value-symbols (`Math.*`, `Number.*`, `NaN`,
33// `Infinity`), and codegen imports every value-global mutable. None are
34// referenced in another global's const-expr, so mutability is safe.
35pub(crate) fn insert_temporal_plain_type(
36    temporal: &mut crate::NamespaceSymbol,
37    temporal_prefix: &crate::mangle::MangledName,
38    local: &str,
39    type_doc: &'static str,
40    properties: &[(&str, &'static str)],
41    extra_methods: Vec<(String, MethodSig)>,
42    has_compare: bool,
43) {
44    let self_ref = || temporal_ref(local);
45
46    let mut methods = BTreeMap::from([
47        (
48            "toString".to_string(),
49            MethodSig {
50                optional: false,
51                generics: Vec::new(),
52                params: Vec::new(),
53                ret: Type::String,
54                predicate: None,
55                doc: doc("/** The ISO 8601 string form. */"),
56            },
57        ),
58        (
59            "toJSON".to_string(),
60            MethodSig {
61                optional: false,
62                generics: Vec::new(),
63                params: Vec::new(),
64                ret: Type::String,
65                predicate: None,
66                doc: doc("/** Returns the ISO string form used by JSON.stringify. */"),
67            },
68        ),
69        (
70            "equals".to_string(),
71            MethodSig {
72                optional: false,
73                generics: Vec::new(),
74                params: vec![Param::new("other", self_ref())],
75                ret: Type::Boolean,
76                predicate: None,
77                doc: doc("/** `true` if `this` and `other` denote the same value. */"),
78            },
79        ),
80    ]);
81    methods.extend(extra_methods);
82
83    let properties = properties
84        .iter()
85        .map(|(name, prop_doc)| {
86            let ty = match *name {
87                "monthCode" => Type::String,
88                "inLeapYear" => Type::Boolean,
89                _ => Type::Number,
90            };
91            (
92                (*name).to_string(),
93                PropertySig {
94                    ty,
95                    readonly: true,
96                    intrinsic: false,
97                    optional: false,
98                    doc: doc(prop_doc),
99                },
100            )
101        })
102        .collect();
103
104    temporal.types.insert(
105        local.to_string(),
106        TypeSymbol {
107            name: format!("Temporal.{local}"),
108            mangled_name: crate::mangle::extend(temporal_prefix, local),
109            declaration_span: Span::at(FileId::PRELUDE),
110            kind: TypeKind::Interface {
111                index: None,
112                generics: Vec::new(),
113                methods,
114                properties,
115                dispatch: Dispatch::Direct,
116                doc: doc(type_doc),
117            },
118        },
119    );
120
121    let ctor_local = format!("{local}Constructor");
122    let mut ctor_methods = BTreeMap::from([(
123        "from".to_string(),
124        MethodSig {
125            optional: false,
126            generics: Vec::new(),
127            params: vec![Param::new("iso", Type::String)],
128            ret: self_ref(),
129            predicate: None,
130            doc: doc(
131                "/** Parse an ISO 8601 string into this Plain type. Throws on malformed input. */",
132            ),
133        },
134    )]);
135    if has_compare {
136        ctor_methods.insert(
137            "compare".to_string(),
138            MethodSig {
139                optional: false,
140                generics: Vec::new(),
141                params: vec![Param::new("a", self_ref()), Param::new("b", self_ref())],
142                ret: Type::Number,
143                predicate: None,
144                doc: doc(
145                    "/** Orders two values: -1, 0, or 1. Usable as an Array#sort comparator. */",
146                ),
147            },
148        );
149    }
150    temporal.types.insert(
151        ctor_local.clone(),
152        TypeSymbol {
153            name: format!("Temporal.{ctor_local}"),
154            mangled_name: crate::mangle::extend(temporal_prefix, &ctor_local),
155            declaration_span: Span::at(FileId::PRELUDE),
156            kind: TypeKind::Interface {
157                index: None,
158                generics: Vec::new(),
159                methods: ctor_methods,
160                properties: BTreeMap::new(),
161                dispatch: Dispatch::Static,
162                doc: doc("/** Constructor object, accessed via the `Temporal` binding. */"),
163            },
164        },
165    );
166
167    temporal.values.insert(
168        local.to_string(),
169        ValueSymbol {
170            name: local.to_string(),
171            mangled_name: crate::mangle::extend(temporal_prefix, local),
172            declaration_span: Span::at(FileId::PRELUDE),
173            kind: ValueKind::Const {
174                ty: temporal_ref(&ctor_local),
175                doc: doc(type_doc),
176            },
177        },
178    );
179}
180
181/// Prelude exports as [`PackageDeclaration`]. Hand-coded — the prelude isn't Submilli source.
182pub fn prelude_package_declaration() -> PackageDeclaration {
183    let mut defs = PackageDeclaration::with_package(crate::mangle::PRELUDE_PACKAGE);
184
185    super::string::declare_types(&mut defs);
186    super::number::declare_types(&mut defs);
187    super::bigint::declare_types(&mut defs);
188    super::boolean::declare_types(&mut defs);
189    super::array::declare_types(&mut defs);
190    super::object::declare_types(&mut defs);
191    super::console::declare_types(&mut defs);
192    super::uint8array::declare_types(&mut defs);
193    super::textcodec::declare_types(&mut defs);
194    super::regex::declare_types(&mut defs);
195    super::map::declare_types(&mut defs);
196    super::set::declare_types(&mut defs);
197    super::iterator::declare_types(&mut defs);
198    super::error::declare_types(&mut defs);
199    super::temporal::shared::declare_types(&mut defs);
200
201    // namespace bindings.
202
203    defs
204}
205
206pub fn cached_runtime_package_declarations() -> (
207    &'static [PackageDeclaration],
208    &'static [PackageDeclaration],
209    &'static [PackageDeclaration],
210) {
211    static CACHE: OnceLock<(
212        Vec<PackageDeclaration>,
213        Vec<PackageDeclaration>,
214        Vec<PackageDeclaration>,
215    )> = OnceLock::new();
216    let (prelude, host, internal) = CACHE.get_or_init(|| {
217        (
218            vec![merged_prelude_declaration()],
219            crate::runtime::host_package_declarations(),
220            crate::runtime::internal_host_package_declarations(),
221        )
222    });
223    (prelude, host, internal)
224}
225
226/// The single `submilli:prelude` declaration: the type/interface surface
227/// declared in this file plus the value symbols declared beside their Rust
228/// implementations in `runtime::prelude`. A Rust-side declaration wins
229/// over a same-key value here (the handful of pre-port symbols like
230/// `string_concat` are declared on both sides).
231fn merged_prelude_declaration() -> PackageDeclaration {
232    let host = crate::runtime::prelude::package_declaration();
233    let mut prelude = prelude_without_host_overrides(prelude_package_declaration(), &host);
234    prelude.types.extend(host.types);
235    prelude.shapes.extend(host.shapes);
236    prelude.values.extend(host.values);
237    merge_namespaces(&mut prelude.namespaces, host.namespaces);
238    prelude
239}
240
241fn merge_namespaces(
242    into: &mut BTreeMap<String, crate::NamespaceSymbol>,
243    from: BTreeMap<String, crate::NamespaceSymbol>,
244) {
245    for (name, mut ns) in from {
246        match into.get_mut(&name) {
247            Some(existing) => {
248                existing.types.append(&mut ns.types);
249                existing.values.append(&mut ns.values);
250                merge_namespaces(&mut existing.namespaces, std::mem::take(&mut ns.namespaces));
251            }
252            None => {
253                into.insert(name, ns);
254            }
255        }
256    }
257}
258
259fn prelude_without_host_overrides(
260    mut prelude: PackageDeclaration,
261    overrides_from: &PackageDeclaration,
262) -> PackageDeclaration {
263    let mut overrides = BTreeSet::new();
264    collect_value_overrides(&overrides_from.values, &mut overrides);
265    collect_namespace_value_overrides(&overrides_from.namespaces, &mut overrides);
266    prelude
267        .values
268        .retain(|_, value| !overrides.contains(&value.mangled_name));
269    remove_namespace_value_overrides(&mut prelude.namespaces, &overrides);
270    prelude
271}
272
273fn collect_value_overrides(
274    values: &BTreeMap<String, ValueSymbol>,
275    out: &mut BTreeSet<crate::MangledName>,
276) {
277    for value in values.values() {
278        out.insert(value.mangled_name.clone());
279    }
280}
281
282fn collect_namespace_value_overrides(
283    namespaces: &BTreeMap<String, crate::NamespaceSymbol>,
284    out: &mut BTreeSet<crate::MangledName>,
285) {
286    for namespace in namespaces.values() {
287        collect_value_overrides(&namespace.values, out);
288        collect_namespace_value_overrides(&namespace.namespaces, out);
289    }
290}
291
292fn remove_namespace_value_overrides(
293    namespaces: &mut BTreeMap<String, crate::NamespaceSymbol>,
294    overrides: &BTreeSet<crate::MangledName>,
295) {
296    for namespace in namespaces.values_mut() {
297        namespace
298            .values
299            .retain(|_, value| !overrides.contains(&value.mangled_name));
300        remove_namespace_value_overrides(&mut namespace.namespaces, overrides);
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::{MODULE_NAME, prelude_package_declaration};
307    use crate::{Param, Type, TypeKind, ValueKind};
308
309    #[test]
310    fn module_name_is_prelude_namespace() {
311        assert_eq!(MODULE_NAME, "submilli:prelude");
312    }
313
314    #[test]
315    fn prelude_definitions_declares_builtin_interfaces() {
316        // built-in capabilities are declared as interfaces.
317        // String / Number / Boolean / Array / Object / Console all
318        // get one entry; method-call dispatch (`find_method`) reads
319        // these tables. `String` itself is still an intrinsic shape
320        // (declared by every consumer's `declare_intrinsic_types`),
321        // but its *interface* (the methods callable on a string) is
322        // declared here.
323        let defs = prelude_package_declaration();
324        let mut names: Vec<&str> = defs.types.keys().map(String::as_str).collect();
325        names.sort();
326        assert_eq!(
327            names,
328            vec![
329                "Array",
330                "ArrayConstructor",
331                "Base64Options",
332                "BigInt",
333                "BigIntConstructor",
334                "Boolean",
335                "Console",
336                "Error",
337                "Iterable",
338                "Iterator",
339                "IteratorResult",
340                "IteratorReturnResult",
341                "IteratorYieldResult",
342                "Map",
343                "MapConstructor",
344                "Number",
345                "NumberConstructor",
346                "Object",
347                "ObjectConstructor",
348                "PermissionDeniedError",
349                "QuotaExceededError",
350                "RangeError",
351                "ReferenceError",
352                "RegExp",
353                "RegExpConstructor",
354                "RegExpMatch",
355                "Set",
356                "SetConstructor",
357                "String",
358                "StringConstructor",
359                "SyntaxError",
360                "Temporal#Duration",
361                "Temporal#DurationCompareOptions",
362                "Temporal#DurationConstructor",
363                "Temporal#DurationFields",
364                "Temporal#DurationRoundOptions",
365                "Temporal#DurationToStringOptions",
366                "Temporal#DurationTotalOptions",
367                "Temporal#Instant",
368                "Temporal#InstantConstructor",
369                "Temporal#InstantRoundOptions",
370                "Temporal#PlainDate",
371                "Temporal#PlainDateConstructor",
372                "Temporal#PlainDateFields",
373                "Temporal#PlainDateTime",
374                "Temporal#PlainDateTimeConstructor",
375                "Temporal#PlainDateTimeFields",
376                "Temporal#PlainDateToZonedOptions",
377                "Temporal#PlainMonthDay",
378                "Temporal#PlainMonthDayConstructor",
379                "Temporal#PlainMonthDayFields",
380                "Temporal#PlainMonthDayToDateFields",
381                "Temporal#PlainTime",
382                "Temporal#PlainTimeConstructor",
383                "Temporal#PlainTimeFields",
384                "Temporal#PlainYearMonth",
385                "Temporal#PlainYearMonthConstructor",
386                "Temporal#PlainYearMonthFields",
387                "Temporal#PlainYearMonthToDateFields",
388                "Temporal#SinceUntilOptions",
389                "Temporal#ZonedDateTime",
390                "Temporal#ZonedDateTimeConstructor",
391                "Temporal#ZonedDateTimeFields",
392                "Temporal#ZonedDateTimeRoundOptions",
393                "TextDecoder",
394                "TextDecoderConstructor",
395                "TextEncoder",
396                "TextEncoderConstructor",
397                "TypeError",
398                "URIError",
399                "Uint8Array",
400                "Uint8ArrayConstructor",
401            ]
402        );
403        for name in &names {
404            // the three `IteratorResult` family aliases
405            // are `TypeKind::Alias`, not `Interface`. Skip them
406            // here — the kind invariant only applies to the
407            // interface declarations.
408            if matches!(
409                *name,
410                "IteratorResult" | "IteratorReturnResult" | "IteratorYieldResult",
411            ) {
412                continue;
413            }
414            // `Error` and its built-in subclasses are the prelude-declared
415            // classes (host-implemented).
416            if matches!(
417                *name,
418                "Error"
419                    | "QuotaExceededError"
420                    | "RangeError"
421                    | "ReferenceError"
422                    | "TypeError"
423                    | "SyntaxError"
424                    | "URIError"
425                    | "PermissionDeniedError"
426            ) {
427                assert!(matches!(&defs.types[*name].kind, TypeKind::Class { .. }));
428                continue;
429            }
430            match &defs.types[*name].kind {
431                TypeKind::Interface { .. } => {}
432                other => panic!("expected Interface for {name}, got {other:?}"),
433            }
434        }
435    }
436
437    #[test]
438    fn prelude_definitions_lists_runtime_functions_and_string_constants() {
439        let defs = prelude_package_declaration();
440        let names: Vec<&str> = defs.values.keys().map(String::as_str).collect();
441        // BTreeMap iteration is alphabetical. `console` is the
442        // InterfaceRef binding — a Const value, no Wasm
443        // representation, dispatched via `find_method`.
444        assert_eq!(
445            names,
446            vec![
447                "Array",
448                "BigInt",
449                "Infinity",
450                "Map",
451                "NaN",
452                "Number",
453                "Object",
454                "RegExp",
455                "Set",
456                "String",
457                "TextDecoder",
458                "TextEncoder",
459                "Uint8Array",
460                "console",
461                "isFinite",
462                "isNaN",
463                "string_cmp",
464                "string_comma",
465                "string_concat",
466                "string_eq",
467                "string_false",
468                "string_length",
469                "string_null",
470                "string_object_function",
471                "string_object_object",
472                "string_true",
473            ]
474        );
475
476        match &defs.values["string_concat"].kind {
477            ValueKind::Function { params, ret, .. } => {
478                assert_eq!(
479                    params,
480                    &[Param::anon(Type::String), Param::anon(Type::String)]
481                );
482                assert_eq!(*ret, Type::String);
483            }
484            other => panic!("expected Function, got {other:?}"),
485        }
486        match &defs.values["string_eq"].kind {
487            ValueKind::Function { params, ret, .. } => {
488                assert_eq!(
489                    params,
490                    &[Param::anon(Type::String), Param::anon(Type::String)]
491                );
492                assert_eq!(*ret, Type::Boolean);
493            }
494            other => panic!("expected Function, got {other:?}"),
495        }
496        match &defs.values["string_length"].kind {
497            ValueKind::Function { params, ret, .. } => {
498                assert_eq!(params, &[Param::anon(Type::String)]);
499                assert_eq!(*ret, Type::Number);
500            }
501            other => panic!("expected Function, got {other:?}"),
502        }
503        for constant in [
504            "string_true",
505            "string_false",
506            "string_comma",
507            "string_object_object",
508            "string_object_function",
509            "string_null",
510        ] {
511            match &defs.values[constant].kind {
512                ValueKind::Const { ty, .. } => assert_eq!(*ty, Type::String),
513                other => panic!("expected Const, got {other:?}"),
514            }
515        }
516    }
517}