@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.
Files changed (97) hide show
  1. package/README.md +37 -21
  2. package/dist/ast-nodes.d.ts +14 -4
  3. package/dist/ast-unions.d.ts +1 -1
  4. package/dist/constants.d.ts +1 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/error-registry.js +228 -0
  7. package/dist/ext/crypto/index.d.ts +3 -3
  8. package/dist/ext/crypto/index.js +62 -59
  9. package/dist/ext/exec/index.d.ts +3 -3
  10. package/dist/ext/exec/index.js +15 -9
  11. package/dist/ext/fetch/index.d.ts +3 -3
  12. package/dist/ext/fetch/index.js +17 -12
  13. package/dist/ext/fetch/request.js +1 -1
  14. package/dist/ext/fs/index.d.ts +3 -3
  15. package/dist/ext/fs/index.js +256 -266
  16. package/dist/ext/fs/sandbox.d.ts +18 -0
  17. package/dist/ext/fs/sandbox.js +33 -0
  18. package/dist/ext/kv/index.d.ts +3 -3
  19. package/dist/ext/kv/index.js +198 -196
  20. package/dist/ext/kv/store.d.ts +1 -1
  21. package/dist/ext/kv/store.js +2 -1
  22. package/dist/ext-parse-bridge.d.ts +10 -0
  23. package/dist/ext-parse-bridge.js +10 -0
  24. package/dist/generated/introspection-data.d.ts +1 -1
  25. package/dist/generated/introspection-data.js +385 -296
  26. package/dist/generated/version-data.d.ts +1 -1
  27. package/dist/generated/version-data.js +2 -2
  28. package/dist/highlight-map.js +1 -0
  29. package/dist/index.d.ts +1 -4
  30. package/dist/index.js +1 -5
  31. package/dist/lexer/operators.js +1 -0
  32. package/dist/parser/helpers.js +1 -0
  33. package/dist/parser/parser-expr.js +44 -5
  34. package/dist/parser/parser-literals.js +111 -4
  35. package/dist/parser/parser-shape.js +2 -2
  36. package/dist/parser/parser-types.js +12 -0
  37. package/dist/parser/parser-use.js +26 -3
  38. package/dist/parser/parser.d.ts +2 -0
  39. package/dist/parser/parser.js +2 -0
  40. package/dist/runtime/core/callable.d.ts +24 -13
  41. package/dist/runtime/core/callable.js +71 -38
  42. package/dist/runtime/core/context.d.ts +2 -13
  43. package/dist/runtime/core/context.js +80 -79
  44. package/dist/runtime/core/eval/base.d.ts +2 -2
  45. package/dist/runtime/core/eval/base.js +2 -0
  46. package/dist/runtime/core/eval/evaluator.d.ts +1 -1
  47. package/dist/runtime/core/eval/index.d.ts +3 -3
  48. package/dist/runtime/core/eval/index.js +11 -0
  49. package/dist/runtime/core/eval/mixins/closures.js +381 -41
  50. package/dist/runtime/core/eval/mixins/collections.js +81 -6
  51. package/dist/runtime/core/eval/mixins/control-flow.js +1 -1
  52. package/dist/runtime/core/eval/mixins/conversion.js +61 -115
  53. package/dist/runtime/core/eval/mixins/core.js +17 -4
  54. package/dist/runtime/core/eval/mixins/expressions.js +36 -27
  55. package/dist/runtime/core/eval/mixins/extraction.js +2 -3
  56. package/dist/runtime/core/eval/mixins/list-dispatch.js +1 -1
  57. package/dist/runtime/core/eval/mixins/literals.js +17 -6
  58. package/dist/runtime/core/eval/mixins/types.js +73 -54
  59. package/dist/runtime/core/eval/mixins/variables.js +12 -8
  60. package/dist/runtime/core/execute.d.ts +1 -1
  61. package/dist/runtime/core/field-descriptor.d.ts +3 -3
  62. package/dist/runtime/core/field-descriptor.js +2 -1
  63. package/dist/runtime/core/introspection.d.ts +2 -2
  64. package/dist/runtime/core/introspection.js +7 -6
  65. package/dist/runtime/core/resolvers.d.ts +1 -1
  66. package/dist/runtime/core/signals.d.ts +6 -1
  67. package/dist/runtime/core/signals.js +9 -0
  68. package/dist/runtime/core/types/constructors.d.ts +54 -0
  69. package/dist/runtime/core/types/constructors.js +201 -0
  70. package/dist/runtime/core/types/guards.d.ts +42 -0
  71. package/dist/runtime/core/types/guards.js +88 -0
  72. package/dist/runtime/core/types/index.d.ts +18 -0
  73. package/dist/runtime/core/types/index.js +19 -0
  74. package/dist/runtime/core/types/markers.d.ts +12 -0
  75. package/dist/runtime/core/types/markers.js +7 -0
  76. package/dist/runtime/core/types/operations.d.ts +98 -0
  77. package/dist/runtime/core/types/operations.js +804 -0
  78. package/dist/runtime/core/types/registrations.d.ts +126 -0
  79. package/dist/runtime/core/types/registrations.js +751 -0
  80. package/dist/runtime/core/{types.d.ts → types/runtime.d.ts} +22 -10
  81. package/dist/runtime/core/types/structures.d.ts +146 -0
  82. package/dist/runtime/core/types/structures.js +12 -0
  83. package/dist/runtime/core/values.d.ts +29 -209
  84. package/dist/runtime/core/values.js +56 -968
  85. package/dist/runtime/ext/builtins.js +88 -68
  86. package/dist/runtime/ext/extensions.d.ts +31 -125
  87. package/dist/runtime/ext/extensions.js +2 -94
  88. package/dist/runtime/ext/test-context.d.ts +28 -0
  89. package/dist/runtime/ext/test-context.js +155 -0
  90. package/dist/runtime/index.d.ts +12 -12
  91. package/dist/runtime/index.js +13 -5
  92. package/dist/signature-parser.d.ts +2 -2
  93. package/dist/signature-parser.js +14 -14
  94. package/dist/token-types.d.ts +1 -0
  95. package/dist/token-types.js +1 -0
  96. package/package.json +1 -1
  97. /package/dist/runtime/core/{types.js → types/runtime.js} +0 -0
