1use 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
12pub(crate) fn doc(literal: &str) -> Option<DocComment> {
15 crate::doc(FileId::PRELUDE, literal)
16}
17
18pub(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
32pub(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
181pub 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 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
226fn 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 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 if matches!(
409 *name,
410 "IteratorResult" | "IteratorReturnResult" | "IteratorYieldResult",
411 ) {
412 continue;
413 }
414 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 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}