@rcrsr/rill 0.16.0 → 0.18.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/README.md +37 -21
- package/dist/ast-nodes.d.ts +14 -4
- package/dist/ast-unions.d.ts +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -0
- package/dist/error-registry.js +228 -0
- package/dist/ext/crypto/index.d.ts +3 -3
- package/dist/ext/crypto/index.js +62 -59
- package/dist/ext/exec/index.d.ts +3 -3
- package/dist/ext/exec/index.js +15 -9
- package/dist/ext/fetch/index.d.ts +3 -3
- package/dist/ext/fetch/index.js +17 -12
- package/dist/ext/fetch/request.js +1 -1
- package/dist/ext/fs/index.d.ts +3 -3
- package/dist/ext/fs/index.js +256 -266
- package/dist/ext/fs/sandbox.d.ts +18 -0
- package/dist/ext/fs/sandbox.js +33 -0
- package/dist/ext/kv/index.d.ts +3 -3
- package/dist/ext/kv/index.js +198 -196
- package/dist/ext/kv/store.d.ts +1 -1
- package/dist/ext/kv/store.js +2 -1
- package/dist/ext-parse-bridge.d.ts +10 -0
- package/dist/ext-parse-bridge.js +10 -0
- package/dist/generated/introspection-data.d.ts +1 -1
- package/dist/generated/introspection-data.js +385 -296
- package/dist/generated/version-data.d.ts +1 -1
- package/dist/generated/version-data.js +2 -2
- package/dist/highlight-map.js +1 -0
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -5
- package/dist/lexer/operators.js +1 -0
- package/dist/parser/helpers.js +1 -0
- package/dist/parser/parser-expr.js +44 -5
- package/dist/parser/parser-literals.js +111 -4
- package/dist/parser/parser-shape.js +2 -2
- package/dist/parser/parser-types.js +12 -0
- package/dist/parser/parser-use.js +26 -3
- package/dist/parser/parser.d.ts +2 -0
- package/dist/parser/parser.js +2 -0
- package/dist/runtime/core/callable.d.ts +24 -13
- package/dist/runtime/core/callable.js +71 -38
- package/dist/runtime/core/context.d.ts +2 -13
- package/dist/runtime/core/context.js +80 -79
- package/dist/runtime/core/eval/base.d.ts +2 -2
- package/dist/runtime/core/eval/base.js +2 -0
- package/dist/runtime/core/eval/evaluator.d.ts +1 -1
- package/dist/runtime/core/eval/index.d.ts +3 -3
- package/dist/runtime/core/eval/index.js +11 -0
- package/dist/runtime/core/eval/mixins/closures.js +381 -41
- package/dist/runtime/core/eval/mixins/collections.js +81 -6
- package/dist/runtime/core/eval/mixins/control-flow.js +1 -1
- package/dist/runtime/core/eval/mixins/conversion.js +61 -115
- package/dist/runtime/core/eval/mixins/core.js +17 -4
- package/dist/runtime/core/eval/mixins/expressions.js +36 -27
- package/dist/runtime/core/eval/mixins/extraction.js +2 -3
- package/dist/runtime/core/eval/mixins/list-dispatch.js +1 -1
- package/dist/runtime/core/eval/mixins/literals.js +17 -6
- package/dist/runtime/core/eval/mixins/types.js +73 -54
- package/dist/runtime/core/eval/mixins/variables.js +12 -8
- package/dist/runtime/core/execute.d.ts +1 -1
- package/dist/runtime/core/field-descriptor.d.ts +3 -3
- package/dist/runtime/core/field-descriptor.js +2 -1
- package/dist/runtime/core/introspection.d.ts +2 -2
- package/dist/runtime/core/introspection.js +7 -6
- package/dist/runtime/core/resolvers.d.ts +1 -1
- package/dist/runtime/core/signals.d.ts +6 -1
- package/dist/runtime/core/signals.js +9 -0
- package/dist/runtime/core/types/constructors.d.ts +54 -0
- package/dist/runtime/core/types/constructors.js +201 -0
- package/dist/runtime/core/types/guards.d.ts +42 -0
- package/dist/runtime/core/types/guards.js +88 -0
- package/dist/runtime/core/types/index.d.ts +18 -0
- package/dist/runtime/core/types/index.js +19 -0
- package/dist/runtime/core/types/markers.d.ts +12 -0
- package/dist/runtime/core/types/markers.js +7 -0
- package/dist/runtime/core/types/operations.d.ts +98 -0
- package/dist/runtime/core/types/operations.js +804 -0
- package/dist/runtime/core/types/registrations.d.ts +126 -0
- package/dist/runtime/core/types/registrations.js +751 -0
- package/dist/runtime/core/{types.d.ts → types/runtime.d.ts} +22 -10
- package/dist/runtime/core/types/structures.d.ts +146 -0
- package/dist/runtime/core/types/structures.js +12 -0
- package/dist/runtime/core/values.d.ts +29 -209
- package/dist/runtime/core/values.js +56 -968
- package/dist/runtime/ext/builtins.js +88 -68
- package/dist/runtime/ext/extensions.d.ts +31 -125
- package/dist/runtime/ext/extensions.js +2 -94
- package/dist/runtime/ext/test-context.d.ts +28 -0
- package/dist/runtime/ext/test-context.js +155 -0
- package/dist/runtime/index.d.ts +12 -12
- package/dist/runtime/index.js +13 -5
- package/dist/signature-parser.d.ts +2 -2
- package/dist/signature-parser.js +14 -14
- package/dist/token-types.d.ts +1 -0
- package/dist/token-types.js +1 -0
- package/package.json +1 -1
- /package/dist/runtime/core/{types.js → types/runtime.js} +0 -0
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Structure Operations
|
|
3
|
+
*
|
|
4
|
+
* Structural comparison, matching, inference, and formatting functions
|
|
5
|
+
* extracted from values.ts. Provides `compareStructuredFields()` to
|
|
6
|
+
* deduplicate dict/tuple/ordered dispatch across structureEquals,
|
|
7
|
+
* structureMatches, and formatStructure.
|
|
8
|
+
*
|
|
9
|
+
* Import constraints:
|
|
10
|
+
* - Imports from ./structures.js, ./guards.js, ./registrations.js
|
|
11
|
+
* - Circular with registrations.ts (runtime-safe: no init-time cross-calls)
|
|
12
|
+
*/
|
|
13
|
+
import { RuntimeError } from '../../../types.js';
|
|
14
|
+
import { isCallable as _isCallableGuard, isDict, isIterator, isOrdered, isStream, isTuple, isTypeValue, isVector, } from './guards.js';
|
|
15
|
+
import { inferType as registryInferType, formatValue as registryFormatValue, deepEquals as registryDeepEquals, } from './registrations.js';
|
|
16
|
+
/** isCallable guard widened to narrow to full RillCallable (not just CallableMarker) */
|
|
17
|
+
const isCallable = _isCallableGuard;
|
|
18
|
+
/**
|
|
19
|
+
* Shared dispatch for dict/tuple/ordered field comparison.
|
|
20
|
+
* Handles valueType check and field iteration across
|
|
21
|
+
* structureEquals, structureMatches, and formatStructure.
|
|
22
|
+
*
|
|
23
|
+
* Constraints:
|
|
24
|
+
* - Dict: keyed fields, sorted key comparison
|
|
25
|
+
* - Tuple: positional elements
|
|
26
|
+
* - Ordered: named positional fields
|
|
27
|
+
* - Recurses via caller-provided callbacks
|
|
28
|
+
*
|
|
29
|
+
* Callers validate that a.kind === b.kind before calling.
|
|
30
|
+
* Invalid kind values fall through to the final return.
|
|
31
|
+
*/
|
|
32
|
+
export function compareStructuredFields(a, b, callbacks, fallback) {
|
|
33
|
+
if (a.kind === 'dict') {
|
|
34
|
+
const aDict = a;
|
|
35
|
+
const bDict = b;
|
|
36
|
+
const aHasValue = aDict.valueType !== undefined;
|
|
37
|
+
const bHasValue = bDict.valueType !== undefined;
|
|
38
|
+
if (aHasValue || bHasValue) {
|
|
39
|
+
if (!aHasValue || !bHasValue)
|
|
40
|
+
return callbacks.onValueTypeMismatch();
|
|
41
|
+
return callbacks.onValueType(aDict.valueType, bDict.valueType);
|
|
42
|
+
}
|
|
43
|
+
if (aDict.fields === undefined && bDict.fields === undefined) {
|
|
44
|
+
return callbacks.onBothEmpty();
|
|
45
|
+
}
|
|
46
|
+
if (aDict.fields === undefined || bDict.fields === undefined) {
|
|
47
|
+
return callbacks.onFieldPresenceMismatch();
|
|
48
|
+
}
|
|
49
|
+
return callbacks.onDictFields(aDict.fields, bDict.fields);
|
|
50
|
+
}
|
|
51
|
+
if (a.kind === 'tuple') {
|
|
52
|
+
const aTuple = a;
|
|
53
|
+
const bTuple = b;
|
|
54
|
+
const aHasValue = aTuple.valueType !== undefined;
|
|
55
|
+
const bHasValue = bTuple.valueType !== undefined;
|
|
56
|
+
if (aHasValue || bHasValue) {
|
|
57
|
+
if (!aHasValue || !bHasValue)
|
|
58
|
+
return callbacks.onValueTypeMismatch();
|
|
59
|
+
return callbacks.onValueType(aTuple.valueType, bTuple.valueType);
|
|
60
|
+
}
|
|
61
|
+
if (aTuple.elements === undefined && bTuple.elements === undefined) {
|
|
62
|
+
return callbacks.onBothEmpty();
|
|
63
|
+
}
|
|
64
|
+
if (aTuple.elements === undefined || bTuple.elements === undefined) {
|
|
65
|
+
return callbacks.onFieldPresenceMismatch();
|
|
66
|
+
}
|
|
67
|
+
return callbacks.onTupleElements(aTuple.elements, bTuple.elements);
|
|
68
|
+
}
|
|
69
|
+
if (a.kind === 'ordered') {
|
|
70
|
+
const aOrd = a;
|
|
71
|
+
const bOrd = b;
|
|
72
|
+
const aHasValue = aOrd.valueType !== undefined;
|
|
73
|
+
const bHasValue = bOrd.valueType !== undefined;
|
|
74
|
+
if (aHasValue || bHasValue) {
|
|
75
|
+
if (!aHasValue || !bHasValue)
|
|
76
|
+
return callbacks.onValueTypeMismatch();
|
|
77
|
+
return callbacks.onValueType(aOrd.valueType, bOrd.valueType);
|
|
78
|
+
}
|
|
79
|
+
if (aOrd.fields === undefined && bOrd.fields === undefined) {
|
|
80
|
+
return callbacks.onBothEmpty();
|
|
81
|
+
}
|
|
82
|
+
if (aOrd.fields === undefined || bOrd.fields === undefined) {
|
|
83
|
+
return callbacks.onFieldPresenceMismatch();
|
|
84
|
+
}
|
|
85
|
+
return callbacks.onOrderedFields(aOrd.fields, bOrd.fields);
|
|
86
|
+
}
|
|
87
|
+
return fallback;
|
|
88
|
+
}
|
|
89
|
+
// ============================================================
|
|
90
|
+
// PRIVATE HELPERS
|
|
91
|
+
// ============================================================
|
|
92
|
+
/**
|
|
93
|
+
* Normalize a TypeStructure that may use the legacy `.type` discriminator.
|
|
94
|
+
* During migration, some code constructs objects with `{ type: 'string' }`
|
|
95
|
+
* instead of `{ kind: 'string' }`. Converts legacy format on the fly.
|
|
96
|
+
*/
|
|
97
|
+
function normalizeStructure(ts) {
|
|
98
|
+
if (ts.kind !== undefined)
|
|
99
|
+
return ts;
|
|
100
|
+
const legacy = ts;
|
|
101
|
+
if (typeof legacy['type'] === 'string') {
|
|
102
|
+
return { ...legacy, kind: legacy['type'] };
|
|
103
|
+
}
|
|
104
|
+
return ts;
|
|
105
|
+
}
|
|
106
|
+
/** Format a RillValue as a rill literal for use in type signatures. */
|
|
107
|
+
export function formatRillLiteral(value) {
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
110
|
+
return `"${escaped}"`;
|
|
111
|
+
}
|
|
112
|
+
if (typeof value === 'number')
|
|
113
|
+
return String(value);
|
|
114
|
+
if (typeof value === 'boolean')
|
|
115
|
+
return value ? 'true' : 'false';
|
|
116
|
+
if (value === null)
|
|
117
|
+
return 'null';
|
|
118
|
+
// List: bare bracket form [elem, ...]
|
|
119
|
+
if (Array.isArray(value)) {
|
|
120
|
+
if (value.length === 0)
|
|
121
|
+
return '[]';
|
|
122
|
+
return `[${value.map(formatRillLiteral).join(', ')}]`;
|
|
123
|
+
}
|
|
124
|
+
// Tuple: tuple[elem, ...]
|
|
125
|
+
if (isTuple(value)) {
|
|
126
|
+
return `tuple[${value.entries.map(formatRillLiteral).join(', ')}]`;
|
|
127
|
+
}
|
|
128
|
+
// Ordered: ordered[key: val, ...]
|
|
129
|
+
if (isOrdered(value)) {
|
|
130
|
+
const inner = value.entries
|
|
131
|
+
.map(([k, v]) => `${k}: ${formatRillLiteral(v)}`)
|
|
132
|
+
.join(', ');
|
|
133
|
+
return `ordered[${inner}]`;
|
|
134
|
+
}
|
|
135
|
+
// Non-literal compound types: fall through to display format
|
|
136
|
+
if (isVector(value) ||
|
|
137
|
+
isIterator(value) ||
|
|
138
|
+
isStream(value) ||
|
|
139
|
+
_isCallableGuard(value) ||
|
|
140
|
+
isTypeValue(value)) {
|
|
141
|
+
return registryFormatValue(value);
|
|
142
|
+
}
|
|
143
|
+
// Dict: bare bracket form [key: val, ...] or [:] for empty
|
|
144
|
+
if (typeof value === 'object' && value !== null) {
|
|
145
|
+
const keys = Object.keys(value);
|
|
146
|
+
if (keys.length === 0)
|
|
147
|
+
return '[:]';
|
|
148
|
+
const inner = keys
|
|
149
|
+
.map((k) => `${k}: ${formatRillLiteral(value[k])}`)
|
|
150
|
+
.join(', ');
|
|
151
|
+
return `[${inner}]`;
|
|
152
|
+
}
|
|
153
|
+
return registryFormatValue(value);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Compare sequential (positional) RillFieldDef arrays for structural equality.
|
|
157
|
+
* Shared by tuple elements and ordered fields. When `named` is true,
|
|
158
|
+
* compares field names in addition to types and defaults.
|
|
159
|
+
*/
|
|
160
|
+
function compareSequentialFields(aFields, bFields, named) {
|
|
161
|
+
if (aFields.length !== bFields.length)
|
|
162
|
+
return false;
|
|
163
|
+
for (let i = 0; i < aFields.length; i++) {
|
|
164
|
+
const aField = aFields[i];
|
|
165
|
+
const bField = bFields[i];
|
|
166
|
+
if (named && aField.name !== bField.name)
|
|
167
|
+
return false;
|
|
168
|
+
if (!structureEquals(aField.type, bField.type))
|
|
169
|
+
return false;
|
|
170
|
+
const aDefault = aField.defaultValue;
|
|
171
|
+
const bDefault = bField.defaultValue;
|
|
172
|
+
if (aDefault === undefined && bDefault === undefined)
|
|
173
|
+
continue;
|
|
174
|
+
if (aDefault === undefined || bDefault === undefined)
|
|
175
|
+
return false;
|
|
176
|
+
if (!registryDeepEquals(aDefault, bDefault))
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
// ============================================================
|
|
182
|
+
// STRUCTURE EQUALS
|
|
183
|
+
// ============================================================
|
|
184
|
+
/** Equality callbacks for compareStructuredFields */
|
|
185
|
+
const equalsCallbacks = {
|
|
186
|
+
onValueType: (aVT, bVT) => structureEquals(aVT, bVT),
|
|
187
|
+
onValueTypeMismatch: () => false,
|
|
188
|
+
onBothEmpty: () => true,
|
|
189
|
+
onFieldPresenceMismatch: () => false,
|
|
190
|
+
onDictFields(aFields, bFields) {
|
|
191
|
+
const aKeys = Object.keys(aFields).sort();
|
|
192
|
+
const bKeys = Object.keys(bFields).sort();
|
|
193
|
+
if (aKeys.length !== bKeys.length)
|
|
194
|
+
return false;
|
|
195
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
196
|
+
const key = aKeys[i];
|
|
197
|
+
if (key !== bKeys[i])
|
|
198
|
+
return false;
|
|
199
|
+
const aField = aFields[key];
|
|
200
|
+
const bField = bFields[key];
|
|
201
|
+
const aHasDefault = aField.defaultValue !== undefined;
|
|
202
|
+
const bHasDefault = bField.defaultValue !== undefined;
|
|
203
|
+
if (aHasDefault !== bHasDefault)
|
|
204
|
+
return false;
|
|
205
|
+
if (!structureEquals(aField.type, bField.type))
|
|
206
|
+
return false;
|
|
207
|
+
if (aHasDefault && bHasDefault) {
|
|
208
|
+
if (!registryDeepEquals(aField.defaultValue, bField.defaultValue))
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
},
|
|
214
|
+
onTupleElements: (a, b) => compareSequentialFields(a, b, false),
|
|
215
|
+
onOrderedFields: (a, b) => compareSequentialFields(a, b, true),
|
|
216
|
+
};
|
|
217
|
+
/** Compare two structural types for equality. */
|
|
218
|
+
export function structureEquals(a, b) {
|
|
219
|
+
if (a.kind !== b.kind)
|
|
220
|
+
return false;
|
|
221
|
+
// Leaf variants compare by kind alone
|
|
222
|
+
if (a.kind === 'number' ||
|
|
223
|
+
a.kind === 'string' ||
|
|
224
|
+
a.kind === 'bool' ||
|
|
225
|
+
a.kind === 'vector' ||
|
|
226
|
+
a.kind === 'type' ||
|
|
227
|
+
a.kind === 'any') {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
if (a.kind === 'list' && b.kind === 'list') {
|
|
231
|
+
const aList = a;
|
|
232
|
+
const bList = b;
|
|
233
|
+
if (aList.element === undefined && bList.element === undefined)
|
|
234
|
+
return true;
|
|
235
|
+
if (aList.element === undefined || bList.element === undefined)
|
|
236
|
+
return false;
|
|
237
|
+
return structureEquals(aList.element, bList.element);
|
|
238
|
+
}
|
|
239
|
+
// Delegate dict/tuple/ordered to compareStructuredFields
|
|
240
|
+
if (a.kind === 'dict' || a.kind === 'tuple' || a.kind === 'ordered') {
|
|
241
|
+
return compareStructuredFields(a, b, equalsCallbacks, false);
|
|
242
|
+
}
|
|
243
|
+
if (a.kind === 'union' && b.kind === 'union') {
|
|
244
|
+
const aUnion = a;
|
|
245
|
+
const bUnion = b;
|
|
246
|
+
if (aUnion.members.length !== bUnion.members.length)
|
|
247
|
+
return false;
|
|
248
|
+
for (let i = 0; i < aUnion.members.length; i++) {
|
|
249
|
+
if (!structureEquals(aUnion.members[i], bUnion.members[i]))
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
if (a.kind === 'stream' && b.kind === 'stream') {
|
|
255
|
+
const aStream = a;
|
|
256
|
+
const bStream = b;
|
|
257
|
+
const chunkEq = aStream.chunk === undefined && bStream.chunk === undefined
|
|
258
|
+
? true
|
|
259
|
+
: aStream.chunk !== undefined && bStream.chunk !== undefined
|
|
260
|
+
? structureEquals(aStream.chunk, bStream.chunk)
|
|
261
|
+
: false;
|
|
262
|
+
if (!chunkEq)
|
|
263
|
+
return false;
|
|
264
|
+
const retEq = aStream.ret === undefined && bStream.ret === undefined
|
|
265
|
+
? true
|
|
266
|
+
: aStream.ret !== undefined && bStream.ret !== undefined
|
|
267
|
+
? structureEquals(aStream.ret, bStream.ret)
|
|
268
|
+
: false;
|
|
269
|
+
return retEq;
|
|
270
|
+
}
|
|
271
|
+
if (a.kind === 'closure' && b.kind === 'closure') {
|
|
272
|
+
const aCls = a;
|
|
273
|
+
const bCls = b;
|
|
274
|
+
if (aCls.params === undefined && bCls.params === undefined) {
|
|
275
|
+
// Both absent: compare ret
|
|
276
|
+
}
|
|
277
|
+
else if (aCls.params === undefined || bCls.params === undefined) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
if (aCls.params.length !== bCls.params.length)
|
|
282
|
+
return false;
|
|
283
|
+
for (let i = 0; i < aCls.params.length; i++) {
|
|
284
|
+
const aParam = aCls.params[i];
|
|
285
|
+
const bParam = bCls.params[i];
|
|
286
|
+
if (aParam.name !== bParam.name)
|
|
287
|
+
return false;
|
|
288
|
+
if (!structureEquals(aParam.type, bParam.type))
|
|
289
|
+
return false;
|
|
290
|
+
const aDefault = aParam.defaultValue;
|
|
291
|
+
const bDefault = bParam.defaultValue;
|
|
292
|
+
if (aDefault === undefined && bDefault === undefined)
|
|
293
|
+
continue;
|
|
294
|
+
if (aDefault === undefined || bDefault === undefined)
|
|
295
|
+
return false;
|
|
296
|
+
if (!registryDeepEquals(aDefault, bDefault))
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (aCls.ret === undefined && bCls.ret === undefined)
|
|
301
|
+
return true;
|
|
302
|
+
if (aCls.ret === undefined || bCls.ret === undefined)
|
|
303
|
+
return false;
|
|
304
|
+
return structureEquals(aCls.ret, bCls.ret);
|
|
305
|
+
}
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
// ============================================================
|
|
309
|
+
// STRUCTURE MATCHES
|
|
310
|
+
// ============================================================
|
|
311
|
+
/**
|
|
312
|
+
* Create structureMatches callbacks that capture the runtime value.
|
|
313
|
+
* Dict checks keys against type fields. Tuple and ordered check
|
|
314
|
+
* entries against type elements/fields.
|
|
315
|
+
*/
|
|
316
|
+
function createMatchesCallbacks(value) {
|
|
317
|
+
return {
|
|
318
|
+
onValueType(valueType) {
|
|
319
|
+
// Uniform valueType: check all values match the type.
|
|
320
|
+
// Order matters: isOrdered/isTuple before isDict because
|
|
321
|
+
// ordered and tuple values also satisfy isDict.
|
|
322
|
+
if (isTuple(value)) {
|
|
323
|
+
return value.entries.every((v) => structureMatches(v, valueType));
|
|
324
|
+
}
|
|
325
|
+
if (isOrdered(value)) {
|
|
326
|
+
return value.entries.every(([, v]) => structureMatches(v, valueType));
|
|
327
|
+
}
|
|
328
|
+
if (isDict(value)) {
|
|
329
|
+
const vals = Object.values(value);
|
|
330
|
+
return vals.every((v) => structureMatches(v, valueType));
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
},
|
|
334
|
+
onValueTypeMismatch: () => false,
|
|
335
|
+
onBothEmpty: () => true,
|
|
336
|
+
onFieldPresenceMismatch: () => false,
|
|
337
|
+
onDictFields(fields) {
|
|
338
|
+
const dictKeys = Object.keys(fields);
|
|
339
|
+
if (dictKeys.length === 0)
|
|
340
|
+
return true;
|
|
341
|
+
const dict = value;
|
|
342
|
+
for (const key of dictKeys) {
|
|
343
|
+
if (!(key in dict)) {
|
|
344
|
+
const field = fields[key];
|
|
345
|
+
if (field.defaultValue !== undefined)
|
|
346
|
+
continue;
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
const field = fields[key];
|
|
350
|
+
if (!structureMatches(dict[key], field.type))
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
return true;
|
|
354
|
+
},
|
|
355
|
+
onTupleElements(elements) {
|
|
356
|
+
if (!isTuple(value))
|
|
357
|
+
return false;
|
|
358
|
+
if (elements.length === 0)
|
|
359
|
+
return value.entries.length === 0;
|
|
360
|
+
if (value.entries.length > elements.length)
|
|
361
|
+
return false;
|
|
362
|
+
if (value.entries.length < elements.length) {
|
|
363
|
+
for (let i = value.entries.length; i < elements.length; i++) {
|
|
364
|
+
const field = elements[i];
|
|
365
|
+
if (field.defaultValue === undefined)
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (let i = 0; i < value.entries.length; i++) {
|
|
370
|
+
if (!structureMatches(value.entries[i], elements[i].type))
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
374
|
+
},
|
|
375
|
+
onOrderedFields(fields) {
|
|
376
|
+
if (!isOrdered(value))
|
|
377
|
+
return false;
|
|
378
|
+
if (fields.length === 0)
|
|
379
|
+
return value.entries.length === 0;
|
|
380
|
+
if (value.entries.length > fields.length)
|
|
381
|
+
return false;
|
|
382
|
+
if (value.entries.length < fields.length) {
|
|
383
|
+
for (let i = value.entries.length; i < fields.length; i++) {
|
|
384
|
+
const field = fields[i];
|
|
385
|
+
if (field.defaultValue === undefined)
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (let i = 0; i < value.entries.length; i++) {
|
|
390
|
+
const field = fields[i];
|
|
391
|
+
const [actualName, actualValue] = value.entries[i];
|
|
392
|
+
if (actualName !== field.name)
|
|
393
|
+
return false;
|
|
394
|
+
if (!structureMatches(actualValue, field.type))
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
return true;
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Check if a value matches a structural type descriptor.
|
|
403
|
+
* Used for runtime type checking (`:?` operator).
|
|
404
|
+
*/
|
|
405
|
+
export function structureMatches(value, type) {
|
|
406
|
+
type = normalizeStructure(type);
|
|
407
|
+
if (typeof value === 'undefined') {
|
|
408
|
+
throw new RuntimeError('RILL-R004', 'Cannot type-check non-value');
|
|
409
|
+
}
|
|
410
|
+
if (type.kind === 'any')
|
|
411
|
+
return true;
|
|
412
|
+
// Leaf primitive variants: match by inferred type name
|
|
413
|
+
if (type.kind === 'number' ||
|
|
414
|
+
type.kind === 'string' ||
|
|
415
|
+
type.kind === 'bool' ||
|
|
416
|
+
type.kind === 'vector' ||
|
|
417
|
+
type.kind === 'type') {
|
|
418
|
+
return registryInferType(value) === type.kind;
|
|
419
|
+
}
|
|
420
|
+
if (type.kind === 'list') {
|
|
421
|
+
const t = type;
|
|
422
|
+
if (!Array.isArray(value))
|
|
423
|
+
return false;
|
|
424
|
+
if (t.element === undefined)
|
|
425
|
+
return true;
|
|
426
|
+
if (t.element.kind === 'any')
|
|
427
|
+
return true;
|
|
428
|
+
return value.every((elem) => structureMatches(elem, t.element));
|
|
429
|
+
}
|
|
430
|
+
// Delegate dict/tuple/ordered to compareStructuredFields
|
|
431
|
+
if (type.kind === 'dict') {
|
|
432
|
+
if (!isDict(value) || isStream(value))
|
|
433
|
+
return false;
|
|
434
|
+
return compareStructuredFields(type, type, createMatchesCallbacks(value), false);
|
|
435
|
+
}
|
|
436
|
+
if (type.kind === 'tuple') {
|
|
437
|
+
if (!isTuple(value))
|
|
438
|
+
return false;
|
|
439
|
+
return compareStructuredFields(type, type, createMatchesCallbacks(value), false);
|
|
440
|
+
}
|
|
441
|
+
if (type.kind === 'ordered') {
|
|
442
|
+
if (!isOrdered(value))
|
|
443
|
+
return false;
|
|
444
|
+
return compareStructuredFields(type, type, createMatchesCallbacks(value), false);
|
|
445
|
+
}
|
|
446
|
+
if (type.kind === 'stream') {
|
|
447
|
+
if (!isStream(value))
|
|
448
|
+
return false;
|
|
449
|
+
const t = type;
|
|
450
|
+
if (t.chunk === undefined && t.ret === undefined)
|
|
451
|
+
return true;
|
|
452
|
+
const raw = value;
|
|
453
|
+
if (t.chunk !== undefined) {
|
|
454
|
+
const valueChunk = raw['__rill_stream_chunk_type'];
|
|
455
|
+
if (valueChunk !== undefined && !structureEquals(valueChunk, t.chunk))
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
if (t.ret !== undefined) {
|
|
459
|
+
const valueRet = raw['__rill_stream_ret_type'];
|
|
460
|
+
if (valueRet !== undefined && !structureEquals(valueRet, t.ret))
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
if (type.kind === 'closure') {
|
|
466
|
+
const t = type;
|
|
467
|
+
if (!isCallable(value))
|
|
468
|
+
return false;
|
|
469
|
+
if (t.params === undefined)
|
|
470
|
+
return true;
|
|
471
|
+
const valueParams = value.params ?? [];
|
|
472
|
+
if (valueParams.length !== t.params.length)
|
|
473
|
+
return false;
|
|
474
|
+
for (let i = 0; i < t.params.length; i++) {
|
|
475
|
+
const field = t.params[i];
|
|
476
|
+
const param = valueParams[i];
|
|
477
|
+
if (param.name !== field.name)
|
|
478
|
+
return false;
|
|
479
|
+
const paramType = param.type ?? { kind: 'any' };
|
|
480
|
+
if (!structureEquals(paramType, field.type))
|
|
481
|
+
return false;
|
|
482
|
+
// Directional default rules: value (superset) satisfies type (contract)
|
|
483
|
+
const valueHasDefault = param.defaultValue !== undefined;
|
|
484
|
+
const typeHasDefault = field.defaultValue !== undefined;
|
|
485
|
+
if (!valueHasDefault && typeHasDefault)
|
|
486
|
+
return false;
|
|
487
|
+
if (valueHasDefault && typeHasDefault) {
|
|
488
|
+
if (!registryDeepEquals(param.defaultValue, field.defaultValue))
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
// value has default + type lacks → compatible (superset satisfies)
|
|
492
|
+
// neither has default → compatible
|
|
493
|
+
}
|
|
494
|
+
const retType = value.returnType.structure;
|
|
495
|
+
if (t.ret === undefined)
|
|
496
|
+
return true;
|
|
497
|
+
return structureEquals(retType, t.ret);
|
|
498
|
+
}
|
|
499
|
+
if (type.kind === 'union') {
|
|
500
|
+
const t = type;
|
|
501
|
+
return t.members.some((member) => structureMatches(value, member));
|
|
502
|
+
}
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
// ============================================================
|
|
506
|
+
// FORMAT STRUCTURE
|
|
507
|
+
// ============================================================
|
|
508
|
+
/**
|
|
509
|
+
* Format callbacks for compareStructuredFields.
|
|
510
|
+
* Returns `null` for bare types (no fields, no valueType) so the caller
|
|
511
|
+
* can distinguish "fields: undefined" from "fields: {}".
|
|
512
|
+
*/
|
|
513
|
+
const formatCallbacks = {
|
|
514
|
+
onValueType(valueType) {
|
|
515
|
+
return formatStructure(valueType);
|
|
516
|
+
},
|
|
517
|
+
onValueTypeMismatch: () => null,
|
|
518
|
+
onBothEmpty: () => null,
|
|
519
|
+
onFieldPresenceMismatch: () => null,
|
|
520
|
+
onDictFields(fields) {
|
|
521
|
+
const parts = Object.keys(fields)
|
|
522
|
+
.sort()
|
|
523
|
+
.map((k) => {
|
|
524
|
+
const field = fields[k];
|
|
525
|
+
const base = `${k}: ${formatStructure(field.type)}`;
|
|
526
|
+
if (field.defaultValue === undefined)
|
|
527
|
+
return base;
|
|
528
|
+
return `${base} = ${formatRillLiteral(field.defaultValue)}`;
|
|
529
|
+
});
|
|
530
|
+
return parts.join(', ');
|
|
531
|
+
},
|
|
532
|
+
onTupleElements(elements) {
|
|
533
|
+
const parts = elements.map((field) => {
|
|
534
|
+
const base = formatStructure(field.type);
|
|
535
|
+
if (field.defaultValue === undefined)
|
|
536
|
+
return base;
|
|
537
|
+
return `${base} = ${formatRillLiteral(field.defaultValue)}`;
|
|
538
|
+
});
|
|
539
|
+
return parts.join(', ');
|
|
540
|
+
},
|
|
541
|
+
onOrderedFields(fields) {
|
|
542
|
+
const parts = fields.map((field) => {
|
|
543
|
+
const base = `${field.name}: ${formatStructure(field.type)}`;
|
|
544
|
+
if (field.defaultValue === undefined)
|
|
545
|
+
return base;
|
|
546
|
+
return `${base} = ${formatRillLiteral(field.defaultValue)}`;
|
|
547
|
+
});
|
|
548
|
+
return parts.join(', ');
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
/** Format a structural type descriptor as a human-readable string. */
|
|
552
|
+
export function formatStructure(type) {
|
|
553
|
+
if (type.kind === 'any' ||
|
|
554
|
+
type.kind === 'number' ||
|
|
555
|
+
type.kind === 'string' ||
|
|
556
|
+
type.kind === 'bool' ||
|
|
557
|
+
type.kind === 'vector' ||
|
|
558
|
+
type.kind === 'type') {
|
|
559
|
+
return type.kind;
|
|
560
|
+
}
|
|
561
|
+
if (type.kind === 'list') {
|
|
562
|
+
const t = type;
|
|
563
|
+
if (t.element === undefined)
|
|
564
|
+
return 'list';
|
|
565
|
+
return `list(${formatStructure(t.element)})`;
|
|
566
|
+
}
|
|
567
|
+
// Delegate dict/tuple/ordered to compareStructuredFields
|
|
568
|
+
if (type.kind === 'dict' ||
|
|
569
|
+
type.kind === 'tuple' ||
|
|
570
|
+
type.kind === 'ordered') {
|
|
571
|
+
const inner = compareStructuredFields(type, type, formatCallbacks, null);
|
|
572
|
+
// Bare type (no fields, no valueType): null signals undefined fields
|
|
573
|
+
if (inner === null)
|
|
574
|
+
return type.kind;
|
|
575
|
+
return `${type.kind}(${inner})`;
|
|
576
|
+
}
|
|
577
|
+
if (type.kind === 'stream') {
|
|
578
|
+
const t = type;
|
|
579
|
+
if (t.chunk === undefined && t.ret === undefined)
|
|
580
|
+
return 'stream';
|
|
581
|
+
const chunkStr = t.chunk !== undefined ? formatStructure(t.chunk) : 'any';
|
|
582
|
+
const retStr = t.ret !== undefined ? `:${formatStructure(t.ret)}` : '';
|
|
583
|
+
return `stream(${chunkStr})${retStr}`;
|
|
584
|
+
}
|
|
585
|
+
if (type.kind === 'closure') {
|
|
586
|
+
const t = type;
|
|
587
|
+
if (t.params === undefined)
|
|
588
|
+
return 'closure';
|
|
589
|
+
const params = t.params
|
|
590
|
+
.map((field) => {
|
|
591
|
+
const base = `${field.name}: ${formatStructure(field.type)}`;
|
|
592
|
+
if (field.defaultValue === undefined)
|
|
593
|
+
return base;
|
|
594
|
+
return `${base} = ${formatRillLiteral(field.defaultValue)}`;
|
|
595
|
+
})
|
|
596
|
+
.join(', ');
|
|
597
|
+
const ret = t.ret !== undefined ? formatStructure(t.ret) : 'any';
|
|
598
|
+
return `|${params}| :${ret}`;
|
|
599
|
+
}
|
|
600
|
+
if (type.kind === 'union') {
|
|
601
|
+
const t = type;
|
|
602
|
+
return t.members.map(formatStructure).join('|');
|
|
603
|
+
}
|
|
604
|
+
return 'any';
|
|
605
|
+
}
|
|
606
|
+
// ============================================================
|
|
607
|
+
// INFER STRUCTURE
|
|
608
|
+
// ============================================================
|
|
609
|
+
/** Build a closure param field definition from name, type, and optional default. */
|
|
610
|
+
export function paramToFieldDef(name, type, defaultValue) {
|
|
611
|
+
const field = { name, type };
|
|
612
|
+
if (defaultValue !== undefined)
|
|
613
|
+
field.defaultValue = defaultValue;
|
|
614
|
+
return field;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Infer the element type for a homogeneous list.
|
|
618
|
+
* Empty arrays return { kind: 'any' }.
|
|
619
|
+
* Mixed types throw RILL-R002.
|
|
620
|
+
*/
|
|
621
|
+
export function inferElementType(elements) {
|
|
622
|
+
if (elements.length === 0)
|
|
623
|
+
return { kind: 'any' };
|
|
624
|
+
const firstElem = elements[0];
|
|
625
|
+
let accType = inferStructure(firstElem);
|
|
626
|
+
for (let i = 1; i < elements.length; i++) {
|
|
627
|
+
const elem = elements[i];
|
|
628
|
+
const elemType = inferStructure(elem);
|
|
629
|
+
const merged = commonType(accType, elemType);
|
|
630
|
+
if (merged === null) {
|
|
631
|
+
throw new RuntimeError('RILL-R002', `List elements must be the same type: expected ${formatStructure(accType)}, got ${formatStructure(elemType)} at index ${i}`);
|
|
632
|
+
}
|
|
633
|
+
accType = merged;
|
|
634
|
+
}
|
|
635
|
+
return accType;
|
|
636
|
+
}
|
|
637
|
+
/** Infer the structural type descriptor for any Rill value. */
|
|
638
|
+
export function inferStructure(value) {
|
|
639
|
+
if (value === null || typeof value === 'string') {
|
|
640
|
+
return { kind: 'string' };
|
|
641
|
+
}
|
|
642
|
+
if (typeof value === 'number') {
|
|
643
|
+
return { kind: 'number' };
|
|
644
|
+
}
|
|
645
|
+
if (typeof value === 'boolean') {
|
|
646
|
+
return { kind: 'bool' };
|
|
647
|
+
}
|
|
648
|
+
if (isTypeValue(value)) {
|
|
649
|
+
return { kind: 'type' };
|
|
650
|
+
}
|
|
651
|
+
if (Array.isArray(value)) {
|
|
652
|
+
return { kind: 'list', element: inferElementType(value) };
|
|
653
|
+
}
|
|
654
|
+
if (isTuple(value)) {
|
|
655
|
+
return {
|
|
656
|
+
kind: 'tuple',
|
|
657
|
+
elements: value.entries.map((e) => ({
|
|
658
|
+
type: inferStructure(e),
|
|
659
|
+
})),
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (isOrdered(value)) {
|
|
663
|
+
return {
|
|
664
|
+
kind: 'ordered',
|
|
665
|
+
fields: value.entries.map(([k, v]) => ({
|
|
666
|
+
name: k,
|
|
667
|
+
type: inferStructure(v),
|
|
668
|
+
})),
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
if (isVector(value)) {
|
|
672
|
+
return { kind: 'vector' };
|
|
673
|
+
}
|
|
674
|
+
if (isStream(value)) {
|
|
675
|
+
const raw = value;
|
|
676
|
+
const chunk = raw['__rill_stream_chunk_type'];
|
|
677
|
+
const ret = raw['__rill_stream_ret_type'];
|
|
678
|
+
const result = {
|
|
679
|
+
kind: 'stream',
|
|
680
|
+
};
|
|
681
|
+
if (chunk !== undefined)
|
|
682
|
+
result.chunk = chunk;
|
|
683
|
+
if (ret !== undefined)
|
|
684
|
+
result.ret = ret;
|
|
685
|
+
return result;
|
|
686
|
+
}
|
|
687
|
+
if (isCallable(value)) {
|
|
688
|
+
const params = (value.params ?? []).map((p) => paramToFieldDef(p.name, p.type ?? { kind: 'any' }, p.defaultValue));
|
|
689
|
+
const ret = value.returnType.structure;
|
|
690
|
+
return { kind: 'closure', params, ret };
|
|
691
|
+
}
|
|
692
|
+
if (typeof value === 'object') {
|
|
693
|
+
const dict = value;
|
|
694
|
+
const fields = {};
|
|
695
|
+
for (const [k, v] of Object.entries(dict)) {
|
|
696
|
+
fields[k] = { type: inferStructure(v) };
|
|
697
|
+
}
|
|
698
|
+
return { kind: 'dict', fields };
|
|
699
|
+
}
|
|
700
|
+
throw new RuntimeError('RILL-R004', `Cannot infer structural type for ${registryFormatValue(value)}`);
|
|
701
|
+
}
|
|
702
|
+
// ============================================================
|
|
703
|
+
// COMMON TYPE
|
|
704
|
+
// ============================================================
|
|
705
|
+
/**
|
|
706
|
+
* Merge uniform value types from two sides of the same compound type.
|
|
707
|
+
* Sub-case A: both carry valueType -> recurse commonType.
|
|
708
|
+
* Sub-case B: both carry structural fields -> extract value types, merge all.
|
|
709
|
+
* Returns the merged TypeStructure on success, undefined when no uniform merge applies.
|
|
710
|
+
*/
|
|
711
|
+
function mergeUniformValueType(aValue, bValue, aFields, bFields) {
|
|
712
|
+
// Sub-case A: both carry valueType
|
|
713
|
+
if (aValue !== undefined && bValue !== undefined) {
|
|
714
|
+
const merged = commonType(aValue, bValue);
|
|
715
|
+
if (merged !== null)
|
|
716
|
+
return merged;
|
|
717
|
+
return undefined;
|
|
718
|
+
}
|
|
719
|
+
// Sub-case B: both carry structural fields
|
|
720
|
+
if (aFields !== undefined && bFields !== undefined) {
|
|
721
|
+
const allTypes = [
|
|
722
|
+
...aFields.map((f) => f.type),
|
|
723
|
+
...bFields.map((f) => f.type),
|
|
724
|
+
];
|
|
725
|
+
if (allTypes.length === 0)
|
|
726
|
+
return undefined;
|
|
727
|
+
let merged = allTypes[0];
|
|
728
|
+
for (let i = 1; i < allTypes.length; i++) {
|
|
729
|
+
const next = commonType(merged, allTypes[i]);
|
|
730
|
+
if (next === null)
|
|
731
|
+
return undefined;
|
|
732
|
+
merged = next;
|
|
733
|
+
}
|
|
734
|
+
return merged;
|
|
735
|
+
}
|
|
736
|
+
return undefined;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Return the most specific shared type for two TypeStructure values.
|
|
740
|
+
* Returns null when types are incompatible at the top level.
|
|
741
|
+
*
|
|
742
|
+
* Cascade priority:
|
|
743
|
+
* 1. Any-narrowing: if either side is `any`, return the other
|
|
744
|
+
* 2. Structural match: delegate to structureEquals; on true, return a
|
|
745
|
+
* 3. Recursive list: merge inner element types
|
|
746
|
+
* 3b. Uniform valueType: merge dict/tuple/ordered value types
|
|
747
|
+
* 4. Bare type fallback: same compound type but structural mismatch
|
|
748
|
+
* 5. Incompatible: different top-level types return null
|
|
749
|
+
*/
|
|
750
|
+
export function commonType(a, b) {
|
|
751
|
+
// 1. Any-narrowing
|
|
752
|
+
if (a.kind === 'any')
|
|
753
|
+
return b;
|
|
754
|
+
if (b.kind === 'any')
|
|
755
|
+
return a;
|
|
756
|
+
// 5. Incompatible top-level types (checked early to short-circuit)
|
|
757
|
+
if (a.kind !== b.kind)
|
|
758
|
+
return null;
|
|
759
|
+
// 2. Structural match
|
|
760
|
+
if (structureEquals(a, b))
|
|
761
|
+
return a;
|
|
762
|
+
// 3. Recursive list element merging
|
|
763
|
+
if (a.kind === 'list' && b.kind === 'list') {
|
|
764
|
+
const aList = a;
|
|
765
|
+
const bList = b;
|
|
766
|
+
if (aList.element !== undefined && bList.element !== undefined) {
|
|
767
|
+
const inner = commonType(aList.element, bList.element);
|
|
768
|
+
if (inner !== null)
|
|
769
|
+
return { kind: 'list', element: inner };
|
|
770
|
+
}
|
|
771
|
+
return { kind: 'list' };
|
|
772
|
+
}
|
|
773
|
+
// 3b. Uniform valueType merging for dict/tuple/ordered
|
|
774
|
+
if (a.kind === 'dict' && b.kind === 'dict') {
|
|
775
|
+
const aDict = a;
|
|
776
|
+
const bDict = b;
|
|
777
|
+
const merged = mergeUniformValueType(aDict.valueType, bDict.valueType, aDict.fields ? Object.values(aDict.fields) : undefined, bDict.fields ? Object.values(bDict.fields) : undefined);
|
|
778
|
+
if (merged !== undefined)
|
|
779
|
+
return { kind: 'dict', valueType: merged };
|
|
780
|
+
}
|
|
781
|
+
if (a.kind === 'tuple' && b.kind === 'tuple') {
|
|
782
|
+
const aTuple = a;
|
|
783
|
+
const bTuple = b;
|
|
784
|
+
const merged = mergeUniformValueType(aTuple.valueType, bTuple.valueType, aTuple.elements, bTuple.elements);
|
|
785
|
+
if (merged !== undefined)
|
|
786
|
+
return { kind: 'tuple', valueType: merged };
|
|
787
|
+
}
|
|
788
|
+
if (a.kind === 'ordered' && b.kind === 'ordered') {
|
|
789
|
+
const aOrd = a;
|
|
790
|
+
const bOrd = b;
|
|
791
|
+
const merged = mergeUniformValueType(aOrd.valueType, bOrd.valueType, aOrd.fields, bOrd.fields);
|
|
792
|
+
if (merged !== undefined)
|
|
793
|
+
return { kind: 'ordered', valueType: merged };
|
|
794
|
+
}
|
|
795
|
+
// 4. Bare type fallback for compound types.
|
|
796
|
+
if (a.kind === 'closure' ||
|
|
797
|
+
a.kind === 'dict' ||
|
|
798
|
+
a.kind === 'tuple' ||
|
|
799
|
+
a.kind === 'ordered' ||
|
|
800
|
+
a.kind === 'union') {
|
|
801
|
+
return { kind: a.kind };
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
}
|