@@ -21,14 +21,13 @@
21
21
  */
22
22
  import { RuntimeError } from '../../types.js';
23
23
  import { astEquals } from './equals.js';
24
- import { formatValue, formatStructuralType, inferType, isOrdered, createOrdered, deepCopyRillValue, isTuple, paramToFieldDef, structuralTypeEquals, structuralTypeMatches, anyTypeValue, hasCollectionFields, emptyForType, } from './values.js';
25
- /** Type guard for any callable */
26
- export function isCallable(value) {
27
- return (typeof value === 'object' &&
28
- value !== null &&
29
- '__type' in value &&
30
- value.__type === 'callable');
31
- }
24
+ import { isCallable as _isCallableGuard, isDict, isOrdered, isTuple, } from './types/guards.js';
25
+ import { formatValue, inferType } from './types/registrations.js';
26
+ import { formatStructure, paramToFieldDef, structureEquals, structureMatches, } from './types/operations.js';
27
+ import { createOrdered, copyValue, emptyForType, } from './types/constructors.js';
28
+ import { anyTypeValue, hasCollectionFields } from './values.js';
29
+ /** Type guard for any callable (delegates to types/guards.ts) */
30
+ export const isCallable = _isCallableGuard;
32
31
  /** Type guard for script callable */
33
32
  export function isScriptCallable(value) {
34
33
  return isCallable(value) && value.kind === 'script';
@@ -61,14 +60,37 @@ export function callable(fn, isProperty = false) {
61
60
  isProperty,
62
61
  };
63
62
  }
