@telorun/cel 0.107.0 → 0.109.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 (78) hide show
  1. package/dist/backend-runtime.d.ts +46 -8
  2. package/dist/backend-runtime.d.ts.map +1 -1
  3. package/dist/backend-runtime.js +133 -30
  4. package/dist/catalog-runtime.d.ts.map +1 -1
  5. package/dist/catalog-runtime.js +110 -109
  6. package/dist/cel-map-value.d.ts +20 -7
  7. package/dist/cel-map-value.d.ts.map +1 -1
  8. package/dist/cel-map-value.js +29 -18
  9. package/dist/cel-value.d.ts +10 -3
  10. package/dist/cel-value.d.ts.map +1 -1
  11. package/dist/check-diagnostic.d.ts +3 -2
  12. package/dist/check-diagnostic.d.ts.map +1 -1
  13. package/dist/checker.d.ts +9 -0
  14. package/dist/checker.d.ts.map +1 -1
  15. package/dist/checker.js +98 -13
  16. package/dist/closure-backend.js +66 -15
  17. package/dist/duration-value.d.ts +11 -0
  18. package/dist/duration-value.d.ts.map +1 -1
  19. package/dist/duration-value.js +17 -3
  20. package/dist/emitted-module.d.ts +1 -1
  21. package/dist/emitted-module.js +1 -1
  22. package/dist/engine-version.d.ts +1 -1
  23. package/dist/engine-version.js +1 -1
  24. package/dist/environment.d.ts +8 -0
  25. package/dist/environment.d.ts.map +1 -1
  26. package/dist/environment.js +20 -5
  27. package/dist/function-registry.d.ts +40 -0
  28. package/dist/function-registry.d.ts.map +1 -1
  29. package/dist/function-registry.js +105 -2
  30. package/dist/index.d.ts +10 -8
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +12 -6
  33. package/dist/js-emitter.d.ts +3 -2
  34. package/dist/js-emitter.d.ts.map +1 -1
  35. package/dist/js-emitter.js +11 -5
  36. package/dist/macro-check.d.ts +8 -0
  37. package/dist/macro-check.d.ts.map +1 -1
  38. package/dist/macro-check.js +1 -1
  39. package/dist/member-read.d.ts +42 -5
  40. package/dist/member-read.d.ts.map +1 -1
  41. package/dist/member-read.js +47 -6
  42. package/dist/resolved-call.d.ts +13 -0
  43. package/dist/resolved-call.d.ts.map +1 -1
  44. package/dist/runtime-library.d.ts +13 -2
  45. package/dist/runtime-library.d.ts.map +1 -1
  46. package/dist/runtime-library.js +134 -125
  47. package/dist/serializer.d.ts.map +1 -1
  48. package/dist/serializer.js +20 -4
  49. package/dist/timestamp-value.d.ts +9 -0
  50. package/dist/timestamp-value.d.ts.map +1 -1
  51. package/dist/timestamp-value.js +15 -0
  52. package/dist/type-expression.d.ts +18 -2
  53. package/dist/type-expression.d.ts.map +1 -1
  54. package/dist/type-expression.js +23 -3
  55. package/dist/value-equality.d.ts.map +1 -1
  56. package/package.json +1 -1
  57. package/src/backend-runtime.ts +180 -30
  58. package/src/catalog-runtime.ts +133 -133
  59. package/src/cel-map-value.ts +31 -20
  60. package/src/cel-value.ts +11 -3
  61. package/src/check-diagnostic.ts +4 -2
  62. package/src/checker.ts +123 -10
  63. package/src/closure-backend.ts +59 -14
  64. package/src/duration-value.ts +18 -3
  65. package/src/emitted-module.ts +1 -1
  66. package/src/engine-version.ts +1 -1
  67. package/src/environment.ts +26 -5
  68. package/src/function-registry.ts +114 -2
  69. package/src/index.ts +24 -3
  70. package/src/js-emitter.ts +13 -5
  71. package/src/macro-check.ts +7 -1
  72. package/src/member-read.ts +48 -7
  73. package/src/resolved-call.ts +13 -0
  74. package/src/runtime-library.ts +167 -143
  75. package/src/serializer.ts +19 -3
  76. package/src/timestamp-value.ts +16 -0
  77. package/src/type-expression.ts +25 -3
  78. package/src/value-equality.ts +3 -3
package/src/index.ts CHANGED
@@ -143,7 +143,7 @@ export type {
143
143
  VariableDefinition,
144
144
  } from "./environment.js";
145
145
 
146
- export { FunctionRegistry } from "./function-registry.js";
146
+ export { FunctionRegistry, UNKNOWN_FUNCTION_CANDIDATES } from "./function-registry.js";
147
147
  export type { RegisteredFunction, Resolution, ResolutionFailure } from "./function-registry.js";
