@rcrsr/rill 0.16.0 → 0.17.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/ext/crypto/index.d.ts +3 -3
- package/dist/ext/crypto/index.js +61 -58
- package/dist/ext/exec/index.d.ts +3 -3
- package/dist/ext/exec/index.js +14 -8
- package/dist/ext/fetch/index.d.ts +3 -3
- package/dist/ext/fetch/index.js +16 -11
- package/dist/ext/fs/index.d.ts +3 -3
- package/dist/ext/fs/index.js +242 -239
- package/dist/ext/kv/index.d.ts +3 -3
- package/dist/ext/kv/index.js +197 -195
- 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/index.d.ts +15 -4
- package/dist/index.js +14 -5
- package/dist/parser/parser-types.js +12 -0
- package/dist/parser/parser-use.js +7 -1
- package/dist/runtime/core/callable.d.ts +20 -8
- package/dist/runtime/core/callable.js +63 -23
- package/dist/runtime/core/context.d.ts +0 -11
- package/dist/runtime/core/context.js +76 -75
- package/dist/runtime/core/eval/index.d.ts +2 -2
- package/dist/runtime/core/eval/index.js +11 -0
- package/dist/runtime/core/eval/mixins/closures.js +15 -15
- package/dist/runtime/core/eval/mixins/conversion.js +51 -110
- package/dist/runtime/core/eval/mixins/core.js +2 -2
- package/dist/runtime/core/eval/mixins/expressions.js +35 -27
- package/dist/runtime/core/eval/mixins/literals.js +3 -3
- package/dist/runtime/core/eval/mixins/types.js +44 -54
- package/dist/runtime/core/eval/mixins/variables.js +10 -8
- package/dist/runtime/core/field-descriptor.d.ts +3 -3
- package/dist/runtime/core/field-descriptor.js +2 -1
- package/dist/runtime/core/introspection.js +6 -6
- package/dist/runtime/core/markers.d.ts +12 -0
- package/dist/runtime/core/markers.js +7 -0
- package/dist/runtime/core/type-registrations.d.ts +136 -0
- package/dist/runtime/core/type-registrations.js +749 -0
- package/dist/runtime/core/type-structures.d.ts +128 -0
- package/dist/runtime/core/type-structures.js +12 -0
- package/dist/runtime/core/types.d.ts +15 -3
- package/dist/runtime/core/values.d.ts +62 -153
- package/dist/runtime/core/values.js +308 -524
- package/dist/runtime/ext/builtins.js +83 -64
- package/dist/runtime/ext/extensions.d.ts +30 -124
- package/dist/runtime/ext/extensions.js +0 -93
- package/dist/runtime/ext/test-context.d.ts +28 -0
- package/dist/runtime/ext/test-context.js +154 -0
- package/dist/runtime/index.d.ts +22 -8
- package/dist/runtime/index.js +18 -4
- package/dist/signature-parser.d.ts +2 -2
- package/dist/signature-parser.js +14 -14
- package/package.json +1 -1
|
@@ -70,6 +70,17 @@ export function assertType(value, expected, location) {
|
|
|
70
70
|
resolverConfigs: new Map(),
|
|
71
71
|
resolvingSchemes: new Set(),
|
|
72
72
|
typeMethodDicts: new Map(),
|
|
73
|
+
leafTypes: new Set([
|
|
74
|
+
'string',
|
|
75
|
+
'number',
|
|
76
|
+
'bool',
|
|
77
|
+
'vector',
|
|
78
|
+
'type',
|
|
79
|
+
'any',
|
|
80
|
+
'closure',
|
|
81
|
+
'field_descriptor',
|
|
82
|
+
]),
|
|
83
|
+
unvalidatedMethodReceivers: new Set(),
|
|
73
84
|
};
|
|
74
85
|
const evaluator = getEvaluator(minimalContext);
|
|
75
86
|
return evaluator.assertType(value, expected, location);
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
*/
|
|
44
44
|
import { RillError, RuntimeError } from '../../../../types.js';
|
|
45
45
|
import { isCallable, isScriptCallable, isApplicationCallable, isDict, marshalArgs, } from '../../callable.js';
|
|
46
|
-
import { getVariable, pushCallFrame, popCallFrame, UNVALIDATED_METHOD_PARAMS,
|
|
47
|
-
import { inferType, isTypeValue, isTuple, isOrdered, paramToFieldDef,
|
|
46
|
+
import { getVariable, pushCallFrame, popCallFrame, UNVALIDATED_METHOD_PARAMS, } from '../../context.js';
|
|
47
|
+
import { inferType, isTypeValue, isTuple, isOrdered, paramToFieldDef, inferStructure, structureMatches, formatStructure, anyTypeValue, structureToTypeValue, } from '../../values.js';
|
|
48
48
|
/**
|
|
49
49
|
* ClosuresMixin implementation.
|
|
50
50
|
*
|
|
@@ -215,8 +215,8 @@ function createClosuresMixin(Base) {
|
|
|
215
215
|
validateParamType(param, value, callLocation) {
|
|
216
216
|
if (param.type === undefined)
|
|
217
217
|
return;
|
|
218
|
-
if (!
|
|
219
|
-
const expectedType =
|
|
218
|
+
if (!structureMatches(value, param.type)) {
|
|
219
|
+
const expectedType = formatStructure(param.type);
|
|
220
220
|
const actualType = inferType(value);
|
|
221
221
|
throw new RuntimeError('RILL-R001', `Parameter type mismatch: ${param.name} expects ${expectedType}, got ${actualType}`, callLocation, { paramName: param.name, expectedType, actualType });
|
|
222
222
|
}
|
|
@@ -541,13 +541,13 @@ function createClosuresMixin(Base) {
|
|
|
541
541
|
throw new RuntimeError('RILL-R003', `Method .${node.name} not available on callable (invoke with -> $() first)`, this.getNodeLocation(node), { methodName: node.name, receiverType: 'callable' });
|
|
542
542
|
}
|
|
543
543
|
// IR-3: .name on type values returns the typeName string (method path)
|
|
544
|
-
// IR-4: .signature on type values returns
|
|
544
|
+
// IR-4: .signature on type values returns formatStructure(structure)
|
|
545
545
|
if (isTypeValue(receiver)) {
|
|
546
546
|
if (node.name === 'name') {
|
|
547
547
|
return receiver.typeName;
|
|
548
548
|
}
|
|
549
549
|
if (node.name === 'signature') {
|
|
550
|
-
return
|
|
550
|
+
return formatStructure(receiver.structure);
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
553
|
const args = await this.evaluateArgs(node.args);
|
|
@@ -617,10 +617,10 @@ function createClosuresMixin(Base) {
|
|
|
617
617
|
throw new RuntimeError('RILL-R009', `Property '${node.name}' not found on type value (available: name, signature)`, this.getNodeLocation(node), { property: node.name, type: 'type value' });
|
|
618
618
|
}
|
|
619
619
|
// RILL-R003: method exists on other types but not this receiver's type.
|
|
620
|
-
// Methods in
|
|
620
|
+
// Methods in unvalidatedMethodReceivers handle their own receiver validation
|
|
621
621
|
// with specific error messages; skip generic RILL-R003 for them and let the
|
|
622
622
|
// method body run its own check (they exist in at least one type dict).
|
|
623
|
-
if (!
|
|
623
|
+
if (!this.ctx.unvalidatedMethodReceivers.has(node.name)) {
|
|
624
624
|
const supportedTypes = [];
|
|
625
625
|
for (const [dictType, dict] of this.ctx.typeMethodDicts) {
|
|
626
626
|
if (dict[node.name] !== undefined) {
|
|
@@ -632,14 +632,14 @@ function createClosuresMixin(Base) {
|
|
|
632
632
|
}
|
|
633
633
|
}
|
|
634
634
|
else {
|
|
635
|
-
//
|
|
635
|
+
// unvalidatedMethodReceivers: dispatch to the method in ANY type dict so the
|
|
636
636
|
// method body can run its own custom receiver validation and error message.
|
|
637
637
|
for (const [, dict] of this.ctx.typeMethodDicts) {
|
|
638
638
|
const fallbackMethod = dict[node.name];
|
|
639
639
|
if (fallbackMethod !== undefined &&
|
|
640
640
|
isApplicationCallable(fallbackMethod)) {
|
|
641
641
|
try {
|
|
642
|
-
//
|
|
642
|
+
// Unvalidated methods handle their own receiver validation.
|
|
643
643
|
// Build named record with receiver so buildMethodEntry extracts it correctly;
|
|
644
644
|
// the method body performs its own receiver type check with a custom error.
|
|
645
645
|
const fbMethodArgs = { receiver };
|
|
@@ -706,7 +706,7 @@ function createClosuresMixin(Base) {
|
|
|
706
706
|
const typeValue = Object.freeze({
|
|
707
707
|
__rill_type: true,
|
|
708
708
|
typeName: inferType(value),
|
|
709
|
-
structure:
|
|
709
|
+
structure: inferStructure(value),
|
|
710
710
|
});
|
|
711
711
|
return typeValue;
|
|
712
712
|
}
|
|
@@ -728,10 +728,10 @@ function createClosuresMixin(Base) {
|
|
|
728
728
|
if (key === 'input') {
|
|
729
729
|
// Untyped host callables have params set to undefined at runtime (see callable() factory)
|
|
730
730
|
if (value.params === undefined) {
|
|
731
|
-
return
|
|
731
|
+
return structureToTypeValue({ kind: 'ordered', fields: [] });
|
|
732
732
|
}
|
|
733
|
-
const fields = value.params.map((param) => paramToFieldDef(param.name, param.type ?? {
|
|
734
|
-
return
|
|
733
|
+
const fields = value.params.map((param) => paramToFieldDef(param.name, param.type ?? { kind: 'any' }, param.defaultValue));
|
|
734
|
+
return structureToTypeValue({ kind: 'ordered', fields });
|
|
735
735
|
}
|
|
736
736
|
// IR-3: ^output reads callable.returnType directly for all kinds
|
|
737
737
|
if (key === 'output') {
|
|
@@ -894,7 +894,7 @@ function createClosuresMixin(Base) {
|
|
|
894
894
|
const paramEntry = {};
|
|
895
895
|
// Add type field if param has type annotation
|
|
896
896
|
if (param.type !== undefined) {
|
|
897
|
-
paramEntry['type'] =
|
|
897
|
+
paramEntry['type'] = formatStructure(param.type);
|
|
898
898
|
}
|
|
899
899
|
// Add __annotations field if param has parameter-level annotations
|
|
900
900
|
if (Object.keys(param.annotations).length > 0) {
|
|
@@ -27,8 +27,9 @@
|
|
|
27
27
|
* @internal
|
|
28
28
|
*/
|
|
29
29
|
import { RuntimeError } from '../../../../types.js';
|
|
30
|
-
import { inferType, isTuple, isOrdered, isTypeValue, createOrdered, createTuple,
|
|
30
|
+
import { inferType, isTuple, isOrdered, isTypeValue, createOrdered, createTuple, deepCopyRillValue, hasCollectionFields, emptyForType, } from '../../values.js';
|
|
31
31
|
import { isDict } from '../../callable.js';
|
|
32
|
+
import { BUILT_IN_TYPES } from '../../type-registrations.js';
|
|
32
33
|
import { getVariable } from '../../context.js';
|
|
33
34
|
/**
|
|
34
35
|
* ConversionMixin implementation.
|
|
@@ -106,7 +107,15 @@ function createConversionMixin(Base) {
|
|
|
106
107
|
}
|
|
107
108
|
/**
|
|
108
109
|
* Apply conversion from source value to target type name.
|
|
109
|
-
*
|
|
110
|
+
* Dispatches to protocol.convertTo on the source type's registration.
|
|
111
|
+
*
|
|
112
|
+
* IR-6: Replaces the hardcoded conversion matrix with protocol dispatch.
|
|
113
|
+
*
|
|
114
|
+
* Special cases preserved:
|
|
115
|
+
* - Same type = no-op (short-circuit)
|
|
116
|
+
* - dict -> :>ordered without structural sig raises RILL-R037 (EC-11)
|
|
117
|
+
* - String-to-number parse failure raises RILL-R038 (EC-12)
|
|
118
|
+
* - Missing convertTo target raises RILL-R036 (EC-10)
|
|
110
119
|
*/
|
|
111
120
|
applyConversion(input, targetType, node) {
|
|
112
121
|
const sourceType = inferType(input);
|
|
@@ -114,105 +123,34 @@ function createConversionMixin(Base) {
|
|
|
114
123
|
if (sourceType === targetType) {
|
|
115
124
|
return input;
|
|
116
125
|
}
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return this.convertToList(input, sourceType, node);
|
|
121
|
-
case 'dict':
|
|
122
|
-
return this.convertToDict(input, sourceType, node);
|
|
123
|
-
case 'tuple':
|
|
124
|
-
return this.convertToTuple(input, sourceType, node);
|
|
125
|
-
case 'ordered':
|
|
126
|
-
// dict -> :>ordered without structural sig is always a runtime error (EC-11)
|
|
127
|
-
if (sourceType === 'dict') {
|
|
128
|
-
throw new RuntimeError('RILL-R037', 'dict to ordered conversion requires structural type signature', this.getNodeLocation(node));
|
|
129
|
-
}
|
|
130
|
-
return this.convertToOrdered(input, sourceType, node);
|
|
131
|
-
case 'number':
|
|
132
|
-
return this.convertToNumber(input, sourceType, node);
|
|
133
|
-
case 'string':
|
|
134
|
-
return this.convertToString(input, sourceType, node);
|
|
135
|
-
case 'bool':
|
|
136
|
-
return this.convertToBoolean(input, sourceType, node);
|
|
137
|
-
default:
|
|
138
|
-
this.throwIncompatible(sourceType, targetType, node);
|
|
126
|
+
// dict -> :>ordered without structural sig is always RILL-R037 (EC-11)
|
|
127
|
+
if (sourceType === 'dict' && targetType === 'ordered') {
|
|
128
|
+
throw new RuntimeError('RILL-R037', 'dict to ordered conversion requires structural type signature', this.getNodeLocation(node));
|
|
139
129
|
}
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (isTuple(input)) {
|
|
146
|
-
return input.entries;
|
|
130
|
+
// Find source type registration and dispatch via protocol.convertTo
|
|
131
|
+
const reg = BUILT_IN_TYPES.find((r) => r.name === sourceType);
|
|
132
|
+
const converter = reg?.protocol.convertTo?.[targetType];
|
|
133
|
+
if (!converter) {
|
|
134
|
+
throw new RuntimeError('RILL-R036', `cannot convert ${sourceType} to ${targetType}`, this.getNodeLocation(node), { source: sourceType, target: targetType });
|
|
147
135
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
/** Convert to dict type. Valid source: ordered. */
|
|
152
|
-
convertToDict(input, sourceType, node) {
|
|
153
|
-
if (isOrdered(input)) {
|
|
154
|
-
const result = {};
|
|
155
|
-
for (const [key, value] of input.entries) {
|
|
156
|
-
result[key] = value;
|
|
157
|
-
}
|
|
158
|
-
return result;
|
|
136
|
+
try {
|
|
137
|
+
return converter(input);
|
|
159
138
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
/** Convert to ordered type. Valid source: dict (with sig, handled separately). */
|
|
172
|
-
convertToOrdered(input, sourceType, node) {
|
|
173
|
-
// Only dict -> ordered is valid, but it requires a sig (checked by caller)
|
|
174
|
-
this.throwIncompatible(sourceType, 'ordered', node);
|
|
175
|
-
return input;
|
|
176
|
-
}
|
|
177
|
-
/** Convert to number type. Valid source: string (parseable) or bool. */
|
|
178
|
-
convertToNumber(input, sourceType, node) {
|
|
179
|
-
if (sourceType === 'string') {
|
|
180
|
-
const str = input;
|
|
181
|
-
const parsed = Number(str);
|
|
182
|
-
if (isNaN(parsed) || str.trim() === '') {
|
|
183
|
-
throw new RuntimeError('RILL-R038', `cannot convert string "${str}" to number`, this.getNodeLocation(node), { value: str });
|
|
139
|
+
catch (err) {
|
|
140
|
+
// Protocol converters throw plain Errors; wrap as RuntimeError
|
|
141
|
+
// with the appropriate error code.
|
|
142
|
+
if (err instanceof RuntimeError)
|
|
143
|
+
throw err;
|
|
144
|
+
// String-to-number parse failures use RILL-R038 (EC-12)
|
|
145
|
+
// Preserve the protocol's detailed message (includes unparseable value).
|
|
146
|
+
if (sourceType === 'string' && targetType === 'number') {
|
|
147
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
148
|
+
throw new RuntimeError('RILL-R038', message, this.getNodeLocation(node), { value: input });
|
|
184
149
|
}
|
|
185
|
-
|
|
150
|
+
// All other conversion failures use RILL-R036 (EC-10)
|
|
151
|
+
// Use consistent "cannot convert X to Y" format.
|
|
152
|
+
throw new RuntimeError('RILL-R036', `cannot convert ${sourceType} to ${targetType}`, this.getNodeLocation(node), { source: sourceType, target: targetType });
|
|
186
153
|
}
|
|
187
|
-
if (sourceType === 'bool') {
|
|
188
|
-
return input ? 1 : 0;
|
|
189
|
-
}
|
|
190
|
-
this.throwIncompatible(sourceType, 'number', node);
|
|
191
|
-
return input;
|
|
192
|
-
}
|
|
193
|
-
/** Convert to bool type. Valid source: number (0 or 1) or string ("true" or "false"). */
|
|
194
|
-
convertToBoolean(input, sourceType, node) {
|
|
195
|
-
if (sourceType === 'number') {
|
|
196
|
-
const n = input;
|
|
197
|
-
if (n === 0)
|
|
198
|
-
return false;
|
|
199
|
-
if (n === 1)
|
|
200
|
-
return true;
|
|
201
|
-
this.throwIncompatible(sourceType, 'bool', node);
|
|
202
|
-
}
|
|
203
|
-
if (sourceType === 'string') {
|
|
204
|
-
const s = input;
|
|
205
|
-
if (s === 'true')
|
|
206
|
-
return true;
|
|
207
|
-
if (s === 'false')
|
|
208
|
-
return false;
|
|
209
|
-
this.throwIncompatible(sourceType, 'bool', node);
|
|
210
|
-
}
|
|
211
|
-
this.throwIncompatible(sourceType, 'bool', node);
|
|
212
|
-
}
|
|
213
|
-
/** Convert to string type. Valid source: any type via formatValue semantics. */
|
|
214
|
-
convertToString(input, _sourceType, _node) {
|
|
215
|
-
return formatValue(input);
|
|
216
154
|
}
|
|
217
155
|
/**
|
|
218
156
|
* Convert dict -> :>ordered(field: type = default, ...) using structural signature.
|
|
@@ -238,7 +176,7 @@ function createConversionMixin(Base) {
|
|
|
238
176
|
// Evaluate the full type constructor to get resolved fields with defaults.
|
|
239
177
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
240
178
|
const typeValue = await this.evaluateTypeConstructor(sigNode);
|
|
241
|
-
const resolvedFields = typeValue.structure.
|
|
179
|
+
const resolvedFields = typeValue.structure.kind === 'ordered' && typeValue.structure.fields
|
|
242
180
|
? typeValue.structure.fields
|
|
243
181
|
: [];
|
|
244
182
|
const entries = [];
|
|
@@ -292,7 +230,7 @@ function createConversionMixin(Base) {
|
|
|
292
230
|
// Evaluate the full type constructor to get resolved fields with defaults.
|
|
293
231
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
294
232
|
const typeValue = await this.evaluateTypeConstructor(sigNode);
|
|
295
|
-
const resolvedFields = typeValue.structure.
|
|
233
|
+
const resolvedFields = typeValue.structure.kind === 'dict' && typeValue.structure.fields
|
|
296
234
|
? typeValue.structure.fields
|
|
297
235
|
: {};
|
|
298
236
|
const result = {};
|
|
@@ -345,7 +283,7 @@ function createConversionMixin(Base) {
|
|
|
345
283
|
// Evaluate the full type constructor to get resolved elements with defaults.
|
|
346
284
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
347
285
|
const typeValue = await this.evaluateTypeConstructor(sigNode);
|
|
348
|
-
const resolvedElements = typeValue.structure.
|
|
286
|
+
const resolvedElements = typeValue.structure.kind === 'tuple' && typeValue.structure.elements
|
|
349
287
|
? typeValue.structure.elements
|
|
350
288
|
: [];
|
|
351
289
|
const inputEntries = isTupleInput
|
|
@@ -379,10 +317,13 @@ function createConversionMixin(Base) {
|
|
|
379
317
|
* Returns the value unchanged if the type has no fields or the value type does not match.
|
|
380
318
|
*/
|
|
381
319
|
hydrateNested(value, fieldType, node) {
|
|
382
|
-
if (fieldType.
|
|
320
|
+
if (fieldType.kind === 'dict' &&
|
|
321
|
+
fieldType.fields &&
|
|
322
|
+
isDict(value)) {
|
|
323
|
+
const ft = fieldType;
|
|
383
324
|
const dictValue = value;
|
|
384
325
|
const result = {};
|
|
385
|
-
for (const [fieldName, resolvedField] of Object.entries(
|
|
326
|
+
for (const [fieldName, resolvedField] of Object.entries(ft.fields)) {
|
|
386
327
|
if (fieldName in dictValue) {
|
|
387
328
|
const fieldValue = this.hydrateNested(dictValue[fieldName], resolvedField.type, node);
|
|
388
329
|
result[fieldName] = fieldValue;
|
|
@@ -401,7 +342,9 @@ function createConversionMixin(Base) {
|
|
|
401
342
|
}
|
|
402
343
|
return result;
|
|
403
344
|
}
|
|
404
|
-
else if (fieldType.
|
|
345
|
+
else if (fieldType.kind === 'ordered' &&
|
|
346
|
+
fieldType.fields) {
|
|
347
|
+
const ft = fieldType;
|
|
405
348
|
// Only hydrate if the runtime value is an ordered or dict; return unchanged otherwise.
|
|
406
349
|
if (!isOrdered(value) && !isDict(value)) {
|
|
407
350
|
return value;
|
|
@@ -412,7 +355,7 @@ function createConversionMixin(Base) {
|
|
|
412
355
|
? value.entries.map(([k, v]) => [k, v])
|
|
413
356
|
: Object.entries(value));
|
|
414
357
|
const resultEntries = [];
|
|
415
|
-
for (const field of
|
|
358
|
+
for (const field of ft.fields) {
|
|
416
359
|
const name = field.name;
|
|
417
360
|
if (lookup.has(name)) {
|
|
418
361
|
const fieldValue = this.hydrateNested(lookup.get(name), field.type, node);
|
|
@@ -436,15 +379,17 @@ function createConversionMixin(Base) {
|
|
|
436
379
|
}
|
|
437
380
|
return createOrdered(resultEntries);
|
|
438
381
|
}
|
|
439
|
-
else if (fieldType.
|
|
382
|
+
else if (fieldType.kind === 'tuple' &&
|
|
383
|
+
fieldType.elements) {
|
|
384
|
+
const ft = fieldType;
|
|
440
385
|
// Only hydrate if the runtime value is a tuple; return unchanged otherwise.
|
|
441
386
|
if (!isTuple(value)) {
|
|
442
387
|
return value;
|
|
443
388
|
}
|
|
444
389
|
const inputEntries = value.entries;
|
|
445
390
|
const resultEntries = [];
|
|
446
|
-
for (let i = 0; i <
|
|
447
|
-
const element =
|
|
391
|
+
for (let i = 0; i < ft.elements.length; i++) {
|
|
392
|
+
const element = ft.elements[i];
|
|
448
393
|
if (i < inputEntries.length) {
|
|
449
394
|
const elementValue = this.hydrateNested(inputEntries[i], element.type, node);
|
|
450
395
|
resultEntries.push(elementValue);
|
|
@@ -463,10 +408,6 @@ function createConversionMixin(Base) {
|
|
|
463
408
|
}
|
|
464
409
|
return value;
|
|
465
410
|
}
|
|
466
|
-
/** Throw EC-10 incompatible conversion error. */
|
|
467
|
-
throwIncompatible(source, target, node) {
|
|
468
|
-
throw new RuntimeError('RILL-R036', `cannot convert ${source} to ${target}`, this.getNodeLocation(node), { source, target });
|
|
469
|
-
}
|
|
470
411
|
};
|
|
471
412
|
}
|
|
472
413
|
/**
|
|
@@ -282,8 +282,8 @@ function createCoreMixin(Base) {
|
|
|
282
282
|
primary.typeName === 'ordered' ||
|
|
283
283
|
primary.typeName === 'vector' ||
|
|
284
284
|
primary.typeName === 'type'
|
|
285
|
-
? {
|
|
286
|
-
: {
|
|
285
|
+
? { kind: primary.typeName }
|
|
286
|
+
: { kind: 'any' },
|
|
287
287
|
});
|
|
288
288
|
case 'TypeConstructor':
|
|
289
289
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -18,9 +18,17 @@
|
|
|
18
18
|
* @internal
|
|
19
19
|
*/
|
|
20
20
|
import { RuntimeError } from '../../../../types.js';
|
|
21
|
-
import { inferType, isTruthy
|
|
21
|
+
import { inferType, isTruthy } from '../../values.js';
|
|
22
|
+
import { BUILT_IN_TYPES } from '../../type-registrations.js';
|
|
22
23
|
import { createChildContext } from '../../context.js';
|
|
23
24
|
import { isCallable } from '../../callable.js';
|
|
25
|
+
/**
|
|
26
|
+
* Find the type registration for a value by type name.
|
|
27
|
+
* Returns undefined when no registration matches.
|
|
28
|
+
*/
|
|
29
|
+
function findRegistration(typeName) {
|
|
30
|
+
return BUILT_IN_TYPES.find((r) => r.name === typeName);
|
|
31
|
+
}
|
|
24
32
|
/**
|
|
25
33
|
* ExpressionsMixin implementation.
|
|
26
34
|
*
|
|
@@ -150,39 +158,39 @@ function createExpressionsMixin(Base) {
|
|
|
150
158
|
}
|
|
151
159
|
}
|
|
152
160
|
/**
|
|
153
|
-
* Evaluate comparison between two values.
|
|
154
|
-
*
|
|
161
|
+
* Evaluate comparison between two values via protocol dispatch.
|
|
162
|
+
*
|
|
163
|
+
* - == / != dispatch to protocol.eq; absent eq raises RILL-R002.
|
|
164
|
+
* - Ordering ops dispatch to protocol.compare; absent compare raises RILL-R002.
|
|
165
|
+
*
|
|
166
|
+
* IR-5: Breaking change: bool ordering (e.g. true > false) raises RILL-R002
|
|
167
|
+
* because the bool registration has no protocol.compare.
|
|
155
168
|
*/
|
|
156
169
|
evaluateBinaryComparison(left, right, op, node) {
|
|
170
|
+
const typeName = inferType(left);
|
|
171
|
+
const reg = findRegistration(typeName);
|
|
172
|
+
if (op === '==' || op === '!=') {
|
|
173
|
+
if (!reg || !reg.protocol.eq) {
|
|
174
|
+
throw new RuntimeError('RILL-R002', `Cannot compare ${typeName} using ${op}`, node.span.start);
|
|
175
|
+
}
|
|
176
|
+
const eqResult = reg.protocol.eq(left, right);
|
|
177
|
+
return op === '==' ? eqResult : !eqResult;
|
|
178
|
+
}
|
|
179
|
+
// Ordering ops: <, >, <=, >=
|
|
180
|
+
const rightTypeName = inferType(right);
|
|
181
|
+
if (!reg || !reg.protocol.compare || typeName !== rightTypeName) {
|
|
182
|
+
throw new RuntimeError('RILL-R002', `Cannot compare ${typeName} with ${rightTypeName} using ${op}`, node.span.start);
|
|
183
|
+
}
|
|
184
|
+
const cmp = reg.protocol.compare(left, right);
|
|
157
185
|
switch (op) {
|
|
158
|
-
case '==':
|
|
159
|
-
return deepEquals(left, right);
|
|
160
|
-
case '!=':
|
|
161
|
-
return !deepEquals(left, right);
|
|
162
186
|
case '<':
|
|
187
|
+
return cmp < 0;
|
|
163
188
|
case '>':
|
|
189
|
+
return cmp > 0;
|
|
164
190
|
case '<=':
|
|
191
|
+
return cmp <= 0;
|
|
165
192
|
case '>=':
|
|
166
|
-
|
|
167
|
-
if (typeof left === 'number' && typeof right === 'number') {
|
|
168
|
-
return op === '<'
|
|
169
|
-
? left < right
|
|
170
|
-
: op === '>'
|
|
171
|
-
? left > right
|
|
172
|
-
: op === '<='
|
|
173
|
-
? left <= right
|
|
174
|
-
: left >= right;
|
|
175
|
-
}
|
|
176
|
-
if (typeof left === 'string' && typeof right === 'string') {
|
|
177
|
-
return op === '<'
|
|
178
|
-
? left < right
|
|
179
|
-
: op === '>'
|
|
180
|
-
? left > right
|
|
181
|
-
: op === '<='
|
|
182
|
-
? left <= right
|
|
183
|
-
: left >= right;
|
|
184
|
-
}
|
|
185
|
-
throw new RuntimeError('RILL-R002', `Cannot compare ${inferType(left)} with ${inferType(right)} using ${op}`, node.span.start);
|
|
193
|
+
return cmp >= 0;
|
|
186
194
|
}
|
|
187
195
|
}
|
|
188
196
|
/**
|
|
@@ -642,13 +642,13 @@ function createLiteralsMixin(Base) {
|
|
|
642
642
|
if (resolvedType === undefined && defaultValue !== undefined) {
|
|
643
643
|
const defaultKind = typeof defaultValue;
|
|
644
644
|
if (defaultKind === 'string') {
|
|
645
|
-
resolvedType = {
|
|
645
|
+
resolvedType = { kind: 'string' };
|
|
646
646
|
}
|
|
647
647
|
else if (defaultKind === 'number') {
|
|
648
|
-
resolvedType = {
|
|
648
|
+
resolvedType = { kind: 'number' };
|
|
649
649
|
}
|
|
650
650
|
else if (defaultKind === 'boolean') {
|
|
651
|
-
resolvedType = {
|
|
651
|
+
resolvedType = { kind: 'bool' };
|
|
652
652
|
}
|
|
653
653
|
}
|
|
654
654
|
// Evaluate per-param annotations inline
|