64
- /** Type guard for dict (plain object, not array, not callable, not tuple) */
65
- export function isDict(value) {
66
- return (typeof value === 'object' &&
67
- value !== null &&
68
- !Array.isArray(value) &&
69
- !isCallable(value) &&
70
- !isTuple(value));
63
+ /**
64
+ * Convert a RillFunction to an ApplicationCallable.
65
+ *
66
+ * Validates the input and produces a callable value accepted by the loader.
67
+ * Pure function with no side effects.
68
+ *
69
+ * @param def - Host function definition to convert
70
+ * @returns ApplicationCallable with __type, kind, isProperty, and preserved annotations
71
+ */
72
+ export function toCallable(def, isProperty = false) {
73
+ if (def == null) {
74
+ throw new TypeError('RillFunction cannot be null or undefined');
75
+ }
76
+ if (typeof def.fn !== 'function') {
77
+ throw new TypeError('RillFunction.fn must be a function');
78
+ }
79
+ if (!Array.isArray(def.params)) {
80
+ throw new TypeError('RillFunction.params must be an array');
81
+ }
82
+ return {
83
+ __type: 'callable',
84
+ kind: 'application',
85
+ isProperty,
86
+ fn: def.fn,
87
+ params: def.params,
88
+ returnType: def.returnType,
89
+ annotations: def.annotations ?? {},
90
+ };
71
91
  }
92
+ // isDict imported from ./types/guards.js and re-exported
93
+ export { isDict };
72
94
  /** Format a callable for display */
73
95
  export function formatCallable(callable) {
74
96
  if (callable.kind === 'script') {
@@ -116,14 +138,14 @@ export function callableEquals(a, b, valueEquals = (x, y) => formatValue(x) ===
116
138
  return false;
117
139
  if (ap.name !== bp.name)
118
140
  return false;
119
- // Compare type via structuralTypeEquals; absent type (any-typed) matches absent type
141
+ // Compare type via structureEquals; absent type (any-typed) matches absent type
120
142
  if (ap.type === undefined && bp.type !== undefined)
121
143
  return false;
122
144
  if (ap.type !== undefined && bp.type === undefined)
123
145
  return false;
124
146
  if (ap.type !== undefined &&
125
147
  bp.type !== undefined &&
126
- !structuralTypeEquals(ap.type, bp.type))
148
+ !structureEquals(ap.type, bp.type))
127
149
  return false;
128
150
  if (!valueEquals(ap.defaultValue ?? null, bp.defaultValue ?? null)) {
129
151
  return false;
@@ -146,24 +168,24 @@ export function callableEquals(a, b, valueEquals = (x, y) => formatValue(x) ===
146
168
  return true;
147
169
  }
148
170
  /**
149
- * Build a RillType closure variant from a closure's parameter list.
171
+ * Build a TypeStructure closure variant from a closure's parameter list.
150
172
  *
151
173
  * Called at closure creation time to build the structural type for `$fn.^input`.
152
174
  * - Typed params use param.type directly when present
153
- * - Untyped params (type: undefined) map to { type: 'any' }
154
- * - Return type is always { type: 'any' }
175
+ * - Untyped params (type: undefined) map to { kind: 'any' }
176
+ * - Return type is always { kind: 'any' }
155
177
  *
156
178
  * No validation: parser already validates type names.
157
179
  *
158
180
  * @param params - Closure parameter definitions (RillParam[])
159
- * @returns Frozen RillType with closure variant
181
+ * @returns Frozen TypeStructure with closure variant
160
182
  */
161
183
  export function paramsToStructuralType(params) {
162
- const closureParams = params.map((param) => paramToFieldDef(param.name, param.type ?? { type: 'any' }, param.defaultValue));
184
+ const closureParams = params.map((param) => paramToFieldDef(param.name, param.type ?? { kind: 'any' }, param.defaultValue));
163
185
  return Object.freeze({
164
- type: 'closure',
186
+ kind: 'closure',
165
187
  params: closureParams,
166
- ret: { type: 'any' },
188
+ ret: { kind: 'any' },
167
189
  });
168
190
  }
169
191
  /**
@@ -182,10 +204,10 @@ export function validateDefaultValueType(param, _functionName) {
182
204
  // Skip validation when type is undefined (any-typed, all defaults valid)
183
205
  if (param.type === undefined)
184
206
  return;
185
- if (!structuralTypeMatches(param.defaultValue, param.type)) {
207
+ if (!structureMatches(param.defaultValue, param.type)) {
186
208
  const actualType = inferType(param.defaultValue);
187
- const expectedType = formatStructuralType(param.type);
188
- throw new Error(`Invalid defaultValue for parameter '${param.name}': expected ${expectedType}, got ${actualType}`);
209
+ const expectedType = formatStructure(param.type);
210
+ throw new RuntimeError('RILL-R077', `Invalid defaultValue for parameter '${param.name}': expected ${expectedType}, got ${actualType}`);
189
211
  }
190
212
  }
191
213
  /**
@@ -198,16 +220,17 @@ export function validateDefaultValueType(param, _functionName) {
198
220
  * Pure function: no class context, no evaluator, no side effects.
199
221
  */
200
222
  export function hydrateFieldDefaults(value, type) {
201
- if (type.type === 'dict' && type.fields && isDict(value)) {
223
+ if (type.kind === 'dict' && type.fields && isDict(value)) {
224
+ const t = type;
202
225
  const dictValue = value;
203
226
  // Seed with all input entries so extra keys survive (structural match allows extras)
204
227
  const result = { ...dictValue };
205
- for (const [fieldName, fieldDef] of Object.entries(type.fields)) {
228
+ for (const [fieldName, fieldDef] of Object.entries(t.fields)) {
206
229
  if (fieldName in dictValue) {
207
230
  result[fieldName] = hydrateFieldDefaults(dictValue[fieldName], fieldDef.type);
208
231
  }
209
232
  else if (fieldDef.defaultValue !== undefined) {
210
- result[fieldName] = hydrateFieldDefaults(deepCopyRillValue(fieldDef.defaultValue), fieldDef.type);
233
+ result[fieldName] = hydrateFieldDefaults(copyValue(fieldDef.defaultValue), fieldDef.type);
211
234
  }
212
235
  else if (hasCollectionFields(fieldDef.type)) {
213
236
  result[fieldName] = hydrateFieldDefaults(emptyForType(fieldDef.type), fieldDef.type);
@@ -216,11 +239,14 @@ export function hydrateFieldDefaults(value, type) {
216
239
  }
217
240
  return result;
218
241
  }
219
- if (type.type === 'ordered' && type.fields && isOrdered(value)) {
242
+ if (type.kind === 'ordered' &&
243
+ type.fields &&
244
+ isOrdered(value)) {
245
+ const t = type;
220
246
  const lookup = new Map(value.entries.map(([k, v]) => [k, v]));
221
- const fieldNames = new Set(type.fields.map((f) => f.name ?? ''));
247
+ const fieldNames = new Set(t.fields.map((f) => f.name ?? ''));
222
248
  const resultEntries = [];
223
- for (const field of type.fields) {
249
+ for (const field of t.fields) {
224
250
  const name = field.name ?? '';
225
251
  if (lookup.has(name)) {
226
252
  resultEntries.push([
@@ -231,7 +257,7 @@ export function hydrateFieldDefaults(value, type) {
231
257
  else if (field.defaultValue !== undefined) {
232
258
  resultEntries.push([
233
259
  name,
234
- hydrateFieldDefaults(deepCopyRillValue(field.defaultValue), field.type),
260
+ hydrateFieldDefaults(copyValue(field.defaultValue), field.type),
235
261
  ]);
236
262
  }
237
263
  else if (hasCollectionFields(field.type)) {
@@ -250,7 +276,9 @@ export function hydrateFieldDefaults(value, type) {
250
276
  }
251
277
  return createOrdered(resultEntries);
252
278
  }
253
- if (type.type === 'tuple' && type.elements && isTuple(value)) {
279
+ if (type.kind === 'tuple' &&
280
+ type.elements &&
281
+ isTuple(value)) {
254
282
  const elements = type.elements;
255
283
  const entries = value.entries;
256
284
  // All fields present: recurse into nested types for present positions
@@ -270,7 +298,7 @@ export function hydrateFieldDefaults(value, type) {
270
298
  resultEntries.push(hydrateFieldDefaults(entries[i], el.type));
271
299
  }
272
300
  else if (el.defaultValue !== undefined) {
273
- resultEntries.push(hydrateFieldDefaults(deepCopyRillValue(el.defaultValue), el.type));
301
+ resultEntries.push(hydrateFieldDefaults(copyValue(el.defaultValue), el.type));
274
302
  }
275
303
  else if (hasCollectionFields(el.type)) {
276
304
  resultEntries.push(hydrateFieldDefaults(emptyForType(el.type), el.type));
@@ -327,6 +355,11 @@ export function marshalArgs(args, params, options) {
327
355
  if (param.defaultValue !== undefined) {
328
356
  value = param.defaultValue;
329
357
  }
358
+ else if (param.type !== undefined && hasCollectionFields(param.type)) {
359
+ // Collection-typed param with field-level defaults: synthesize empty
360
+ // collection so Stage 2.5 (hydrateFieldDefaults) can fill in defaults
361
+ value = emptyForType(param.type);
362
+ }
330
363
  else {
331
364
  // Stage 2: Missing required parameter
332
365
  throw new RuntimeError('RILL-R044', `Missing argument for parameter '${param.name}'`, location, {
@@ -341,8 +374,8 @@ export function marshalArgs(args, params, options) {
341
374
  }
342
375
  // Stage 3: Type check when param.type is defined
343
376
  if (param.type !== undefined) {
344
- if (!structuralTypeMatches(value, param.type)) {
345
- const expectedType = formatStructuralType(param.type);
377
+ if (!structureMatches(value, param.type)) {
378
+ const expectedType = formatStructure(param.type);
346
379
  const actualType = inferType(value);
347
380
  throw new RuntimeError('RILL-R001', `Parameter type mismatch: ${param.name} expects ${expectedType}, got ${actualType}`, location, {
348
381
  functionName,
@@ -4,20 +4,9 @@
4
4
  * Creates and configures the runtime context for script execution.
5
5
  * Public API for host applications.
6
6
  */
7
- import type { RuntimeContext, RuntimeOptions } from './types.js';
8
- import { type RillValue } from './values.js';
9
- import { type RillFunction } from './callable.js';
7
+ import type { RuntimeContext, RuntimeOptions } from './types/runtime.js';
8
+ import type { RillValue } from './types/structures.js';
10
9
  export declare const UNVALIDATED_METHOD_PARAMS: Set<string>;
11
- export declare const UNVALIDATED_METHOD_RECEIVERS: Set<string>;
12
- /**
13
- * Build a ReadonlyMap of frozen ApplicationCallable dicts from an array of
14
- * [typeName, methods] pairs. Accepts pairs (not a plain object) so the same
15
- * typeName can appear more than once — duplicate method names across entries
16
- * for the same type trigger an Error (EC-6).
17
- *
18
- * Re-exported from the public barrel index for host integration use.
19
- */
20
- export declare function buildTypeMethodDicts(pairs: Array<[string, Record<string, RillFunction>]>): ReadonlyMap<string, Readonly<Record<string, RillValue>>>;
21
10
  /**
22
11
  * Create a runtime context for script execution.
23
12
  * This is the main entry point for configuring the Rill runtime.
@@ -5,9 +5,10 @@
5
5
  * Public API for host applications.
6
6
  */
7
7
  import { RuntimeError } from '../../types.js';
8
- import { BUILTIN_FUNCTIONS, BUILTIN_METHODS } from '../ext/builtins.js';
9
- import { bindDictCallables } from './types.js';
10
- import { inferType } from './values.js';
8
+ import { BUILTIN_FUNCTIONS } from '../ext/builtins.js';
9
+ import { BUILT_IN_TYPES } from './types/registrations.js';
10
+ import { bindDictCallables } from './types/runtime.js';
11
+ import { inferType } from './types/registrations.js';
11
12
  import { callable, validateDefaultValueType, } from './callable.js';
12
13
  // Built-in functions that are genuinely variadic and must skip arg validation.
13
14
  // log: tests call log("msg", extraValue) — extra args are silently ignored.
@@ -17,69 +18,23 @@ const UNTYPED_BUILTINS = new Set(['log', 'chain']);
17
18
  // messages expected by protected language tests. Generic marshalArgs
18
19
  // must not fire before the method body's own check.
19
20
  export const UNVALIDATED_METHOD_PARAMS = new Set(['has', 'has_any', 'has_all']);
20
- // Built-in methods that perform their own receiver type checking with specific
21
- // error messages. Generic RILL-R003 must not fire before the method body runs.
22
- // Mirrors the old flat-structure convention of receiverTypes: [].
23
- export const UNVALIDATED_METHOD_RECEIVERS = new Set([
24
- 'head',
25
- 'tail',
26
- 'first',
27
- 'at',
28
- 'eq',
29
- 'ne',
30
- 'keys',
31
- 'values',
32
- 'entries',
33
- 'has',
34
- 'has_any',
35
- 'has_all',
36
- 'dimensions',
37
- 'model',
38
- 'similarity',
39
- 'dot',
40
- 'distance',
41
- 'norm',
42
- 'normalize',
43
- ]);
44
21
  /**
45
- * Build a ReadonlyMap of frozen ApplicationCallable dicts from an array of
46
- * [typeName, methods] pairs. Accepts pairs (not a plain object) so the same
47
- * typeName can appear more than once duplicate method names across entries
48
- * for the same type trigger an Error (EC-6).
49
- *
50
- * Re-exported from the public barrel index for host integration use.
22
+ * Derive the set of method names that handle their own receiver type checking.
23
+ * Collects names from methods where skipReceiverValidation is true.
24
+ * Methods without the flag default to standard RILL-R003 receiver validation.
51
25
  */
52
- export function buildTypeMethodDicts(pairs) {
53
- const registry = new Map();
54
- const result = new Map();
55
- for (const [typeName, methods] of pairs) {
56
- const seen = registry.get(typeName) ?? new Set();
57
- registry.set(typeName, seen);
58
- const existing = result.get(typeName) ?? {};
59
- const dict = { ...existing };
60
- for (const [name, fn] of Object.entries(methods)) {
61
- if (seen.has(name)) {
62
- throw new Error(`Duplicate method '${name}' on type '${typeName}'`);
26
+ function deriveUnvalidatedMethodReceivers(registrations) {
27
+ const bypass = new Set();
28
+ for (const reg of registrations) {
29
+ for (const [name, method] of Object.entries(reg.methods)) {
30
+ if (method.skipReceiverValidation) {
31
+ bypass.add(name);
63
32
  }
64
- seen.add(name);
65
- const appCallable = {
66
- __type: 'callable',
67
- kind: 'application',
68
- params: fn.params,
69
- returnType: fn.returnType,
70
- annotations: fn.annotations ?? {},
71
- isProperty: false,
72
- fn: fn.fn,
73
- };
74
- dict[name] = appCallable;
75
33
  }
76
- result.set(typeName, Object.freeze(dict));
77
34
  }
78
- return result;
35
+ return Object.freeze(bypass);
79
36
  }
80
37
  const BUILTIN_FN_CACHE = new Map();
81
- const BUILTIN_METHOD_PARAMS_CACHE = new Map();
82
- const BUILTIN_METHOD_RECEIVER_TYPES_CACHE = new Map();
83
38
  function initBuiltinCaches() {
84
39
  for (const [name, entry] of Object.entries(BUILTIN_FUNCTIONS)) {
85
40
  if (UNTYPED_BUILTINS.has(name)) {
@@ -94,21 +49,6 @@ function initBuiltinCaches() {
94
49
  };
95
50
  BUILTIN_FN_CACHE.set(name, { appCallable: typedCallable });
96
51
  }
97
- for (const [typeName, methods] of Object.entries(BUILTIN_METHODS)) {
98
- for (const [name, impl] of Object.entries(methods)) {
99
- // Accumulate receiver types across all type groups for this method name.
100
- // Skip methods that perform their own receiver type checking.
101
- if (!UNVALIDATED_METHOD_RECEIVERS.has(name)) {
102
- const existing = BUILTIN_METHOD_RECEIVER_TYPES_CACHE.get(name);
103
- BUILTIN_METHOD_RECEIVER_TYPES_CACHE.set(name, existing !== undefined ? [...existing, typeName] : [typeName]);
104
- }
105
- if (!UNVALIDATED_METHOD_PARAMS.has(name)) {
106
- if (impl.params.length > 0) {
107
- BUILTIN_METHOD_PARAMS_CACHE.set(name, impl.params);
108
- }
109
- }
110
- }
111
- }
112
52
  }
113
53
  // Initialise once at module load.
114
54
  initBuiltinCaches();
@@ -153,7 +93,7 @@ export function createRuntimeContext(options = {}) {
153
93
  if (description === undefined ||
154
94
  typeof description !== 'string' ||
155
95
  description.trim().length === 0) {
156
- throw new Error(`Function '${name}' requires description (requireDescriptions enabled)`);
96
+ throw new RuntimeError('RILL-R069', `Function '${name}' requires description (requireDescriptions enabled)`);
157
97
  }
158
98
  // Check parameter descriptions (EC-11)
159
99
  for (const param of params) {
@@ -161,7 +101,7 @@ export function createRuntimeContext(options = {}) {
161
101
  if (paramDesc === undefined ||
162
102
  typeof paramDesc !== 'string' ||
163
103
  paramDesc.trim().length === 0) {
164
- throw new Error(`Parameter '${param.name}' of function '${name}' requires description (requireDescriptions enabled)`);
104
+ throw new RuntimeError('RILL-R070', `Parameter '${param.name}' of function '${name}' requires description (requireDescriptions enabled)`);
165
105
  }
166
106
  }
167
107
  }
@@ -194,15 +134,74 @@ export function createRuntimeContext(options = {}) {
194
134
  const resolverConfigs = new Map(options.configurations?.resolvers
195
135
  ? Object.entries(options.configurations.resolvers)
196
136
  : []);
197
- // Build typeMethodDicts fresh per context so duplicate detection (EC-6)
198
- // uses isolated state and does not leak across context instances.
199
- const typeMethodDicts = buildTypeMethodDicts(Object.entries(BUILTIN_METHODS));
137
+ // EC-1: Validate no duplicate type names in registrations.
138
+ const seenTypeNames = new Set();
139
+ for (const reg of BUILT_IN_TYPES) {
140
+ if (seenTypeNames.has(reg.name)) {
141
+ throw new RuntimeError('RILL-R071', `Duplicate type registration '${reg.name}'`);
142
+ }
143
+ seenTypeNames.add(reg.name);
144
+ }
145
+ // EC-2: Validate every registration has protocol.format.
146
+ for (const reg of BUILT_IN_TYPES) {
147
+ if (!reg.protocol.format) {
148
+ throw new RuntimeError('RILL-R072', `Type '${reg.name}' missing required format protocol`);
149
+ }
150
+ }
151
+ // Derive typeNames from registrations (replaces VALID_TYPE_NAMES in context).
152
+ const typeNames = Object.freeze(BUILT_IN_TYPES.map((r) => r.name));
153
+ // Derive leafTypes from registrations where isLeaf === true, plus 'any'
154
+ // which has no registration but rejects type arguments (AC-4).
155
+ const leafTypes = Object.freeze(new Set([
156
+ ...BUILT_IN_TYPES.filter((r) => r.isLeaf).map((r) => r.name),
157
+ 'any',
158
+ ]));
159
+ // Derive method dicts from registration.methods (absorbs buildTypeMethodDicts
160
+ // logic). Validates EC-6: duplicate method on same type.
161
+ const methodRegistry = new Map();
162
+ const typeMethodDicts = new Map();
163
+ for (const reg of BUILT_IN_TYPES) {
164
+ const methods = reg.methods;
165
+ if (!methods || Object.keys(methods).length === 0)
166
+ continue;
167
+ const seen = methodRegistry.get(reg.name) ?? new Set();
168
+ methodRegistry.set(reg.name, seen);
169
+ const existing = typeMethodDicts.get(reg.name) ?? {};
170
+ const dict = { ...existing };
171
+ for (const [name, fn] of Object.entries(methods)) {
172
+ if (seen.has(name)) {
173
+ throw new RuntimeError('RILL-R073', `Duplicate method '${name}' on type '${reg.name}'`);
174
+ }
175
+ seen.add(name);
176
+ const appCallable = {
177
+ __type: 'callable',
178
+ kind: 'application',
179
+ params: fn.params,
180
+ returnType: fn.returnType,
181
+ annotations: fn.annotations ?? {},
182
+ isProperty: false,
183
+ fn: fn.fn,
184
+ };
185
+ dict[name] = appCallable;
186
+ }
187
+ typeMethodDicts.set(reg.name, Object.freeze(dict));
188
+ }
189
+ // Derive bypass set from registrations: method names that handle their own
190
+ // receiver type checking. Generic RILL-R003 must not fire before the method body.
191
+ const unvalidatedMethodReceivers = deriveUnvalidatedMethodReceivers(BUILT_IN_TYPES);
192
+ // BC-5: Freeze all derived collections after creation.
193
+ Object.freeze(typeNames);
194
+ Object.freeze(typeMethodDicts);
195
+ // Suppress unused-variable warning for typeNames (consumed in later phases).
196
+ void typeNames;
200
197
  return {
201
198
  parent: undefined,
202
199
  variables,
203
200
  variableTypes,
204
201
  functions,
205
202
  typeMethodDicts,
203
+ leafTypes,
204
+ unvalidatedMethodReceivers,
206
205
  callbacks: { ...defaultCallbacks, ...options.callbacks },
207
206
  observability: options.observability ?? {},
208
207
  pipeValue: null,
@@ -232,6 +231,8 @@ export function createChildContext(parent, overrides) {
232
231
  variableTypes: new Map(),
233
232
  functions: parent.functions,
234
233
  typeMethodDicts: parent.typeMethodDicts,
234
+ leafTypes: parent.leafTypes,
235
+ unvalidatedMethodReceivers: parent.unvalidatedMethodReceivers,
235
236
  callbacks: parent.callbacks,
236
237
  observability: parent.observability,
237
238
  pipeValue: parent.pipeValue,
@@ -7,8 +7,8 @@
7
7
  * @internal
8
8
  */
9
9
  import type { ASTNode, CaptureNode, SourceLocation } from '../../../types.js';
10
- import type { RuntimeContext } from '../types.js';
11
- import type { RillValue } from '../values.js';
10
+ import type { RuntimeContext } from '../types/runtime.js';
11
+ import type { RillValue } from '../types/structures.js';
12
12
  /**
13
13
  * Base class for the evaluator.
14
14
  * Contains shared utilities used by all mixins.
@@ -74,6 +74,8 @@ export class EvaluatorBase {
74
74
  * Phase 1-3 use the functional evaluator which has its own handleCapture.
75
75
  */
76
76
  handleCapture(_capture, _value) {
77
+ // AC-13: Intentional raw throw - internal mixin guard, not user-reachable.
78
+ // This stub only runs if mixin composition is incomplete (programming error).
77
79
  throw new Error('handleCapture requires full Evaluator composition with VariablesMixin');
78
80
  }
79
81
  /**
@@ -100,7 +100,7 @@
100
100
  *
101
101
  * @internal
102
102
  */
103
- import type { RuntimeContext } from '../types.js';
103
+ import type { RuntimeContext } from '../types/runtime.js';
104
104
  /**
105
105
  * Complete Evaluator class composed from all mixins.
106
106
  *
@@ -8,8 +8,8 @@
8
8
  */
9
9
  import type { AnnotatedStatementNode, ASTNode, CaptureNode, ExpressionNode, RillTypeName, SourceLocation, StatementNode } from '../../../types.js';
10
10
  import type { RillCallable } from '../callable.js';
11
- import type { RuntimeContext } from '../types.js';
12
- import type { RillValue, RillType } from '../values.js';
11
+ import type { RuntimeContext } from '../types/runtime.js';
12
+ import type { RillValue, TypeStructure } from '../types/structures.js';
13
13
  /**
14
14
  * Capture information returned by handleCapture.
15
15
  */
@@ -39,7 +39,7 @@ export declare function handleCapture(capture: CaptureNode | null, value: RillVa
39
39
  * Assert that a value is of the expected type.
40
40
  * Returns the value unchanged if assertion passes, throws on mismatch.
41
41
  */
42
- export declare function assertType(value: RillValue, expected: RillTypeName | RillType, location?: SourceLocation): RillValue;
42
+ export declare function assertType(value: RillValue, expected: RillTypeName | TypeStructure, location?: SourceLocation): RillValue;
43
43
  /**
44
44
  * Evaluate an expression and return its value.
45
45
  * Main entry point for expression evaluation.
@@ -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);