@kernlang/core 3.3.9 → 3.4.1

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/dist/capability-matrix.d.ts +15 -0
  2. package/dist/capability-matrix.js +245 -0
  3. package/dist/capability-matrix.js.map +1 -0
  4. package/dist/codegen/body-ts.d.ts +68 -0
  5. package/dist/codegen/body-ts.js +214 -0
  6. package/dist/codegen/body-ts.js.map +1 -0
  7. package/dist/codegen/data-layer.d.ts +1 -1
  8. package/dist/codegen/data-layer.js +59 -23
  9. package/dist/codegen/data-layer.js.map +1 -1
  10. package/dist/codegen/events.js +1 -1
  11. package/dist/codegen/events.js.map +1 -1
  12. package/dist/codegen/functions.js +48 -7
  13. package/dist/codegen/functions.js.map +1 -1
  14. package/dist/codegen/ground-layer.js +10 -6
  15. package/dist/codegen/ground-layer.js.map +1 -1
  16. package/dist/codegen/helpers.d.ts +3 -1
  17. package/dist/codegen/helpers.js +5 -1
  18. package/dist/codegen/helpers.js.map +1 -1
  19. package/dist/codegen/kern-stdlib.d.ts +63 -0
  20. package/dist/codegen/kern-stdlib.js +160 -0
  21. package/dist/codegen/kern-stdlib.js.map +1 -0
  22. package/dist/codegen/machines.js +4 -3
  23. package/dist/codegen/machines.js.map +1 -1
  24. package/dist/codegen/modules.d.ts +1 -0
  25. package/dist/codegen/modules.js +52 -1
  26. package/dist/codegen/modules.js.map +1 -1
  27. package/dist/codegen/screens.js +31 -9
  28. package/dist/codegen/screens.js.map +1 -1
  29. package/dist/codegen/stdlib-preamble.d.ts +58 -0
  30. package/dist/codegen/stdlib-preamble.js +271 -0
  31. package/dist/codegen/stdlib-preamble.js.map +1 -0
  32. package/dist/codegen/type-system.d.ts +113 -1
  33. package/dist/codegen/type-system.js +404 -31
  34. package/dist/codegen/type-system.js.map +1 -1
  35. package/dist/codegen-core.d.ts +2 -2
  36. package/dist/codegen-core.js +65 -10
  37. package/dist/codegen-core.js.map +1 -1
  38. package/dist/codegen-expression.d.ts +11 -0
  39. package/dist/codegen-expression.js +199 -0
  40. package/dist/codegen-expression.js.map +1 -0
  41. package/dist/concepts.d.ts +3 -3
  42. package/dist/config.d.ts +16 -0
  43. package/dist/config.js +13 -0
  44. package/dist/config.js.map +1 -1
  45. package/dist/decompiler.js +575 -4
  46. package/dist/decompiler.js.map +1 -1
  47. package/dist/importer.d.ts +1 -0
  48. package/dist/importer.js +574 -34
  49. package/dist/importer.js.map +1 -1
  50. package/dist/index.d.ts +16 -3
  51. package/dist/index.js +19 -3
  52. package/dist/index.js.map +1 -1
  53. package/dist/node-props.d.ts +181 -1
  54. package/dist/node-props.js.map +1 -1
  55. package/dist/parser-core.d.ts +7 -1
  56. package/dist/parser-core.js +33 -7
  57. package/dist/parser-core.js.map +1 -1
  58. package/dist/parser-diagnostics.js +8 -0
  59. package/dist/parser-diagnostics.js.map +1 -1
  60. package/dist/parser-expression.d.ts +22 -0
  61. package/dist/parser-expression.js +774 -0
  62. package/dist/parser-expression.js.map +1 -0
  63. package/dist/parser-keywords.js +16 -0
  64. package/dist/parser-keywords.js.map +1 -1
  65. package/dist/parser-tokenizer.d.ts +5 -3
  66. package/dist/parser-tokenizer.js +97 -16
  67. package/dist/parser-tokenizer.js.map +1 -1
  68. package/dist/parser-validate-effects.d.ts +21 -0
  69. package/dist/parser-validate-effects.js +188 -0
  70. package/dist/parser-validate-effects.js.map +1 -0
  71. package/dist/parser-validate-expressions.d.ts +6 -0
  72. package/dist/parser-validate-expressions.js +41 -0
  73. package/dist/parser-validate-expressions.js.map +1 -0
  74. package/dist/parser-validate-propagation.d.ts +105 -0
  75. package/dist/parser-validate-propagation.js +684 -0
  76. package/dist/parser-validate-propagation.js.map +1 -0
  77. package/dist/parser-validate-union-kind.d.ts +24 -0
  78. package/dist/parser-validate-union-kind.js +97 -0
  79. package/dist/parser-validate-union-kind.js.map +1 -0
  80. package/dist/parser.d.ts +10 -3
  81. package/dist/parser.js +11 -5
  82. package/dist/parser.js.map +1 -1
  83. package/dist/schema.d.ts +1 -1
  84. package/dist/schema.js +562 -30
  85. package/dist/schema.js.map +1 -1
  86. package/dist/semantic-validator.js +24 -13
  87. package/dist/semantic-validator.js.map +1 -1
  88. package/dist/spec.d.ts +5 -2
  89. package/dist/spec.js +36 -2
  90. package/dist/spec.js.map +1 -1
  91. package/dist/types.d.ts +7 -1
  92. package/dist/types.js +7 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/value-ir.d.ts +96 -0
  95. package/dist/value-ir.js +25 -0
  96. package/dist/value-ir.js.map +1 -0
  97. package/package.json +1 -1
package/dist/importer.js CHANGED
@@ -44,6 +44,16 @@ function typeToString(typeNode, source) {
44
44
  // consumes the value as a string either way.
45
45
  return /\s/.test(raw) ? `"${raw.replace(/"/g, '\\"')}"` : raw;
46
46
  }