148
148
 
149
149
  export {
@@ -225,7 +225,11 @@ export {
225
225
  } from "./zoned-calendar.js";
226
226
  export type { CivilTime } from "./zoned-calendar.js";
227
227
 
228
- export { CelTypeExpressionError, parseTypeExpression } from "./type-expression.js";
228
+ export {
229
+ CelTypeExpressionError,
230
+ CelUnknownTypeNameError,
231
+ parseTypeExpression,
232
+ } from "./type-expression.js";
229
233
  export type { NominalResolver } from "./type-expression.js";
230
234
 
231
235
  // --- the value domain, the semantics and the closure backend ----------------
@@ -234,7 +238,10 @@ export type { CelActivation } from "./activation.js";
234
238
 
235
239
  export { BoundedCache } from "./bounded-cache.js";
236
240
 
237
- export { celMapFromEntries, celMapKeys, mapKeyIdentity } from "./cel-map-value.js";
241
+ // `mapKeyIdentity` is deliberately NOT exported: what identifies an entry is the entries
242
+ // map's own business, and publishing the rule is what let a consumer grow a second copy of
243
+ // it. A host reads a map through the member-read seam and walks `entries` for the pairs.
244
+ export { celMapFromEntries, celMapKeys } from "./cel-map-value.js";
238
245
 
239
246
  export { CelEvaluationError, programOfStep } from "./cel-program.js";
240
247
  export type { CelProgram, EvaluateOptions } from "./cel-program.js";
@@ -295,6 +302,7 @@ export type {
295
302
  CelEvaluationCode,
296
303
  CelHostValue,
297
304
  CelMap,
305
+ CelMapKey,
298
306
  CelMapValueEntry,
299
307
  CelOptional,
300
308
  CelRecord,
@@ -314,6 +322,7 @@ export type {
314
322
  CompileTarget,
315
323
  EvaluationFrame,
316
324
  NamespaceDispatch,
325
+ NamespaceImplementation,
317
326
  } from "./backend-runtime.js";
318
327
 
319
328
  export {
@@ -329,6 +338,7 @@ export {
329
338
  celDurationFromNanos,
330
339
  durationField,
331
340
  durationNanos,
341
+ durationNanosFromText,
332
342
  durationOutOfRange,
333
343
  formatDuration,
334
344
  MAX_DURATION_NANOS,
@@ -341,6 +351,7 @@ export {
341
351
  celIterable,
342
352
  celLookup,
343
353
  celRead,
354
+ lookupAbsence,
344
355
  lookupError,
345
356
  MISSING,
346
357
  OUT_OF_RANGE,
@@ -369,9 +380,12 @@ export {
369
380
  typeValueName,
370
381
  } from "./runtime-library.js";
371
382
  export type { CelCallContext, CelImplementation } from "./runtime-library.js";
383
+ // The bound a host writing an implementation is held to, and what the refusal names.
384
+ export { CALL_SITE_DIRECT_ARITY } from "./runtime-library.js";
372
385
 
373
386
  export {
374
387
  celTimestamp,
388
+ celTimestampFromMillis,
375
389
  formatTimestamp,
376
390
  MAX_TIMESTAMP_SECONDS,
377
391
  MIN_TIMESTAMP_SECONDS,
@@ -384,3 +398,10 @@ export {
384
398
  export { celCompare, celEqual } from "./value-equality.js";
385
399
 
386
400
  export { bytesToText, doubleText, textToBytes } from "./value-text.js";
401
+
402
+ export {
403
+ BINDING_FORMS,
404
+ namespaceMacroBinding,
405
+ receiverMacroBinding,
406
+ type ComprehensionBinding,
407
+ } from "./comprehension-bindings.js";
package/src/js-emitter.ts CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  import { namespaceMacroBinding, receiverMacroBinding } from "./comprehension-bindings.js";
37
37
  import { splitDeclaredChain } from "./declared-chain.js";
38
38
  import { isMacroCall } from "./macro-check.js";
39
+ import { CALL_SITE_DIRECT_ARITY } from "./runtime-library.js";
39
40
  import type { CelLiteral, CelNode, CelSelectNode, SourceRange } from "./syntax-tree.js";
40
41
 
41
42
  /**
@@ -347,8 +348,8 @@ export class ModuleEmitter {
347
348
  const kept = entry.optional ? fn.next() : undefined;
348
349
  const written = kept
349
350
  ? `(${kept} = optionalEntry(${value}, ${at}), isCelError(${kept}) ? ${kept} : ` +
350
- `(${kept}.present && ${out}.push([${key}, ${kept}.held]), ${body}))`
351
- : `(${out}.push([${key}, ${value}]), ${body})`;
351
+ `(${kept}.present && ${out}.push(${key}, ${kept}.held), ${body}))`
352
+ : `(${out}.push(${key}, ${value}), ${body})`;
352
353
  body =
353
354
  `(${key} = ${entry.key}, isCelError(${key}) ? ${key} : ` +
354
355
  `(${value} = ${entry.value}, isCelError(${value}) ? ${value} : ${written}))`;
@@ -427,8 +428,9 @@ export class ModuleEmitter {
427
428
 
428
429
  /**
429
430
  * One dispatch: evaluate the arguments, carry the first error out, then hand the values to
430
- * the site. The site is hoisted, so it is built once per loaded module and holds the
431
- * overloads it resolved — exactly as a compiled closure's does.
431
+ * the site **positionally**. The site is hoisted, so it is built once per loaded module and
432
+ * holds the overloads it resolved — exactly as a compiled closure's does. The arity is the
433
+ * dispatch key's, so the entry point is chosen here and no argument array is built.
432
434
  */
433
435
  private call(
434
436
  name: string,
@@ -440,7 +442,13 @@ export class ModuleEmitter {
440
442
  const site = this.hoist(
441
443
  `callSite(${textSource(name)}, ${textSource(form)}, ${this.range(range)})`,
442
444
  );
443
- return this.carrying(args, fn, (values) => `${site}.call([${values.join(", ")}])`);
445
+ // A call written wider than the bound takes the array form — the arity is the SOURCE's,
446
+ // so a width no signature can declare is still something an author may write.
447
+ return this.carrying(args, fn, (values) =>
448
+ values.length <= CALL_SITE_DIRECT_ARITY
449
+ ? `${site}.call${values.length}(${values.join(", ")})`
450
+ : `${site}.call([${values.join(", ")}])`,
451
+ );
444
452
  }
445
453
 
446
454
  private qualifiedCall(
@@ -25,6 +25,12 @@ export interface MacroHost {
25
25
  typeOf(node: CelNode): CelType;
26
26
  /** The type of a subexpression with extra names in scope. */
27
27
  typeOfBinding(node: CelNode, bindings: ReadonlyMap<string, CelType>): CelType;
28
+ /**
29
+ * The type of a select read as a question about PRESENCE — `has()`'s argument. It is
30
+ * the same reading `.?b` gets, which is what keeps the two forms of one question from
31
+ * answering differently about a union whose branches do not all hold the member.
32
+ */
33
+ typeOfPresence(node: Extract<CelNode, { kind: "select" }>): CelType;
28
34
  report(code: CelCheckCode, message: string, range: SourceRange): void;
29
35
  }
30
36
 
@@ -99,7 +105,7 @@ function checkOptionalBinding(node: Extract<CelNode, { kind: "receiverCall" }>,
99
105
  /** `has(a.b)` — a question about presence. Its shape is already validated. */
100
106
  function checkHas(node: Extract<CelNode, { kind: "call" }>, host: MacroHost): CelType {
101
107
  const argument = node.args[0]!;
102
- if (argument.kind === "select") host.typeOf(argument);
108
+ if (argument.kind === "select") host.typeOfPresence(argument);
103
109
  return BOOL;
104
110
  }
105
111
 
@@ -16,6 +16,10 @@
16
16
  *
17
17
  * A key the value does not hold is the `no_such_key` **error value**, which
18
18
  * participates in short-circuit. Never `undefined` passed along.
19
+ *
20
+ * **What each of the four verdicts answers is written once here**, in `lookupAbsence`,
21
+ * and read by both presence callers — so the closure backend, the emitter and the
22
+ * `has()` macro cannot differ by construction.
19
23
  */
20
24
 
21
25
  import { celMapKeys, mapKeyIdentity } from "./cel-map-value.js";
@@ -86,6 +90,38 @@ export function celRead(container: CelValue, key: CelValue, range?: SourceRange)
86
90
  return lookupError(found, key, range);
87
91
  }
88
92
 
93
+ /**
94
+ * Whether a lookup that found nothing answers **absence** rather than an error. This is
95
+ * the seam's contract, in the one place both backends and the emitter read it: they all
96
+ * reach a member read through `backend-runtime.ts`, which reaches the four verdicts
97
+ * through here.
98
+ *
99
+ * - A **presence-shaped** read — `a.?b`, `a[?k]` and `has(a.b)` — answers absence for
100
+ * *missing*, *out of range* and *holds no members* alike, and the error only for an
101
+ * **unusable key**. It asks whether a member is THERE, and a value that cannot hold one
102
+ * has none to find. The authority is the optional library's own: it enters this engine
103
+ * from cel-go whole, and cel-go's attribute qualification answers "not found" for a
104
+ * receiver that is neither a mapper, a lister nor an indexer **whenever the read is a
105
+ * presence test**, erroring only otherwise — the error reading being an explicitly named
106
+ * opt-in (`EnableErrorOnBadPresenceTest`), which Telo does not carry, because a
107
+ * per-environment switch over what an expression MEANS would let the analyzer and a
108
+ * kernel disagree about one manifest.
109
+ * - A read **through a present optional, written in the ordinary form** — the `.c` of
110
+ * `a.?b.c` — is not a presence question about `c`. It answers absence for a key the held
111
+ * value does not hold (`optional.of({'c': {}}).c.missing` is absent, cel-spec's
112
+ * `optional_chaining_5`) and the error for a held value that holds no members at all
113
+ * (`{true: dyn(0)}[?true].absent` is cel-spec's error).
114
+ * - A **plain** read never asks: each of the four is the error `lookupError` words.
115
+ *
116
+ * An unusable key is the one refusal no form forgives: `[?3.1]` names an entry no
117
+ * container of that shape could hold, so it is a mistake in the READ rather than a member
118
+ * that happens to be absent.
119
+ */
120
+ export function lookupAbsence(found: symbol, presence: boolean): boolean {
121
+ if (found === MISSING || found === OUT_OF_RANGE) return true;
122
+ return presence && found === UNSUPPORTED_CONTAINER;
123
+ }
124
+
89
125
  /** The error a refused lookup is. */
90
126
  export function lookupError(found: symbol, key: CelValue, range?: SourceRange): CelError {
91
127
  if (found === MISSING) return celError("no_such_key", `no such key: ${describe(key)}`, range);
@@ -99,20 +135,25 @@ export function lookupError(found: symbol, key: CelValue, range?: SourceRange):
99
135
  }
100
136
 
101
137
  /**
102
- * Whether a key is there — `has(a.b)`. It asks about presence, so a missing key is
103
- * `false` rather than the error a read would be; a container that holds no members at
104
- * all is still a mistake in the question.
138
+ * Whether a key is there — `has(a.b)`. The question is presence-shaped, so a value that
139
+ * holds no members answers `false` exactly as a missing key does: it has no member to
140
+ * find. Only an unusable key is a mistake in the question itself.
105
141
  */
106
142
  export function celHas(container: CelValue, key: CelValue, range?: SourceRange): boolean | CelError {
107
143
  const found = celLookup(container, key);
108
144
  if (typeof found !== "symbol") return true;
109
- if (found === MISSING || found === OUT_OF_RANGE) return false;
145
+ if (lookupAbsence(found, true)) return false;
110
146
  return lookupError(found, key, range);
111
147
  }
112
148
 
113
- /** The elements a comprehension ranges over: a list's items, a map's keys. */
114
- export function celIterable(container: CelValue, range?: SourceRange): CelValue[] | CelError {
115
- if (Array.isArray(container)) return [...container];
149
+ /**
150
+ * The elements a comprehension ranges over: a list's items, a map's keys. A list is handed
151
+ * over as it is — nothing in CEL mutates a value, and every comprehension reads its range —
152
+ * so copying it would cost the length of the list per comprehension to defend against a
153
+ * write no expression can perform.
154
+ */
155
+ export function celIterable(container: CelValue, range?: SourceRange): readonly CelValue[] | CelError {
156
+ if (Array.isArray(container)) return container as readonly CelValue[];
116
157
  if (isCelMap(container)) return celMapKeys(container);
117
158
  if (isCelRecord(container)) return Object.keys(container);
118
159
  return celError("unsupported_container", "a comprehension reads a list or a map", range);
@@ -31,4 +31,17 @@ export interface ResolvedCall {
31
31
  readonly deterministic?: boolean;
32
32
  readonly hostBacked?: boolean;
33
33
  readonly throws?: readonly string[];
34
+ /**
35
+ * Each argument's own type, for a NAMESPACED call only.
36
+ *
37
+ * The host judges such a call against its own, richer signature grammar — a JSON Schema
38
+ * per parameter, an optional trailing parameter — so it needs what was actually passed.
39
+ * It cannot re-derive it: checking an argument's subtree on its own loses whatever the
40
+ * expression bound around it, so an argument inside a comprehension (`xs.map(i,
41
+ * Billing.total(i))`) would type as `dyn` and the host's argument check would silently
42
+ * stop asking. Here the types come from the same pass that typed the call.
43
+ *
44
+ * Absent for a call the registry resolved: its parameter types are the signature's.
45
+ */
46
+ readonly argumentTypes?: readonly string[];
34
47
  }