@telorun/cel 0.0.0-stage → 0.107.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 (223) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +281 -2
  3. package/dist/activation.d.ts +27 -0
  4. package/dist/activation.d.ts.map +1 -0
  5. package/dist/activation.js +24 -0
  6. package/dist/backend-runtime.d.ts +146 -0
  7. package/dist/backend-runtime.d.ts.map +1 -0
  8. package/dist/backend-runtime.js +322 -0
  9. package/dist/bounded-cache.d.ts +21 -0
  10. package/dist/bounded-cache.d.ts.map +1 -0
  11. package/dist/bounded-cache.js +42 -0
  12. package/dist/catalog-runtime.d.ts +59 -0
  13. package/dist/catalog-runtime.d.ts.map +1 -0
  14. package/dist/catalog-runtime.js +785 -0
  15. package/dist/cel-expression.d.ts +32 -0
  16. package/dist/cel-expression.d.ts.map +1 -0
  17. package/dist/cel-expression.js +29 -0
  18. package/dist/cel-map-value.d.ts +34 -0
  19. package/dist/cel-map-value.d.ts.map +1 -0
  20. package/dist/cel-map-value.js +74 -0
  21. package/dist/cel-program.d.ts +44 -0
  22. package/dist/cel-program.d.ts.map +1 -0
  23. package/dist/cel-program.js +72 -0
  24. package/dist/cel-type.d.ts +131 -0
  25. package/dist/cel-type.d.ts.map +1 -0
  26. package/dist/cel-type.js +293 -0
  27. package/dist/cel-value.d.ts +159 -0
  28. package/dist/cel-value.d.ts.map +1 -0
  29. package/dist/cel-value.js +225 -0
  30. package/dist/check-diagnostic.d.ts +53 -0
  31. package/dist/check-diagnostic.d.ts.map +1 -0
  32. package/dist/check-diagnostic.js +66 -0
  33. package/dist/checker.d.ts +77 -0
  34. package/dist/checker.d.ts.map +1 -0
  35. package/dist/checker.js +721 -0
  36. package/dist/closure-backend.d.ts +21 -0
  37. package/dist/closure-backend.d.ts.map +1 -0
  38. package/dist/closure-backend.js +436 -0
  39. package/dist/comprehension-bindings.d.ts +33 -0
  40. package/dist/comprehension-bindings.d.ts.map +1 -0
  41. package/dist/comprehension-bindings.js +50 -0
  42. package/dist/comprehension-runtime.d.ts +44 -0
  43. package/dist/comprehension-runtime.d.ts.map +1 -0
  44. package/dist/comprehension-runtime.js +137 -0
  45. package/dist/declared-chain.d.ts +35 -0
  46. package/dist/declared-chain.d.ts.map +1 -0
  47. package/dist/declared-chain.js +36 -0
  48. package/dist/duration-value.d.ts +59 -0
  49. package/dist/duration-value.d.ts.map +1 -0
  50. package/dist/duration-value.js +135 -0
  51. package/dist/emitted-module.d.ts +207 -0
  52. package/dist/emitted-module.d.ts.map +1 -0
  53. package/dist/emitted-module.js +359 -0
  54. package/dist/engine-version.d.ts +3 -0
  55. package/dist/engine-version.d.ts.map +1 -0
  56. package/dist/engine-version.js +8 -0
  57. package/dist/environment-digest.d.ts +44 -0
  58. package/dist/environment-digest.d.ts.map +1 -0
  59. package/dist/environment-digest.js +98 -0
  60. package/dist/environment.d.ts +283 -0
  61. package/dist/environment.d.ts.map +1 -0
  62. package/dist/environment.js +459 -0
  63. package/dist/function-catalog.d.ts +66 -0
  64. package/dist/function-catalog.d.ts.map +1 -0
  65. package/dist/function-catalog.js +77 -0
  66. package/dist/function-registry.d.ts +78 -0
  67. package/dist/function-registry.d.ts.map +1 -0
  68. package/dist/function-registry.js +189 -0
  69. package/dist/index.d.ts +91 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +59 -0
  72. package/dist/integer-arithmetic.d.ts +27 -0
  73. package/dist/integer-arithmetic.d.ts.map +1 -0
  74. package/dist/integer-arithmetic.js +58 -0
  75. package/dist/js-emitter.d.ts +132 -0
  76. package/dist/js-emitter.d.ts.map +1 -0
  77. package/dist/js-emitter.js +562 -0
  78. package/dist/json-schema-type.d.ts +182 -0
  79. package/dist/json-schema-type.d.ts.map +1 -0
  80. package/dist/json-schema-type.js +487 -0
  81. package/dist/json-text-scan.d.ts +28 -0
  82. package/dist/json-text-scan.d.ts.map +1 -0
  83. package/dist/json-text-scan.js +159 -0
  84. package/dist/lexer.d.ts +103 -0
  85. package/dist/lexer.d.ts.map +1 -0
  86. package/dist/lexer.js +458 -0
  87. package/dist/macro-check.d.ts +33 -0
  88. package/dist/macro-check.d.ts.map +1 -0
  89. package/dist/macro-check.js +162 -0
  90. package/dist/macro-shape.d.ts +24 -0
  91. package/dist/macro-shape.d.ts.map +1 -0
  92. package/dist/macro-shape.js +55 -0
  93. package/dist/member-read.d.ts +52 -0
  94. package/dist/member-read.d.ts.map +1 -0
  95. package/dist/member-read.js +125 -0
  96. package/dist/namespace-resolution.d.ts +35 -0
  97. package/dist/namespace-resolution.d.ts.map +1 -0
  98. package/dist/namespace-resolution.js +160 -0
  99. package/dist/nominal-type.d.ts +63 -0
  100. package/dist/nominal-type.d.ts.map +1 -0
  101. package/dist/nominal-type.js +98 -0
  102. package/dist/nullable-access.d.ts +38 -0
  103. package/dist/nullable-access.d.ts.map +1 -0
  104. package/dist/nullable-access.js +93 -0
  105. package/dist/parse-limits.d.ts +26 -0
  106. package/dist/parse-limits.d.ts.map +1 -0
  107. package/dist/parse-limits.js +21 -0
  108. package/dist/parser.d.ts +48 -0
  109. package/dist/parser.d.ts.map +1 -0
  110. package/dist/parser.js +503 -0
  111. package/dist/qualified-calls.d.ts +22 -0
  112. package/dist/qualified-calls.d.ts.map +1 -0
  113. package/dist/qualified-calls.js +27 -0
  114. package/dist/regular-expression.d.ts +48 -0
  115. package/dist/regular-expression.d.ts.map +1 -0
  116. package/dist/regular-expression.js +77 -0
  117. package/dist/reserved-words.d.ts +52 -0
  118. package/dist/reserved-words.d.ts.map +1 -0
  119. package/dist/reserved-words.js +77 -0
  120. package/dist/resolved-call.d.ts +33 -0
  121. package/dist/resolved-call.d.ts.map +1 -0
  122. package/dist/resolved-call.js +15 -0
  123. package/dist/root-references.d.ts +23 -0
  124. package/dist/root-references.d.ts.map +1 -0
  125. package/dist/root-references.js +120 -0
  126. package/dist/runtime-library.d.ts +56 -0
  127. package/dist/runtime-library.d.ts.map +1 -0
  128. package/dist/runtime-library.js +545 -0
  129. package/dist/serializer.d.ts +24 -0
  130. package/dist/serializer.d.ts.map +1 -0
  131. package/dist/serializer.js +240 -0
  132. package/dist/sha256.d.ts +20 -0
  133. package/dist/sha256.d.ts.map +1 -0
  134. package/dist/sha256.js +103 -0
  135. package/dist/signature.d.ts +72 -0
  136. package/dist/signature.d.ts.map +1 -0
  137. package/dist/signature.js +61 -0
  138. package/dist/signatures/function-catalog.json +788 -0
  139. package/dist/signatures/standard-library.json +229 -0
  140. package/dist/standard-library.d.ts +41 -0
  141. package/dist/standard-library.d.ts.map +1 -0
  142. package/dist/standard-library.js +85 -0
  143. package/dist/syntax-diagnostic.d.ts +61 -0
  144. package/dist/syntax-diagnostic.d.ts.map +1 -0
  145. package/dist/syntax-diagnostic.js +28 -0
  146. package/dist/syntax-tree.d.ts +160 -0
  147. package/dist/syntax-tree.d.ts.map +1 -0
  148. package/dist/syntax-tree.js +58 -0
  149. package/dist/timestamp-value.d.ts +53 -0
  150. package/dist/timestamp-value.d.ts.map +1 -0
  151. package/dist/timestamp-value.js +223 -0
  152. package/dist/tree-equality.d.ts +15 -0
  153. package/dist/tree-equality.d.ts.map +1 -0
  154. package/dist/tree-equality.js +105 -0
  155. package/dist/type-expression.d.ts +26 -0
  156. package/dist/type-expression.d.ts.map +1 -0
  157. package/dist/type-expression.js +134 -0
  158. package/dist/value-equality.d.ts +38 -0
  159. package/dist/value-equality.d.ts.map +1 -0
  160. package/dist/value-equality.js +196 -0
  161. package/dist/value-text.d.ts +25 -0
  162. package/dist/value-text.d.ts.map +1 -0
  163. package/dist/value-text.js +44 -0
  164. package/dist/zoned-calendar.d.ts +61 -0
  165. package/dist/zoned-calendar.d.ts.map +1 -0
  166. package/dist/zoned-calendar.js +143 -0
  167. package/package.json +56 -3
  168. package/src/activation.ts +32 -0
  169. package/src/backend-runtime.ts +454 -0
  170. package/src/bounded-cache.ts +45 -0
  171. package/src/catalog-runtime.ts +921 -0
  172. package/src/cel-expression.ts +53 -0
  173. package/src/cel-map-value.ts +86 -0
  174. package/src/cel-program.ts +103 -0
  175. package/src/cel-type.ts +359 -0
  176. package/src/cel-value.ts +353 -0
  177. package/src/check-diagnostic.ts +102 -0
  178. package/src/checker.ts +932 -0
  179. package/src/closure-backend.ts +502 -0
  180. package/src/comprehension-bindings.ts +66 -0
  181. package/src/comprehension-runtime.ts +157 -0
  182. package/src/declared-chain.ts +45 -0
  183. package/src/duration-value.ts +145 -0
  184. package/src/emitted-module.ts +494 -0
  185. package/src/engine-version.ts +9 -0
  186. package/src/environment-digest.ts +111 -0
  187. package/src/environment.ts +740 -0
  188. package/src/function-catalog.ts +140 -0
  189. package/src/function-registry.ts +229 -0
  190. package/src/index.ts +386 -0
  191. package/src/integer-arithmetic.ts +64 -0
  192. package/src/js-emitter.ts +713 -0
  193. package/src/json-schema-type.ts +664 -0
  194. package/src/json-text-scan.ts +163 -0
  195. package/src/lexer.ts +562 -0
  196. package/src/macro-check.ts +191 -0
  197. package/src/macro-shape.ts +66 -0
  198. package/src/member-read.ts +126 -0
  199. package/src/namespace-resolution.ts +167 -0
  200. package/src/nominal-type.ts +149 -0
  201. package/src/nullable-access.ts +95 -0
  202. package/src/parse-limits.ts +36 -0
  203. package/src/parser.ts +554 -0
  204. package/src/qualified-calls.ts +39 -0
  205. package/src/regular-expression.ts +101 -0
  206. package/src/reserved-words.ts +94 -0
  207. package/src/resolved-call.ts +34 -0
  208. package/src/root-references.ts +126 -0
  209. package/src/runtime-library.ts +615 -0
  210. package/src/serializer.ts +246 -0
  211. package/src/sha256.ts +112 -0
  212. package/src/signature.ts +127 -0
  213. package/src/signatures/function-catalog.json +788 -0
  214. package/src/signatures/standard-library.json +235 -0
  215. package/src/standard-library.ts +149 -0
  216. package/src/syntax-diagnostic.ts +72 -0
  217. package/src/syntax-tree.ts +229 -0
  218. package/src/timestamp-value.ts +294 -0
  219. package/src/tree-equality.ts +130 -0
  220. package/src/type-expression.ts +160 -0
  221. package/src/value-equality.ts +201 -0
  222. package/src/value-text.ts +45 -0
  223. package/src/zoned-calendar.ts +178 -0
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Loading the function catalog into an environment.
3
+ *
4
+ * The catalog is the **dialect**: the functions a manifest may call beside CEL's own
5
+ * library. Like the standard library it is **data** (`signatures/function-catalog.json`)
6
+ * read through the same registration surface a host uses — here literally the public one,
7
+ * `CelEnvironment.registerFunction`, with no privileged door of any kind. A host may
8
+ * register the catalog, leave it out, replace one of its signatures or remove a name, and
9
+ * nothing in the engine behaves differently for it.
10
+ *
11
+ * **One surface, read by everything that lists the catalog.** `functionCatalog()` answers
12
+ * each function with its display signature, its category, its summary and its two flags,
13
+ * which is exactly what a `functions` listing prints; a consumer never reconstructs that
14
+ * from registrations.
15
+ *
16
+ * What a function DOES is beside it, per runtime — `catalog-runtime.ts`, keyed by the same
17
+ * dispatch key the registry resolves on. A declared signature with no behaviour there is
18
+ * refused at registration rather than at evaluation, which is the failure class the two
19
+ * artifacts exist to remove.
20
+ */
21
+
22
+ import data from "./signatures/function-catalog.json" with { type: "json" };
23
+ import type { CelCatalogHandlers } from "./catalog-runtime.js";
24
+ import { catalogImplementation, catalogLiteralCheck } from "./catalog-runtime.js";
25
+ import type { CelEnvironment } from "./environment.js";
26
+ import { parseSignature, signatureKey } from "./signature.js";
27
+
28
+ interface CatalogEntryData {
29
+ readonly name: string;
30
+ readonly signature: string;
31
+ readonly signatures: readonly string[];
32
+ readonly category: string;
33
+ readonly summary: string;
34
+ readonly deterministic: boolean;
35
+ readonly hostBacked: boolean;
36
+ readonly checksLiteralArguments?: boolean;
37
+ }
38
+
39
+ interface CatalogData {
40
+ readonly generation: number;
41
+ readonly spec: boolean;
42
+ readonly reason: string;
43
+ readonly categories: readonly string[];
44
+ readonly functions: readonly CatalogEntryData[];
45
+ }
46
+
47
+ const DATA = data as unknown as CatalogData;
48
+
49
+ export const FUNCTION_CATALOG_GENERATION = DATA.generation;
50
+
51
+ /** The categories the catalog groups its functions under, for a grouped listing. */
52
+ export function catalogCategories(): readonly string[] {
53
+ return DATA.categories;
54
+ }
55
+
56
+ /** One function of the catalog, as a listing prints it. */
57
+ export interface CatalogFunction {
58
+ readonly name: string;
59
+ /**
60
+ * The signature a human reads, which is not always one registration: an optional
61
+ * parameter is written `fn(string?): string` and registers one signature per arity.
62
+ */
63
+ readonly signature: string;
64
+ /** Every signature the function registers, in registration order. */
65
+ readonly signatures: readonly string[];
66
+ readonly category: string;
67
+ readonly summary: string;
68
+ /** False where two calls with the same arguments may answer differently. */
69
+ readonly deterministic: boolean;
70
+ /** Whether the implementation is the host's rather than the engine's. */
71
+ readonly hostBacked: boolean;
72
+ /** Whether the function refuses an argument written as a literal, at check. */
73
+ readonly checksLiteralArguments: boolean;
74
+ }
75
+
76
+ /** The catalog, as the one surface a listing reads. */
77
+ export function functionCatalog(): readonly CatalogFunction[] {
78
+ return DATA.functions.map((entry) => ({
79
+ name: entry.name,
80
+ signature: entry.signature,
81
+ signatures: entry.signatures,
82
+ category: entry.category,
83
+ summary: entry.summary,
84
+ deterministic: entry.deterministic,
85
+ hostBacked: entry.hostBacked,
86
+ checksLiteralArguments: entry.checksLiteralArguments ?? false,
87
+ }));
88
+ }
89
+
90
+ /** Every signature the catalog registers, for a validator and for a docs listing. */
91
+ export function catalogSignatures(): readonly string[] {
92
+ return DATA.functions.flatMap((entry) => entry.signatures);
93
+ }
94
+
95
+ export interface RegisterCatalogOptions {
96
+ /**
97
+ * The implementations of the nine host-backed functions. Each may be left out — the
98
+ * registration is still made, so an expression calling it still type-checks, and
99
+ * evaluating one answers an `unbound_function` error naming the function. That is what
100
+ * a consumer which only ever CHECKS (an analyzer, an editor) needs.
101
+ */
102
+ readonly handlers?: Partial<CelCatalogHandlers>;
103
+ }
104
+
105
+ /**
106
+ * Registers the catalog onto an environment, through the public registration surface.
107
+ *
108
+ * A signature the runtime table does not answer for is refused here: a declaration with
109
+ * no behaviour type-checks and then fails at evaluation, which is precisely the failure
110
+ * the declaration/behaviour split exists to catch at registration instead.
111
+ */
112
+ export function registerFunctionCatalog(
113
+ environment: CelEnvironment,
114
+ options: RegisterCatalogOptions = {},
115
+ ): void {
116
+ const handlers = options.handlers ?? {};
117
+ for (const entry of DATA.functions) {
118
+ const check = entry.checksLiteralArguments ? catalogLiteralCheck(entry.name) : undefined;
119
+ if (entry.checksLiteralArguments && check === undefined) {
120
+ throw new Error(
121
+ `the catalog declares that ${entry.name} checks its literal arguments, and no guard answers for it`,
122
+ );
123
+ }
124
+ for (const signature of entry.signatures) {
125
+ const key = signatureKey(parseSignature(signature));
126
+ const implementation = catalogImplementation(key, handlers);
127
+ if (implementation === undefined) {
128
+ throw new Error(`the catalog declares ${signature} and nothing implements ${key}`);
129
+ }
130
+ environment.registerFunction(signature, {
131
+ implementation,
132
+ deterministic: entry.deterministic,
133
+ hostBacked: entry.hostBacked,
134
+ description: entry.summary,
135
+ origin: "function-catalog",
136
+ ...(check === undefined ? {} : { checkArguments: check }),
137
+ });
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Which functions an environment has, and which one a call resolves to.
3
+ *
4
+ * **Nothing here is privileged.** The standard library registers through exactly this
5
+ * surface, so a host can replace one of its signatures or remove it — the capability
6
+ * the whole package exists for. Registration is last-wins by dispatch key
7
+ * (`signature.ts`), removal is by the same key, and both are per environment: a
8
+ * clone inherits and may then diverge without touching its parent.
9
+ *
10
+ * **Resolution answers WHY it failed**, never just "no". A call on a name nothing
11
+ * registers, a call written in the wrong form, and a call whose arguments no overload
12
+ * takes are three different mistakes with three different repairs, and the engine
13
+ * being replaced reported one sentence for all three — which is why a separate
14
+ * classifier had to re-derive the cause after the fact. The checker decides it here,
15
+ * once, with the candidates it considered.
16
+ */
17
+
18
+ import type { CelType } from "./cel-type.js";
19
+ import { assignable, DYN, formatType, isDyn } from "./cel-type.js";
20
+ import type { CallForm, CelSignature, FunctionMetadata } from "./signature.js";
21
+ import { formatSignature, signatureKey } from "./signature.js";
22
+
23
+ export interface RegisteredFunction {
24
+ readonly signature: CelSignature;
25
+ readonly metadata: FunctionMetadata;
26
+ }
27
+
28
+ export type ResolutionFailure =
29
+ /** No function of that name is registered, in any form. */
30
+ | { readonly reason: "unknown"; readonly candidates: readonly RegisteredFunction[] }
31
+ /** The name is registered, but only in the other call form. */
32
+ | { readonly reason: "wrong-form"; readonly candidates: readonly RegisteredFunction[] }
33
+ /** The name and form are registered; no overload takes these arguments. */
34
+ | { readonly reason: "no-overload"; readonly candidates: readonly RegisteredFunction[] };
35
+
36
+ export interface Resolution {
37
+ readonly resolved: RegisteredFunction;
38
+ /** The return type with every type parameter substituted by what this call bound. */
39
+ readonly returns: CelType;
40
+ }
41
+
42
+ export class FunctionRegistry {
43
+ private readonly byName: Map<string, RegisteredFunction[]>;
44
+
45
+ constructor(inherited?: FunctionRegistry) {
46
+ this.byName = new Map();
47
+ if (inherited) {
48
+ for (const [name, entries] of inherited.byName) this.byName.set(name, [...entries]);
49
+ }
50
+ }
51
+
52
+ /** Registers a function, replacing any registration answering the same call. */
53
+ register(signature: CelSignature, metadata: FunctionMetadata = {}): void {
54
+ const entries = this.byName.get(signature.name) ?? [];
55
+ const key = signatureKey(signature);
56
+ const at = entries.findIndex((entry) => signatureKey(entry.signature) === key);
57
+ const entry: RegisteredFunction = { signature, metadata };
58
+ if (at === -1) entries.push(entry);
59
+ else entries[at] = entry;
60
+ this.byName.set(signature.name, entries);
61
+ }
62
+
63
+ /** Removes the one registration answering that call. Answers whether it was there. */
64
+ remove(signature: CelSignature): boolean {
65
+ const entries = this.byName.get(signature.name);
66
+ if (!entries) return false;
67
+ const key = signatureKey(signature);
68
+ const at = entries.findIndex((entry) => signatureKey(entry.signature) === key);
69
+ if (at === -1) return false;
70
+ entries.splice(at, 1);
71
+ if (entries.length === 0) this.byName.delete(signature.name);
72
+ return true;
73
+ }
74
+
75
+ /** Removes every registration of a name. Answers how many went. */
76
+ removeName(name: string): number {
77
+ const entries = this.byName.get(name);
78
+ if (!entries) return 0;
79
+ this.byName.delete(name);
80
+ return entries.length;
81
+ }
82
+
83
+ has(name: string): boolean {
84
+ return this.byName.has(name);
85
+ }
86
+
87
+ /** Every registration, in name order then registration order. */
88
+ list(): readonly RegisteredFunction[] {
89
+ return [...this.byName.keys()].sort().flatMap((name) => this.byName.get(name)!);
90
+ }
91
+
92
+ named(name: string): readonly RegisteredFunction[] {
93
+ return this.byName.get(name) ?? [];
94
+ }
95
+
96
+ /**
97
+ * The function a call resolves to. `receiver` is the type the call is written on,
98
+ * absent for a global call.
99
+ */
100
+ resolve(
101
+ name: string,
102
+ form: CallForm,
103
+ args: readonly CelType[],
104
+ receiver?: CelType,
105
+ ): Resolution | ResolutionFailure {
106
+ const all = this.byName.get(name);
107
+ if (!all || all.length === 0) return { reason: "unknown", candidates: [] };
108
+ const inForm = all.filter((entry) => entry.signature.form === form);
109
+ if (inForm.length === 0) return { reason: "wrong-form", candidates: all };
110
+
111
+ const arity = inForm.filter((entry) => entry.signature.parameters.length === args.length);
112
+ const viable = arity.length === 0 ? inForm : arity;
113
+ // Exact first, then a candidate `dyn` makes viable, which is what keeps an
114
+ // unlisted variable from turning one mistake into two.
115
+ const exact = this.match(viable, args, receiver, false);
116
+ if (exact) return exact;
117
+ const loose = this.match(viable, args, receiver, true);
118
+ if (loose) return loose;
119
+ return { reason: "no-overload", candidates: inForm };
120
+ }
121
+
122
+ private match(
123
+ candidates: readonly RegisteredFunction[],
124
+ args: readonly CelType[],
125
+ receiver: CelType | undefined,
126
+ allowDyn: boolean,
127
+ ): Resolution | undefined {
128
+ for (const candidate of candidates) {
129
+ const { signature } = candidate;
130
+ if (signature.parameters.length !== args.length) continue;
131
+ const bindings = new Map<string, CelType>();
132
+ if (signature.receiver && !this.fits(receiver ?? DYN, signature.receiver, bindings, allowDyn)) continue;
133
+ if (!signature.parameters.every((want, at) => this.fits(args[at]!, want, bindings, allowDyn))) continue;
134
+ return { resolved: candidate, returns: substitute(signature.returns, bindings) };
135
+ }
136
+ return undefined;
137
+ }
138
+
139
+ /**
140
+ * Whether an argument fits a parameter, binding type parameters as it goes.
141
+ *
142
+ * **A type parameter is bound by the first argument that mentions it**, and a later
143
+ * argument does not refine it: `[] + [3, 4]` is `list<T>`, because the empty list
144
+ * fixed the element type as "unknown" and the second argument cannot retroactively
145
+ * decide what the first one held.
146
+ */
147
+ private fits(
148
+ argument: CelType,
149
+ parameter: CelType,
150
+ bindings: Map<string, CelType>,
151
+ allowDyn: boolean,
152
+ ): boolean {
153
+ if (parameter.kind === "parameter") {
154
+ const bound = bindings.get(parameter.name);
155
+ if (!bound) {
156
+ bindings.set(parameter.name, argument);
157
+ return true;
158
+ }
159
+ // A binding that holds an unresolved parameter holds no information, so a later
160
+ // concrete argument refines it: `optional.none().orValue(42)` is an int.
161
+ if (bound.kind === "parameter" && argument.kind !== "parameter") {
162
+ bindings.set(parameter.name, argument);
163
+ return true;
164
+ }
165
+ if (bound.kind === "parameter" || argument.kind === "parameter") return true;
166
+ return assignable(argument, bound) || (allowDyn && (isDyn(argument) || isDyn(bound)));
167
+ }
168
+ if (parameter.kind === "list" && argument.kind === "list") {
169
+ return this.fits(argument.element, parameter.element, bindings, allowDyn);
170
+ }
171
+ if (parameter.kind === "map" && argument.kind === "map") {
172
+ return (
173
+ this.fits(argument.key, parameter.key, bindings, allowDyn) &&
174
+ this.fits(argument.value, parameter.value, bindings, allowDyn)
175
+ );
176
+ }
177
+ if (parameter.kind === "optional" && argument.kind === "optional") {
178
+ return this.fits(argument.value, parameter.value, bindings, allowDyn);
179
+ }
180
+ if (
181
+ parameter.kind === "nominal" &&
182
+ argument.kind === "nominal" &&
183
+ parameter.name === argument.name &&
184
+ parameter.args.length === argument.args.length
185
+ ) {
186
+ // A named type's arguments are invariant, so they fit one for one — which is also
187
+ // where a signature written over `Self` binds its own type parameters.
188
+ return parameter.args.every((want, at) => this.fits(argument.args[at]!, want, bindings, allowDyn));
189
+ }
190
+ if (isDyn(argument)) return allowDyn || isDyn(parameter);
191
+ return assignable(argument, parameter);
192
+ }
193
+ }
194
+
195
+ /** The type with every bound parameter replaced; an unbound one stays as it is. */
196
+ export function substitute(type: CelType, bindings: ReadonlyMap<string, CelType>): CelType {
197
+ switch (type.kind) {
198
+ case "parameter":
199
+ return bindings.get(type.name) ?? type;
200
+ case "list": {
201
+ const element = substitute(type.element, bindings);
202
+ return element === type.element ? type : { kind: "list", element };
203
+ }
204
+ case "map": {
205
+ const key = substitute(type.key, bindings);
206
+ const value = substitute(type.value, bindings);
207
+ return key === type.key && value === type.value ? type : { kind: "map", key, value };
208
+ }
209
+ case "optional": {
210
+ const value = substitute(type.value, bindings);
211
+ return value === type.value ? type : { kind: "optional", value };
212
+ }
213
+ case "nominal": {
214
+ const args = type.args.map((argument) => substitute(argument, bindings));
215
+ return args.every((argument, at) => argument === type.args[at]) ? type : { ...type, args };
216
+ }
217
+ default:
218
+ return type;
219
+ }
220
+ }
221
+
222
+ /** How a resolution failure names what it looked at, for a message a human reads. */
223
+ export function describeCandidates(candidates: readonly RegisteredFunction[]): string {
224
+ return candidates.map((candidate) => formatSignature(candidate.signature)).join(", ");
225
+ }
226
+
227
+ export function describeArguments(args: readonly CelType[]): string {
228
+ return args.map((type) => formatType(type)).join(", ");
229
+ }