@elaraai/east 0.0.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +682 -0
- package/README.md +276 -0
- package/dist/src/analyze.d.ts +95 -0
- package/dist/src/analyze.d.ts.map +1 -0
- package/dist/src/analyze.js +1110 -0
- package/dist/src/analyze.js.map +1 -0
- package/dist/src/ast.d.ts +263 -0
- package/dist/src/ast.d.ts.map +1 -0
- package/dist/src/ast.js +151 -0
- package/dist/src/ast.js.map +1 -0
- package/dist/src/ast_to_ir.d.ts +24 -0
- package/dist/src/ast_to_ir.d.ts.map +1 -0
- package/dist/src/ast_to_ir.js +834 -0
- package/dist/src/ast_to_ir.js.map +1 -0
- package/dist/src/builtins.d.ts +18 -0
- package/dist/src/builtins.d.ts.map +1 -0
- package/dist/src/builtins.js +1105 -0
- package/dist/src/builtins.js.map +1 -0
- package/dist/src/comparison.d.ts +28 -0
- package/dist/src/comparison.d.ts.map +1 -0
- package/dist/src/comparison.js +1017 -0
- package/dist/src/comparison.js.map +1 -0
- package/dist/src/compile.d.ts +22 -0
- package/dist/src/compile.d.ts.map +1 -0
- package/dist/src/compile.js +3260 -0
- package/dist/src/compile.js.map +1 -0
- package/dist/src/containers/ref.d.ts +106 -0
- package/dist/src/containers/ref.d.ts.map +1 -0
- package/dist/src/containers/ref.js +100 -0
- package/dist/src/containers/ref.js.map +1 -0
- package/dist/src/containers/sortedmap.d.ts +165 -0
- package/dist/src/containers/sortedmap.d.ts.map +1 -0
- package/dist/src/containers/sortedmap.js +237 -0
- package/dist/src/containers/sortedmap.js.map +1 -0
- package/dist/src/containers/sortedset.d.ts +185 -0
- package/dist/src/containers/sortedset.d.ts.map +1 -0
- package/dist/src/containers/sortedset.js +312 -0
- package/dist/src/containers/sortedset.js.map +1 -0
- package/dist/src/containers/variant.d.ts +131 -0
- package/dist/src/containers/variant.d.ts.map +1 -0
- package/dist/src/containers/variant.js +68 -0
- package/dist/src/containers/variant.js.map +1 -0
- package/dist/src/datetime_format/parse.d.ts +50 -0
- package/dist/src/datetime_format/parse.d.ts.map +1 -0
- package/dist/src/datetime_format/parse.js +908 -0
- package/dist/src/datetime_format/parse.js.map +1 -0
- package/dist/src/datetime_format/print.d.ts +35 -0
- package/dist/src/datetime_format/print.d.ts.map +1 -0
- package/dist/src/datetime_format/print.js +157 -0
- package/dist/src/datetime_format/print.js.map +1 -0
- package/dist/src/datetime_format/tokenize.d.ts +76 -0
- package/dist/src/datetime_format/tokenize.d.ts.map +1 -0
- package/dist/src/datetime_format/tokenize.js +271 -0
- package/dist/src/datetime_format/tokenize.js.map +1 -0
- package/dist/src/datetime_format/types.d.ts +99 -0
- package/dist/src/datetime_format/types.d.ts.map +1 -0
- package/dist/src/datetime_format/types.js +103 -0
- package/dist/src/datetime_format/types.js.map +1 -0
- package/dist/src/datetime_format/validate.d.ts +51 -0
- package/dist/src/datetime_format/validate.d.ts.map +1 -0
- package/dist/src/datetime_format/validate.js +208 -0
- package/dist/src/datetime_format/validate.js.map +1 -0
- package/dist/src/default.d.ts +21 -0
- package/dist/src/default.d.ts.map +1 -0
- package/dist/src/default.js +82 -0
- package/dist/src/default.js.map +1 -0
- package/dist/src/eastir.d.ts +33 -0
- package/dist/src/eastir.d.ts.map +1 -0
- package/dist/src/eastir.js +92 -0
- package/dist/src/eastir.js.map +1 -0
- package/dist/src/error.d.ts +13 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +8 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/expr/array.d.ts +1711 -0
- package/dist/src/expr/array.d.ts.map +1 -0
- package/dist/src/expr/array.js +1805 -0
- package/dist/src/expr/array.js.map +1 -0
- package/dist/src/expr/ast.d.ts +17 -0
- package/dist/src/expr/ast.d.ts.map +1 -0
- package/dist/src/expr/ast.js +302 -0
- package/dist/src/expr/ast.js.map +1 -0
- package/dist/src/expr/blob.d.ts +141 -0
- package/dist/src/expr/blob.d.ts.map +1 -0
- package/dist/src/expr/blob.js +198 -0
- package/dist/src/expr/blob.js.map +1 -0
- package/dist/src/expr/block.d.ts +201 -0
- package/dist/src/expr/block.d.ts.map +1 -0
- package/dist/src/expr/block.js +1505 -0
- package/dist/src/expr/block.js.map +1 -0
- package/dist/src/expr/boolean.d.ts +207 -0
- package/dist/src/expr/boolean.d.ts.map +1 -0
- package/dist/src/expr/boolean.js +261 -0
- package/dist/src/expr/boolean.js.map +1 -0
- package/dist/src/expr/datetime.d.ts +544 -0
- package/dist/src/expr/datetime.d.ts.map +1 -0
- package/dist/src/expr/datetime.js +980 -0
- package/dist/src/expr/datetime.js.map +1 -0
- package/dist/src/expr/dict.d.ts +1242 -0
- package/dist/src/expr/dict.d.ts.map +1 -0
- package/dist/src/expr/dict.js +1492 -0
- package/dist/src/expr/dict.js.map +1 -0
- package/dist/src/expr/expr.d.ts +95 -0
- package/dist/src/expr/expr.d.ts.map +1 -0
- package/dist/src/expr/expr.js +171 -0
- package/dist/src/expr/expr.js.map +1 -0
- package/dist/src/expr/float.d.ts +357 -0
- package/dist/src/expr/float.d.ts.map +1 -0
- package/dist/src/expr/float.js +637 -0
- package/dist/src/expr/float.js.map +1 -0
- package/dist/src/expr/function.d.ts +46 -0
- package/dist/src/expr/function.d.ts.map +1 -0
- package/dist/src/expr/function.js +58 -0
- package/dist/src/expr/function.js.map +1 -0
- package/dist/src/expr/index.d.ts +450 -0
- package/dist/src/expr/index.d.ts.map +1 -0
- package/dist/src/expr/index.js +423 -0
- package/dist/src/expr/index.js.map +1 -0
- package/dist/src/expr/integer.d.ts +256 -0
- package/dist/src/expr/integer.d.ts.map +1 -0
- package/dist/src/expr/integer.js +311 -0
- package/dist/src/expr/integer.js.map +1 -0
- package/dist/src/expr/libs/array.d.ts +106 -0
- package/dist/src/expr/libs/array.d.ts.map +1 -0
- package/dist/src/expr/libs/array.js +140 -0
- package/dist/src/expr/libs/array.js.map +1 -0
- package/dist/src/expr/libs/blob.d.ts +42 -0
- package/dist/src/expr/libs/blob.d.ts.map +1 -0
- package/dist/src/expr/libs/blob.js +70 -0
- package/dist/src/expr/libs/blob.js.map +1 -0
- package/dist/src/expr/libs/datetime.d.ts +479 -0
- package/dist/src/expr/libs/datetime.d.ts.map +1 -0
- package/dist/src/expr/libs/datetime.js +624 -0
- package/dist/src/expr/libs/datetime.js.map +1 -0
- package/dist/src/expr/libs/dict.d.ts +66 -0
- package/dist/src/expr/libs/dict.d.ts.map +1 -0
- package/dist/src/expr/libs/dict.js +77 -0
- package/dist/src/expr/libs/dict.js.map +1 -0
- package/dist/src/expr/libs/float.d.ts +299 -0
- package/dist/src/expr/libs/float.d.ts.map +1 -0
- package/dist/src/expr/libs/float.js +564 -0
- package/dist/src/expr/libs/float.js.map +1 -0
- package/dist/src/expr/libs/integer.d.ts +228 -0
- package/dist/src/expr/libs/integer.d.ts.map +1 -0
- package/dist/src/expr/libs/integer.js +398 -0
- package/dist/src/expr/libs/integer.js.map +1 -0
- package/dist/src/expr/libs/set.d.ts +59 -0
- package/dist/src/expr/libs/set.d.ts.map +1 -0
- package/dist/src/expr/libs/set.js +69 -0
- package/dist/src/expr/libs/set.js.map +1 -0
- package/dist/src/expr/libs/string.d.ts +71 -0
- package/dist/src/expr/libs/string.d.ts.map +1 -0
- package/dist/src/expr/libs/string.js +75 -0
- package/dist/src/expr/libs/string.js.map +1 -0
- package/dist/src/expr/never.d.ts +15 -0
- package/dist/src/expr/never.d.ts.map +1 -0
- package/dist/src/expr/never.js +12 -0
- package/dist/src/expr/never.js.map +1 -0
- package/dist/src/expr/null.d.ts +15 -0
- package/dist/src/expr/null.d.ts.map +1 -0
- package/dist/src/expr/null.js +12 -0
- package/dist/src/expr/null.js.map +1 -0
- package/dist/src/expr/ref.d.ts +103 -0
- package/dist/src/expr/ref.d.ts.map +1 -0
- package/dist/src/expr/ref.js +131 -0
- package/dist/src/expr/ref.js.map +1 -0
- package/dist/src/expr/regex_validation.d.ts +25 -0
- package/dist/src/expr/regex_validation.d.ts.map +1 -0
- package/dist/src/expr/regex_validation.js +130 -0
- package/dist/src/expr/regex_validation.js.map +1 -0
- package/dist/src/expr/set.d.ts +1071 -0
- package/dist/src/expr/set.d.ts.map +1 -0
- package/dist/src/expr/set.js +1137 -0
- package/dist/src/expr/set.js.map +1 -0
- package/dist/src/expr/string.d.ts +414 -0
- package/dist/src/expr/string.d.ts.map +1 -0
- package/dist/src/expr/string.js +683 -0
- package/dist/src/expr/string.js.map +1 -0
- package/dist/src/expr/struct.d.ts +48 -0
- package/dist/src/expr/struct.d.ts.map +1 -0
- package/dist/src/expr/struct.js +65 -0
- package/dist/src/expr/struct.js.map +1 -0
- package/dist/src/expr/types.d.ts +68 -0
- package/dist/src/expr/types.d.ts.map +1 -0
- package/dist/src/expr/types.js +6 -0
- package/dist/src/expr/types.js.map +1 -0
- package/dist/src/expr/variant.d.ts +137 -0
- package/dist/src/expr/variant.d.ts.map +1 -0
- package/dist/src/expr/variant.js +105 -0
- package/dist/src/expr/variant.js.map +1 -0
- package/dist/src/fuzz.d.ts +80 -0
- package/dist/src/fuzz.d.ts.map +1 -0
- package/dist/src/fuzz.js +300 -0
- package/dist/src/fuzz.js.map +1 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/internal.d.ts +36 -0
- package/dist/src/internal.d.ts.map +1 -0
- package/dist/src/internal.js +11 -0
- package/dist/src/internal.js.map +1 -0
- package/dist/src/ir.d.ts +1571 -0
- package/dist/src/ir.d.ts.map +1 -0
- package/dist/src/ir.js +56 -0
- package/dist/src/ir.js.map +1 -0
- package/dist/src/location.d.ts +48 -0
- package/dist/src/location.d.ts.map +1 -0
- package/dist/src/location.js +62 -0
- package/dist/src/location.js.map +1 -0
- package/dist/src/platform.d.ts +21 -0
- package/dist/src/platform.d.ts.map +1 -0
- package/dist/src/platform.js +8 -0
- package/dist/src/platform.js.map +1 -0
- package/dist/src/serialization/beast.d.ts +39 -0
- package/dist/src/serialization/beast.d.ts.map +1 -0
- package/dist/src/serialization/beast.js +555 -0
- package/dist/src/serialization/beast.js.map +1 -0
- package/dist/src/serialization/beast2-stream.d.ts +38 -0
- package/dist/src/serialization/beast2-stream.d.ts.map +1 -0
- package/dist/src/serialization/beast2-stream.js +665 -0
- package/dist/src/serialization/beast2-stream.js.map +1 -0
- package/dist/src/serialization/beast2.d.ts +41 -0
- package/dist/src/serialization/beast2.d.ts.map +1 -0
- package/dist/src/serialization/beast2.js +489 -0
- package/dist/src/serialization/beast2.js.map +1 -0
- package/dist/src/serialization/binary-utils.d.ts +151 -0
- package/dist/src/serialization/binary-utils.d.ts.map +1 -0
- package/dist/src/serialization/binary-utils.js +929 -0
- package/dist/src/serialization/binary-utils.js.map +1 -0
- package/dist/src/serialization/east.d.ts +84 -0
- package/dist/src/serialization/east.d.ts.map +1 -0
- package/dist/src/serialization/east.js +1802 -0
- package/dist/src/serialization/east.js.map +1 -0
- package/dist/src/serialization/index.d.ts +11 -0
- package/dist/src/serialization/index.d.ts.map +1 -0
- package/dist/src/serialization/index.js +12 -0
- package/dist/src/serialization/index.js.map +1 -0
- package/dist/src/serialization/json.d.ts +36 -0
- package/dist/src/serialization/json.d.ts.map +1 -0
- package/dist/src/serialization/json.js +849 -0
- package/dist/src/serialization/json.js.map +1 -0
- package/dist/src/type_of_type.d.ts +115 -0
- package/dist/src/type_of_type.d.ts.map +1 -0
- package/dist/src/type_of_type.js +362 -0
- package/dist/src/type_of_type.js.map +1 -0
- package/dist/src/types.d.ts +648 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1631 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1631 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
import { isRef } from "./containers/ref.js";
|
|
6
|
+
import { SortedMap } from "./containers/sortedmap.js";
|
|
7
|
+
import { SortedSet } from "./containers/sortedset.js";
|
|
8
|
+
import { variant_symbol } from "./containers/variant.js";
|
|
9
|
+
/**
|
|
10
|
+
* Error thrown when type operations encounter incompatible types.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Used by type union, intersection, and equality operations when types
|
|
14
|
+
* cannot be combined or compared as requested.
|
|
15
|
+
*/
|
|
16
|
+
class TypeMismatchError extends Error {
|
|
17
|
+
constructor(message, options) {
|
|
18
|
+
super(message, options);
|
|
19
|
+
this.name = "TypeMismatchError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Singleton instance of the Never type. */
|
|
23
|
+
export const NeverType = { type: "Never" };
|
|
24
|
+
/** Singleton instance of the Null type. */
|
|
25
|
+
export const NullType = { type: "Null" };
|
|
26
|
+
/** Singleton instance of the Boolean type. */
|
|
27
|
+
export const BooleanType = { type: "Boolean" };
|
|
28
|
+
/** Singleton instance of the Integer type. */
|
|
29
|
+
export const IntegerType = { type: "Integer" };
|
|
30
|
+
/** Singleton instance of the Float type. */
|
|
31
|
+
export const FloatType = { type: "Float" };
|
|
32
|
+
/** Singleton instance of the String type. */
|
|
33
|
+
export const StringType = { type: "String" };
|
|
34
|
+
/** Singleton instance of the DateTime type. */
|
|
35
|
+
export const DateTimeType = { type: "DateTime" };
|
|
36
|
+
/** Singleton instance of the Blob type. */
|
|
37
|
+
export const BlobType = { type: "Blob" };
|
|
38
|
+
/**
|
|
39
|
+
* Constructs a reference (or cell) type with the specified element type.
|
|
40
|
+
*
|
|
41
|
+
* Conceptually, a reference is a box or cell that can hold a value of a specified type.
|
|
42
|
+
* The reference can be mutated by updating the contents of the reference.
|
|
43
|
+
* Multiple references can point to the same underlying value.
|
|
44
|
+
*
|
|
45
|
+
* @typeParam T - The type of value held by the reference
|
|
46
|
+
* @param type - The element type
|
|
47
|
+
* @returns A reference type
|
|
48
|
+
* @throws When the element type contains functions
|
|
49
|
+
*/
|
|
50
|
+
export function RefType(type) {
|
|
51
|
+
if (typeof type !== "string" && !isDataType(type)) {
|
|
52
|
+
throw new Error(`Ref value type must be a (non-function) data type, got ${printType(type)}`);
|
|
53
|
+
}
|
|
54
|
+
return { type: "Ref", value: type };
|
|
55
|
+
}
|
|
56
|
+
;
|
|
57
|
+
/**
|
|
58
|
+
* Constructs an Array type with the specified element type.
|
|
59
|
+
*
|
|
60
|
+
* @typeParam T - The type of elements in the array
|
|
61
|
+
* @param type - The element type
|
|
62
|
+
* @returns An Array type
|
|
63
|
+
* @throws When the element type contains functions
|
|
64
|
+
*/
|
|
65
|
+
export function ArrayType(type) {
|
|
66
|
+
if (typeof type !== "string" && !isDataType(type)) {
|
|
67
|
+
throw new Error(`Array value type must be a (non-function) data type, got ${printType(type)}`);
|
|
68
|
+
}
|
|
69
|
+
return { type: "Array", value: type };
|
|
70
|
+
}
|
|
71
|
+
;
|
|
72
|
+
/**
|
|
73
|
+
* Constructs a Set type with the specified key type.
|
|
74
|
+
*
|
|
75
|
+
* @typeParam T - The type of keys in the set
|
|
76
|
+
* @param type - The key type
|
|
77
|
+
* @returns A Set type
|
|
78
|
+
* @throws When the key type is not immutable
|
|
79
|
+
*/
|
|
80
|
+
export function SetType(type) {
|
|
81
|
+
if (typeof type !== "string" && !isImmutableType(type)) {
|
|
82
|
+
throw new Error(`Set key type must be an immutable type, got ${printType(type)}`);
|
|
83
|
+
}
|
|
84
|
+
return { type: "Set", key: type };
|
|
85
|
+
}
|
|
86
|
+
;
|
|
87
|
+
/**
|
|
88
|
+
* Constructs a Dict type with the specified key and value types.
|
|
89
|
+
*
|
|
90
|
+
* @typeParam K - The type of keys in the dictionary
|
|
91
|
+
* @typeParam T - The type of values in the dictionary
|
|
92
|
+
* @param key - The key type
|
|
93
|
+
* @param value - The value type
|
|
94
|
+
* @returns A Dict type
|
|
95
|
+
* @throws When the key type is not immutable or value type contains functions
|
|
96
|
+
*/
|
|
97
|
+
export function DictType(key, value) {
|
|
98
|
+
if (typeof key !== "string" && !isImmutableType(key)) {
|
|
99
|
+
throw new Error(`Dict key type must be an immutable type, got ${printType(key)}`);
|
|
100
|
+
}
|
|
101
|
+
if (typeof value !== "string" && !isDataType(value)) {
|
|
102
|
+
throw new Error(`Dict value type must be a (non-function) data type, got ${printType(value)}`);
|
|
103
|
+
}
|
|
104
|
+
return { type: "Dict", key, value };
|
|
105
|
+
}
|
|
106
|
+
;
|
|
107
|
+
/**
|
|
108
|
+
* Constructs a Struct type with the specified field types.
|
|
109
|
+
*
|
|
110
|
+
* @typeParam Fields - Object type mapping field names to their types
|
|
111
|
+
* @param field_types - Object mapping field names to {@link EastType} instances
|
|
112
|
+
* @returns A Struct type
|
|
113
|
+
*/
|
|
114
|
+
export function StructType(field_types) {
|
|
115
|
+
// Validate all field types are data types (no functions)
|
|
116
|
+
for (const [field_name, field_type] of Object.entries(field_types)) {
|
|
117
|
+
if (typeof field_type !== "string" && !isDataType(field_type)) {
|
|
118
|
+
throw new Error(`Struct field ${field_name} must be a (non-function) data type, got ${printType(field_type)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { type: "Struct", fields: field_types };
|
|
122
|
+
}
|
|
123
|
+
;
|
|
124
|
+
/**
|
|
125
|
+
* Constructs a Variant type with the specified cases.
|
|
126
|
+
*
|
|
127
|
+
* @typeParam Cases - Object type mapping case names to their value types
|
|
128
|
+
* @param case_types - Object mapping case names to {@link EastType} instances
|
|
129
|
+
* @returns A Variant type with cases sorted alphabetically
|
|
130
|
+
*/
|
|
131
|
+
export function VariantType(case_types) {
|
|
132
|
+
// Validate all case types are data types (no functions)
|
|
133
|
+
for (const [case_name, case_type] of Object.entries(case_types)) {
|
|
134
|
+
if (typeof case_type !== "string" && !isDataType(case_type)) {
|
|
135
|
+
throw new Error(`Variant case ${case_name} must be a (non-function) data type, got ${printType(case_type)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Cases are sorted alphabetically by their name
|
|
139
|
+
const cases_sorted = Object.fromEntries(Object.entries(case_types).sort((x, y) => x[0] < y[0] ? -1 : x[0] === y[0] ? 0 : 1));
|
|
140
|
+
return { type: "Variant", cases: cases_sorted };
|
|
141
|
+
}
|
|
142
|
+
;
|
|
143
|
+
/**
|
|
144
|
+
* Validates that a recursive type has SCC (Strongly Connected Component) size 1.
|
|
145
|
+
*
|
|
146
|
+
* @param type - The type to validate
|
|
147
|
+
* @param allowedMarker - The recursive marker that is allowed (the one being defined)
|
|
148
|
+
* @throws When nested RecursiveTypes with cross-references are detected
|
|
149
|
+
*
|
|
150
|
+
* @remarks
|
|
151
|
+
* This ensures that recursive types don't have mutual recursion or nested
|
|
152
|
+
* RecursiveTypes that reference outer scopes. Only simple recursion (SCC size 1)
|
|
153
|
+
* is supported for predictable behavior and implementation simplicity.
|
|
154
|
+
*/
|
|
155
|
+
function validateNotMutuallyRecursive(type, allowedMarker) {
|
|
156
|
+
const visited = new Set();
|
|
157
|
+
function check(t) {
|
|
158
|
+
// Skip string placeholders (used for generic builtins)
|
|
159
|
+
if (typeof t === "string")
|
|
160
|
+
return;
|
|
161
|
+
if (typeof t !== "object" || t === null) {
|
|
162
|
+
let allowedMarkerString = null;
|
|
163
|
+
try {
|
|
164
|
+
allowedMarkerString = printType(allowedMarker);
|
|
165
|
+
}
|
|
166
|
+
catch { }
|
|
167
|
+
throw new Error(`Invalid type encountered during recursion validation: ${t}${allowedMarkerString === null ? "" : ` (allowed marker: ${allowedMarkerString})`}`);
|
|
168
|
+
}
|
|
169
|
+
// Skip allowedMarker
|
|
170
|
+
if (t === allowedMarker)
|
|
171
|
+
return;
|
|
172
|
+
// Avoid infinite loops
|
|
173
|
+
if (visited.has(t))
|
|
174
|
+
return;
|
|
175
|
+
visited.add(t);
|
|
176
|
+
if (t.type === "Recursive") {
|
|
177
|
+
if (t.node === undefined) {
|
|
178
|
+
throw new Error("RecursiveType must have SCC size 1: nested RecursiveTypes with cross-references are not supported. " +
|
|
179
|
+
"Each RecursiveType can only reference itself, not other recursive scopes.");
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Recurse into the recursive type's body, with t as the new root
|
|
183
|
+
validateNotMutuallyRecursive(t.node, t);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (t.type === "Struct") {
|
|
187
|
+
for (const [_, field_type] of Object.entries(t.fields)) {
|
|
188
|
+
check(field_type);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (t.type === "Variant") {
|
|
192
|
+
for (const [_, case_type] of Object.entries(t.cases)) {
|
|
193
|
+
check(case_type);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (t.type === "Array") {
|
|
197
|
+
check(t.value);
|
|
198
|
+
}
|
|
199
|
+
else if (t.type === "Set") {
|
|
200
|
+
check(t.key);
|
|
201
|
+
}
|
|
202
|
+
else if (t.type === "Dict") {
|
|
203
|
+
check(t.key);
|
|
204
|
+
check(t.value);
|
|
205
|
+
}
|
|
206
|
+
else if (t.type === "Function") {
|
|
207
|
+
t.inputs.forEach(check);
|
|
208
|
+
check(t.output);
|
|
209
|
+
}
|
|
210
|
+
// Primitive types don't need recursion
|
|
211
|
+
}
|
|
212
|
+
check(type);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Constructs a recursive type with the specified node structure.
|
|
216
|
+
*
|
|
217
|
+
* @typeParam F - Function type that defines the recursive structure
|
|
218
|
+
* @param f - Function that receives a self-reference marker and returns the node type
|
|
219
|
+
* @returns A RecursiveType representing the recursive structure
|
|
220
|
+
* @throws When the type contains functions or has SCC size > 1 (mutual recursion)
|
|
221
|
+
*
|
|
222
|
+
* @remarks
|
|
223
|
+
* The function `f` receives a `RecursiveTypeMarker` representing the recursive
|
|
224
|
+
* reference point. This marker should be used wherever the type recursively refers to itself.
|
|
225
|
+
*
|
|
226
|
+
* Only simple recursion is supported - the type can reference itself multiple times, but
|
|
227
|
+
* nested RecursiveTypes with cross-references (mutual recursion) are not allowed.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* // Linked list
|
|
232
|
+
* const ListType = RecursiveType(self =>
|
|
233
|
+
* VariantType({
|
|
234
|
+
* nil: NullType,
|
|
235
|
+
* cons: StructType({ head: IntegerType, tail: self })
|
|
236
|
+
* })
|
|
237
|
+
* );
|
|
238
|
+
*
|
|
239
|
+
* // Binary tree
|
|
240
|
+
* const TreeType = RecursiveType(self =>
|
|
241
|
+
* StructType({
|
|
242
|
+
* value: IntegerType,
|
|
243
|
+
* left: OptionType(self),
|
|
244
|
+
* right: OptionType(self)
|
|
245
|
+
* })
|
|
246
|
+
* );
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
export function RecursiveType(f) {
|
|
250
|
+
const ret = { type: "Recursive", node: undefined };
|
|
251
|
+
const type = f(ret);
|
|
252
|
+
ret.node = type;
|
|
253
|
+
// Validate SCC size 1 (no nested RecursiveTypes with cross-references)
|
|
254
|
+
validateNotMutuallyRecursive(type, ret);
|
|
255
|
+
if (typeof type !== "string" && !isDataType(type)) {
|
|
256
|
+
throw new Error(`Recursive node type must be a (non-function) data type, got ${printType(type)}`);
|
|
257
|
+
}
|
|
258
|
+
return ret;
|
|
259
|
+
}
|
|
260
|
+
;
|
|
261
|
+
/**
|
|
262
|
+
* Constructs a Function type with the specified input and output types.
|
|
263
|
+
*
|
|
264
|
+
* @typeParam I - Tuple type of input parameter types
|
|
265
|
+
* @typeParam O - The output/return type
|
|
266
|
+
* @param inputs - Array of {@link EastType} instances for each parameter
|
|
267
|
+
* @param output - The {@link EastType} of the return value
|
|
268
|
+
* @returns A Function type
|
|
269
|
+
*/
|
|
270
|
+
export function FunctionType(inputs, output, platforms) {
|
|
271
|
+
// TODO - for the moment we default function types to being pure, meaning users may need to annotate their higher-order functions.
|
|
272
|
+
// In future we may want to track side-effects more precisely.
|
|
273
|
+
return { type: "Function", inputs, output, platforms };
|
|
274
|
+
}
|
|
275
|
+
;
|
|
276
|
+
/**
|
|
277
|
+
* Checks if a type is a pure data type (excludes functions).
|
|
278
|
+
*
|
|
279
|
+
* @param type - The {@link EastType} to check
|
|
280
|
+
* @param recursive_type - Internal parameter for tracking the current recursive type being checked
|
|
281
|
+
* @returns `true` if the type is a pure data type, `false` if it contains functions
|
|
282
|
+
*
|
|
283
|
+
* @remarks
|
|
284
|
+
* Data types can be fully serialized and transmitted between runtimes.
|
|
285
|
+
* This recursively checks {@link StructType}, {@link VariantType}, and {@link RecursiveType} fields.
|
|
286
|
+
*/
|
|
287
|
+
export function isDataType(type, recursive_type) {
|
|
288
|
+
// Avoid infinite loops
|
|
289
|
+
if (type === recursive_type) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
if (type.type === "Ref") {
|
|
293
|
+
// Ref constructors check their value type are data types
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
else if (type.type === "Array") {
|
|
297
|
+
// Array constructors check their value type are data types
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
else if (type.type === "Set") {
|
|
301
|
+
// Set constructors check their value type are (immutable) data types
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
else if (type.type === "Dict") {
|
|
305
|
+
// Dict constructors check their key and value types are data types
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
else if (type.type === "Struct") {
|
|
309
|
+
for (const field_type of Object.values(type.fields)) {
|
|
310
|
+
if (!isDataType(field_type, recursive_type)) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
else if (type.type === "Variant") {
|
|
317
|
+
for (const variant of Object.values(type.cases)) {
|
|
318
|
+
if (!isDataType(variant, recursive_type)) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
else if (type.type === "Recursive") {
|
|
325
|
+
return type.node === recursive_type ? true : isDataType(type.node, type);
|
|
326
|
+
}
|
|
327
|
+
else if (type.type === "Function") {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
// Primitive types are data types
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Checks if a type is immutable (can be used as dict keys or set elements).
|
|
337
|
+
*
|
|
338
|
+
* @param type - The {@link EastType} to check
|
|
339
|
+
* @param recursive_type - Internal parameter for tracking the current recursive type being checked
|
|
340
|
+
* @returns `true` if the type is immutable, `false` if it contains mutable collections or functions
|
|
341
|
+
*
|
|
342
|
+
* @remarks
|
|
343
|
+
* Immutable types exclude {@link ArrayType}, {@link SetType}, {@link DictType}, and {@link FunctionType}.
|
|
344
|
+
* This recursively checks {@link StructType}, {@link VariantType}, and {@link RecursiveType} fields.
|
|
345
|
+
*/
|
|
346
|
+
export function isImmutableType(type, recursive_type) {
|
|
347
|
+
// Avoid infinite loops
|
|
348
|
+
if (type === recursive_type) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
if (type.type === "Ref") {
|
|
352
|
+
// Refs are mutable
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
else if (type.type === "Array") {
|
|
356
|
+
// Arrays are mutable
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
else if (type.type === "Set") {
|
|
360
|
+
// Set are mutable
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
else if (type.type === "Dict") {
|
|
364
|
+
// Dict are mutable
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
else if (type.type === "Struct") {
|
|
368
|
+
for (const field_type of Object.values(type.fields)) {
|
|
369
|
+
if (!isImmutableType(field_type, recursive_type)) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
else if (type.type === "Variant") {
|
|
376
|
+
for (const variant of Object.values(type.cases)) {
|
|
377
|
+
if (!isImmutableType(variant, recursive_type)) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
else if (type.type === "Recursive") {
|
|
384
|
+
return type.node === recursive_type ? true : isImmutableType(type.node, type);
|
|
385
|
+
}
|
|
386
|
+
else if (type.type === "Function") {
|
|
387
|
+
// Functions are not data types, and "immutable data" is a subset of "data"
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
// Primitive types are immutable
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Infers the East type from a runtime JavaScript value.
|
|
397
|
+
*
|
|
398
|
+
* @typeParam V - The TypeScript type of the value
|
|
399
|
+
* @param value - The JavaScript value to analyze
|
|
400
|
+
* @returns The corresponding {@link EastType}
|
|
401
|
+
* @throws When the value is a JavaScript function or cannot be typed
|
|
402
|
+
*
|
|
403
|
+
* @remarks
|
|
404
|
+
* This is the runtime version of the {@link EastTypeOf} type.
|
|
405
|
+
* For arrays, it infers the element type from the first element.
|
|
406
|
+
*/
|
|
407
|
+
export function EastTypeOf(value) {
|
|
408
|
+
if (value === null) {
|
|
409
|
+
return NullType;
|
|
410
|
+
}
|
|
411
|
+
else if (typeof value === "boolean") {
|
|
412
|
+
return BooleanType;
|
|
413
|
+
}
|
|
414
|
+
else if (typeof value === "bigint") {
|
|
415
|
+
return IntegerType;
|
|
416
|
+
}
|
|
417
|
+
else if (typeof value === "number") {
|
|
418
|
+
return FloatType;
|
|
419
|
+
}
|
|
420
|
+
else if (typeof value === "string") {
|
|
421
|
+
return StringType;
|
|
422
|
+
}
|
|
423
|
+
if (value instanceof Date) {
|
|
424
|
+
return DateTimeType;
|
|
425
|
+
}
|
|
426
|
+
else if (value instanceof Uint8Array) {
|
|
427
|
+
return BlobType;
|
|
428
|
+
}
|
|
429
|
+
else if (Array.isArray(value)) {
|
|
430
|
+
if (value.length === 0) {
|
|
431
|
+
throw new Error(`Cannot infer East type of empty array`);
|
|
432
|
+
}
|
|
433
|
+
return ArrayType(EastTypeOf(value[0]));
|
|
434
|
+
}
|
|
435
|
+
else if (value instanceof Set || value instanceof SortedSet) {
|
|
436
|
+
if (value.size === 0) {
|
|
437
|
+
throw new Error(`Cannot infer East type of empty set`);
|
|
438
|
+
}
|
|
439
|
+
const first = value.values().next().value;
|
|
440
|
+
return SetType(EastTypeOf(first));
|
|
441
|
+
}
|
|
442
|
+
else if (value instanceof Map || value instanceof SortedMap) {
|
|
443
|
+
if (value.size === 0) {
|
|
444
|
+
throw new Error(`Cannot infer East type of empty map`);
|
|
445
|
+
}
|
|
446
|
+
const first = value.entries().next().value;
|
|
447
|
+
return DictType(EastTypeOf(first[0]), EastTypeOf(first[1]));
|
|
448
|
+
}
|
|
449
|
+
else if (typeof value === "function") {
|
|
450
|
+
throw new Error(`Javascript functions cannot be converted to East functions`);
|
|
451
|
+
}
|
|
452
|
+
else if (typeof value === "object") {
|
|
453
|
+
return StructType(Object.fromEntries(Object.entries(value).map(([k, v]) => [k, EastTypeOf(v)])));
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
throw new Error(`Cannot determine East type for value ${value}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Checks if two East types are structurally equal.
|
|
461
|
+
*
|
|
462
|
+
* @param t1 - First {@link EastType} to compare
|
|
463
|
+
* @param t2 - Second {@link EastType} to compare
|
|
464
|
+
* @param r1 - Internal parameter for tracking the first recursive type being compared
|
|
465
|
+
* @param r2 - Internal parameter for tracking the second recursive type being compared
|
|
466
|
+
* @returns `true` if the types are equal, `false` otherwise
|
|
467
|
+
*
|
|
468
|
+
* @remarks
|
|
469
|
+
* Performs structural equality checking, recursively comparing compound types.
|
|
470
|
+
* For {@link StructType} and {@link VariantType}, field/case order matters.
|
|
471
|
+
* For {@link RecursiveType}, uses cycle tracking to handle recursive references.
|
|
472
|
+
*/
|
|
473
|
+
export function isTypeEqual(t1, t2, r1 = t1, r2 = t2) {
|
|
474
|
+
if (t1.type === "Recursive") {
|
|
475
|
+
if (t2.type === "Recursive") {
|
|
476
|
+
if (t1.node === r1) {
|
|
477
|
+
return t2.node === r2;
|
|
478
|
+
}
|
|
479
|
+
else if (t2.node === r2) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
return isTypeEqual(t1.node, t2.node, t1.node, t2.node);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
// The RecursiveType wrapper is transparent
|
|
488
|
+
return isTypeEqual(t1.node, t2, t1.node, r2);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else if (t2.type === "Recursive") {
|
|
492
|
+
// The RecursiveType wrapper is transparent
|
|
493
|
+
return isTypeEqual(t1, t2.node, r1, t2.node);
|
|
494
|
+
}
|
|
495
|
+
else if (t1.type === "Never") {
|
|
496
|
+
return t2.type === "Never";
|
|
497
|
+
}
|
|
498
|
+
else if (t1.type === "Null") {
|
|
499
|
+
return t2.type === "Null";
|
|
500
|
+
}
|
|
501
|
+
else if (t1.type === "Boolean") {
|
|
502
|
+
return t2.type === "Boolean";
|
|
503
|
+
}
|
|
504
|
+
else if (t1.type === "Integer") {
|
|
505
|
+
return t2.type === "Integer";
|
|
506
|
+
}
|
|
507
|
+
else if (t1.type === "Float") {
|
|
508
|
+
return t2.type === "Float";
|
|
509
|
+
}
|
|
510
|
+
else if (t1.type === "String") {
|
|
511
|
+
return t2.type === "String";
|
|
512
|
+
}
|
|
513
|
+
else if (t1.type === "DateTime") {
|
|
514
|
+
return t2.type === "DateTime";
|
|
515
|
+
}
|
|
516
|
+
else if (t1.type === "Blob") {
|
|
517
|
+
return t2.type === "Blob";
|
|
518
|
+
}
|
|
519
|
+
else if (t1.type === "Ref") {
|
|
520
|
+
if (t2.type === "Ref") {
|
|
521
|
+
return isTypeEqual(t1.value, t2.value, r1, r2);
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else if (t1.type === "Array") {
|
|
528
|
+
if (t2.type === "Array") {
|
|
529
|
+
return isTypeEqual(t1.value, t2.value, r1, r2);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
else if (t1.type === "Set") {
|
|
536
|
+
if (t2.type === "Set") {
|
|
537
|
+
return isTypeEqual(t1.key, t2.key, r1, r2);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
else if (t1.type === "Dict") {
|
|
544
|
+
if (t2.type === "Dict") {
|
|
545
|
+
return isTypeEqual(t1.key, t2.key, r1, r2) && isTypeEqual(t1.value, t2.value, r1, r2);
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else if (t1.type === "Struct") {
|
|
552
|
+
if (t2.type === "Struct") {
|
|
553
|
+
const e1 = Object.entries(t1.fields);
|
|
554
|
+
const e2 = Object.entries(t2.fields);
|
|
555
|
+
if (e1.length !== e2.length)
|
|
556
|
+
return false;
|
|
557
|
+
let i = 0;
|
|
558
|
+
for (const [k1, f1] of e1) {
|
|
559
|
+
const [k2, f2] = e2[i];
|
|
560
|
+
if (k1 !== k2)
|
|
561
|
+
return false;
|
|
562
|
+
if (!isTypeEqual(f1, f2, r1, r2))
|
|
563
|
+
return false;
|
|
564
|
+
i += 1;
|
|
565
|
+
}
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else if (t1.type === "Variant") {
|
|
573
|
+
if (t2.type === "Variant") {
|
|
574
|
+
const e1 = Object.entries(t1.cases);
|
|
575
|
+
const e2 = Object.entries(t2.cases);
|
|
576
|
+
if (e1.length !== e2.length)
|
|
577
|
+
return false;
|
|
578
|
+
let i = 0;
|
|
579
|
+
for (const [k1, f1] of e1) {
|
|
580
|
+
const [k2, f2] = e2[i];
|
|
581
|
+
if (k1 !== k2)
|
|
582
|
+
return false;
|
|
583
|
+
if (!isTypeEqual(f1, f2, r1, r2))
|
|
584
|
+
return false;
|
|
585
|
+
i += 1;
|
|
586
|
+
}
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else if (t1.type === "Function") {
|
|
594
|
+
if (t2.type !== "Function") {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
// Check input types match
|
|
598
|
+
if (t1.inputs.length !== t2.inputs.length) {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
for (let i = 0; i < t1.inputs.length; i++) {
|
|
602
|
+
if (!isTypeEqual(t1.inputs[i], t2.inputs[i], r1, r2)) {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// Check output type matches
|
|
607
|
+
if (!isTypeEqual(t1.output, t2.output, r1, r2)) {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
// Check platform requirements match
|
|
611
|
+
if (t1.platforms === null && t2.platforms === null) {
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
if (t1.platforms === null || t2.platforms === null) {
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
if (t1.platforms.length !== t2.platforms.length) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
for (let i = 0; i < t1.platforms.length; i++) {
|
|
621
|
+
if (t1.platforms[i] !== t2.platforms[i]) {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
throw new Error(`Unknown type encountered during type equality check: ${t1.type}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Checks if a JavaScript value conforms to an East type.
|
|
633
|
+
*
|
|
634
|
+
* @param value - The JavaScript value to check
|
|
635
|
+
* @param type - The {@link EastType} to validate against
|
|
636
|
+
* @param node_type - Internal parameter for tracking the current recursive type node
|
|
637
|
+
* @param nodes_visited - Internal parameter for tracking visited nodes to detect cycles
|
|
638
|
+
* @returns `true` if the value matches the type, `false` otherwise
|
|
639
|
+
*
|
|
640
|
+
* @remarks
|
|
641
|
+
* Performs runtime type checking, recursively validating compound types.
|
|
642
|
+
* Accepts both native JavaScript `Set`/`Map` and {@link SortedSet}/{@link SortedMap}.
|
|
643
|
+
* For {@link RecursiveType}, uses cycle detection to handle recursive values.
|
|
644
|
+
*/
|
|
645
|
+
export function isValueOf(value, type, node_type, nodes_visited) {
|
|
646
|
+
if (type.type === "Never") {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
else if (type.type === "Null") {
|
|
650
|
+
return value === null;
|
|
651
|
+
}
|
|
652
|
+
else if (type.type === "Boolean") {
|
|
653
|
+
return typeof value === "boolean";
|
|
654
|
+
}
|
|
655
|
+
else if (type.type === "Integer") {
|
|
656
|
+
return typeof value === "bigint";
|
|
657
|
+
}
|
|
658
|
+
else if (type.type === "Float") {
|
|
659
|
+
return typeof value === "number";
|
|
660
|
+
}
|
|
661
|
+
else if (type.type === "String") {
|
|
662
|
+
return typeof value === "string";
|
|
663
|
+
}
|
|
664
|
+
else if (type.type === "DateTime") {
|
|
665
|
+
return value instanceof Date;
|
|
666
|
+
}
|
|
667
|
+
else if (type.type === "Blob") {
|
|
668
|
+
return value instanceof Uint8Array;
|
|
669
|
+
}
|
|
670
|
+
else if (type.type === "Ref") {
|
|
671
|
+
if (!isRef(value)) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
return isValueOf(value.value, type.value, node_type, nodes_visited);
|
|
675
|
+
}
|
|
676
|
+
else if (type.type === "Array") {
|
|
677
|
+
if (!Array.isArray(value)) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
for (const x of value) {
|
|
681
|
+
if (!isValueOf(x, type.value, node_type, nodes_visited)) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
else if (type.type === "Set") {
|
|
688
|
+
if (!(value instanceof Set || value instanceof SortedSet)) {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
for (const x of value) {
|
|
692
|
+
if (!isValueOf(x, type.key, node_type, nodes_visited)) {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
else if (type.type === "Dict") {
|
|
699
|
+
if (!(value instanceof Map || value instanceof SortedMap)) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
for (const [k, v] of value) {
|
|
703
|
+
if (!isValueOf(k, type.key, node_type, nodes_visited) || !isValueOf(v, type.value, node_type, nodes_visited)) {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
else if (type.type === "Struct") {
|
|
710
|
+
if (typeof value !== "object" || value === null || Object.getPrototypeOf(value) !== Object.getPrototypeOf({}))
|
|
711
|
+
return false;
|
|
712
|
+
const vs = Object.entries(value);
|
|
713
|
+
const ts = Object.entries(type.fields);
|
|
714
|
+
if (vs.length !== ts.length)
|
|
715
|
+
return false;
|
|
716
|
+
let i = 0;
|
|
717
|
+
for (const [k1, ft] of ts) {
|
|
718
|
+
const [k2, fv] = vs[i];
|
|
719
|
+
if (k1 !== k2)
|
|
720
|
+
return false;
|
|
721
|
+
if (!isValueOf(fv, ft, node_type, nodes_visited))
|
|
722
|
+
return false;
|
|
723
|
+
i += 1;
|
|
724
|
+
}
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
else if (type.type === "Variant") {
|
|
728
|
+
if (typeof value !== "object" || value === null || value[variant_symbol] !== null)
|
|
729
|
+
return false;
|
|
730
|
+
const t = type.cases[value.type];
|
|
731
|
+
if (t === undefined) {
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
;
|
|
735
|
+
return isValueOf(value.value, t, node_type, nodes_visited);
|
|
736
|
+
}
|
|
737
|
+
else if (type.type === "Recursive") {
|
|
738
|
+
if (node_type === type.node) {
|
|
739
|
+
if (nodes_visited.has(value)) {
|
|
740
|
+
return true; // already seen this object
|
|
741
|
+
}
|
|
742
|
+
nodes_visited.add(value);
|
|
743
|
+
return isValueOf(value, type.node, type.node, nodes_visited);
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
// new recursive type, reset stack
|
|
747
|
+
return isValueOf(value, type.node, type.node, new Set([value]));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
else if (type.type === "Function") {
|
|
751
|
+
throw new Error('Javascript functions cannot be converted to East functions');
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
throw new Error(`Unknown type encountered during value type check: ${type.type}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Converts an East type to its string representation.
|
|
759
|
+
*
|
|
760
|
+
* @param type - The {@link EastType} to print
|
|
761
|
+
* @param stack - Internal parameter for tracking recursive types
|
|
762
|
+
* @returns A human-readable string representation of the type
|
|
763
|
+
*
|
|
764
|
+
* @remarks
|
|
765
|
+
* Uses East's value syntax with leading dots (e.g., `.Integer`, `.Array .String`).
|
|
766
|
+
*/
|
|
767
|
+
export function printType(type, stack = []) {
|
|
768
|
+
// Note this is essentially a bootstrap function.
|
|
769
|
+
// It should print the same output as printing an `EastType` as an `EastTypeType`, but
|
|
770
|
+
// printing values hasn't been defined by this stage and printType is crucial for
|
|
771
|
+
// internal debugging and `Expr` error messages.
|
|
772
|
+
// We do skip mutable aliasing of struct field or variant case lists for clarity.
|
|
773
|
+
// Types are intended to be immutable in any case.
|
|
774
|
+
if (type.type === "Never") {
|
|
775
|
+
return ".Never";
|
|
776
|
+
}
|
|
777
|
+
else if (type.type === "Null") {
|
|
778
|
+
return ".Null";
|
|
779
|
+
}
|
|
780
|
+
else if (type.type === "Boolean") {
|
|
781
|
+
return ".Boolean";
|
|
782
|
+
}
|
|
783
|
+
else if (type.type === "Integer") {
|
|
784
|
+
return ".Integer";
|
|
785
|
+
}
|
|
786
|
+
else if (type.type === "Float") {
|
|
787
|
+
return ".Float";
|
|
788
|
+
}
|
|
789
|
+
else if (type.type === "String") {
|
|
790
|
+
return ".String";
|
|
791
|
+
}
|
|
792
|
+
else if (type.type === "DateTime") {
|
|
793
|
+
return ".DateTime";
|
|
794
|
+
}
|
|
795
|
+
else if (type.type === "Blob") {
|
|
796
|
+
return ".Blob";
|
|
797
|
+
}
|
|
798
|
+
else if (type.type === "Ref") {
|
|
799
|
+
stack.push(type);
|
|
800
|
+
const ret = `.Ref ${printType(type.value, stack)}`;
|
|
801
|
+
stack.pop();
|
|
802
|
+
return ret;
|
|
803
|
+
}
|
|
804
|
+
else if (type.type === "Array") {
|
|
805
|
+
stack.push(type);
|
|
806
|
+
const ret = `.Array ${printType(type.value, stack)}`;
|
|
807
|
+
stack.pop();
|
|
808
|
+
return ret;
|
|
809
|
+
}
|
|
810
|
+
else if (type.type === "Set") {
|
|
811
|
+
stack.push(type);
|
|
812
|
+
const ret = `.Set ${printType(type.key, stack)}`;
|
|
813
|
+
stack.pop();
|
|
814
|
+
return ret;
|
|
815
|
+
}
|
|
816
|
+
else if (type.type === "Dict") {
|
|
817
|
+
stack.push(type);
|
|
818
|
+
const ret = `.Dict (key=${printType(type.key, stack)}, value=${printType(type.value, stack)})`;
|
|
819
|
+
stack.pop();
|
|
820
|
+
return ret;
|
|
821
|
+
}
|
|
822
|
+
else if (type.type === "Struct") {
|
|
823
|
+
stack.push(type);
|
|
824
|
+
const ret = `.Struct [${Object.entries(type.fields).map(([k, t]) => `(name=${JSON.stringify(k)}, type=${printType(t, stack)})`).join(", ")}]`;
|
|
825
|
+
stack.pop();
|
|
826
|
+
return ret;
|
|
827
|
+
}
|
|
828
|
+
else if (type.type === "Variant") {
|
|
829
|
+
stack.push(type);
|
|
830
|
+
const ret = `.Variant [${Object.entries(type.cases).map(([k, t]) => `(name=${JSON.stringify(k)}, type=${printType(t, stack)})`).join(", ")}]`;
|
|
831
|
+
stack.pop();
|
|
832
|
+
return ret;
|
|
833
|
+
}
|
|
834
|
+
else if (type.type === "Recursive") {
|
|
835
|
+
// TODO update for our new recursive type representation
|
|
836
|
+
const idx = stack.indexOf(type.node);
|
|
837
|
+
if (idx !== -1) {
|
|
838
|
+
// Recursive reference
|
|
839
|
+
return `.Recursive ${stack.length - idx}`;
|
|
840
|
+
}
|
|
841
|
+
return printType(type.node, stack);
|
|
842
|
+
}
|
|
843
|
+
else if (type.type === "Function") {
|
|
844
|
+
// Note: functions can't be inside recursive types
|
|
845
|
+
stack.push(type);
|
|
846
|
+
const ret = `.Function (inputs=[${type.inputs.map(t => printType(t, stack)).join(", ")}], output=${printType(type.output, stack)}, platforms=${type.platforms === null ? "null" : `[${type.platforms.map(p => JSON.stringify(p)).join(", ")}]`})`;
|
|
847
|
+
stack.pop();
|
|
848
|
+
return ret;
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
throw new Error(`Unknown type encountered during type printing: ${type.type}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Checks if one East type is a subtype of another.
|
|
856
|
+
*
|
|
857
|
+
* @param t1 - The potential subtype
|
|
858
|
+
* @param t2 - The potential supertype
|
|
859
|
+
* @returns `true` if t1 is a subtype of t2, `false` otherwise
|
|
860
|
+
*
|
|
861
|
+
* @remarks
|
|
862
|
+
* Implements East's subtyping rules:
|
|
863
|
+
* - {@link NeverType} is a subtype of all types
|
|
864
|
+
* - Mutable collections ({@link ArrayType}, {@link SetType}, {@link DictType}) are invariant
|
|
865
|
+
* - {@link VariantType} supports width subtyping (more cases → fewer cases)
|
|
866
|
+
* - {@link FunctionType} uses contravariant inputs and covariant outputs
|
|
867
|
+
*/
|
|
868
|
+
export function isSubtype(t1, t2) {
|
|
869
|
+
// Equi-recursive type subtyping: unfold recursive types when only one side is recursive
|
|
870
|
+
if (t1.type === "Recursive") {
|
|
871
|
+
if (t2.type === "Recursive") {
|
|
872
|
+
// Recursive types are invariant - we want any compatible heap-allocated objects to have exactly the same layout
|
|
873
|
+
return isTypeEqual(t1.node, t2.node);
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
// The recursive type wrapper is "transparent", but invariant
|
|
877
|
+
return isTypeEqual(t1.node, t2);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
else if (t2.type === "Recursive") {
|
|
881
|
+
// The recursive type wrapper is "transparent"
|
|
882
|
+
// If the head is "on the stack" we can do head covariance by unfolding once
|
|
883
|
+
return isSubtype(t1, t2.node);
|
|
884
|
+
}
|
|
885
|
+
else if (t1.type === "Never") {
|
|
886
|
+
// t2 must be a supertype of Never
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
else if (t1.type === "Null") {
|
|
890
|
+
return t2.type === "Null";
|
|
891
|
+
}
|
|
892
|
+
else if (t1.type === "Boolean") {
|
|
893
|
+
return t2.type === "Boolean";
|
|
894
|
+
}
|
|
895
|
+
else if (t1.type === "Integer") {
|
|
896
|
+
return t2.type === "Integer";
|
|
897
|
+
}
|
|
898
|
+
else if (t1.type === "Float") {
|
|
899
|
+
return t2.type === "Float";
|
|
900
|
+
}
|
|
901
|
+
else if (t1.type === "String") {
|
|
902
|
+
return t2.type === "String";
|
|
903
|
+
}
|
|
904
|
+
else if (t1.type === "DateTime") {
|
|
905
|
+
return t2.type === "DateTime";
|
|
906
|
+
}
|
|
907
|
+
else if (t1.type === "Blob") {
|
|
908
|
+
return t2.type === "Blob";
|
|
909
|
+
}
|
|
910
|
+
else if (t1.type === "Ref") {
|
|
911
|
+
if (t2.type === "Ref") {
|
|
912
|
+
return isTypeEqual(t1.value, t2.value);
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
else if (t1.type === "Array") {
|
|
919
|
+
if (t2.type === "Array") {
|
|
920
|
+
return isTypeEqual(t1.value, t2.value);
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
else if (t1.type === "Set") {
|
|
927
|
+
if (t2.type === "Set") {
|
|
928
|
+
return isTypeEqual(t1.key, t2.key);
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
else if (t1.type === "Dict") {
|
|
935
|
+
if (t2.type === "Dict") {
|
|
936
|
+
return isTypeEqual(t1.key, t2.key) && isTypeEqual(t1.value, t2.value);
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
else if (t1.type === "Struct") {
|
|
943
|
+
if (t2.type === "Struct") {
|
|
944
|
+
const e1 = Object.entries(t1.fields);
|
|
945
|
+
const e2 = Object.entries(t2.fields);
|
|
946
|
+
if (e1.length !== e2.length)
|
|
947
|
+
return false;
|
|
948
|
+
let i = 0;
|
|
949
|
+
for (const [k1, f1] of e1) {
|
|
950
|
+
const [k2, f2] = e2[i];
|
|
951
|
+
if (k1 !== k2)
|
|
952
|
+
return false;
|
|
953
|
+
if (!isSubtype(f1, f2))
|
|
954
|
+
return false;
|
|
955
|
+
i += 1;
|
|
956
|
+
}
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
else if (t1.type === "Variant") {
|
|
964
|
+
if (t2.type === "Variant") {
|
|
965
|
+
// Unlike structs, we don't care about the order (they are sorted by name already) and we only need a subset of cases in t2
|
|
966
|
+
for (const [k, v1] of Object.entries(t1.cases)) {
|
|
967
|
+
const v2 = t2.cases[k] ?? NeverType;
|
|
968
|
+
if (!isSubtype(v1, v2))
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
else if (t1.type === "Function") {
|
|
978
|
+
if (t2.type === "Function") {
|
|
979
|
+
return t1.inputs.length === t2.inputs.length && t1.inputs.every((t, i) => isSubtype(t2.inputs[i], t)) && isSubtype(t1.output, t2.output); // contravariant inputs and covariant output
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
throw new Error(`Unknown type encountered during subtype check: ${t1.type}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Computes the union of two East types at runtime.
|
|
991
|
+
*
|
|
992
|
+
* @typeParam T1 - First {@link EastType}
|
|
993
|
+
* @typeParam T2 - Second {@link EastType}
|
|
994
|
+
* @param t1 - First type
|
|
995
|
+
* @param t2 - Second type
|
|
996
|
+
* @returns The union type
|
|
997
|
+
* @throws {TypeMismatchError} When the types cannot be unified
|
|
998
|
+
*
|
|
999
|
+
* @remarks
|
|
1000
|
+
* This is the runtime version of the {@link TypeUnion} type.
|
|
1001
|
+
* Error messages include the full type path for debugging.
|
|
1002
|
+
*/
|
|
1003
|
+
export function TypeUnion(t1, t2) {
|
|
1004
|
+
// TODO this is broken for recursive types (need to do cycle tracking in tandem with TypeEqual/TypeIntersect)
|
|
1005
|
+
try {
|
|
1006
|
+
if (t1.type === "Never") {
|
|
1007
|
+
return t2;
|
|
1008
|
+
}
|
|
1009
|
+
else if (t2.type === "Never") {
|
|
1010
|
+
return t1;
|
|
1011
|
+
}
|
|
1012
|
+
else if (t1.type === "Recursive") {
|
|
1013
|
+
if (t2.type === "Recursive") {
|
|
1014
|
+
// Both recursive - require exact match (heap invariance)
|
|
1015
|
+
return TypeEqual(t1, t2);
|
|
1016
|
+
// return RecursiveType(() => TypeEqual(t1.node, t2.node, t1.node, t2.node)) as TypeUnion<T1, T2>;
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
// Rec(A) ∪ NonRec: If NonRec <: A, union is Rec(A) (NonRec can be widened to heap type)
|
|
1020
|
+
if (isSubtype(t2, t1.node)) {
|
|
1021
|
+
return t1;
|
|
1022
|
+
}
|
|
1023
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
else if (t2.type === "Recursive") {
|
|
1027
|
+
// NonRec ∪ Rec(B): If NonRec <: B, union is Rec(B) (symmetric case)
|
|
1028
|
+
if (isSubtype(t1, t2.node)) {
|
|
1029
|
+
return t2;
|
|
1030
|
+
}
|
|
1031
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1032
|
+
}
|
|
1033
|
+
else if (t1.type === "Ref") {
|
|
1034
|
+
if (t2.type === "Ref") {
|
|
1035
|
+
return RefType(TypeEqual(t1.value, t2.value));
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
else if (t1.type === "Array") {
|
|
1042
|
+
if (t2.type === "Array") {
|
|
1043
|
+
return ArrayType(TypeEqual(t1.value, t2.value));
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
else if (t1.type === "Set") {
|
|
1050
|
+
if (t2.type === "Set") {
|
|
1051
|
+
return SetType(TypeEqual(t1.key, t2.key));
|
|
1052
|
+
}
|
|
1053
|
+
else {
|
|
1054
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
else if (t1.type === "Dict") {
|
|
1058
|
+
if (t2.type === "Dict") {
|
|
1059
|
+
return DictType(TypeEqual(t1.key, t2.key), TypeEqual(t1.value, t2.value));
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
else if (t1.type === "Struct") {
|
|
1066
|
+
if (t2.type === "Struct") {
|
|
1067
|
+
const e1 = Object.entries(t1.fields);
|
|
1068
|
+
const e2 = Object.entries(t2.fields);
|
|
1069
|
+
if (e1.length !== e2.length)
|
|
1070
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: structs contain different number of fields`);
|
|
1071
|
+
let i = 0;
|
|
1072
|
+
const e = {};
|
|
1073
|
+
for (const [k1, f1] of e1) {
|
|
1074
|
+
const [k2, f2] = e2[i];
|
|
1075
|
+
if (k1 !== k2)
|
|
1076
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: struct field ${i} has mismatched names ${printIdentifier(k1)} and ${printIdentifier(k2)}`);
|
|
1077
|
+
e[k1] = TypeUnion(f1, f2);
|
|
1078
|
+
i += 1;
|
|
1079
|
+
}
|
|
1080
|
+
return StructType(e);
|
|
1081
|
+
}
|
|
1082
|
+
else {
|
|
1083
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
else if (t1.type === "Variant") {
|
|
1087
|
+
if (t2.type === "Variant") {
|
|
1088
|
+
const cases = {};
|
|
1089
|
+
for (const [k1, f1] of Object.entries(t1.cases)) {
|
|
1090
|
+
const f2 = t2.cases[k1];
|
|
1091
|
+
if (f2 === undefined) {
|
|
1092
|
+
cases[k1] = f1;
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
cases[k1] = TypeUnion(f1, f2);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
for (const [k2, f2] of Object.entries(t2.cases)) {
|
|
1099
|
+
const f1 = t1.cases[k2];
|
|
1100
|
+
if (f1 === undefined) {
|
|
1101
|
+
cases[k2] = f2;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return VariantType(cases);
|
|
1105
|
+
}
|
|
1106
|
+
else {
|
|
1107
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
else if (t1.type === "Function") {
|
|
1111
|
+
if (t2.type === "Function") {
|
|
1112
|
+
if (t1.inputs.length !== t2.inputs.length) {
|
|
1113
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: functions take different number of arguments`);
|
|
1114
|
+
}
|
|
1115
|
+
let platforms;
|
|
1116
|
+
if (t1.platforms === null) {
|
|
1117
|
+
platforms = null;
|
|
1118
|
+
}
|
|
1119
|
+
else if (t2.platforms === null) {
|
|
1120
|
+
platforms = null;
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
platforms = [...new Set([...t1.platforms, ...t2.platforms])].sort();
|
|
1124
|
+
}
|
|
1125
|
+
return FunctionType(t1.inputs.map((t, i) => TypeIntersect(t, t2.inputs[i])), TypeUnion(t1.output, t2.output), platforms);
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
if (t1.type === t2.type) {
|
|
1133
|
+
return t1;
|
|
1134
|
+
}
|
|
1135
|
+
else {
|
|
1136
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
catch (cause) {
|
|
1141
|
+
if (cause instanceof TypeMismatchError) {
|
|
1142
|
+
throw cause; // Don't wrap our own errors - they already have the full path
|
|
1143
|
+
}
|
|
1144
|
+
throw new TypeMismatchError(`Cannot union ${printType(t1)} with ${printType(t2)}`, { cause });
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Computes the intersection of two East types at runtime.
|
|
1149
|
+
*
|
|
1150
|
+
* @typeParam T1 - First {@link EastType}
|
|
1151
|
+
* @typeParam T2 - Second {@link EastType}
|
|
1152
|
+
* @param t1 - First type
|
|
1153
|
+
* @param t2 - Second type
|
|
1154
|
+
* @returns The intersection type
|
|
1155
|
+
* @throws {TypeMismatchError} When the types cannot be intersected
|
|
1156
|
+
*
|
|
1157
|
+
* @remarks
|
|
1158
|
+
* This is the runtime version of the {@link TypeIntersect} type.
|
|
1159
|
+
* For {@link VariantType}, returns {@link NeverType} if no common cases exist.
|
|
1160
|
+
*/
|
|
1161
|
+
export function TypeIntersect(t1, t2) {
|
|
1162
|
+
// TODO this is broken for recursive types (need to do cycle tracking in tandem with TypeEqual/TypeUnion)
|
|
1163
|
+
try {
|
|
1164
|
+
if (t1.type === "Never") {
|
|
1165
|
+
return NeverType;
|
|
1166
|
+
}
|
|
1167
|
+
else if (t2.type === "Never") {
|
|
1168
|
+
return NeverType;
|
|
1169
|
+
}
|
|
1170
|
+
else if (t1.type === "Recursive") {
|
|
1171
|
+
if (t2.type === "Recursive") {
|
|
1172
|
+
// Both recursive - require exact match (heap invariance)
|
|
1173
|
+
return RecursiveType(() => TypeEqual(t1.node, t2.node, t1.node, t2.node));
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
// Rec(A) ∩ NonRec: If NonRec <: A, intersection is NonRec (the more specific type)
|
|
1177
|
+
if (isSubtype(t2, t1.node)) {
|
|
1178
|
+
return t2;
|
|
1179
|
+
}
|
|
1180
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
else if (t2.type === "Recursive") {
|
|
1184
|
+
// NonRec ∩ Rec(B): If NonRec <: B, intersection is NonRec (symmetric case)
|
|
1185
|
+
if (isSubtype(t1, t2.node)) {
|
|
1186
|
+
return t1;
|
|
1187
|
+
}
|
|
1188
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1189
|
+
}
|
|
1190
|
+
else if (t1.type === "Ref") {
|
|
1191
|
+
if (t2.type === "Ref") {
|
|
1192
|
+
return RefType(TypeEqual(t1.value, t2.value));
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
else if (t1.type === "Array") {
|
|
1199
|
+
if (t2.type === "Array") {
|
|
1200
|
+
return ArrayType(TypeEqual(t1.value, t2.value));
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
else if (t1.type === "Set") {
|
|
1207
|
+
if (t2.type === "Set") {
|
|
1208
|
+
return SetType(TypeEqual(t1.key, t2.key));
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
else if (t1.type === "Dict") {
|
|
1215
|
+
if (t2.type === "Dict") {
|
|
1216
|
+
return DictType(TypeEqual(t1.key, t2.key), TypeEqual(t1.value, t2.value));
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
else if (t1.type === "Struct") {
|
|
1223
|
+
if (t2.type === "Struct") {
|
|
1224
|
+
const e1 = Object.entries(t1.fields);
|
|
1225
|
+
const e2 = Object.entries(t2.fields);
|
|
1226
|
+
if (e1.length !== e2.length)
|
|
1227
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: structs contain different number of fields`);
|
|
1228
|
+
let i = 0;
|
|
1229
|
+
const e = {};
|
|
1230
|
+
for (const [k1, f1] of e1) {
|
|
1231
|
+
const [k2, f2] = e2[i];
|
|
1232
|
+
if (k1 !== k2)
|
|
1233
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: struct field ${i} has mismatched names ${printIdentifier(k1)} and ${printIdentifier(k2)}`);
|
|
1234
|
+
e[k1] = TypeIntersect(f1, f2);
|
|
1235
|
+
i += 1;
|
|
1236
|
+
}
|
|
1237
|
+
return StructType(e);
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
else if (t1.type === "Variant") {
|
|
1244
|
+
if (t2.type === "Variant") {
|
|
1245
|
+
const cases = {};
|
|
1246
|
+
let empty = true;
|
|
1247
|
+
for (const [k1, f1] of Object.entries(t1.cases)) {
|
|
1248
|
+
const f2 = t2.cases[k1];
|
|
1249
|
+
if (f2 !== undefined) {
|
|
1250
|
+
cases[k1] = TypeIntersect(f1, f2);
|
|
1251
|
+
empty = false;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (empty) {
|
|
1255
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: variants have no overlapping cases`);
|
|
1256
|
+
}
|
|
1257
|
+
return VariantType(cases);
|
|
1258
|
+
}
|
|
1259
|
+
else {
|
|
1260
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
else if (t1.type === "Function") {
|
|
1264
|
+
if (t2.type === "Function") {
|
|
1265
|
+
if (t1.inputs.length !== t2.inputs.length) {
|
|
1266
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: functions take different number of arguments`);
|
|
1267
|
+
}
|
|
1268
|
+
let platforms;
|
|
1269
|
+
if (t1.platforms === null) {
|
|
1270
|
+
platforms = t2.platforms;
|
|
1271
|
+
}
|
|
1272
|
+
else if (t2.platforms === null) {
|
|
1273
|
+
platforms = t1.platforms;
|
|
1274
|
+
}
|
|
1275
|
+
else {
|
|
1276
|
+
platforms = t1.platforms.filter(p => t2.platforms.includes(p));
|
|
1277
|
+
}
|
|
1278
|
+
return FunctionType(t1.inputs.map((t, i) => TypeUnion(t, t2.inputs[i])), TypeIntersect(t1.output, t2.output), platforms);
|
|
1279
|
+
}
|
|
1280
|
+
else {
|
|
1281
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
else {
|
|
1285
|
+
if (t1.type === t2.type) {
|
|
1286
|
+
return t1;
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
catch (cause) {
|
|
1294
|
+
if (cause instanceof TypeMismatchError) {
|
|
1295
|
+
throw cause; // Don't wrap our own errors - they already have the full path
|
|
1296
|
+
}
|
|
1297
|
+
throw new TypeMismatchError(`Cannot intersect ${printType(t1)} with ${printType(t2)}`, { cause });
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Asserts that two East types are equal, returning the first type.
|
|
1302
|
+
*
|
|
1303
|
+
* @typeParam T1 - First {@link EastType}
|
|
1304
|
+
* @typeParam T2 - Second {@link EastType}
|
|
1305
|
+
* @param t1 - First type
|
|
1306
|
+
* @param t2 - Second type
|
|
1307
|
+
* @param r1 - Internal parameter for tracking the first recursive type being compared
|
|
1308
|
+
* @param r2 - Internal parameter for tracking the second recursive type being compared
|
|
1309
|
+
* @returns The first type if types are equal
|
|
1310
|
+
* @throws {TypeMismatchError} When the types are not equal
|
|
1311
|
+
*
|
|
1312
|
+
* @remarks
|
|
1313
|
+
* This function recursively validates type equality and throws detailed errors
|
|
1314
|
+
* on mismatch. Used to enforce type constraints in compound type constructors.
|
|
1315
|
+
* Unlike {@link isTypeEqual}, this throws rather than returning a boolean.
|
|
1316
|
+
*/
|
|
1317
|
+
export function TypeEqual(t1, t2, r1 = t1, r2 = t2) {
|
|
1318
|
+
// TODO this is broken for recursive types (need to do cycle tracking in tandem with TypeUnion/TypeIntersect)
|
|
1319
|
+
try {
|
|
1320
|
+
if (t1.type === "Ref") {
|
|
1321
|
+
if (t2.type === "Ref") {
|
|
1322
|
+
return RefType(TypeEqual(t1.value, t2.value, r1, r2));
|
|
1323
|
+
}
|
|
1324
|
+
else {
|
|
1325
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: incompatible types`);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
else if (t1.type === "Array") {
|
|
1329
|
+
if (t2.type === "Array") {
|
|
1330
|
+
return ArrayType(TypeEqual(t1.value, t2.value, r1, r2));
|
|
1331
|
+
}
|
|
1332
|
+
else {
|
|
1333
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: incompatible types`);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
else if (t1.type === "Set") {
|
|
1337
|
+
if (t2.type === "Set") {
|
|
1338
|
+
return SetType(TypeEqual(t1.key, t2.key, r1, r2));
|
|
1339
|
+
}
|
|
1340
|
+
else {
|
|
1341
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: incompatible types`);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
else if (t1.type === "Dict") {
|
|
1345
|
+
if (t2.type === "Dict") {
|
|
1346
|
+
return DictType(TypeEqual(t1.key, t2.key, r1, r2), TypeEqual(t1.value, t2.value, r1, r2));
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: incompatible types`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
else if (t1.type === "Struct") {
|
|
1353
|
+
if (t2.type === "Struct") {
|
|
1354
|
+
const e1 = Object.entries(t1.fields);
|
|
1355
|
+
const e2 = Object.entries(t2.fields);
|
|
1356
|
+
if (e1.length !== e2.length)
|
|
1357
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: structs contain different number of fields`);
|
|
1358
|
+
let i = 0;
|
|
1359
|
+
const e = {};
|
|
1360
|
+
for (const [k1, f1] of e1) {
|
|
1361
|
+
const [k2, f2] = e2[i];
|
|
1362
|
+
if (k1 !== k2)
|
|
1363
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: struct field ${i} has mismatched names ${printIdentifier(k1)} and ${printIdentifier(k2)}`);
|
|
1364
|
+
e[k1] = TypeEqual(f1, f2, r1, r2);
|
|
1365
|
+
i += 1;
|
|
1366
|
+
}
|
|
1367
|
+
return StructType(e);
|
|
1368
|
+
}
|
|
1369
|
+
else {
|
|
1370
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: incompatible types`);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
else if (t1.type === "Variant") {
|
|
1374
|
+
// We can reuse the logic for structs here, taking advantage of the ordering guarantees
|
|
1375
|
+
if (t2.type === "Variant") {
|
|
1376
|
+
const e1 = Object.entries(t1.cases);
|
|
1377
|
+
const e2 = Object.entries(t2.cases);
|
|
1378
|
+
if (e1.length !== e2.length)
|
|
1379
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: variants contain different number of cases`);
|
|
1380
|
+
let i = 0;
|
|
1381
|
+
const e = {};
|
|
1382
|
+
for (const [k1, f1] of e1) {
|
|
1383
|
+
const [k2, f2] = e2[i];
|
|
1384
|
+
if (k1 !== k2) {
|
|
1385
|
+
if (k1 < k2) {
|
|
1386
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: variant case ${k1} is not present in both variants`);
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: variant case ${k2} is not present in both variants`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
e[k1] = TypeEqual(f1, f2, r1, r2);
|
|
1393
|
+
i += 1;
|
|
1394
|
+
}
|
|
1395
|
+
return VariantType(e);
|
|
1396
|
+
}
|
|
1397
|
+
else {
|
|
1398
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: incompatible types`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
else if (t1.type === "Recursive") {
|
|
1402
|
+
if (t2.type === "Recursive") {
|
|
1403
|
+
if (t1.node === r1) {
|
|
1404
|
+
if (t2.node === r2) {
|
|
1405
|
+
return t1; // both are references to the same recursive type
|
|
1406
|
+
}
|
|
1407
|
+
else {
|
|
1408
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: recursive types do not match`);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
else if (t2.node === r2) {
|
|
1412
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: recursive types do not match`);
|
|
1413
|
+
}
|
|
1414
|
+
// this is the root of a new recursive type - assert the node types are equal
|
|
1415
|
+
return RecursiveType(() => TypeEqual(t1.node, t2.node, t1.node, t2.node));
|
|
1416
|
+
}
|
|
1417
|
+
else {
|
|
1418
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: incompatible types`);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
else if (t1.type === "Function") {
|
|
1422
|
+
if (t2.type === "Function") {
|
|
1423
|
+
if (t1.inputs.length !== t2.inputs.length) {
|
|
1424
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: functions take different number of arguments`);
|
|
1425
|
+
}
|
|
1426
|
+
if (t1.platforms === null) {
|
|
1427
|
+
if (t2.platforms !== null) {
|
|
1428
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: functions have different platform effects`);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
else if (t2.platforms === null) {
|
|
1432
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: functions have different platform effects`);
|
|
1433
|
+
}
|
|
1434
|
+
else {
|
|
1435
|
+
if (t1.platforms.length !== t2.platforms.length || !t1.platforms.every((p, i) => p === t2.platforms[i])) {
|
|
1436
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: functions have different platform effects`);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return FunctionType(t1.inputs.map((t, i) => TypeEqual(t, t2.inputs[i], r1, r2)), TypeEqual(t1.output, t2.output, r1, r2), t1.platforms);
|
|
1440
|
+
}
|
|
1441
|
+
else {
|
|
1442
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: incompatible types`);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
if (t1.type === t2.type) {
|
|
1447
|
+
return t1;
|
|
1448
|
+
}
|
|
1449
|
+
else {
|
|
1450
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}: incompatible types`);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
catch (cause) {
|
|
1455
|
+
if (cause instanceof TypeMismatchError) {
|
|
1456
|
+
throw cause; // Don't wrap our own errors - they already have the full path
|
|
1457
|
+
}
|
|
1458
|
+
throw new TypeMismatchError(`${printType(t1)} is not equal to ${printType(t2)}`, { cause });
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Create a type wide enough to hold values from two East types at runtime.
|
|
1463
|
+
*
|
|
1464
|
+
* @param t1 - First type
|
|
1465
|
+
* @param t2 - Second type
|
|
1466
|
+
* @returns The union type
|
|
1467
|
+
* @throws {TypeMismatchError} When the types cannot be unified
|
|
1468
|
+
*
|
|
1469
|
+
* @remarks
|
|
1470
|
+
* This is a more forgiving version of the {@link TypeUnion} type, designed for inferring types from values.
|
|
1471
|
+
* Functions and recursive types are not supported.
|
|
1472
|
+
* Error messages include the full type path for debugging.
|
|
1473
|
+
*/
|
|
1474
|
+
export function TypeWiden(t1, t2) {
|
|
1475
|
+
try {
|
|
1476
|
+
if (t1.type === "Never") {
|
|
1477
|
+
return t2;
|
|
1478
|
+
}
|
|
1479
|
+
else if (t2.type === "Never") {
|
|
1480
|
+
return t1;
|
|
1481
|
+
}
|
|
1482
|
+
else if (t1.type === "Recursive") {
|
|
1483
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: recursive types not supported`);
|
|
1484
|
+
}
|
|
1485
|
+
else if (t2.type === "Recursive") {
|
|
1486
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: recursive types not supported`);
|
|
1487
|
+
}
|
|
1488
|
+
else if (t1.type === "Ref") {
|
|
1489
|
+
if (t2.type === "Ref") {
|
|
1490
|
+
return RefType(TypeWiden(t1.value, t2.value));
|
|
1491
|
+
}
|
|
1492
|
+
else {
|
|
1493
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
else if (t1.type === "Array") {
|
|
1497
|
+
if (t2.type === "Array") {
|
|
1498
|
+
return ArrayType(TypeWiden(t1.value, t2.value));
|
|
1499
|
+
}
|
|
1500
|
+
else {
|
|
1501
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
else if (t1.type === "Set") {
|
|
1505
|
+
if (t2.type === "Set") {
|
|
1506
|
+
return SetType(TypeWiden(t1.key, t2.key));
|
|
1507
|
+
}
|
|
1508
|
+
else {
|
|
1509
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
else if (t1.type === "Dict") {
|
|
1513
|
+
if (t2.type === "Dict") {
|
|
1514
|
+
return DictType(TypeWiden(t1.key, t2.key), TypeWiden(t1.value, t2.value));
|
|
1515
|
+
}
|
|
1516
|
+
else {
|
|
1517
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
else if (t1.type === "Struct") {
|
|
1521
|
+
if (t2.type === "Struct") {
|
|
1522
|
+
const e1 = Object.entries(t1.fields);
|
|
1523
|
+
const e2 = Object.entries(t2.fields);
|
|
1524
|
+
if (e1.length !== e2.length)
|
|
1525
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: structs contain different number of fields`);
|
|
1526
|
+
let i = 0;
|
|
1527
|
+
const e = {};
|
|
1528
|
+
for (const [k1, f1] of e1) {
|
|
1529
|
+
const [k2, f2] = e2[i];
|
|
1530
|
+
if (k1 !== k2)
|
|
1531
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: struct field ${i} has mismatched names ${printIdentifier(k1)} and ${printIdentifier(k2)}`);
|
|
1532
|
+
e[k1] = TypeWiden(f1, f2);
|
|
1533
|
+
i += 1;
|
|
1534
|
+
}
|
|
1535
|
+
return StructType(e);
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
else if (t1.type === "Variant") {
|
|
1542
|
+
if (t2.type === "Variant") {
|
|
1543
|
+
const cases = {};
|
|
1544
|
+
for (const [k1, f1] of Object.entries(t1.cases)) {
|
|
1545
|
+
const f2 = t2.cases[k1];
|
|
1546
|
+
if (f2 === undefined) {
|
|
1547
|
+
cases[k1] = f1;
|
|
1548
|
+
}
|
|
1549
|
+
else {
|
|
1550
|
+
cases[k1] = TypeWiden(f1, f2);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
for (const [k2, f2] of Object.entries(t2.cases)) {
|
|
1554
|
+
const f1 = t1.cases[k2];
|
|
1555
|
+
if (f1 === undefined) {
|
|
1556
|
+
cases[k2] = f2;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return VariantType(cases);
|
|
1560
|
+
}
|
|
1561
|
+
else {
|
|
1562
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
else if (t1.type === "Function") {
|
|
1566
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: functions not supported`);
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
if (t1.type === t2.type) {
|
|
1570
|
+
return t1;
|
|
1571
|
+
}
|
|
1572
|
+
else {
|
|
1573
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}: incompatible types`);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
catch (cause) {
|
|
1578
|
+
if (cause instanceof TypeMismatchError) {
|
|
1579
|
+
throw cause; // Don't wrap our own errors - they already have the full path
|
|
1580
|
+
}
|
|
1581
|
+
throw new TypeMismatchError(`Cannot widen ${printType(t1)} with ${printType(t2)}`, { cause });
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Formats an identifier for display in type strings.
|
|
1586
|
+
*
|
|
1587
|
+
* @param x - The identifier to format
|
|
1588
|
+
* @returns The identifier, escaped with backticks if it contains special characters
|
|
1589
|
+
*
|
|
1590
|
+
* @remarks
|
|
1591
|
+
* Used by {@link printType} to format field and case names.
|
|
1592
|
+
* Identifiers matching `/^[a-zA-Z_][a-zA-Z0-9_]*$/` are returned as-is,
|
|
1593
|
+
* others are wrapped in backticks with escaping.
|
|
1594
|
+
*/
|
|
1595
|
+
export function printIdentifier(x) {
|
|
1596
|
+
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(x)) {
|
|
1597
|
+
return x;
|
|
1598
|
+
}
|
|
1599
|
+
else {
|
|
1600
|
+
return `\`${x.replace('\\', '\\\\').replace('`', '\\`')}\``;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
/** Singleton instance of {@link NoneType}. */
|
|
1604
|
+
export const NoneType = VariantType({ none: NullType });
|
|
1605
|
+
/**
|
|
1606
|
+
* Constructs a Some type wrapping a value type.
|
|
1607
|
+
*
|
|
1608
|
+
* @typeParam T - The type of the wrapped value
|
|
1609
|
+
* @param type - The {@link EastType} to wrap
|
|
1610
|
+
* @returns A SomeType variant
|
|
1611
|
+
*/
|
|
1612
|
+
export function SomeType(type) {
|
|
1613
|
+
return VariantType({ some: type });
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Constructs an Option type for optional values.
|
|
1617
|
+
*
|
|
1618
|
+
* @typeParam T - The type of the value when present
|
|
1619
|
+
* @param type - The {@link EastType} of the wrapped value
|
|
1620
|
+
* @returns An OptionType variant with none and some cases
|
|
1621
|
+
*
|
|
1622
|
+
* @example
|
|
1623
|
+
* ```ts
|
|
1624
|
+
* const maybeInt = OptionType(IntegerType);
|
|
1625
|
+
* // Type: VariantType<{ none: NullType, some: IntegerType }>
|
|
1626
|
+
* ```
|
|
1627
|
+
*/
|
|
1628
|
+
export function OptionType(type) {
|
|
1629
|
+
return VariantType({ none: NullType, some: type });
|
|
1630
|
+
}
|
|
1631
|
+
//# sourceMappingURL=types.js.map
|