47
+ /** Slice 2f — extract `<T, U extends Foo = Bar>` from any declaration that
48
+ * carries typeParameters and return a `generics="..."` attribute fragment
49
+ * ready to splice into a KERN line. Returns empty string when there are none. */
50
+ function genericsAttr(typeParameters, source) {
51
+ if (!typeParameters || typeParameters.length === 0)
52
+ return '';
53
+ const parts = typeParameters.map((tp) => tp.getText(source));
54
+ const block = `<${parts.join(', ')}>`;
55
+ return ` generics="${escapeKernString(block)}"`;
56
+ }
47
57
  function getJSDoc(node, source) {
48
58
  const jsDocs = node.jsDoc;
49
59
  if (!jsDocs || jsDocs.length === 0)
@@ -64,7 +74,7 @@ function _indent(lines, depth) {
64
74
  const prefix = ' '.repeat(depth);
65
75
  return lines.map((l) => `${prefix}${l}`);
66
76
  }
67
- function escapeKernString(s) {
77
+ export function escapeKernString(s) {
68
78
  return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
69
79
  }
70
80
  function formatParams(params, source) {
@@ -73,11 +83,153 @@ function formatParams(params, source) {
73
83
  const name = p.name.getText(source);
74
84
  const type = typeToString(p.type, source);
75
85
  const optional = p.questionToken ? '?' : '';
86
+ // Codex review fix: variadic `...` was dropped by the legacy serializer.
87
+ // Without this, `function f(...[first]: T[])` and `function f(...args: T[])`
88
+ // both round-tripped without their rest marker, silently changing the
89
+ // call signature.
90
+ const dots = p.dotDotDotToken ? '...' : '';
76
91
  const defaultVal = p.initializer ? `=${p.initializer.getText(source)}` : '';
77
- return type ? `${name}${optional}:${type}${defaultVal}` : `${name}${optional}${defaultVal}`;
92
+ return type ? `${dots}${name}${optional}:${type}${defaultVal}` : `${dots}${name}${optional}${defaultVal}`;
78
93
  })
79
94
  .join(',');
80
95
  }
96
+ /**
97
+ * Slice 3c — try to emit fn parameters as structured `param` child nodes
98
+ * (for ValueIR canonicalisation of defaults). Returns the child lines on
99
+ * success, or `null` if the signature contains any feature not yet
100
+ * supported by structured params: optional `?`, variadic `...`, or
101
+ * destructuring patterns. Producers fall back to the legacy `params="..."`
102
+ * string for those signatures (all-or-nothing per signature).
103
+ *
104
+ * Caller is responsible for indenting the returned lines and omitting the
105
+ * `params="..."` attribute from the parent.
106
+ */
107
+ function tryFormatParamChildren(parameters, source) {
108
+ if (parameters.length === 0)
109
+ return [];
110
+ for (const p of parameters) {
111
+ // Slice 3c-extension: optional `?`, variadic `...`, and destructured
112
+ // `{a,b}` / `[x,y]` are now structurable via `optional=true` /
113
+ // `variadic=true` / `binding`+`element` children (gates dropped).
114
+ // Anything else on the LHS still bails to legacy.
115
+ if (!ts.isIdentifier(p.name) && !ts.isObjectBindingPattern(p.name) && !ts.isArrayBindingPattern(p.name)) {
116
+ return null;
117
+ }
118
+ // Multi-line types (inline object shapes spread across lines) can't be
119
+ // round-tripped through a single-line `type="..."` quoted attribute —
120
+ // KERN's tokeniser treats newlines as record separators. Leave the
121
+ // whole signature legacy when any param has a multi-line type.
122
+ if (p.type && /\n/.test(p.type.getText(source)))
123
+ return null;
124
+ }
125
+ const lines = [];
126
+ for (const p of parameters) {
127
+ const isObj = ts.isObjectBindingPattern(p.name);
128
+ const isArr = ts.isArrayBindingPattern(p.name);
129
+ // Codex P1 fix: read the RAW type text here (not via `typeToString`,
130
+ // which pre-quotes whitespace types like `"string | null"`). Structured
131
+ // `param` children always wrap the type in quotes, so a pre-quoted
132
+ // value would double-wrap to `type="\"string | null\""` — invalid TS
133
+ // (the resulting fn would have `x: "string | null"` as a string-literal
134
+ // type, not a union). Legacy `params="..."` callers still go through
135
+ // `typeToString` and its space-aware pre-quoting.
136
+ const type = p.type ? p.type.getText(source) : '';
137
+ if (isObj || isArr) {
138
+ // Slice 3c-extension #3: destructured param. Convert the binding pattern
139
+ // to `binding`/`element` child lines (slice 3d's nodes). Bail to legacy
140
+ // if the pattern uses rest/defaults/nesting, since slice 3d's structured
141
+ // form doesn't represent those — the whole signature falls back to
142
+ // `params="..."` so the parent can still emit valid TS.
143
+ //
144
+ // Codex review fix: `function f(...[first]: T[])` (variadic + array
145
+ // destructure) is valid TS but the structured form has no slot for the
146
+ // outer `...`. Bail to legacy so the rest marker is preserved verbatim
147
+ // in `params="..."` rather than silently dropped on round-trip.
148
+ if (p.dotDotDotToken)
149
+ return null;
150
+ const childLines = tryFormatParamBindingPattern(p.name, source);
151
+ if (childLines === null)
152
+ return null;
153
+ // The destructured-param header line carries no `name=`. Type / optional
154
+ // / default still apply to the whole pattern: `({a,b}: T = {a:1,b:2})`.
155
+ const parts = ['param'];
156
+ if (type)
157
+ parts.push(`type="${escapeKernString(type)}"`);
158
+ if (p.questionToken)
159
+ parts.push('optional=true');
160
+ if (p.initializer)
161
+ parts.push(`value={{ ${p.initializer.getText(source)} }}`);
162
+ lines.push(parts.join(' '));
163
+ for (const child of childLines)
164
+ lines.push(` ${child}`);
165
+ continue;
166
+ }
167
+ const name = p.name.getText(source);
168
+ const parts = [`param name=${name}`];
169
+ if (type)
170
+ parts.push(`type="${escapeKernString(type)}"`);
171
+ if (p.questionToken)
172
+ parts.push('optional=true');
173
+ if (p.dotDotDotToken)
174
+ parts.push('variadic=true');
175
+ if (p.initializer) {
176
+ parts.push(`value={{ ${p.initializer.getText(source)} }}`);
177
+ }
178
+ lines.push(parts.join(' '));
179
+ }
180
+ return lines;
181
+ }
182
+ /**
183
+ * Slice 3c-extension #3 — convert a TS parameter `BindingPattern` (object or
184
+ * array) into structured `binding` / `element` KERN child lines (slice 3d's
185
+ * shape). Returns null when the pattern uses features the structured form
186
+ * can't represent: rest (`...rest`), defaults (`{a = 1}`), or nested patterns
187
+ * (`{a: {b}}`). Caller falls back to legacy `params="..."` when null.
188
+ *
189
+ * Mirrors `tryFormatDestructure`'s sub-branch for slice 3d, but tightened to
190
+ * just the LHS pattern (no source/kind/type — those live on the param node).
191
+ */
192
+ function tryFormatParamBindingPattern(pattern, source) {
193
+ const childLines = [];
194
+ if (ts.isObjectBindingPattern(pattern)) {
195
+ for (const el of pattern.elements) {
196
+ if (el.dotDotDotToken)
197
+ return null;
198
+ if (el.initializer)
199
+ return null;
200
+ if (!ts.isIdentifier(el.name))
201
+ return null;
202
+ const localName = el.name.getText(source);
203
+ let line = `binding name=${localName}`;
204
+ if (el.propertyName) {
205
+ if (!ts.isIdentifier(el.propertyName))
206
+ return null;
207
+ line += ` key=${el.propertyName.getText(source)}`;
208
+ }
209
+ childLines.push(line);
210
+ }
211
+ }
212
+ else {
213
+ let idx = 0;
214
+ for (const el of pattern.elements) {
215
+ if (ts.isOmittedExpression(el)) {
216
+ idx++;
217
+ continue;
218
+ }
219
+ if (el.dotDotDotToken)
220
+ return null;
221
+ if (el.initializer)
222
+ return null;
223
+ if (!ts.isIdentifier(el.name))
224
+ return null;
225
+ childLines.push(`element name=${el.name.getText(source)} index=${idx}`);
226
+ idx++;
227
+ }
228
+ }
229
+ if (childLines.length === 0)
230
+ return null;
231
+ return childLines;
232
+ }
81
233
  function getBodyText(body, source) {
82
234
  if (!body)
83
235
  return undefined;
@@ -151,6 +303,7 @@ function convertTypeAlias(node, source) {
151
303
  const lines = [];
152
304
  const name = node.name.getText(source);
153
305
  const exp = isExported(node) ? ' export=true' : '';
306
+ const generics = genericsAttr(node.typeParameters, source);
154
307
  const doc = getJSDoc(node, source);
155
308
  if (doc)
156
309
  lines.push(`doc text="${escapeKernString(doc)}"`);
@@ -160,19 +313,20 @@ function convertTypeAlias(node, source) {
160
313
  const allStringLiterals = members.every((m) => ts.isLiteralTypeNode(m) && m.literal.kind === ts.SyntaxKind.StringLiteral);
161
314
  if (allStringLiterals) {
162
315
  const values = members.map((m) => m.literal.text).join('|');
163
- lines.push(`type name=${name} values="${values}"${exp}`);
316
+ lines.push(`type name=${name} values="${values}"${generics}${exp}`);
164
317
  return lines;
165
318
  }
166
319
  }
167
320
  // General type alias
168
321
  const typeText = typeToString(node.type, source);
169
- lines.push(`type name=${name} alias="${escapeKernString(typeText)}"${exp}`);
322
+ lines.push(`type name=${name} alias="${escapeKernString(typeText)}"${generics}${exp}`);
170
323
  return lines;
171
324
  }
172
325
  function convertInterface(node, source) {
173
326
  const lines = [];
174
327
  const name = node.name.getText(source);
175
328
  const exp = isExported(node) ? ' export=true' : '';
329
+ const generics = genericsAttr(node.typeParameters, source);
176
330
  const doc = getJSDoc(node, source);
177
331
  if (doc)
178
332
  lines.push(`doc text="${escapeKernString(doc)}"`);
@@ -180,7 +334,7 @@ function convertInterface(node, source) {
180
334
  ?.filter((h) => h.token === ts.SyntaxKind.ExtendsKeyword)
181
335
  .flatMap((h) => h.types.map((t) => t.getText(source)));
182
336
  const extendsStr = extends_ && extends_.length > 0 ? ` extends=${extends_.join(',')}` : '';
183
- lines.push(`interface name=${name}${extendsStr}${exp}`);
337
+ lines.push(`interface name=${name}${generics}${extendsStr}${exp}`);
184
338
  for (const member of node.members) {
185
339
  if (ts.isPropertySignature(member)) {
186
340
  const fieldName = member.name.getText(source);
@@ -196,10 +350,26 @@ function convertInterface(node, source) {
196
350
  const methodName = member.name.getText(source);
197
351
  const params = member.parameters ? formatParams(member.parameters, source) : '';
198
352
  const returns = typeToString(member.type, source) || 'void';
199
- const funcType = `(${params}) => ${returns}`;
353
+ // Slice 2f preserve method-level generics by prepending <T,...> to the function-type
354
+ // string when the method has typeParameters. Format: `<T>(params) => R`.
355
+ const tp = member.typeParameters;
356
+ const genericsBlock = tp && tp.length > 0 ? `<${tp.map((t) => t.getText(source)).join(', ')}>` : '';
357
+ const funcType = `${genericsBlock}(${params}) => ${returns}`;
200
358
  const optional = member.questionToken ? ' optional=true' : '';
201
359
  lines.push(` field name=${methodName} type="${escapeKernString(funcType)}"${optional}`);
202
360
  }
361
+ else if (ts.isIndexSignatureDeclaration(member)) {
362
+ // [keyName: keyType]: type → indexer keyName=... keyType=... type=...
363
+ const param = member.parameters[0];
364
+ if (param) {
365
+ const keyName = param.name.getText(source);
366
+ const keyType = typeToString(param.type, source) || 'string';
367
+ const valueType = typeToString(member.type, source) || 'unknown';
368
+ const isReadonly = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword);
369
+ const ro = isReadonly ? ' readonly=true' : '';
370
+ lines.push(` indexer keyName=${keyName} keyType=${keyType} type=${valueType}${ro}`);
371
+ }
372
+ }
203
373
  }
204
374
  return lines;
205
375
  }
@@ -207,19 +377,47 @@ function convertEnum(node, source) {
207
377
  const lines = [];
208
378
  const name = node.name.getText(source);
209
379
  const exp = isExported(node) ? ' export=true' : '';
380
+ const isConst = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ConstKeyword);
381
+ const constStr = isConst ? ' const=true' : '';
210
382
  const doc = getJSDoc(node, source);
211
383
  if (doc)
212
384
  lines.push(`doc text="${escapeKernString(doc)}"`);
213
- // Check if all members are string literals type with values
214
- const allString = node.members.every((m) => m.initializer && ts.isStringLiteral(m.initializer));
215
- if (allString) {
216
- const values = node.members.map((m) => m.initializer.text).join('|');
217
- lines.push(`type name=${name} values="${values}"${exp}`);
218
- }
219
- else {
220
- // Numeric or mixed enum → type alias
385
+ // Auto-increment numeric enum: no explicit initializerscompact `values="A|B|C"` form.
386
+ const allBare = node.members.length > 0 && node.members.every((m) => !m.initializer);
387
+ if (allBare) {
221
388
  const values = node.members.map((m) => m.name.getText(source)).join('|');
222
- lines.push(`type name=${name} values="${values}"${exp}`);
389
+ lines.push(`enum name=${name} values="${values}"${constStr}${exp}`);
390
+ return lines;
391
+ }
392
+ // Otherwise emit `member` children to preserve explicit values.
393
+ lines.push(`enum name=${name}${constStr}${exp}`);
394
+ for (const m of node.members) {
395
+ const mname = m.name.getText(source);
396
+ if (!m.initializer) {
397
+ lines.push(` member name=${mname}`);
398
+ }
399
+ else if (ts.isStringLiteral(m.initializer)) {
400
+ const text = m.initializer.text;
401
+ if (text === '') {
402
+ // Empty string would be dropped by the codegen's "no value" guard
403
+ // (`if (rawVal === '') valueStr = ''`). Route via the expression
404
+ // form so it round-trips as the literal `""` instead of `Empty,`.
405
+ lines.push(` member name=${mname} value={{""}}`);
406
+ }
407
+ else {
408
+ lines.push(` member name=${mname} value="${escapeKernString(text)}"`);
409
+ }
410
+ }
411
+ else if (ts.isNumericLiteral(m.initializer)) {
412
+ lines.push(` member name=${mname} value=${m.initializer.text}`);
413
+ }
414
+ else {
415
+ // Any other initializer (computed `1 << 2`, identifier reference, prefix
416
+ // unary like `-1`, etc.) — emit via {{expr}} so the codegen round-trips
417
+ // the raw expression instead of fabricating a string-quoted version.
418
+ const exprText = m.initializer.getText(source);
419
+ lines.push(` member name=${mname} value={{${exprText}}}`);
420
+ }
223
421
  }
224
422
  return lines;
225
423
  }
@@ -227,19 +425,32 @@ function convertFunction(node, source) {
227
425
  const lines = [];
228
426
  const name = node.name?.getText(source) ?? 'anonymous';
229
427
  const exp = isExported(node) ? ' export=true' : '';
428
+ const generics = genericsAttr(node.typeParameters, source);
230
429
  const doc = getJSDoc(node, source);
231
430
  const asyncStr = isAsync(node) ? ' async=true' : '';
232
431
  const isGenerator = node.asteriskToken != null;
233
432
  const generatorStr = isGenerator ? (isAsync(node) ? ' stream=true' : ' generator=true') : '';
234
433
  if (doc)
235
434
  lines.push(`doc text="${escapeKernString(doc)}"`);
236
- const params = formatParams(node.parameters, source);
435
+ // Slice 3c prefer structured `param` child nodes when the signature is
436
+ // simple enough (no `?`/`...`/destructuring). Falls back to the legacy
437
+ // `params="..."` string for unsupported shapes.
438
+ const paramChildren = tryFormatParamChildren(node.parameters, source);
237
439
  const returns = typeToString(node.type, source);
238
- const paramsStr = params ? ` params="${params}"` : '';
239
440
  const returnsStr = returns ? ` returns=${returns}` : '';
240
- // For async generators, use stream=true instead of async=true + generator=true
241
441
  const asyncFinal = isGenerator && isAsync(node) ? '' : asyncStr;
242
- lines.push(`fn name=${name}${paramsStr}${returnsStr}${asyncFinal}${generatorStr}${exp}`);
442
+ const paramsStr = paramChildren !== null
443
+ ? ''
444
+ : (() => {
445
+ const params = formatParams(node.parameters, source);
446
+ return params ? ` params="${params}"` : '';
447
+ })();
448
+ lines.push(`fn name=${name}${paramsStr}${returnsStr}${asyncFinal}${generatorStr}${generics}${exp}`);
449
+ if (paramChildren) {
450
+ for (const childLine of paramChildren) {
451
+ lines.push(` ${childLine}`);
452
+ }
453
+ }
243
454
  const body = getBodyText(node.body, source);
244
455
  if (body) {
245
456
  lines.push(' handler <<<');
@@ -250,10 +461,46 @@ function convertFunction(node, source) {
250
461
  }
251
462
  return lines;
252
463
  }
464
+ /** Slice 2e — convert a TS overload group (N signatures + 1 implementation)
465
+ * to a single KERN `fn` node with `overload` children. The implementation is
466
+ * the declaration that has a body; if none has a body (rare — ambient module
467
+ * declarations), we treat the LAST as implementation for round-trip safety. */
468
+ function convertFunctionGroup(group, source, stats) {
469
+ // Find the implementation (the one with a body). If none, fall back to last.
470
+ const implIdx = group.findIndex((fn) => fn.body != null);
471
+ const impl = group[implIdx >= 0 ? implIdx : group.length - 1];
472
+ const overloads = group.filter((fn) => fn !== impl);
473
+ // Convert the implementation as a normal function (fn). Then append overload children.
474
+ stats.functions++;
475
+ const implLines = convertFunction(impl, source);
476
+ // Insert overload child lines BEFORE the handler block (which lives under
477
+ // the `fn name=...` header line). We slice at the index of the handler open.
478
+ const overloadLines = [];
479
+ for (const ov of overloads) {
480
+ const params = formatParams(ov.parameters, source);
481
+ const returns = typeToString(ov.type, source);
482
+ const paramsStr = params ? ` params="${params}"` : '';
483
+ const returnsStr = returns ? ` returns=${returns}` : '';
484
+ // Slice 2f — overload signatures can declare their own type parameters
485
+ // (e.g. `function id<T>(x: T): T;` paired with non-generic impl).
486
+ // Without this, the overload would round-trip to invalid TS where T is undefined.
487
+ const generics = genericsAttr(ov.typeParameters, source);
488
+ overloadLines.push(` overload${paramsStr}${returnsStr}${generics}`);
489
+ }
490
+ // Splice overload lines after the `fn name=...` (and its leading doc, if any)
491
+ // and before the ` handler <<<` block. We find the first line that starts
492
+ // with ` handler` (handler is the first child of fn) — overloads go before it.
493
+ const handlerIdx = implLines.findIndex((l) => l.startsWith(' handler'));
494
+ if (handlerIdx === -1) {
495
+ return [...implLines, ...overloadLines];
496
+ }
497
+ return [...implLines.slice(0, handlerIdx), ...overloadLines, ...implLines.slice(handlerIdx)];
498
+ }
253
499
  function convertClass(node, source) {
254
500
  const lines = [];
255
501
  const name = node.name?.getText(source) ?? 'AnonymousClass';
256
502
  const exp = isExported(node) ? ' export=true' : '';
503
+ const generics = genericsAttr(node.typeParameters, source);
257
504
  const doc = getJSDoc(node, source);
258
505
  // Check if it extends Error → error node
259
506
  const extendsClause = node.heritageClauses?.find((h) => h.token === ts.SyntaxKind.ExtendsKeyword);
@@ -272,7 +519,7 @@ function convertClass(node, source) {
272
519
  : '';
273
520
  const extendsStr = extendsClause && !isError ? ` extends=${extendsClause.types.map((t) => t.getText(source)).join(',')}` : '';
274
521
  const abstractStr = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) ? ' abstract=true' : '';
275
- lines.push(`class name=${name}${extendsStr}${implementsStr}${abstractStr}${exp}`);
522
+ lines.push(`class name=${name}${extendsStr}${implementsStr}${abstractStr}${generics}${exp}`);
276
523
  for (const member of node.members) {
277
524
  if (ts.isPropertyDeclaration(member)) {
278
525
  const fieldName = member.name.getText(source);
@@ -280,16 +527,33 @@ function convertClass(node, source) {
280
527
  const priv = isPrivate(member) ? ' private=true' : '';
281
528
  const ro = isReadonly(member) ? ' readonly=true' : '';
282
529
  const staticStr = isStatic(member) ? ' static=true' : '';
283
- const defaultVal = member.initializer ? ` default={{ ${member.initializer.getText(source)} }}` : '';
530
+ // Slice 3b: emit `value={{ <init> }}` (canonical), not `default={{ ... }}`.
531
+ // The `{{...}}` wrap stays — TS initializers can be arbitrary expressions
532
+ // (function calls, ternaries, etc.) that ValueIR may not parse, and the
533
+ // ExprObject form is the safe escape hatch through emitConstValue.
534
+ const valueAttr = member.initializer ? ` value={{ ${member.initializer.getText(source)} }}` : '';
284
535
  const memberDoc = getJSDoc(member, source);
285
536
  if (memberDoc)
286
537
  lines.push(` doc text="${escapeKernString(memberDoc)}"`);
287
- lines.push(` field name=${fieldName}${fieldType ? ` type=${fieldType}` : ''}${priv}${staticStr}${ro}${defaultVal}`);
538
+ lines.push(` field name=${fieldName}${fieldType ? ` type=${fieldType}` : ''}${priv}${staticStr}${ro}${valueAttr}`);
288
539
  }
289
540
  else if (ts.isConstructorDeclaration(member)) {
290
- const ctorParams = formatParams(member.parameters, source);
291
- const ctorParamsStr = ctorParams ? ` params="${ctorParams}"` : '';
292
- lines.push(` constructor${ctorParamsStr}`);
541
+ // Slice 3c try structured `param` children for default-bearing
542
+ // signatures; fall back to legacy `params="..."` string otherwise.
543
+ const ctorParamChildren = tryFormatParamChildren(member.parameters, source);
544
+ const ctorParamsStr = ctorParamChildren !== null
545
+ ? ''
546
+ : (() => {
547
+ const ctorParams = formatParams(member.parameters, source);
548
+ return ctorParams ? ` params="${ctorParams}"` : '';
549
+ })();
550
+ const generics = genericsAttr(member.typeParameters, source);
551
+ lines.push(` constructor${ctorParamsStr}${generics}`);
552
+ if (ctorParamChildren) {
553
+ for (const childLine of ctorParamChildren) {
554
+ lines.push(` ${childLine}`);
555
+ }
556
+ }
293
557
  const body = getBodyText(member.body, source);
294
558
  if (body) {
295
559
  lines.push(' handler <<<');
@@ -301,17 +565,28 @@ function convertClass(node, source) {
301
565
  }
302
566
  else if (ts.isMethodDeclaration(member)) {
303
567
  const methodName = member.name.getText(source);
304
- const params = formatParams(member.parameters, source);
568
+ const paramChildren = tryFormatParamChildren(member.parameters, source);
305
569
  const returns = typeToString(member.type, source);
570
+ const generics = genericsAttr(member.typeParameters, source);
306
571
  const asyncStr = isAsync(member) ? ' async=true' : '';
307
572
  const staticStr = isStatic(member) ? ' static=true' : '';
308
573
  const privStr = isPrivate(member) ? ' private=true' : '';
309
- const paramsStr = params ? ` params="${params}"` : '';
574
+ const paramsStr = paramChildren !== null
575
+ ? ''
576
+ : (() => {
577
+ const params = formatParams(member.parameters, source);
578
+ return params ? ` params="${params}"` : '';
579
+ })();
310
580
  const returnsStr = returns ? ` returns=${returns}` : '';
311
581
  const memberDoc = getJSDoc(member, source);
312
582
  if (memberDoc)
313
583
  lines.push(` doc text="${escapeKernString(memberDoc)}"`);
314
- lines.push(` method name=${methodName}${paramsStr}${returnsStr}${asyncStr}${staticStr}${privStr}`);
584
+ lines.push(` method name=${methodName}${paramsStr}${returnsStr}${asyncStr}${staticStr}${privStr}${generics}`);
585
+ if (paramChildren) {
586
+ for (const childLine of paramChildren) {
587
+ lines.push(` ${childLine}`);
588
+ }
589
+ }
315
590
  const body = getBodyText(member.body, source);
316
591
  if (body) {
317
592
  lines.push(' handler <<<');
@@ -338,11 +613,21 @@ function convertClass(node, source) {
338
613
  }
339
614
  else if (ts.isSetAccessorDeclaration(member)) {
340
615
  const sname = member.name.getText(source);
341
- const params = formatParams(member.parameters, source);
342
- const paramsStr = params ? ` params="${params}"` : '';
616
+ const setterParamChildren = tryFormatParamChildren(member.parameters, source);
617
+ const paramsStr = setterParamChildren !== null
618
+ ? ''
619
+ : (() => {
620
+ const params = formatParams(member.parameters, source);
621
+ return params ? ` params="${params}"` : '';
622
+ })();
343
623
  const privStr = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword) ? ' private=true' : '';
344
624
  const staticStr = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ? ' static=true' : '';
345
625
  lines.push(` setter name=${sname}${paramsStr}${privStr}${staticStr}`);
626
+ if (setterParamChildren) {
627
+ for (const childLine of setterParamChildren) {
628
+ lines.push(` ${childLine}`);
629
+ }
630
+ }
346
631
  const body = getBodyText(member.body, source);
347
632
  if (body) {
348
633
  lines.push(' handler <<<');
@@ -386,11 +671,223 @@ function convertErrorClass(node, source, name, baseClass, exp, lines) {
386
671
  }
387
672
  return lines;
388
673
  }
674
+ /**
675
+ * Slice 3d — try to format a destructured `const`/`let` declaration as a
676
+ * structured `destructure` IR node with `binding` (object) or `element`
677
+ * (array) children. Returns `null` for non-destructured patterns; returns
678
+ * a single fallback line carrying the raw statement in `expr={{...}}` for
679
+ * patterns we can't structure (rest `...`, defaults `=v`, nested `{a:{b}}`,
680
+ * computed/string keys).
681
+ */
682
+ function tryFormatDestructure(decl, kind, exported, source, fullStmtText) {
683
+ if (!decl.initializer)
684
+ return null;
685
+ const isObj = ts.isObjectBindingPattern(decl.name);
686
+ const isArr = ts.isArrayBindingPattern(decl.name);
687
+ if (!isObj && !isArr)
688
+ return null;
689
+ const fallback = () => [`destructure expr={{ ${fullStmtText} }}`];
690
+ const childLines = [];
691
+ if (isObj) {
692
+ for (const el of decl.name.elements) {
693
+ if (el.dotDotDotToken)
694
+ return fallback();
695
+ if (el.initializer)
696
+ return fallback();
697
+ if (!ts.isIdentifier(el.name))
698
+ return fallback();
699
+ const localName = el.name.getText(source);
700
+ let line = `binding name=${localName}`;
701
+ if (el.propertyName) {
702
+ if (!ts.isIdentifier(el.propertyName))
703
+ return fallback();
704
+ line += ` key=${el.propertyName.getText(source)}`;
705
+ }
706
+ childLines.push(line);
707
+ }
708
+ }
709
+ else {
710
+ let idx = 0;
711
+ for (const el of decl.name.elements) {
712
+ if (ts.isOmittedExpression(el)) {
713
+ idx++;
714
+ continue;
715
+ }
716
+ if (el.dotDotDotToken)
717
+ return fallback();
718
+ if (el.initializer)
719
+ return fallback();
720
+ if (!ts.isIdentifier(el.name))
721
+ return fallback();
722
+ childLines.push(`element name=${el.name.getText(source)} index=${idx}`);
723
+ idx++;
724
+ }
725
+ }
726
+ if (childLines.length === 0)
727
+ return fallback();
728
+ const sourceText = decl.initializer.getText(source);
729
+ const isSimpleIdent = /^[A-Za-z_$][\w$]*$/.test(sourceText);
730
+ const sourceAttr = isSimpleIdent ? ` source=${sourceText}` : ` source={{ ${sourceText} }}`;
731
+ const type = typeToString(decl.type, source);
732
+ const typeAttr = type ? ` type=${type}` : '';
733
+ const expAttr = exported ? ' export=true' : '';
734
+ const lines = [`destructure kind=${kind}${typeAttr}${sourceAttr}${expAttr}`];
735
+ for (const line of childLines)
736
+ lines.push(` ${line}`);
737
+ return lines;
738
+ }
739
+ /**
740
+ * Slice 3e — pick the right KERN attribute form for a TS expression node
741
+ * used as a key/value in a Map/Set literal entry.
742
+ *
743
+ * - String literals → `prop="foo"` so the quoted source flows through
744
+ * __quotedProps and codegen JSON-stringifies.
745
+ * - Primitive literals (numeric, true, false, null) → `prop=42` bare so
746
+ * they go through ValueIR canonicalisation per slice 3a-c precedent.
747
+ * - Anything else → `prop={{ raw-ts }}` ExprObject escape hatch.
748
+ */
749
+ function formatLitEntryAttr(propName, expr, source) {
750
+ const text = expr.getText(source);
751
+ if (ts.isStringLiteral(expr))
752
+ return `${propName}=${text}`;
753
+ if (ts.isNumericLiteral(expr) ||
754
+ expr.kind === ts.SyntaxKind.TrueKeyword ||
755
+ expr.kind === ts.SyntaxKind.FalseKeyword ||
756
+ expr.kind === ts.SyntaxKind.NullKeyword) {
757
+ return `${propName}=${text}`;
758
+ }
759
+ return `${propName}={{ ${text} }}`;
760
+ }
761
+ /**
762
+ * Slice 3e — try to format `const x = new Map([['k', v], ...])` as a
763
+ * structured `mapLit` IR node with `mapEntry` children. Returns null when
764
+ * the initializer is not a Map constructor call, or when the array argument
765
+ * contains non-canonical entries (spread, computed keys, non-tuple entries).
766
+ */
767
+ function tryFormatMapLit(decl, kind, exported, source) {
768
+ if (!decl.initializer || !ts.isNewExpression(decl.initializer))
769
+ return null;
770
+ if (decl.initializer.expression.getText(source) !== 'Map')
771
+ return null;
772
+ if (!ts.isIdentifier(decl.name))
773
+ return null;
774
+ // Empty `new Map()` or unusual form — pass through.
775
+ const args = decl.initializer.arguments;
776
+ if (!args || args.length === 0)
777
+ return null;
778
+ const arg = args[0];
779
+ if (!ts.isArrayLiteralExpression(arg))
780
+ return null;
781
+ const entryLines = [];
782
+ for (const el of arg.elements) {
783
+ if (!ts.isArrayLiteralExpression(el))
784
+ return null; // non-tuple entry
785
+ if (el.elements.length !== 2)
786
+ return null; // not [k, v]
787
+ if (el.elements.some((e) => ts.isSpreadElement(e)))
788
+ return null;
789
+ const keyAttr = formatLitEntryAttr('key', el.elements[0], source);
790
+ const valAttr = formatLitEntryAttr('value', el.elements[1], source);
791
+ entryLines.push(`mapEntry ${keyAttr} ${valAttr}`);
792
+ }
793
+ if (entryLines.length === 0)
794
+ return null; // empty array — pass through
795
+ const name = decl.name.getText(source);
796
+ const type = typeToString(decl.type, source);
797
+ const typeAttr = type ? ` type=${type}` : '';
798
+ const expAttr = exported ? ' export=true' : '';
799
+ const kindAttr = kind === 'let' ? ' kind=let' : '';
800
+ const lines = [`mapLit name=${name}${typeAttr}${kindAttr}${expAttr}`];
801
+ for (const l of entryLines)
802
+ lines.push(` ${l}`);
803
+ return lines;
804
+ }
805
+ /**
806
+ * Slice 3e — try to format `const x = new Set([v1, v2, ...])` as a
807
+ * structured `setLit` IR node with `setItem` children. Returns null when
808
+ * the initializer is not a Set constructor call, or when the array argument
809
+ * contains non-canonical members (spread).
810
+ */
811
+ function tryFormatSetLit(decl, kind, exported, source) {
812
+ if (!decl.initializer || !ts.isNewExpression(decl.initializer))
813
+ return null;
814
+ if (decl.initializer.expression.getText(source) !== 'Set')
815
+ return null;
816
+ if (!ts.isIdentifier(decl.name))
817
+ return null;
818
+ const args = decl.initializer.arguments;
819
+ if (!args || args.length === 0)
820
+ return null;
821
+ const arg = args[0];
822
+ if (!ts.isArrayLiteralExpression(arg))
823
+ return null;
824
+ const itemLines = [];
825
+ for (const el of arg.elements) {
826
+ if (ts.isSpreadElement(el))
827
+ return null;
828
+ itemLines.push(`setItem ${formatLitEntryAttr('value', el, source)}`);
829
+ }
830
+ if (itemLines.length === 0)
831
+ return null;
832
+ const name = decl.name.getText(source);
833
+ const type = typeToString(decl.type, source);
834
+ const typeAttr = type ? ` type=${type}` : '';
835
+ const expAttr = exported ? ' export=true' : '';
836
+ const kindAttr = kind === 'let' ? ' kind=let' : '';
837
+ const lines = [`setLit name=${name}${typeAttr}${kindAttr}${expAttr}`];
838
+ for (const l of itemLines)
839
+ lines.push(` ${l}`);
840
+ return lines;
841
+ }
389
842
  function convertVariableStatement(node, source) {
390
843
  const lines = [];
391
844
  const exp = isExported(node) ? ' export=true' : '';
392
845
  const doc = getJSDoc(node, source);
846
+ // Slice 3d — derive const|let|var from the declaration list flags.
847
+ const declKind = (node.declarationList.flags & ts.NodeFlags.Const) !== 0
848
+ ? 'const'
849
+ : (node.declarationList.flags & ts.NodeFlags.Let) !== 0
850
+ ? 'let'
851
+ : 'const';
393
852
  for (const decl of node.declarationList.declarations) {
853
+ // Slice 3d — destructured LHS handled BEFORE the simple-value branch
854
+ // because `decl.name` for `const {a,b}=obj` is a binding pattern, not
855
+ // an identifier. Falls through to the legacy paths only when LHS is a
856
+ // plain identifier.
857
+ if (ts.isObjectBindingPattern(decl.name) || ts.isArrayBindingPattern(decl.name)) {
858
+ if (doc)
859
+ lines.push(`doc text="${escapeKernString(doc)}"`);
860
+ // Reconstruct the full statement text for the expr= fallback path.
861
+ // Prefer node.getText(source) so we capture `export const {a}=obj;` shape.
862
+ const fullStmtText = node.getText(source).trim().replace(/;$/, '');
863
+ const result = tryFormatDestructure(decl, declKind, isExported(node), source, fullStmtText);
864
+ if (result) {
865
+ for (const l of result)
866
+ lines.push(l);
867
+ continue;
868
+ }
869
+ }
870
+ // Slice 3e — Map/Set literal detection. Falls through to legacy const
871
+ // when the constructor argument shape isn't structurable (spread,
872
+ // computed keys, non-tuple entries, etc.).
873
+ {
874
+ const mapLines = tryFormatMapLit(decl, declKind, isExported(node), source);
875
+ if (mapLines) {
876
+ if (doc)
877
+ lines.push(`doc text="${escapeKernString(doc)}"`);
878
+ for (const l of mapLines)
879
+ lines.push(l);
880
+ continue;
881
+ }
882
+ const setLines = tryFormatSetLit(decl, declKind, isExported(node), source);
883
+ if (setLines) {
884
+ if (doc)
885
+ lines.push(`doc text="${escapeKernString(doc)}"`);
886
+ for (const l of setLines)
887
+ lines.push(l);
888
+ continue;
889
+ }
890
+ }
394
891
  const name = decl.name.getText(source);
395
892
  const type = typeToString(decl.type, source);
396
893
  const typeStr = type ? ` type=${type}` : '';
@@ -419,14 +916,27 @@ function convertVariableStatement(node, source) {
419
916
  // Arrow function or function expression → fn
420
917
  const func = decl.initializer;
421
918
  const asyncStr = isAsync(func) ? ' async=true' : '';
422
- const params = formatParams(func.parameters, source);
919
+ // Slice 3c try structured `param` children for default-bearing
920
+ // signatures; fall back to legacy `params="..."` string otherwise.
921
+ const paramChildren = tryFormatParamChildren(func.parameters, source);
423
922
  const returns = typeToString(func.type, source);
424
- const paramsStr = params ? ` params="${params}"` : '';
923
+ const generics = genericsAttr(func.typeParameters, source);
924
+ const paramsStr = paramChildren !== null
925
+ ? ''
926
+ : (() => {
927
+ const params = formatParams(func.parameters, source);
928
+ return params ? ` params="${params}"` : '';
929
+ })();
425
930
  const returnsStr = returns ? ` returns=${returns}` : '';
426
931
  const isGen = ts.isFunctionExpression(func) && func.asteriskToken != null;
427
932
  const genStr = isGen ? (isAsync(func) ? ' stream=true' : ' generator=true') : '';
428
933
  const asyncFinal = isGen && isAsync(func) ? '' : asyncStr;
429
- lines.push(`fn name=${name}${paramsStr}${returnsStr}${asyncFinal}${genStr}${exp}`);
934
+ lines.push(`fn name=${name}${paramsStr}${returnsStr}${asyncFinal}${genStr}${generics}${exp}`);
935
+ if (paramChildren) {
936
+ for (const childLine of paramChildren) {
937
+ lines.push(` ${childLine}`);
938
+ }
939
+ }
430
940
  const body = ts.isArrowFunction(func)
431
941
  ? getBodyText(func.body, source)
432
942
  : getBodyText(func.body, source);
@@ -1084,7 +1594,37 @@ export function importTypeScript(tsSource, fileName = 'input.ts') {
1084
1594
  enums: 0,
1085
1595
  components: 0,
1086
1596
  };
1087
- for (const statement of sourceFile.statements) {
1597
+ // Slice 2e — TS overloaded functions are a sequence of FunctionDeclaration
1598
+ // nodes sharing a name, where the LAST one has a body and the earlier ones
1599
+ // are signature-only. Group them so the importer emits a single `fn` with
1600
+ // `overload` children rather than N separate fn nodes.
1601
+ const statements = sourceFile.statements;
1602
+ for (let i = 0; i < statements.length; i++) {
1603
+ const statement = statements[i];
1604
+ if (ts.isFunctionDeclaration(statement) && statement.name) {
1605
+ const fnName = statement.name.getText(sourceFile);
1606
+ // Look ahead to collect consecutive same-named function declarations.
1607
+ const group = [statement];
1608
+ while (i + 1 < statements.length) {
1609
+ const next = statements[i + 1];
1610
+ if (ts.isFunctionDeclaration(next) && next.name && next.name.getText(sourceFile) === fnName) {
1611
+ group.push(next);
1612
+ i++;
1613
+ }
1614
+ else {
1615
+ break;
1616
+ }
1617
+ }
1618
+ if (group.length > 1) {
1619
+ const converted = convertFunctionGroup(group, sourceFile, stats);
1620
+ if (converted.length > 0) {
1621
+ kernLines.push(...converted);
1622
+ kernLines.push('');
1623
+ }
1624
+ continue;
1625
+ }
1626
+ // Single declaration — fall through to ordinary path.
1627
+ }
1088
1628
  const converted = convertStatement(statement, sourceFile, unmapped, stats);
1089
1629
  if (converted.length > 0) {
1090
1630
  kernLines.push(...converted);