@jalvin/compiler 2.0.41 → 2.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/codegen.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  // ─────────────────────────────────────────────────────────────────────────────
3
- // Jalvin Code Generator — AST → TypeScript
3
+ // Jalvin Code Generator — AST → TypeScript/TSX
4
4
  //
5
5
  // Design principles:
6
- // • component declarations → Functional components (standard TS)
6
+ // • component declarations → React functional components (TSX)
7
7
  // • data class → class with auto-generated copy(), equals(), toString()
8
8
  // • sealed class → TypeScript discriminated union + base class
9
9
  // • extension functions → module-level functions with receiver as first param,
@@ -89,6 +89,7 @@ class Writer {
89
89
  setSourceLine(line) { this.currentLine = line + 1; }
90
90
  }
91
91
  exports.DEFAULT_CODEGEN_OPTIONS = {
92
+ jsx: false,
92
93
  module: "esm",
93
94
  emitTypes: false,
94
95
  runtimeImport: "@jalvin/runtime",
@@ -98,10 +99,8 @@ class CodeGenerator {
98
99
  opts;
99
100
  hasComponents = false;
100
101
  runtimeSymbolsNeeded = new Set();
101
- /** Names of true Jalvin components — used to detect functional calls */
102
- userComponentNames = new Set();
103
- /** Names of all potential components (including UI primitives) — used to detect props-object calls */
104
- allComponentLikeNames = new Set();
102
+ /** Names of component functions — used to detect component calls and emit them as JSX */
103
+ componentNames = new Set();
105
104
  /**
106
105
  * Param names for components resolved from imported local .jalvin files.
107
106
  * Maps component name → ordered list of param names.
@@ -109,8 +108,6 @@ class CodeGenerator {
109
108
  componentParamNames = new Map();
110
109
  /** True when the program contains `import @jalvin/ui.*` — used to detect UI primitive calls */
111
110
  hasUiStarImport = false;
112
- /** Names of symbols explicitly imported from @jalvin/ui */
113
- uiNamedImports = new Set();
114
111
  /** Operator overload resolutions from the type checker */
115
112
  operatorOverloadMap = new Map();
116
113
  /** Type map from the type checker */
@@ -134,7 +131,6 @@ class CodeGenerator {
134
131
  * Maps module specifier (e.g. "src/views") → sorted list of exported names.
135
132
  */
136
133
  localStarExports = new Map();
137
- isInClass = false;
138
134
  constructor(opts = {}) {
139
135
  this.opts = { ...exports.DEFAULT_CODEGEN_OPTIONS, ...opts };
140
136
  }
@@ -145,40 +141,34 @@ class CodeGenerator {
145
141
  this.typeMap = typeMap;
146
142
  // Pre-scan for components (local declarations OR @jalvin/ui imports)
147
143
  this.hasComponents = program.declarations.some((d) => d.kind === "ComponentDecl" ||
148
- (d.kind === "ClassDecl" && d.body?.members.some((m) => m.kind === "ComponentDecl"))) || program.imports.some((imp) => imp.path[0] === "@jalvin" && imp.path[1] === "ui")
149
- || this.containsJsx(program);
144
+ (d.kind === "ClassDecl" && d.body?.members.some((m) => m.kind === "ComponentDecl"))) || program.imports.some((imp) => imp.path[0] === "@jalvin" && imp.path[1] === "ui");
150
145
  // Collect component names for component call detection
151
- this.userComponentNames = new Set();
152
- this.allComponentLikeNames = new Set();
146
+ this.componentNames = new Set();
153
147
  this.componentParamNames = new Map();
154
148
  for (const decl of program.declarations) {
155
149
  if (decl.kind === "ComponentDecl") {
156
- this.userComponentNames.add(decl.name);
157
- this.allComponentLikeNames.add(decl.name);
150
+ this.componentNames.add(decl.name);
158
151
  this.componentParamNames.set(decl.name, decl.params.map((p) => p.name));
159
152
  }
160
153
  if (decl.kind === "ClassDecl" && decl.body) {
161
154
  for (const m of decl.body.members) {
162
155
  if (m.kind === "ComponentDecl") {
163
- this.userComponentNames.add(m.name);
164
- this.allComponentLikeNames.add(m.name);
156
+ this.componentNames.add(m.name);
165
157
  this.componentParamNames.set(m.name, m.params.map((p) => p.name));
166
158
  }
167
159
  }
168
160
  }
169
161
  }
170
162
  this.hasUiStarImport = false;
171
- this.uiNamedImports = new Set();
172
163
  for (const imp of program.imports) {
173
- if (imp.path[0] === "@jalvin" && imp.path[1] === "ui") {
174
- if (imp.star) {
175
- this.hasUiStarImport = true;
176
- }
177
- else {
178
- const name = imp.path[imp.path.length - 1];
179
- this.uiNamedImports.add(name);
180
- this.allComponentLikeNames.add(name);
181
- }
164
+ // Named imports from @jalvin/ui are treated as potential component functions.
165
+ // Hooks (e.g. useIsHovered) are filtered out at call-site by the type-based guard below.
166
+ if (imp.path[0] === "@jalvin" && imp.path[1] === "ui" && !imp.star) {
167
+ this.componentNames.add(imp.path[imp.path.length - 1]);
168
+ }
169
+ // Star import from @jalvin/ui — UI primitives won't be in componentNames
170
+ if (imp.path[0] === "@jalvin" && imp.path[1] === "ui" && imp.star) {
171
+ this.hasUiStarImport = true;
182
172
  }
183
173
  }
184
174
  // Resolve component fun symbols from locally imported .jalvin files
@@ -212,42 +202,14 @@ class CodeGenerator {
212
202
  return {
213
203
  code,
214
204
  lineMap: this.w.lineMap,
215
- hasComponents: this.hasComponents,
216
- };
217
- }
218
- /** Walk AST to see if it contains any JsxElement nodes */
219
- containsJsx(program) {
220
- let found = false;
221
- const walk = (val) => {
222
- if (found || !val || typeof val !== "object")
223
- return;
224
- if (Array.isArray(val)) {
225
- for (const item of val)
226
- walk(item);
227
- return;
228
- }
229
- const obj = val;
230
- if (obj["kind"] === "JsxElement") {
231
- found = true;
232
- return;
233
- }
234
- for (const propVal of Object.values(obj)) {
235
- walk(propVal);
236
- }
205
+ isJsx: this.hasComponents,
237
206
  };
238
- for (const decl of program.declarations)
239
- walk(decl);
240
- return found;
241
207
  }
242
208
  // ── Preamble & imports ─────────────────────────────────────────────────────
243
209
  buildPreamble() {
244
210
  const lines = [];
245
- lines.push("// ─────────────────────────────────────────────────────────────────────────────");
246
- lines.push("// JALVIN COMPILER V5 - PURE VANILLA - NO REACT ALLOWED");
247
- lines.push(`// BUILD ID: ${Date.now()}`);
248
- lines.push("// ─────────────────────────────────────────────────────────────────────────────");
249
211
  if (this.hasComponents) {
250
- this.runtimeSymbolsNeeded.add("jalvinCreateElement");
212
+ lines.push('import React from "react";');
251
213
  }
252
214
  // Emit compiler-injected runtime symbols, but only those NOT already covered
253
215
  // by a star-import's named-import expansion (to avoid duplicate imports).
@@ -264,6 +226,19 @@ class CodeGenerator {
264
226
  const sourceRoot = this.opts.sourceRoot ?? process.cwd();
265
227
  // Re-emit user imports as ES imports
266
228
  for (const imp of program.imports) {
229
+ // Build module specifier.
230
+ //
231
+ // Scoped packages (@org/pkg.Symbol): symbol is an export from the package,
232
+ // strip the last segment.
233
+ // import @jalvin/ui.Column → import { Column } from "@jalvin/ui"
234
+ // import @jalvin/runtime.* → import * as runtime from "@jalvin/runtime"
235
+ //
236
+ // Local imports (a.b.C): check whether the preceding segments already
237
+ // resolve to a file on disk.
238
+ // import src.models.Rotation → src/models/Rotation.ts does NOT exist
239
+ // → import { Rotation } from "src/models/Rotation"
240
+ // import src.models.css.Css → src/models/css.ts DOES exist
241
+ // → import { Css } from "src/models/css"
267
242
  const isScoped = imp.path[0].startsWith("@");
268
243
  let moduleSpecifier;
269
244
  if (imp.star) {
@@ -278,28 +253,35 @@ class CodeGenerator {
278
253
  moduleSpecifier = moduleParts[0] + "/" + moduleParts.slice(1).join("/");
279
254
  }
280
255
  else {
281
- // Non-scoped import: prefer local-file convention
256
+ // Non-scoped import: prefer local-file convention unless this looks like
257
+ // an installed npm package path (e.g. cubing/twisty.TwistyPlayer).
282
258
  const precedingParts = imp.path.slice(0, -1);
283
259
  const precedingRelPath = precedingParts.join("/");
284
260
  const fileExts = [".ts", ".tsx", ".jalvin"];
285
261
  const precedingIsFile = precedingParts.length > 0 && fileExts.some((ext) => fs.existsSync(nodePath.join(sourceRoot, precedingRelPath + ext)));
286
262
  const looksLikeNpmPackage = this.isLikelyNodeModuleImport(imp.path, sourceRoot);
287
263
  moduleSpecifier = precedingIsFile
288
- ? precedingRelPath
264
+ ? precedingRelPath // src.models.css.Css → "src/models/css"
289
265
  : looksLikeNpmPackage
290
- ? precedingRelPath
291
- : imp.path.join("/");
266
+ ? precedingRelPath // cubing.twisty.TwistyPlayer → "cubing/twisty"
267
+ : imp.path.join("/"); // src.models.Rotation → "src/models/Rotation"
292
268
  }
293
269
  if (imp.star && isScoped && this.externalStarCandidates.size > 0) {
270
+ // Named-import expansion of a scoped star import.
271
+ // Emit explicit named imports for each external symbol used in the file so
272
+ // that symbols like `ViewModel`, `mutableStateOf` are directly in scope.
294
273
  const symbols = [...this.externalStarCandidates].sort();
295
274
  this.w.writeIndentedLine(`import { ${symbols.join(", ")} } from "${moduleSpecifier}";`);
296
275
  this.handledStarImportModules.add(moduleSpecifier);
297
276
  }
298
277
  else if (imp.star && !isScoped && this.localStarExports.has(moduleSpecifier)) {
278
+ // Local wildcard import: import src.module.* — expand to named imports
279
+ // so all exported symbols are directly in scope (no namespace qualifier needed).
299
280
  const symbols = this.localStarExports.get(moduleSpecifier).slice().sort();
300
281
  this.w.writeIndentedLine(`import { ${symbols.join(", ")} } from "${moduleSpecifier}";`);
301
282
  }
302
283
  else if (imp.star) {
284
+ // Fallback: namespace import (multiple scoped star imports or no candidates).
303
285
  this.w.writeIndentedLine(`import * as ${imp.path[imp.path.length - 1]} from "${moduleSpecifier}";`);
304
286
  }
305
287
  else if (imp.alias) {
@@ -313,6 +295,11 @@ class CodeGenerator {
313
295
  if (program.imports.length > 0)
314
296
  this.w.writeLine();
315
297
  }
298
+ /**
299
+ * Heuristic for non-scoped imports: if the first path segment resolves to an
300
+ * installed package in node_modules, treat the import as npm/ESM and strip
301
+ * the trailing symbol name from the module specifier.
302
+ */
316
303
  isLikelyNodeModuleImport(pathParts, sourceRoot) {
317
304
  if (pathParts.length < 2)
318
305
  return false;
@@ -330,12 +317,14 @@ class CodeGenerator {
330
317
  return false;
331
318
  }
332
319
  // ── Top-level declarations ─────────────────────────────────────────────────
320
+ /** Emit a @deprecated JSDoc block if the declaration has @Nuked. */
333
321
  emitAnnotations(mods) {
334
322
  for (const ann of mods.annotations) {
335
323
  if (ann.name === "Nuked") {
336
324
  const reason = ann.args ? ` ${ann.args.replace(/^["']|["']$/g, "")}` : "";
337
325
  this.w.writeIndentedLine(`/** @deprecated${reason} */`);
338
326
  }
327
+ // Other annotations are silently ignored for now (pass-through model)
339
328
  }
340
329
  }
341
330
  emitTopLevelDecl(decl) {
@@ -390,6 +379,7 @@ class CodeGenerator {
390
379
  ? `: ${this.emitTypeRef(decl.returnType)}`
391
380
  : "";
392
381
  if (!decl.body) {
382
+ // Abstract / interface method
393
383
  this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType};`);
394
384
  return;
395
385
  }
@@ -406,6 +396,7 @@ class CodeGenerator {
406
396
  this.w.writeIndentedLine(`}`);
407
397
  }
408
398
  else {
399
+ // Expression body
409
400
  if (memberOf) {
410
401
  this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
411
402
  }
@@ -446,19 +437,21 @@ class CodeGenerator {
446
437
  this.w.writeIndentedLine(`}`);
447
438
  }
448
439
  }
449
- // ── component declarations → Functional components ────────────────────
440
+ // ── component declarations → React function components ────────────────────
450
441
  emitComponentDecl(decl) {
451
442
  this.emitAnnotations(decl.modifiers);
452
443
  this.hasComponents = true;
453
- const vis = this.isInClass ? this.visibilityPrefix(decl.modifiers) : this.exportPrefix(decl.modifiers);
454
- const hasChildren = decl.params.some((p) => p.name === "children");
444
+ const vis = this.exportPrefix(decl.modifiers);
445
+ // "children" is passed as the second positional argument by emitComponentCall,
446
+ // so it must NOT be destructured from the props object.
455
447
  const propsParams = decl.params.filter((p) => p.name !== "children");
448
+ const hasChildren = decl.params.some((p) => p.name === "children");
456
449
  // Props interface
457
- if (!this.isInClass && propsParams.length > 0) {
450
+ if (decl.params.length > 0) {
458
451
  this.w.writeIndentedLine(`interface ${decl.name}Props {`);
459
452
  this.w.pushIndent();
460
- for (const p of propsParams) {
461
- const optional = p.defaultValue ? "?" : "";
453
+ for (const p of decl.params) {
454
+ const optional = p.defaultValue || p.name === "children" ? "?" : "";
462
455
  const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
463
456
  this.w.writeIndentedLine(`readonly ${p.name}${optional}: ${typeStr};`);
464
457
  }
@@ -466,32 +459,21 @@ class CodeGenerator {
466
459
  this.w.writeIndentedLine(`}`);
467
460
  this.w.writeLine();
468
461
  }
469
- const propsType = this.isInClass
470
- ? `{ ${propsParams.map(p => {
471
- const optional = p.defaultValue ? "?" : "";
472
- const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
473
- return `readonly ${p.name}${optional}: ${typeStr}`;
474
- }).join("; ")} }`
475
- : `${decl.name}Props`;
476
- let signature = "";
477
- if (propsParams.length > 0) {
478
- signature = `{ ${propsParams.map((p) => p.name + (p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "")).join(", ")} }: ${propsType}`;
479
- }
480
- else if (hasChildren) {
481
- signature = "{}";
482
- }
483
- if (hasChildren) {
484
- signature += signature ? ", children?: any" : "children?: any";
485
- }
486
- const sig = this.isInClass
487
- ? `${decl.name}(${signature})`
488
- : `function ${decl.name}(${signature})`;
489
- this.w.writeIndentedLine(`${vis}${sig} {`);
462
+ const propsDestructure = decl.params.length > 0
463
+ ? `{ ${decl.params.map((p) => p.name + (p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "")).join(", ")} }: ${decl.name}Props`
464
+ : "";
465
+ this.w.writeIndentedLine(`${vis}function ${decl.name}(${propsDestructure}) {`);
490
466
  this.w.pushIndent();
491
467
  this.emitComponentBlock(decl.body);
492
468
  this.w.popIndent();
493
469
  this.w.writeIndentedLine(`}`);
494
470
  }
471
+ /**
472
+ * Emit the body of a `component fun` block.
473
+ * The last statement is emitted in tail position — if it is an expression,
474
+ * an if/else chain, or a when block, the innermost UI call is implicitly
475
+ * returned (implicit return of the last expression).
476
+ */
495
477
  emitComponentBlock(block) {
496
478
  const stmts = block.statements;
497
479
  for (let i = 0; i < stmts.length; i++) {
@@ -504,8 +486,10 @@ class CodeGenerator {
504
486
  }
505
487
  }
506
488
  }
489
+ /** Emit a statement in tail position, adding implicit return where applicable. */
507
490
  emitTailStmt(stmt) {
508
491
  if (stmt.kind === "ExprStmt") {
492
+ // Implicit return: last expression in a component block
509
493
  this.w.writeIndentedLine(`return ${this.emitExpr(stmt.expr)};`);
510
494
  }
511
495
  else if (stmt.kind === "IfStmt") {
@@ -518,6 +502,7 @@ class CodeGenerator {
518
502
  this.emitStmt(stmt);
519
503
  }
520
504
  }
505
+ /** Emit a block in tail position, treating its last statement as a tail expression. */
521
506
  emitTailBlock(block) {
522
507
  const stmts = block.statements;
523
508
  for (let i = 0; i < stmts.length; i++) {
@@ -530,6 +515,7 @@ class CodeGenerator {
530
515
  }
531
516
  }
532
517
  }
518
+ /** Like emitIfStmt but with tail-return applied recursively to each branch body. */
533
519
  emitIfStmtWithTailReturn(stmt) {
534
520
  this.w.writeIndentedLine(`if (${this.emitExpr(stmt.condition)}) {`);
535
521
  this.w.pushIndent();
@@ -552,6 +538,7 @@ class CodeGenerator {
552
538
  this.w.writeIndentedLine(`}`);
553
539
  }
554
540
  }
541
+ /** Like emitWhenStmt but with tail-return applied recursively to each branch body. */
555
542
  emitWhenStmtWithTailReturn(stmt) {
556
543
  const subjectVar = stmt.subject ? "__when_subject__" : null;
557
544
  if (stmt.subject) {
@@ -588,12 +575,15 @@ class CodeGenerator {
588
575
  const superTypes = this.emitSuperTypesStr(decl.superTypes);
589
576
  this.w.writeIndentedLine(`${vis}${abstract}class ${decl.name}${typeParams}${superTypes} {`);
590
577
  this.w.pushIndent();
578
+ // The first supertype's delegateArgs become the `super(args)` call inside the constructor.
591
579
  const superDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
592
580
  if (decl.primaryConstructor) {
593
581
  const initBlocks = decl.body?.members.filter((m) => m.kind === "InitBlock") ?? [];
594
582
  this.emitPrimaryConstructorProps(decl.primaryConstructor.params, initBlocks, superDelegateArgs);
595
583
  }
596
584
  else if (superDelegateArgs !== null) {
585
+ // No primary constructor but the supertype has explicit delegation args —
586
+ // emit a minimal constructor that forwards them to super().
597
587
  const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
598
588
  this.w.writeIndentedLine(`constructor() {`);
599
589
  this.w.pushIndent();
@@ -616,20 +606,24 @@ class CodeGenerator {
616
606
  const props = decl.primaryConstructor.params;
617
607
  this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams}${superTypes} {`);
618
608
  this.w.pushIndent();
609
+ // Discriminant for sealed class sub-types
619
610
  if (kindName) {
620
611
  this.w.writeIndentedLine(`readonly __kind = "${kindName}" as const;`);
621
612
  }
613
+ // Constructor params → readonly properties
622
614
  for (const p of props) {
623
615
  const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
624
616
  this.w.writeIndentedLine(`readonly ${p.name}${t};`);
625
617
  }
626
618
  this.w.writeLine();
619
+ // Constructor
627
620
  const ctorParams = props.map((p) => {
628
621
  const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
629
622
  return `${p.name}${t}`;
630
623
  }).join(", ");
631
624
  this.w.writeIndentedLine(`constructor(${ctorParams}) {`);
632
625
  this.w.pushIndent();
626
+ // Emit super() with actual delegation args (or empty call if delegateArgs is []).
633
627
  const dataSuperDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
634
628
  if (dataSuperDelegateArgs !== null) {
635
629
  const superArgsStr = dataSuperDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
@@ -641,6 +635,7 @@ class CodeGenerator {
641
635
  this.w.popIndent();
642
636
  this.w.writeIndentedLine(`}`);
643
637
  this.w.writeLine();
638
+ // copy()
644
639
  const copyParams = props.map((p) => {
645
640
  const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
646
641
  return `${p.name}${t} = this.${p.name}`;
@@ -652,6 +647,7 @@ class CodeGenerator {
652
647
  this.w.popIndent();
653
648
  this.w.writeIndentedLine(`}`);
654
649
  this.w.writeLine();
650
+ // equals()
655
651
  this.runtimeSymbolsNeeded.add("jalvinEquals");
656
652
  const eqChecks = props.map((p) => `jalvinEquals(this.${p.name}, other.${p.name})`).join(" && ") || "true";
657
653
  this.w.writeIndentedLine(`equals(other: unknown): boolean {`);
@@ -661,6 +657,7 @@ class CodeGenerator {
661
657
  this.w.popIndent();
662
658
  this.w.writeIndentedLine(`}`);
663
659
  this.w.writeLine();
660
+ // toString()
664
661
  const toStringParts = props.map((p) => `${p.name}=\${this.${p.name}}`).join(", ");
665
662
  this.w.writeIndentedLine(`toString(): string {`);
666
663
  this.w.pushIndent();
@@ -668,6 +665,7 @@ class CodeGenerator {
668
665
  this.w.popIndent();
669
666
  this.w.writeIndentedLine(`}`);
670
667
  this.w.writeLine();
668
+ // hashCode() — djb2
671
669
  this.w.writeIndentedLine(`hashCode(): number {`);
672
670
  this.w.pushIndent();
673
671
  this.w.writeIndentedLine(`let h = 17;`);
@@ -703,6 +701,7 @@ class CodeGenerator {
703
701
  }
704
702
  }
705
703
  }
704
+ // Emit the abstract base class (only non-subtype members in the body)
706
705
  this.w.writeIndentedLine(`${vis}abstract class ${decl.name}${typeParams} {`);
707
706
  this.w.pushIndent();
708
707
  this.w.writeIndentedLine(`abstract readonly __kind: string;`);
@@ -715,6 +714,7 @@ class CodeGenerator {
715
714
  this.w.writeLine();
716
715
  if (subDecls.length === 0)
717
716
  return;
717
+ // Emit non-object sub-declarations at module level (before namespace)
718
718
  for (const sub of subDecls) {
719
719
  if (sub.kind === "DataClassDecl") {
720
720
  this.emitDataClassDecl(sub, sub.name);
@@ -725,10 +725,12 @@ class CodeGenerator {
725
725
  this.w.writeLine();
726
726
  }
727
727
  }
728
+ // Emit namespace merging so SealedClass.SubType is accessible
728
729
  this.w.writeIndentedLine(`${vis}namespace ${decl.name} {`);
729
730
  this.w.pushIndent();
730
731
  for (const sub of subDecls) {
731
732
  if (sub.kind === "ObjectDecl") {
733
+ // Singleton object — emit inline in namespace with __kind discriminant
732
734
  const superStr = sub.superTypes.length > 0
733
735
  ? this.emitSuperTypesStr(sub.superTypes)
734
736
  : ` extends ${decl.name}`;
@@ -744,6 +746,7 @@ class CodeGenerator {
744
746
  this.w.writeIndentedLine(`export const ${sub.name} = new (class${superStr} { ${membersStr} })();`);
745
747
  }
746
748
  else {
749
+ // DataClassDecl / ClassDecl — already emitted at module level, re-export
747
750
  this.w.writeIndentedLine(`export { ${sub.name} };`);
748
751
  }
749
752
  }
@@ -754,8 +757,12 @@ class CodeGenerator {
754
757
  emitEnumClassDecl(decl) {
755
758
  const vis = this.exportPrefix(decl.modifiers);
756
759
  const typeParams = this.emitTypeParamsStr(decl.typeParams);
760
+ // Emit the enum as a TypeScript class with static singleton instances.
761
+ // This preserves the `EnumClass.ENTRY` access pattern and allows
762
+ // methods/properties to be attached to entries.
757
763
  this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams} {`);
758
764
  this.w.pushIndent();
765
+ // Private constructor so entries are the only instances
759
766
  if (decl.primaryConstructor && decl.primaryConstructor.params.length > 0) {
760
767
  const ctorParams = decl.primaryConstructor.params
761
768
  .map((p) => {
@@ -770,6 +777,7 @@ class CodeGenerator {
770
777
  this.w.writeIndentedLine(`private constructor(readonly name: string, readonly ordinal: number) {}`);
771
778
  }
772
779
  this.w.writeLine();
780
+ // Static entry instances
773
781
  for (let i = 0; i < decl.entries.length; i++) {
774
782
  const entry = decl.entries[i];
775
783
  const args = entry.args.length > 0
@@ -779,6 +787,7 @@ class CodeGenerator {
779
787
  }
780
788
  if (decl.entries.length > 0) {
781
789
  this.w.writeLine();
790
+ // values() helper
782
791
  const entryList = decl.entries.map((e) => `${decl.name}.${e.name}`).join(", ");
783
792
  this.w.writeIndentedLine(`static values(): ${decl.name}[] { return [${entryList}]; }`);
784
793
  this.w.writeIndentedLine(`static valueOf(name: string): ${decl.name} {`);
@@ -796,12 +805,14 @@ class CodeGenerator {
796
805
  this.w.popIndent();
797
806
  this.w.writeIndentedLine(`}`);
798
807
  }
808
+ // ── destructuring declaration — val (a, b) = expr ─────────────────────────
799
809
  emitDestructuringDecl(decl, _memberOf) {
800
810
  const kw = decl.mutable ? "let" : "const";
801
811
  const names = decl.names.map((n) => n ?? "_").join(", ");
802
812
  const init = this.emitExpr(decl.initializer);
803
813
  this.w.writeIndentedLine(`${kw} [${names}] = ${init};`);
804
814
  }
815
+ // ── interface ──────────────────────────────────────────────────────────────
805
816
  emitInterfaceDecl(decl) {
806
817
  this.emitAnnotations(decl.modifiers);
807
818
  const vis = this.exportPrefix(decl.modifiers);
@@ -819,9 +830,12 @@ class CodeGenerator {
819
830
  this.w.popIndent();
820
831
  this.w.writeIndentedLine(`}`);
821
832
  }
833
+ // ── object declaration → singleton ────────────────────────────────────────
822
834
  emitObjectDecl(decl) {
823
- if (!decl.name)
835
+ if (!decl.name) {
836
+ // Anonymous object — emitted inline
824
837
  return;
838
+ }
825
839
  this.emitAnnotations(decl.modifiers);
826
840
  const vis = this.exportPrefix(decl.modifiers);
827
841
  const superTypes = this.emitSuperTypesStr(decl.superTypes);
@@ -831,11 +845,16 @@ class CodeGenerator {
831
845
  this.w.popIndent();
832
846
  this.w.writeIndentedLine(`})();`);
833
847
  }
848
+ // ── type alias ─────────────────────────────────────────────────────────────
834
849
  emitTypeAliasDecl(decl) {
835
850
  const vis = this.exportPrefix(decl.modifiers);
836
851
  const typeParams = this.emitTypeParamsStr(decl.typeParams);
837
852
  this.w.writeIndentedLine(`${vis}type ${decl.name}${typeParams} = ${this.emitTypeRef(decl.type)};`);
838
853
  }
854
+ // ── extension functions ────────────────────────────────────────────────────
855
+ // Emitted as standalone free functions with receiver as explicit first param.
856
+ // For class types, also patched onto the prototype for dot-call syntax.
857
+ // For primitive types, call sites are rewritten via `primitiveExtensions`.
839
858
  emitExtensionFunDecl(decl) {
840
859
  const isSuspend = AST.isSuspend(decl.modifiers);
841
860
  const asyncKw = isSuspend ? "async " : "";
@@ -846,6 +865,9 @@ class CodeGenerator {
846
865
  ? `: ${this.emitTypeRef(decl.returnType)}`
847
866
  : "";
848
867
  const fnName = `__ext_${receiverType.replace(/[^a-zA-Z0-9_]/g, "_")}_${decl.name}`;
868
+ // Emit as a free function: receiver is the first explicit parameter `$receiver`.
869
+ // Inside the function body, `this` is rebound to `$receiver` so Jalvin's
870
+ // `this.prop` references work correctly.
849
871
  this.w.writeIndentedLine(`${asyncKw}function ${fnName}${typeParams}($receiver: ${receiverType}${params ? ", " + params : ""})${retType} {`);
850
872
  this.w.pushIndent();
851
873
  if (decl.body.kind === "Block") {
@@ -864,12 +886,14 @@ class CodeGenerator {
864
886
  const simpleReceiverName = this.receiverClassName(decl.receiver);
865
887
  if (simpleReceiverName) {
866
888
  if (PRIMITIVE_TYPES.has(simpleReceiverName)) {
889
+ // Register for call-site rewriting — cannot patch primitive prototypes
867
890
  if (!this.primitiveExtensions.has(simpleReceiverName)) {
868
891
  this.primitiveExtensions.set(simpleReceiverName, new Map());
869
892
  }
870
893
  this.primitiveExtensions.get(simpleReceiverName).set(decl.name, fnName);
871
894
  }
872
895
  else {
896
+ // Class types — prototype monkey-patch for dot-call syntax
873
897
  this.w.writeIndentedLine(`// Extension: ${receiverType}.${decl.name}`);
874
898
  this.w.writeIndentedLine(`(${simpleReceiverName}.prototype as any).${decl.name} = function(this: ${receiverType}, ...args: unknown[]) { return (${fnName} as Function)(this, ...args); };`);
875
899
  }
@@ -882,6 +906,11 @@ class CodeGenerator {
882
906
  return ref.base.name[ref.base.name.length - 1] ?? null;
883
907
  return null;
884
908
  }
909
+ /**
910
+ * Maps a JType to the primitive receiver type name used as a key
911
+ * in `primitiveExtensions` (e.g. JType `{ tag: "string" }` → `"String"`).
912
+ * Returns null for non-primitive / unknown types.
913
+ */
885
914
  jTypeToReceiverName(type) {
886
915
  const tagToReceiverName = {
887
916
  string: "String",
@@ -901,16 +930,19 @@ class CodeGenerator {
901
930
  m.name === "invoke" &&
902
931
  m.modifiers.modifiers.includes("operator"));
903
932
  }
933
+ // ── property declaration ───────────────────────────────────────────────────
904
934
  emitPropertyDecl(decl, member, isLocal = false) {
905
935
  this.emitAnnotations(decl.modifiers);
906
936
  const vis = member ? this.visibilityPrefix(decl.modifiers) : isLocal ? "" : this.exportPrefix(decl.modifiers);
907
937
  const isConst = decl.modifiers.modifiers.includes("const");
908
938
  const isLateinit = decl.modifiers.modifiers.includes("lateinit");
939
+ // `const val` → `const` at module level; inside classes treated as `static readonly`
909
940
  const kw = member
910
941
  ? (decl.mutable ? "" : "readonly ")
911
942
  : (isConst ? "const " : decl.mutable ? "let " : "const ");
912
943
  const type = decl.type && this.opts.emitTypes ? `: ${this.emitTypeRef(decl.type)}` : "";
913
944
  if (decl.delegate) {
945
+ // Delegated property — wrap in a getter/setter pair using the delegate
914
946
  this.runtimeSymbolsNeeded.add("delegate");
915
947
  const delegateExpr = this.emitExpr(decl.delegate);
916
948
  if (member) {
@@ -920,22 +952,28 @@ class CodeGenerator {
920
952
  }
921
953
  }
922
954
  else {
955
+ // Non-member delegate: resolve via delegate().getValue() so the identifier
956
+ // holds the delegated value, not the raw delegate wrapper object.
923
957
  this.w.writeIndentedLine(`${kw}${decl.name}${type} = delegate(${delegateExpr}, "${decl.name}", null).getValue();`);
924
958
  }
925
959
  return;
926
960
  }
927
- const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` : "";
961
+ const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` : (isLateinit ? "" : "");
928
962
  if (member && !decl.getter) {
963
+ // Skip backing field when a getter is defined — TypeScript forbids both
964
+ // `readonly name: T` and `get name()` with the same name in a class.
929
965
  const modStr = isConst ? "static readonly " : decl.mutable ? "" : "readonly ";
930
966
  this.w.writeIndentedLine(`${vis}${modStr}${decl.name}${type}${init};`);
931
967
  }
932
968
  else if (!member) {
933
969
  this.w.writeIndentedLine(`${vis}${kw}${decl.name}${type}${init};`);
934
970
  }
935
- if (decl.getter)
971
+ if (decl.getter) {
936
972
  this.emitPropertyAccessor("get", decl.name, decl.getter, member, type);
937
- if (decl.setter)
973
+ }
974
+ if (decl.setter) {
938
975
  this.emitPropertyAccessor("set", decl.name, decl.setter, member, type);
976
+ }
939
977
  }
940
978
  emitPropertyAccessor(kind, name, acc, member, type) {
941
979
  const vis = this.visibilityPrefix(acc.modifiers);
@@ -955,19 +993,14 @@ class CodeGenerator {
955
993
  this.w.popIndent();
956
994
  this.w.writeIndentedLine(`}`);
957
995
  }
996
+ // ── class body ─────────────────────────────────────────────────────────────
958
997
  emitClassBody(body) {
959
- const old = this.isInClass;
960
- this.isInClass = true;
961
- try {
962
- for (const member of body.members) {
963
- if (member.kind === "InitBlock")
964
- continue;
965
- this.emitClassMember(member);
966
- this.w.writeLine();
967
- }
968
- }
969
- finally {
970
- this.isInClass = old;
998
+ for (const member of body.members) {
999
+ // InitBlocks are emitted inside the constructor by emitPrimaryConstructorProps
1000
+ if (member.kind === "InitBlock")
1001
+ continue;
1002
+ this.emitClassMember(member);
1003
+ this.w.writeLine();
971
1004
  }
972
1005
  }
973
1006
  emitClassMember(member) {
@@ -999,6 +1032,7 @@ class CodeGenerator {
999
1032
  case "CompanionObject":
1000
1033
  this.emitCompanionObject(member);
1001
1034
  break;
1035
+ case "InitBlock": /* handled in constructor */ break;
1002
1036
  case "SecondaryConstructor":
1003
1037
  this.emitSecondaryConstructor(member);
1004
1038
  break;
@@ -1015,13 +1049,16 @@ class CodeGenerator {
1015
1049
  this.w.writeIndentedLine(`${ro}${p.name}${type};`);
1016
1050
  }
1017
1051
  }
1052
+ // Constructor
1018
1053
  const ctorParams = params.map((p) => {
1054
+ const ro = p.propertyKind === "val" ? "readonly " : p.propertyKind === "var" ? "" : "";
1019
1055
  const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
1020
1056
  const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
1021
1057
  return `${p.name}${type}${def}`;
1022
1058
  });
1023
1059
  this.w.writeIndentedLine(`constructor(${ctorParams.join(", ")}) {`);
1024
1060
  this.w.pushIndent();
1061
+ // super() call must be the first statement if there is a supertype.
1025
1062
  if (superDelegateArgs !== null && superDelegateArgs !== undefined) {
1026
1063
  const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
1027
1064
  this.w.writeIndentedLine(`super(${superArgsStr});`);
@@ -1031,13 +1068,17 @@ class CodeGenerator {
1031
1068
  this.w.writeIndentedLine(`this.${p.name} = ${p.name};`);
1032
1069
  }
1033
1070
  }
1034
- for (const ib of (initBlocks ?? []))
1071
+ // Emit init{} blocks inside the constructor, in declaration order
1072
+ for (const ib of (initBlocks ?? [])) {
1035
1073
  this.emitBlock(ib.body);
1074
+ }
1036
1075
  this.w.popIndent();
1037
1076
  this.w.writeIndentedLine(`}`);
1038
1077
  this.w.writeLine();
1039
1078
  }
1040
1079
  emitCompanionObject(co) {
1080
+ // Emit companion object members as `static` members of the outer class so that
1081
+ // `MyClass.factoryMethod()` works directly (standard semantics).
1041
1082
  for (const member of co.body.members) {
1042
1083
  if (member.kind === "FunDecl") {
1043
1084
  const vis = this.visibilityPrefix(member.modifiers);
@@ -1082,9 +1123,11 @@ class CodeGenerator {
1082
1123
  this.w.popIndent();
1083
1124
  this.w.writeIndentedLine(`}`);
1084
1125
  }
1126
+ // ── Statements ─────────────────────────────────────────────────────────────
1085
1127
  emitBlock(block) {
1086
- for (const stmt of block.statements)
1128
+ for (const stmt of block.statements) {
1087
1129
  this.emitStmt(stmt);
1130
+ }
1088
1131
  }
1089
1132
  emitStmt(stmt) {
1090
1133
  switch (stmt.kind) {
@@ -1173,6 +1216,8 @@ class CodeGenerator {
1173
1216
  }
1174
1217
  }
1175
1218
  emitWhenStmt(stmt) {
1219
+ // when(subject) { is Foo -> ... else -> ... }
1220
+ // Compiles to a series of if/else-if chains
1176
1221
  const subjectVar = stmt.subject ? "__when_subject__" : null;
1177
1222
  if (stmt.subject) {
1178
1223
  const binding = stmt.subject.binding ?? subjectVar;
@@ -1189,10 +1234,12 @@ class CodeGenerator {
1189
1234
  }
1190
1235
  first = false;
1191
1236
  this.w.pushIndent();
1192
- if (branch.body.kind === "Block")
1237
+ if (branch.body.kind === "Block") {
1193
1238
  this.emitBlock(branch.body);
1194
- else
1239
+ }
1240
+ else {
1195
1241
  this.w.writeIndentedLine(`${this.emitExpr(branch.body)};`);
1242
+ }
1196
1243
  this.w.popIndent();
1197
1244
  }
1198
1245
  this.w.writeIndentedLine(`}`);
@@ -1200,6 +1247,8 @@ class CodeGenerator {
1200
1247
  emitWhenCondition(cond, subject) {
1201
1248
  switch (cond.kind) {
1202
1249
  case "WhenIsCondition": {
1250
+ // Qualified name (e.g. UiState.Loading) → use __kind discriminant
1251
+ // Single name (e.g. BibiError) → use instanceof
1203
1252
  const isQualified = cond.type.kind === "SimpleTypeRef" && cond.type.name.length > 1;
1204
1253
  const check = isQualified
1205
1254
  ? `(${subject} as any).__kind === "${cond.type.kind === "SimpleTypeRef" ? cond.type.name[cond.type.name.length - 1] : ""}"`
@@ -1229,6 +1278,7 @@ class CodeGenerator {
1229
1278
  }
1230
1279
  else {
1231
1280
  const { key, value } = stmt.binding;
1281
+ // Use .entries() for JS Map (Map<K,V>), Object.entries() for plain objects
1232
1282
  const iterableType = this.typeMap.get(stmt.iterable);
1233
1283
  const isMap = iterableType?.tag === "class" && iterableType.name === "Map";
1234
1284
  const entries = isMap ? `${iter}.entries()` : `Object.entries(${iter})`;
@@ -1247,6 +1297,9 @@ class CodeGenerator {
1247
1297
  for (const c of stmt.catches) {
1248
1298
  this.w.writeIndentedLine(`} catch (${c.name}: unknown) {`);
1249
1299
  this.w.pushIndent();
1300
+ // Narrow type — always emit the guard so catch clauses are type-safe
1301
+ // regardless of the emitTypes option (which controls explicit type annotations,
1302
+ // not runtime type checks).
1250
1303
  this.w.writeIndentedLine(`if (!(${c.name} instanceof ${this.emitTypeRef(c.type)})) throw ${c.name};`);
1251
1304
  this.emitBlock(c.body);
1252
1305
  this.w.popIndent();
@@ -1259,6 +1312,7 @@ class CodeGenerator {
1259
1312
  }
1260
1313
  this.w.writeIndentedLine(`}`);
1261
1314
  }
1315
+ // ── Expressions ────────────────────────────────────────────────────────────
1262
1316
  emitExpr(expr) {
1263
1317
  switch (expr.kind) {
1264
1318
  case "IntLiteralExpr": return String(expr.value);
@@ -1268,12 +1322,17 @@ class CodeGenerator {
1268
1322
  case "BooleanLiteralExpr": return String(expr.value);
1269
1323
  case "NullLiteralExpr": return "null";
1270
1324
  case "StringLiteralExpr":
1271
- return expr.raw ? `\`${expr.value}\`` : JSON.stringify(expr.value);
1325
+ return expr.raw
1326
+ ? `\`${expr.value}\``
1327
+ : JSON.stringify(expr.value);
1272
1328
  case "StringTemplateExpr":
1273
1329
  return this.emitStringTemplate(expr);
1274
1330
  case "NameExpr":
1275
- if (expr.name === "Int" || expr.name === "Long")
1331
+ // Ensure Int/Long companion objects are imported from the runtime
1332
+ if (expr.name === "Int" || expr.name === "Long") {
1276
1333
  this.runtimeSymbolsNeeded.add(expr.name);
1334
+ }
1335
+ // Unit singleton emits as undefined (void semantics)
1277
1336
  if (expr.name === "Unit")
1278
1337
  return "undefined";
1279
1338
  return expr.name;
@@ -1289,23 +1348,34 @@ class CodeGenerator {
1289
1348
  if (opMethod) {
1290
1349
  const l = this.emitExpr(expr.left);
1291
1350
  const r = this.emitExpr(expr.right);
1292
- if (opMethod === "compareTo")
1293
- return `(${l}.compareTo(${r}) ${expr.op} 0)`;
1294
- if (opMethod === "contains")
1351
+ // `compareTo` overloads: wrap result in a comparison against 0
1352
+ if (opMethod === "compareTo") {
1353
+ const cmpOp = expr.op;
1354
+ return `(${l}.compareTo(${r}) ${cmpOp} 0)`;
1355
+ }
1356
+ // `contains` overloads: `element in collection` → `collection.contains(element)`
1357
+ if (opMethod === "contains") {
1295
1358
  return `${r}.contains(${l})`;
1296
- if (opMethod === "!contains")
1359
+ }
1360
+ if (opMethod === "!contains") {
1297
1361
  return `!${r}.contains(${l})`;
1362
+ }
1363
+ // Generic operator overload: emit as method call `left.method(right)`
1298
1364
  return `${l}.${opMethod}(${r})`;
1299
1365
  }
1366
+ // `==` → structural equality, `!=` → negation
1300
1367
  if (expr.op === "==" || expr.op === "!=") {
1301
1368
  this.runtimeSymbolsNeeded.add("jalvinEquals");
1302
1369
  const eq = `jalvinEquals(${this.emitExpr(expr.left)}, ${this.emitExpr(expr.right)})`;
1303
1370
  return expr.op === "==" ? eq : `!${eq}`;
1304
1371
  }
1305
- if (expr.op === "in")
1372
+ // `in` / `!in` without a user-defined operator: use JS `in` / array includes
1373
+ if (expr.op === "in") {
1306
1374
  return `${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)})`;
1307
- if (expr.op === "!in")
1375
+ }
1376
+ if (expr.op === "!in") {
1308
1377
  return `!(${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)}))`;
1378
+ }
1309
1379
  return `(${this.emitExpr(expr.left)} ${this.binaryOpStr(expr.op)} ${this.emitExpr(expr.right)})`;
1310
1380
  }
1311
1381
  case "AssignExpr":
@@ -1316,81 +1386,124 @@ class CodeGenerator {
1316
1386
  return expr.prefix
1317
1387
  ? `${expr.op}${this.emitExpr(expr.target)}`
1318
1388
  : `${this.emitExpr(expr.target)}${expr.op}`;
1319
- case "MemberExpr": return `${this.emitExpr(expr.target)}.${expr.member}`;
1320
- case "SafeMemberExpr": return `${this.emitExpr(expr.target)}?.${expr.member}`;
1321
- case "IndexExpr": return `${this.emitExpr(expr.target)}[${this.emitExpr(expr.index)}]`;
1389
+ case "MemberExpr":
1390
+ return `${this.emitExpr(expr.target)}.${expr.member}`;
1391
+ case "SafeMemberExpr":
1392
+ return `${this.emitExpr(expr.target)}?.${expr.member}`;
1393
+ case "IndexExpr":
1394
+ return `${this.emitExpr(expr.target)}[${this.emitExpr(expr.index)}]`;
1322
1395
  case "NotNullExpr":
1396
+ // Runtime check
1323
1397
  this.runtimeSymbolsNeeded.add("notNull");
1324
1398
  return `notNull(${this.emitExpr(expr.expr)})`;
1325
1399
  case "SafeCallExpr": {
1400
+ // x?.() — safe invocation of a nullable function value
1326
1401
  const callee = this.emitExpr(expr.callee);
1327
- const restArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1402
+ const restArgs = [];
1403
+ for (const a of expr.args) {
1404
+ restArgs.push(a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1405
+ }
1328
1406
  if (expr.trailingLambda)
1329
1407
  restArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1330
1408
  return `${callee}?.(${restArgs.join(", ")})`;
1331
1409
  }
1332
- case "ElvisExpr": return `(${this.emitExpr(expr.left)} ?? ${this.emitExpr(expr.right)})`;
1333
- case "CallExpr": return this.emitCallExpr(expr);
1334
- case "LambdaExpr": return this.emitLambdaExpr(expr);
1335
- case "IfExpr": return this.emitIfExpr(expr);
1336
- case "WhenExpr": return this.emitWhenExpr(expr);
1337
- case "TryCatchExpr": return this.emitTryCatchExpr(expr);
1338
- case "TypeCheckExpr": return `${expr.negated ? "!(" : ""}${this.emitExpr(expr.expr)} instanceof ${this.emitTypeRef(expr.type)}${expr.negated ? ")" : ""}`;
1339
- case "TypeCastExpr": return `(${this.emitExpr(expr.expr)} as unknown as ${this.emitTypeRef(expr.type)})`;
1340
- case "SafeCastExpr":
1410
+ case "ElvisExpr": {
1411
+ // left ?? right (null coalescing)
1412
+ return `(${this.emitExpr(expr.left)} ?? ${this.emitExpr(expr.right)})`;
1413
+ }
1414
+ case "CallExpr":
1415
+ return this.emitCallExpr(expr);
1416
+ case "LambdaExpr":
1417
+ return this.emitLambdaExpr(expr);
1418
+ case "IfExpr":
1419
+ return this.emitIfExpr(expr);
1420
+ case "WhenExpr":
1421
+ return this.emitWhenExpr(expr);
1422
+ case "TryCatchExpr":
1423
+ return this.emitTryCatchExpr(expr);
1424
+ case "TypeCheckExpr":
1425
+ return `${expr.negated ? "!(" : ""}${this.emitExpr(expr.expr)} instanceof ${this.emitTypeRef(expr.type)}${expr.negated ? ")" : ""}`;
1426
+ case "TypeCastExpr":
1427
+ // Unsafe cast — emit as `(expr as Type)` which is stripped at runtime
1428
+ return `(${this.emitExpr(expr.expr)} as unknown as ${this.emitTypeRef(expr.type)})`;
1429
+ case "SafeCastExpr": {
1341
1430
  this.runtimeSymbolsNeeded.add("safeCast");
1342
1431
  return `safeCast(${this.emitExpr(expr.expr)}, ${this.emitTypeRef(expr.type)})`;
1343
- case "RangeExpr":
1432
+ }
1433
+ case "RangeExpr": {
1344
1434
  this.runtimeSymbolsNeeded.add("range");
1345
1435
  return `range(${this.emitExpr(expr.from)}, ${this.emitExpr(expr.to)}, ${expr.inclusive})`;
1436
+ }
1346
1437
  case "LaunchExpr": {
1438
+ // fire-and-forget → void IIFE
1347
1439
  const stmts = this.captureBlock(expr.body);
1348
1440
  return `(async () => { ${stmts} })()`;
1349
1441
  }
1350
1442
  case "AsyncExpr": {
1443
+ // returns Promise<T>
1351
1444
  const stmts = this.captureBlock(expr.body);
1352
1445
  return `(async () => { ${stmts} })()`;
1353
1446
  }
1354
- case "CollectionLiteralExpr": return this.emitCollectionLiteral(expr);
1355
- case "ObjectExpr": return this.emitObjectExpr(expr);
1447
+ case "CollectionLiteralExpr":
1448
+ return this.emitCollectionLiteral(expr);
1449
+ case "ObjectExpr":
1450
+ return this.emitObjectExpr(expr);
1356
1451
  case "ReturnExpr": {
1357
1452
  const val = expr.value ? ` ${this.emitExpr(expr.value)}` : "";
1358
1453
  return `((() => { return${val}; })())`;
1359
1454
  }
1360
1455
  case "BreakExpr": return "undefined /* break */";
1361
1456
  case "ContinueExpr": return "undefined /* continue */";
1362
- case "JsxElement": return this.emitJsxElement(expr);
1363
- default: return "undefined";
1457
+ case "JsxElement":
1458
+ return this.emitJsxElement(expr);
1459
+ default:
1460
+ return "undefined";
1364
1461
  }
1365
1462
  }
1463
+ // ── JSX ──────────────────────────────────────────────────────────────────
1366
1464
  emitJsxElement(expr) {
1367
- this.runtimeSymbolsNeeded.add("jalvinCreateElement");
1368
1465
  const attrsStr = expr.attrs.length > 0
1369
- ? "{ " + expr.attrs.map((a) => this.emitJsxAttr(a)).join(", ") + " }"
1370
- : "{}";
1371
- const childrenArr = expr.children.length > 0
1372
- ? "[" + expr.children.map((c) => this.emitJsxChild(c)).join(", ") + "]"
1373
- : "[]";
1374
- return `jalvinCreateElement(${JSON.stringify(expr.tag)}, ${attrsStr}, ${childrenArr})`;
1466
+ ? " " + expr.attrs.map((a) => this.emitJsxAttr(a)).join(" ")
1467
+ : "";
1468
+ if (expr.children.length === 0) {
1469
+ return `<${expr.tag}${attrsStr} />`;
1470
+ }
1471
+ const children = expr.children.map((c) => this.emitJsxChild(c)).join("");
1472
+ return `<${expr.tag}${attrsStr}>${children}</${expr.tag}>`;
1375
1473
  }
1376
1474
  emitJsxAttr(attr) {
1377
- const name = attr.name === "class" ? "className" : attr.name === "for" ? "htmlFor" : attr.name;
1378
- const val = attr.value === null ? "true" : typeof attr.value === "string" ? JSON.stringify(attr.value) : this.emitExpr(attr.value);
1379
- return `${name}: ${val}`;
1475
+ // Map HTML attribute names to React prop names
1476
+ const name = attr.name === "class" ? "className"
1477
+ : attr.name === "for" ? "htmlFor"
1478
+ : attr.name;
1479
+ if (attr.value === null)
1480
+ return name;
1481
+ if (typeof attr.value === "string")
1482
+ return `${name}="${attr.value}"`;
1483
+ return `${name}={${this.emitExpr(attr.value)}}`;
1380
1484
  }
1381
1485
  emitJsxChild(child) {
1382
1486
  switch (child.kind) {
1383
1487
  case "JsxElement": return this.emitJsxElement(child);
1384
- case "JsxExprChild": return this.emitExpr(child.expr);
1385
- case "JsxTextChild": return `document.createTextNode(${JSON.stringify(child.text)})`;
1488
+ case "JsxExprChild": return `{${this.emitExpr(child.expr)}}`;
1489
+ case "JsxTextChild": return child.text;
1386
1490
  }
1387
1491
  }
1388
1492
  emitStringTemplate(expr) {
1389
- const inner = expr.parts.map((p) => p.kind === "LiteralPart" ? p.value.replace(/`/g, "\\`").replace(/\\/g, "\\\\") : `\${${this.emitExpr(p.expr)}}`).join("");
1493
+ const inner = expr.parts.map((p) => {
1494
+ if (p.kind === "LiteralPart") {
1495
+ return p.value.replace(/`/g, "\\`").replace(/\\/g, "\\\\");
1496
+ }
1497
+ return `\${${this.emitExpr(p.expr)}}`;
1498
+ }).join("");
1390
1499
  return `\`${inner}\``;
1391
1500
  }
1392
1501
  emitCallExpr(expr) {
1393
- if (expr.callee.kind === "MemberExpr" && (expr.callee.member === "downTo" || expr.callee.member === "until" || expr.callee.member === "step")) {
1502
+ // Rewrite infix numeric extension calls that JS numbers don't natively have:
1503
+ // `a.downTo(b)` → `downTo(a, b)` `a.until(b)` → `range(a, b, false)`
1504
+ // `range.step(n)` → `step(range, n)`
1505
+ if (expr.callee.kind === "MemberExpr" &&
1506
+ (expr.callee.member === "downTo" || expr.callee.member === "until" || expr.callee.member === "step")) {
1394
1507
  const obj = this.emitExpr(expr.callee.target);
1395
1508
  const arg = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "0";
1396
1509
  if (expr.callee.member === "downTo") {
@@ -1406,39 +1519,51 @@ class CodeGenerator {
1406
1519
  return `step(${obj}, ${arg})`;
1407
1520
  }
1408
1521
  }
1522
+ // Rewrite calls on primitive-receiver extension functions.
1523
+ // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1409
1524
  if (expr.callee.kind === "MemberExpr") {
1410
1525
  const scopeMember = expr.callee.member;
1411
- const receiver = this.emitExpr(expr.callee.target);
1412
- const lambda = expr.trailingLambda ?? (expr.args.length === 1 && expr.args[0].value.kind === "LambdaExpr" ? expr.args[0].value : null);
1413
- if (lambda) {
1414
- if (scopeMember === "let") {
1415
- this.runtimeSymbolsNeeded.add("let_");
1416
- return `let_(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1417
- }
1418
- if (scopeMember === "also") {
1419
- this.runtimeSymbolsNeeded.add("also");
1420
- return `also(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1421
- }
1422
- if (scopeMember === "apply") {
1423
- this.runtimeSymbolsNeeded.add("apply");
1424
- const body = this.captureBlockStatements(lambda.body);
1425
- return `apply(${receiver}, function(this: any) { ${body} })`;
1426
- }
1427
- if (scopeMember === "run") {
1428
- this.runtimeSymbolsNeeded.add("run_");
1429
- const body = this.captureBlockStatements(lambda.body);
1430
- return `run_(${receiver}, function(this: any) { ${body} })`;
1431
- }
1432
- if (scopeMember === "takeIf") {
1433
- this.runtimeSymbolsNeeded.add("takeIf");
1434
- return `takeIf(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1435
- }
1436
- if (scopeMember === "takeUnless") {
1437
- this.runtimeSymbolsNeeded.add("takeUnless");
1438
- return `takeUnless(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1526
+ // Scope function call rewriting: `x.let { ... }` → `let_(x, ...)`
1527
+ // `x.apply { ... }` `apply(x, function(this:any) { ... })`
1528
+ if (scopeMember === "let" || scopeMember === "also" || scopeMember === "apply" || scopeMember === "run" || scopeMember === "takeIf" || scopeMember === "takeUnless") {
1529
+ const receiver = this.emitExpr(expr.callee.target);
1530
+ const lambda = expr.trailingLambda ??
1531
+ (expr.args.length === 1 && expr.args[0].value.kind === "LambdaExpr"
1532
+ ? expr.args[0].value
1533
+ : null);
1534
+ if (lambda) {
1535
+ if (scopeMember === "let") {
1536
+ this.runtimeSymbolsNeeded.add("let_");
1537
+ return `let_(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1538
+ }
1539
+ if (scopeMember === "also") {
1540
+ this.runtimeSymbolsNeeded.add("also");
1541
+ return `also(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1542
+ }
1543
+ if (scopeMember === "apply") {
1544
+ this.runtimeSymbolsNeeded.add("apply");
1545
+ const body = this.captureBlockStatements(lambda.body);
1546
+ return `apply(${receiver}, function(this: any) { ${body} })`;
1547
+ }
1548
+ if (scopeMember === "run") {
1549
+ this.runtimeSymbolsNeeded.add("run_");
1550
+ const body = this.captureBlockStatements(lambda.body);
1551
+ return `run_(${receiver}, function(this: any) { ${body} })`;
1552
+ }
1553
+ if (scopeMember === "takeIf") {
1554
+ this.runtimeSymbolsNeeded.add("takeIf");
1555
+ return `takeIf(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1556
+ }
1557
+ if (scopeMember === "takeUnless") {
1558
+ this.runtimeSymbolsNeeded.add("takeUnless");
1559
+ return `takeUnless(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1560
+ }
1439
1561
  }
1440
1562
  }
1563
+ // `with(x) { ... }` is a free function handled via seedBuiltins; but rewrite if emitted as member
1441
1564
  }
1565
+ // Rewrite calls on primitive-receiver extension functions.
1566
+ // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1442
1567
  if (expr.callee.kind === "MemberExpr") {
1443
1568
  const receiverType = this.typeMap.get(expr.callee.target);
1444
1569
  const memberName = expr.callee.member;
@@ -1458,69 +1583,105 @@ class CodeGenerator {
1458
1583
  }
1459
1584
  }
1460
1585
  const callee = this.emitExpr(expr.callee);
1461
- const typeArgs = expr.typeArgs.length > 0 ? `<${expr.typeArgs.map((t) => t.star ? "*" : this.emitTypeRef(t.type)).join(", ")}>` : "";
1462
- if (expr.callee.kind === "NameExpr" && expr.callee.name === "with" && (expr.trailingLambda || (expr.args.length === 2 && expr.args[1].value.kind === "LambdaExpr"))) {
1586
+ const typeArgs = expr.typeArgs.length > 0
1587
+ ? `<${expr.typeArgs.map((t) => t.star ? "*" : this.emitTypeRef(t.type)).join(", ")}>`
1588
+ : "";
1589
+ // Rewrite top-level `with(obj) { ... }` → `with_(obj, function(this: any) { ... })`
1590
+ if (expr.callee.kind === "NameExpr" && expr.callee.name === "with" &&
1591
+ (expr.trailingLambda || (expr.args.length === 2 && expr.args[1].value.kind === "LambdaExpr"))) {
1463
1592
  const obj = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "undefined";
1464
- const lambda = expr.trailingLambda ?? expr.args[1].value;
1593
+ const lambda = expr.trailingLambda ??
1594
+ expr.args[1].value;
1465
1595
  this.runtimeSymbolsNeeded.add("with_");
1466
1596
  const body = this.captureBlockStatements(lambda.body);
1467
1597
  return `with_(${obj}, function(this: any) { ${body} })`;
1468
1598
  }
1469
- if (expr.callee.kind === "NameExpr" && expr.callee.name === "run" && expr.args.length === 0 && expr.trailingLambda) {
1599
+ // Rewrite top-level `run { ... }` (no receiver) just call the lambda
1600
+ if (expr.callee.kind === "NameExpr" && expr.callee.name === "run" &&
1601
+ expr.args.length === 0 && expr.trailingLambda) {
1470
1602
  return `(${this.emitLambdaExpr(expr.trailingLambda)})()`;
1471
1603
  }
1604
+ // Handle named arguments by reordering them to match positional parameters
1472
1605
  const calleeType = this.typeMap.get(expr.callee);
1606
+ // Component call: Column(modifier = ...) { ... } → Column({ modifier: ... }, [children])
1607
+ // Also catches star-imported @jalvin/ui primitives (Row, Button, etc.) whose type is T_UNKNOWN
1608
+ //
1609
+ // Skip the component path when the typechecker resolved a func type with explicit paramNames.
1610
+ // That indicates a regular function (fun) or a built-in hook — both use positional calling.
1611
+ // component fun declarations intentionally omit paramNames from their func type.
1473
1612
  const isKnownPositionalFunc = calleeType?.tag === "func" && calleeType.paramNames !== undefined;
1474
- let isComponentLike = false;
1475
- if (expr.callee.kind === "NameExpr") {
1476
- isComponentLike = this.allComponentLikeNames.has(expr.callee.name) || (this.hasUiStarImport && (!calleeType || calleeType.tag === "unknown") && /^[A-Z]/.test(expr.callee.name));
1477
- }
1478
- else if (expr.callee.kind === "MemberExpr" || expr.callee.kind === "SafeMemberExpr") {
1479
- isComponentLike = this.allComponentLikeNames.has(expr.callee.member);
1480
- }
1481
- if (!isKnownPositionalFunc && isComponentLike)
1613
+ if (!isKnownPositionalFunc && expr.callee.kind === "NameExpr" && (this.componentNames.has(expr.callee.name) ||
1614
+ (this.hasUiStarImport && (!calleeType || calleeType.tag === "unknown") && /^[A-Z]/.test(expr.callee.name)))) {
1482
1615
  return this.emitComponentCall(expr);
1616
+ }
1483
1617
  let finalArgs = [];
1484
1618
  if (calleeType && calleeType.tag === "func" && calleeType.paramNames) {
1485
1619
  const paramNames = calleeType.paramNames;
1486
1620
  const argsByName = new Map();
1487
1621
  const positionalArgs = [];
1488
1622
  for (const arg of expr.args) {
1489
- if (arg.name)
1623
+ if (arg.name) {
1490
1624
  argsByName.set(arg.name, arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1491
- else
1625
+ }
1626
+ else {
1492
1627
  positionalArgs.push(arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1628
+ }
1493
1629
  }
1630
+ // Reconstruct args based on paramNames
1494
1631
  for (let i = 0; i < paramNames.length; i++) {
1495
1632
  const name = paramNames[i];
1496
- if (argsByName.has(name))
1633
+ if (argsByName.has(name)) {
1497
1634
  finalArgs.push(argsByName.get(name));
1498
- else if (i < positionalArgs.length)
1635
+ }
1636
+ else if (i < positionalArgs.length) {
1499
1637
  finalArgs.push(positionalArgs[i]);
1500
- else
1638
+ }
1639
+ else {
1640
+ // Missing argument - JS default value logic will handle it if we pass undefined
1501
1641
  finalArgs.push("undefined");
1642
+ }
1502
1643
  }
1503
- if (positionalArgs.length > paramNames.length)
1644
+ // Handle trailing positional args if any
1645
+ if (positionalArgs.length > paramNames.length) {
1504
1646
  finalArgs.push(...positionalArgs.slice(paramNames.length));
1647
+ }
1505
1648
  }
1506
1649
  else {
1507
- finalArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1650
+ // Fallback for types we don't know param names for (like constructors or any)
1651
+ finalArgs = expr.args.map((a) => {
1652
+ return a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value);
1653
+ });
1508
1654
  }
1509
- if (expr.trailingLambda)
1655
+ if (expr.trailingLambda) {
1510
1656
  finalArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1511
- if (calleeType && calleeType.tag === "class" && calleeType.decl && this.classHasInvokeOperator(calleeType.decl)) {
1657
+ }
1658
+ // If the callee is a class instance (not a constructor / function), emit `.invoke(args)`
1659
+ if (calleeType &&
1660
+ calleeType.tag === "class" &&
1661
+ calleeType.decl &&
1662
+ this.classHasInvokeOperator(calleeType.decl)) {
1512
1663
  return `${callee}.invoke(${finalArgs.join(", ")})`;
1513
1664
  }
1514
- if (this.isConstructorCall(expr.callee))
1665
+ // Constructor calls: class names start with uppercase by convention
1666
+ if (this.isConstructorCall(expr.callee)) {
1515
1667
  return `new ${callee}${typeArgs}(${finalArgs.join(", ")})`;
1668
+ }
1516
1669
  return `${callee}${typeArgs}(${finalArgs.join(", ")})`;
1517
1670
  }
1671
+ /**
1672
+ * For each non-scoped local import (e.g. `import src.views.MoveCounterView.MoveCounter`),
1673
+ * attempt to load and parse the corresponding .jalvin source file. If the named symbol is
1674
+ * a `component fun`, register it in `componentNames` and record its param names so that
1675
+ * call sites can emit the correct props-object invocation.
1676
+ */
1518
1677
  resolveLocalComponentImports(program, sourceRoot) {
1519
1678
  this.localStarExports.clear();
1520
1679
  for (const imp of program.imports) {
1680
+ // Skip scoped packages (@org/pkg)
1521
1681
  if (imp.path[0]?.startsWith("@"))
1522
1682
  continue;
1523
1683
  if (imp.star) {
1684
+ // Local wildcard import: import src.module.* — load all exports from src/module.jalvin
1524
1685
  if (imp.path.length < 1)
1525
1686
  continue;
1526
1687
  const candidatePath = nodePath.join(sourceRoot, imp.path.join("/") + ".jalvin");
@@ -1543,27 +1704,21 @@ class CodeGenerator {
1543
1704
  continue;
1544
1705
  exportedNames.push(name);
1545
1706
  if (d.kind === "ComponentDecl") {
1546
- this.userComponentNames.add(name);
1547
- this.allComponentLikeNames.add(name);
1707
+ this.componentNames.add(name);
1548
1708
  this.componentParamNames.set(name, d.params.map((p) => p.name));
1549
1709
  this.hasComponents = true;
1550
1710
  }
1551
- if (d.kind === "ClassDecl" && d.body) {
1552
- for (const m of d.body.members) {
1553
- if (m.kind === "ComponentDecl") {
1554
- this.allComponentLikeNames.add(m.name);
1555
- this.componentParamNames.set(m.name, m.params.map((p) => p.name));
1556
- this.hasComponents = true;
1557
- }
1558
- }
1559
- }
1560
1711
  }
1561
- if (exportedNames.length > 0)
1712
+ if (exportedNames.length > 0) {
1562
1713
  this.localStarExports.set(moduleSpecifier, exportedNames);
1714
+ }
1715
+ }
1716
+ catch {
1717
+ // Silently skip if the file cannot be read or parsed
1563
1718
  }
1564
- catch { }
1565
1719
  continue;
1566
1720
  }
1721
+ // Named import: last path segment is the symbol, preceding segments form the file path
1567
1722
  if (imp.path.length < 2)
1568
1723
  continue;
1569
1724
  const rawSymbolName = imp.path[imp.path.length - 1];
@@ -1581,237 +1736,574 @@ class CodeGenerator {
1581
1736
  const ast = (0, parser_js_1.parse)(tokens, candidatePath, diag, source);
1582
1737
  if (diag.hasErrors)
1583
1738
  continue;
1584
- const decl = ast.declarations.find((d) => ((d.kind === "ComponentDecl" || d.kind === "ClassDecl") && d.name === rawSymbolName));
1585
- if (decl?.kind === "ComponentDecl") {
1586
- this.userComponentNames.add(symbolName);
1587
- this.allComponentLikeNames.add(symbolName);
1588
- this.componentParamNames.set(symbolName, decl.params.map((p) => p.name));
1739
+ // Look for a top-level ComponentDecl matching the imported symbol name
1740
+ const compDecl = ast.declarations.find((d) => d.kind === "ComponentDecl" && d.name === rawSymbolName);
1741
+ if (compDecl) {
1742
+ this.componentNames.add(symbolName);
1743
+ this.componentParamNames.set(symbolName, compDecl.params.map((p) => p.name));
1589
1744
  this.hasComponents = true;
1590
1745
  }
1591
- else if (decl?.kind === "ClassDecl" && decl.body) {
1592
- for (const m of decl.body.members) {
1593
- if (m.kind === "ComponentDecl") {
1594
- this.userComponentNames.add(m.name);
1595
- this.allComponentLikeNames.add(m.name);
1596
- this.componentParamNames.set(m.name, m.params.map((p) => p.name));
1597
- this.hasComponents = true;
1598
- }
1599
- }
1600
- }
1601
1746
  }
1602
- catch { }
1747
+ catch {
1748
+ // Silently skip if the file cannot be read or parsed
1749
+ }
1603
1750
  }
1604
1751
  }
1752
+ /** Returns true if a call expression's callee looks like a class constructor.
1753
+ * In Jalvin, class names always start with an uppercase letter. */
1605
1754
  isConstructorCall(calleeExpr) {
1606
1755
  if (calleeExpr.kind === "NameExpr") {
1607
- if (this.allComponentLikeNames.has(calleeExpr.name))
1756
+ // Names in componentNames are factory functions (UI primitives, components), not constructors
1757
+ if (this.componentNames.has(calleeExpr.name))
1608
1758
  return false;
1759
+ // Class names start with uppercase; function names start with lowercase
1609
1760
  return /^[A-Z]/.test(calleeExpr.name);
1610
1761
  }
1611
1762
  if (calleeExpr.kind === "MemberExpr") {
1612
- if (this.allComponentLikeNames.has(calleeExpr.member))
1613
- return false;
1763
+ // e.g. UiState.Success(data), Discount.Percentage(0.1)
1614
1764
  return /^[A-Z]/.test(calleeExpr.member);
1615
1765
  }
1616
1766
  return false;
1617
1767
  }
1768
+ // ── Component call → props object ──────────────────────────────────────
1769
+ /** Emit `Column(modifier = ...) { ... }` as `React.createElement(Column, { modifier: ... }, [children])` */
1618
1770
  emitComponentCall(expr) {
1619
- const callee = this.emitExpr(expr.callee);
1620
- const tagName = expr.callee.kind === "NameExpr" ? expr.callee.name : expr.callee.member;
1771
+ const tag = expr.callee.name;
1621
1772
  const calleeType = this.typeMap.get(expr.callee);
1622
- const paramNames = (calleeType?.tag === "func" ? calleeType.paramNames : undefined) ?? (tagName ? this.componentParamNames.get(tagName) : undefined);
1773
+ // Param names from type checker (for same-file components) or resolved from imported files
1774
+ const paramNames = (calleeType?.tag === "func" ? calleeType.paramNames : undefined) ??
1775
+ this.componentParamNames.get(tag);
1776
+ // Build props object from named/positional args
1623
1777
  const props = [];
1624
1778
  for (let i = 0; i < expr.args.length; i++) {
1625
1779
  const arg = expr.args[i];
1626
- const propName = arg.name ?? paramNames?.[i] ?? (arg.value.kind === "NameExpr" ? arg.value.name : undefined);
1780
+ // Prop name: explicit named arg > known param name > NameExpr variable name (shorthand)
1781
+ const propName = arg.name ??
1782
+ paramNames?.[i] ??
1783
+ (arg.value.kind === "NameExpr" ? arg.value.name : undefined);
1627
1784
  if (!propName)
1628
1785
  continue;
1629
1786
  const val = this.emitExpr(arg.value);
1787
+ // Use JS shorthand `{ key }` when key and value are the same identifier
1630
1788
  props.push(propName === val ? propName : `${propName}: ${val}`);
1631
1789
  }
1632
1790
  const propsStr = props.length > 0 ? `{ ${props.join(", ")} }` : "{}";
1633
1791
  if (expr.trailingLambda) {
1634
1792
  const children = this.emitLambdaBodyAsDomChildren(expr.trailingLambda);
1635
- return `${callee}(${propsStr}, [${children}])`;
1793
+ return `React.createElement(${tag}, ${propsStr}, [${children}])`;
1636
1794
  }
1637
- return `${callee}(${propsStr})`;
1795
+ return `React.createElement(${tag}, ${propsStr})`;
1638
1796
  }
1797
+ /** Collect each expression statement in a trailing-lambda body as DOM children.
1798
+ * For-loop statements are emitted as spread IIFEs so their dynamic output is preserved. */
1639
1799
  emitLambdaBodyAsDomChildren(lambda) {
1640
1800
  const parts = [];
1641
1801
  for (const stmt of lambda.body) {
1642
1802
  if (stmt.kind === "ExprStmt") {
1643
1803
  parts.push(this.emitExpr(stmt.expr));
1644
1804
  }
1645
- else if (stmt.kind === "IfStmt") {
1646
- const s = stmt;
1647
- const cond = this.emitExpr(s.condition);
1648
- const thenExprs = s.then.statements.filter((st) => st.kind === "ExprStmt").map((st) => this.emitExpr(st.expr));
1649
- const elseExprs = s.else && s.else.kind === "Block" ? s.else.statements.filter((st) => st.kind === "ExprStmt").map((st) => this.emitExpr(st.expr)) : [];
1650
- const thenStr = thenExprs.length > 0 ? `...(${cond} ? [${thenExprs.join(", ")}] : [])` : "";
1651
- const elseStr = elseExprs.length > 0 ? `...(!(${cond}) ? [${elseExprs.join(", ")}] : [])` : "";
1652
- if (thenStr)
1653
- parts.push(thenStr);
1654
- if (elseStr)
1655
- parts.push(elseStr);
1656
- }
1657
1805
  else if (stmt.kind === "ForStmt") {
1658
1806
  const s = stmt;
1659
1807
  const iter = this.emitExpr(s.iterable);
1660
- let bindingStr = typeof s.binding === "string" ? `const ${s.binding}` : s.binding.kind === "TupleDestructure" ? `const [${s.binding.names.map((n) => n ?? "_").join(", ")}]` : `const [${s.binding.key}, ${s.binding.value}]`;
1661
- const innerExprs = s.body.statements.filter((inner) => inner.kind === "ExprStmt").map((inner) => this.emitExpr(inner.expr));
1808
+ let bindingStr;
1809
+ if (typeof s.binding === "string") {
1810
+ bindingStr = `const ${s.binding}`;
1811
+ }
1812
+ else if (s.binding.kind === "TupleDestructure") {
1813
+ const names = s.binding.names.map((n) => n ?? "_").join(", ");
1814
+ bindingStr = `const [${names}]`;
1815
+ }
1816
+ else {
1817
+ bindingStr = `const [${s.binding.key}, ${s.binding.value}]`;
1818
+ }
1819
+ const innerExprs = s.body.statements
1820
+ .filter((inner) => inner.kind === "ExprStmt")
1821
+ .map((inner) => this.emitExpr(inner.expr));
1662
1822
  if (innerExprs.length > 0) {
1663
1823
  const pushes = innerExprs.map((e) => `__c.push(${e});`).join(" ");
1664
1824
  parts.push(`...(() => { const __c: any[] = []; for (${bindingStr} of ${iter}) { ${pushes} } return __c; })()`);
1665
1825
  }
1666
1826
  }
1827
+ // Other statement kinds (if, while, etc.) are not renderable as UI children inline
1667
1828
  }
1668
1829
  return parts.join(", ");
1669
1830
  }
1670
1831
  emitLambdaExpr(expr) {
1671
- const params = expr.params.length === 0 ? "it" : expr.params.map((p) => p.name ?? "_").join(", ");
1672
- if (expr.body.length === 1 && expr.body[0].kind === "ExprStmt")
1673
- return `(${params}) => ${this.emitExpr(expr.body[0].expr)}`;
1674
- return `(${params}) => { ${expr.body.map((s) => this.captureStmt(s)).join(" ")} }`;
1832
+ // If no explicit params, emit `it` as the single implicit parameter
1833
+ // (Convention for single-argument lambdas)
1834
+ const params = expr.params.length === 0
1835
+ ? "it"
1836
+ : expr.params.map((p) => p.name ?? "_").join(", ");
1837
+ const body = expr.body;
1838
+ if (body.length === 1 && body[0].kind === "ExprStmt") {
1839
+ return `(${params}) => ${this.emitExpr(body[0].expr)}`;
1840
+ }
1841
+ return `(${params}) => { ${body.map((s) => this.captureStmt(s)).join(" ")} }`;
1675
1842
  }
1676
1843
  emitIfExpr(expr) {
1677
1844
  const cond = this.emitExpr(expr.condition);
1678
- const thenStr = expr.then.kind === "Block" ? `(() => { ${this.captureBlockStatements(expr.then.statements)} })()` : this.emitExpr(expr.then);
1679
- const elseStr = expr.else.kind === "Block" ? `(() => { ${this.captureBlockStatements(expr.else.statements)} })()` : expr.else.kind === "IfExpr" ? this.emitIfExpr(expr.else) : this.emitExpr(expr.else);
1845
+ const thenStr = expr.then.kind === "Block"
1846
+ ? `(() => { ${this.captureBlockStatements(expr.then.statements)} })()`
1847
+ : this.emitExpr(expr.then);
1848
+ const elseExpr = expr.else;
1849
+ const elseStr = elseExpr.kind === "Block"
1850
+ ? `(() => { ${this.captureBlockStatements(elseExpr.statements)} })()`
1851
+ : elseExpr.kind === "IfExpr"
1852
+ ? this.emitIfExpr(elseExpr)
1853
+ : this.emitExpr(elseExpr);
1680
1854
  return `(${cond} ? ${thenStr} : ${elseStr})`;
1681
1855
  }
1682
1856
  emitWhenExpr(expr) {
1857
+ // Compile as an IIFE with if/else chain
1683
1858
  const parts = [];
1684
1859
  const subject = expr.subject ? `const __s = ${this.emitExpr(expr.subject.expr)};` : "";
1685
1860
  const subjectRef = expr.subject ? (expr.subject.binding ?? "__s") : "";
1686
1861
  for (const branch of expr.branches) {
1687
- const body = branch.body.kind === "Block" ? this.captureBlock(branch.body) : `return ${this.emitExpr(branch.body)};`;
1688
- if (branch.isElse)
1862
+ if (branch.isElse) {
1863
+ const body = branch.body.kind === "Block"
1864
+ ? this.captureBlock(branch.body)
1865
+ : `return ${this.emitExpr(branch.body)};`;
1689
1866
  parts.push(`{ ${body} }`);
1690
- else
1691
- parts.push(`if (${branch.conditions.map((c) => this.emitWhenCondition(c, subjectRef)).join(" || ")}) { ${body} }`);
1867
+ }
1868
+ else {
1869
+ const cond = branch.conditions.map((c) => this.emitWhenCondition(c, subjectRef)).join(" || ");
1870
+ const body = branch.body.kind === "Block"
1871
+ ? this.captureBlock(branch.body)
1872
+ : `return ${this.emitExpr(branch.body)};`;
1873
+ parts.push(`if (${cond}) { ${body} }`);
1874
+ }
1692
1875
  }
1693
1876
  return `(() => { ${subject} ${parts.join(" else ")} })()`;
1694
1877
  }
1695
1878
  emitTryCatchExpr(expr) {
1696
1879
  const body = this.captureBlock(expr.body);
1697
- const catches = expr.catches.map((c) => `catch (${c.name}) { ${this.captureBlock(c.body)} }`).join(" ");
1880
+ const catches = expr.catches.map((c) => {
1881
+ const cb = this.captureBlock(c.body);
1882
+ return `catch (${c.name}) { ${cb} }`;
1883
+ }).join(" ");
1698
1884
  const fin = expr.finally ? `finally { ${this.captureBlock(expr.finally)} }` : "";
1699
1885
  return `(() => { try { ${body} } ${catches} ${fin} })()`;
1700
1886
  }
1701
1887
  emitCollectionLiteral(expr) {
1702
- if (expr.collectionKind === "map")
1703
- return `new Map([${expr.elements.map((e) => ("kind" in e && e.kind === "MapEntry") ? `[${this.emitExpr(e.key)}, ${this.emitExpr(e.value)}]` : "null").join(", ")}])`;
1704
- if (expr.collectionKind === "set")
1705
- return `new Set([${expr.elements.map((e) => this.emitExpr(e)).join(", ")}])`;
1706
- return `[${expr.elements.map((e) => this.emitExpr(e)).join(", ")}]`;
1888
+ if (expr.collectionKind === "map") {
1889
+ const entries = expr.elements.map((e) => {
1890
+ if ("kind" in e && e.kind === "MapEntry") {
1891
+ return `[${this.emitExpr(e.key)}, ${this.emitExpr(e.value)}]`;
1892
+ }
1893
+ return "null";
1894
+ });
1895
+ return `new Map([${entries.join(", ")}])`;
1896
+ }
1897
+ if (expr.collectionKind === "set") {
1898
+ const items = expr.elements.map((e) => this.emitExpr(e));
1899
+ return `new Set([${items.join(", ")}])`;
1900
+ }
1901
+ const items = expr.elements.map((e) => this.emitExpr(e));
1902
+ return `[${items.join(", ")}]`;
1707
1903
  }
1708
1904
  emitObjectExpr(expr) {
1709
1905
  const superType = expr.superTypes[0];
1710
1906
  const ext = superType ? ` extends ${this.emitTypeRef(superType.type)}` : "";
1711
- return `(new (class${ext} { ${expr.body.members.map((m) => this.captureClassMember(m)).join(" ")} })())`;
1712
- }
1713
- captureBlock(block) { return block.statements.map((s) => this.captureStmt(s)).join(" "); }
1714
- captureBlockStatements(stmts) { return stmts.map((s) => this.captureStmt(s)).join(" "); }
1715
- captureStmt(stmt) { const saved = this.w; const tmp = new Writer(); this.w = tmp; this.emitStmt(stmt); this.w = saved; return tmp.output.trim(); }
1716
- captureClassMember(member) { const saved = this.w; const tmp = new Writer(); this.w = tmp; this.emitClassMember(member); this.w = saved; return tmp.output.trim(); }
1907
+ const members = expr.body.members.map((m) => this.captureClassMember(m)).join(" ");
1908
+ return `(new (class${ext} { ${members} })())`;
1909
+ }
1910
+ // ── capture helpers (emit to temp string) ─────────────────────────────────
1911
+ captureBlock(block) {
1912
+ return block.statements.map((s) => this.captureStmt(s)).join(" ");
1913
+ }
1914
+ captureBlockStatements(stmts) {
1915
+ return stmts.map((s) => this.captureStmt(s)).join(" ");
1916
+ }
1917
+ captureStmt(stmt) {
1918
+ const saved = this.w;
1919
+ const tmp = new Writer();
1920
+ this.w = tmp;
1921
+ this.emitStmt(stmt);
1922
+ this.w = saved;
1923
+ return tmp.output.trim();
1924
+ }
1925
+ captureClassMember(member) {
1926
+ const saved = this.w;
1927
+ const tmp = new Writer();
1928
+ this.w = tmp;
1929
+ this.emitClassMember(member);
1930
+ this.w = saved;
1931
+ return tmp.output.trim();
1932
+ }
1933
+ // ── Type reference emission ────────────────────────────────────────────────
1717
1934
  emitTypeRef(ref) {
1718
1935
  switch (ref.kind) {
1719
- case "SimpleTypeRef": return PRIMITIVE_TYPE_MAP[ref.name.join(".")] ?? ref.name.join(".");
1720
- case "NullableTypeRef": return `${this.emitTypeRef(ref.base)} | null | undefined`;
1721
- case "GenericTypeRef": return `${GENERIC_TYPE_MAP[ref.base.name.join(".")] ?? ref.base.name.join(".")}<${ref.args.map((a) => a.star ? "any" : a.type ? this.emitTypeRef(a.type) : "unknown").join(", ")}>`;
1722
- case "FunctionTypeRef": return `(${ref.params.map((p, i) => `p${i}: ${this.emitTypeRef(p)}`).join(", ")}) => ${this.emitTypeRef(ref.returnType)}`;
1723
- case "StarProjection": return "any";
1724
- }
1725
- }
1726
- emitTypeParamsStr(params) { return params.length === 0 ? "" : `<${params.map((p) => `${p.name}${p.upperBound ? ` extends ${this.emitTypeRef(p.upperBound)}` : ""}`).join(", ")}>`; }
1727
- emitParamsStr(params) { return params.map((p) => `${p.vararg ? "..." : ""}${p.name}${this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}${p.vararg ? "[]" : ""}` : ""}${p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : ""}`).join(", "); }
1728
- emitSuperTypesStr(superTypes) { return superTypes.length === 0 ? "" : superTypes.map((s, i) => i === 0 ? ` extends ${this.emitTypeRef(s.type)}` : ` implements ${this.emitTypeRef(s.type)}`).join(""); }
1729
- exportPrefix(mods) { return (mods.visibility === "private" || mods.visibility === "internal") ? "" : "export "; }
1730
- visibilityPrefix(mods) { switch (mods.visibility) {
1731
- case "private": return "private ";
1732
- case "protected": return "protected ";
1733
- case "internal": return "/* internal */ ";
1734
- default: return "";
1735
- } }
1736
- unaryOpStr(op) { return op === "not" ? "!" : op; }
1737
- binaryOpStr(op) { switch (op) {
1738
- case "and": return "&&";
1739
- case "or": return "||";
1740
- case "xor": return "^";
1741
- case "shl": return "<<";
1742
- case "shr": return ">>";
1743
- case "ushr": return ">>>";
1744
- case "===": return "===";
1745
- case "!==": return "!==";
1746
- default: return op;
1747
- } }
1936
+ case "SimpleTypeRef": {
1937
+ const name = ref.name.join(".");
1938
+ return PRIMITIVE_TYPE_MAP[name] ?? name;
1939
+ }
1940
+ case "NullableTypeRef":
1941
+ return `${this.emitTypeRef(ref.base)} | null | undefined`;
1942
+ case "GenericTypeRef": {
1943
+ const base = ref.base.name.join(".");
1944
+ const mapped = GENERIC_TYPE_MAP[base] ?? base;
1945
+ const args = ref.args.map((a) => a.star ? "any" : a.type ? this.emitTypeRef(a.type) : "unknown");
1946
+ return `${mapped}<${args.join(", ")}>`;
1947
+ }
1948
+ case "FunctionTypeRef": {
1949
+ const params = ref.params.map((p, i) => `p${i}: ${this.emitTypeRef(p)}`).join(", ");
1950
+ const ret = this.emitTypeRef(ref.returnType);
1951
+ return `(${params}) => ${ret}`;
1952
+ }
1953
+ case "StarProjection":
1954
+ return "any";
1955
+ }
1956
+ }
1957
+ // ── Utility helpers ────────────────────────────────────────────────────────
1958
+ emitTypeParamsStr(params) {
1959
+ if (params.length === 0)
1960
+ return "";
1961
+ const ps = params.map((p) => {
1962
+ const bound = p.upperBound ? ` extends ${this.emitTypeRef(p.upperBound)}` : "";
1963
+ return `${p.name}${bound}`;
1964
+ });
1965
+ return `<${ps.join(", ")}>`;
1966
+ }
1967
+ emitParamsStr(params) {
1968
+ return params.map((p) => {
1969
+ const spread = p.vararg ? "..." : "";
1970
+ // vararg params are rest params in TS: `...name: T[]`
1971
+ const type = this.opts.emitTypes
1972
+ ? `: ${this.emitTypeRef(p.type)}${p.vararg ? "[]" : ""}`
1973
+ : "";
1974
+ const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
1975
+ return `${spread}${p.name}${type}${def}`;
1976
+ }).join(", ");
1977
+ }
1978
+ emitComponentPropsStr(params) {
1979
+ if (params.length === 0)
1980
+ return "";
1981
+ return params.map((p) => {
1982
+ const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
1983
+ return `${p.name}${type}`;
1984
+ }).join(", ");
1985
+ }
1986
+ emitSuperTypesStr(superTypes) {
1987
+ if (superTypes.length === 0)
1988
+ return "";
1989
+ const parts = [];
1990
+ let first = true;
1991
+ for (const s of superTypes) {
1992
+ // TypeScript does NOT allow constructor arguments in the `extends` clause.
1993
+ // Delegation args are passed via `super(args)` inside the constructor.
1994
+ if (first) {
1995
+ parts.push(` extends ${this.emitTypeRef(s.type)}`);
1996
+ first = false;
1997
+ }
1998
+ else {
1999
+ parts.push(` implements ${this.emitTypeRef(s.type)}`);
2000
+ }
2001
+ }
2002
+ return parts.join("");
2003
+ }
2004
+ exportPrefix(mods) {
2005
+ if (mods.visibility === "private" || mods.visibility === "internal")
2006
+ return "";
2007
+ return "export ";
2008
+ }
2009
+ visibilityPrefix(mods) {
2010
+ switch (mods.visibility) {
2011
+ case "private": return "private ";
2012
+ case "protected": return "protected ";
2013
+ case "internal": return "/* internal */ ";
2014
+ default: return "";
2015
+ }
2016
+ }
2017
+ unaryOpStr(op) {
2018
+ if (op === "not")
2019
+ return "!";
2020
+ return op;
2021
+ }
2022
+ binaryOpStr(op) {
2023
+ switch (op) {
2024
+ case "and": return "&&";
2025
+ case "or": return "||";
2026
+ case "xor": return "^";
2027
+ case "shl": return "<<";
2028
+ case "shr": return ">>";
2029
+ case "ushr": return ">>>";
2030
+ // === / !== are JS reference equality (Jalvin's triple-equals)
2031
+ case "===": return "===";
2032
+ case "!==": return "!==";
2033
+ case "..": return "/* .. */ +"; // handled by range()
2034
+ case "..<": return "/* ..< */ +";
2035
+ default: return op;
2036
+ }
2037
+ }
2038
+ // ── AST name walkers (for wildcard import resolution) ─────────────────────
2039
+ /**
2040
+ * Walk the entire program AST and collect all identifier names that are
2041
+ * REFERENCED (i.e., used) in the code: NameExpr values and the first name
2042
+ * component of SimpleTypeRef / GenericTypeRef nodes.
2043
+ *
2044
+ * Jalvin primitive type names (String, Boolean, …) that map to TS primitives
2045
+ * are excluded from TypeRef collection since they never need an import.
2046
+ */
1748
2047
  gatherReferencedNames(program) {
1749
2048
  const names = new Set();
1750
2049
  const visited = new WeakSet();
1751
2050
  const walk = (val) => {
1752
- if (!val || typeof val !== "object" || visited.has(val))
2051
+ if (!val || typeof val !== "object")
1753
2052
  return;
1754
- visited.add(val);
1755
2053
  if (Array.isArray(val)) {
1756
- val.forEach(walk);
2054
+ for (const item of val)
2055
+ walk(item);
1757
2056
  return;
1758
2057
  }
1759
2058
  const obj = val;
1760
- if (obj.kind === "NameExpr") {
1761
- if (typeof obj.name === "string")
1762
- names.add(obj.name);
2059
+ if (visited.has(obj))
1763
2060
  return;
1764
- }
1765
- if (obj.kind === "SimpleTypeRef") {
1766
- if (obj.name?.[0] && !(obj.name[0] in PRIMITIVE_TYPE_MAP))
1767
- names.add(obj.name[0]);
2061
+ visited.add(obj);
2062
+ const kind = obj["kind"];
2063
+ if (kind === "NameExpr") {
2064
+ const name = obj["name"];
2065
+ if (typeof name === "string")
2066
+ names.add(name);
2067
+ return; // leaf node
2068
+ }
2069
+ if (kind === "SimpleTypeRef") {
2070
+ const nameArr = obj["name"];
2071
+ if (Array.isArray(nameArr) && nameArr.length > 0) {
2072
+ const first = nameArr[0];
2073
+ // Skip Jalvin primitive type names — they're erased to TS primitives.
2074
+ if (!(first in PRIMITIVE_TYPE_MAP))
2075
+ names.add(first);
2076
+ }
1768
2077
  return;
1769
2078
  }
1770
- if (obj.kind === "GenericTypeRef") {
1771
- if (obj.base?.name?.[0] && !(obj.base.name[0] in PRIMITIVE_TYPE_MAP))
1772
- names.add(obj.base.name[0]);
2079
+ if (kind === "GenericTypeRef") {
2080
+ const base = obj["base"];
2081
+ const nameArr = base?.["name"];
2082
+ if (Array.isArray(nameArr) && nameArr.length > 0) {
2083
+ const first = nameArr[0];
2084
+ if (!(first in PRIMITIVE_TYPE_MAP))
2085
+ names.add(first);
2086
+ }
2087
+ // Fall through to recurse into type arguments
2088
+ }
2089
+ for (const propVal of Object.values(obj)) {
2090
+ walk(propVal);
1773
2091
  }
1774
- Object.values(obj).forEach(walk);
1775
2092
  };
1776
- program.declarations.forEach(walk);
2093
+ for (const decl of program.declarations)
2094
+ walk(decl);
1777
2095
  return names;
1778
2096
  }
2097
+ /**
2098
+ * Walk the entire program AST and collect all names that are LOCALLY DEFINED:
2099
+ * top-level declaration names, non-star import aliases, function/lambda
2100
+ * parameters, local val/var bindings, for-loop variables, catch-clause names,
2101
+ * when-subject bindings, and type parameter names.
2102
+ */
1779
2103
  gatherAllLocalBindings(program) {
1780
2104
  const names = new Set();
1781
2105
  const visited = new WeakSet();
1782
- program.declarations.forEach((d) => { if (d.name)
1783
- names.add(d.name); });
1784
- program.imports.forEach((i) => { if (!i.star)
1785
- names.add(i.alias ?? i.path[i.path.length - 1]); });
2106
+ // Top-level declaration names
2107
+ for (const decl of program.declarations) {
2108
+ const d = decl;
2109
+ if (typeof d.name === "string" && d.name)
2110
+ names.add(d.name);
2111
+ }
2112
+ // Non-star import aliases
2113
+ for (const imp of program.imports) {
2114
+ if (!imp.star) {
2115
+ const name = imp.alias ?? imp.path[imp.path.length - 1];
2116
+ if (name)
2117
+ names.add(name);
2118
+ }
2119
+ }
1786
2120
  const walk = (val) => {
1787
- if (!val || typeof val !== "object" || visited.has(val))
2121
+ if (!val || typeof val !== "object")
1788
2122
  return;
1789
- visited.add(val);
1790
2123
  if (Array.isArray(val)) {
1791
- val.forEach(walk);
2124
+ for (const item of val)
2125
+ walk(item);
1792
2126
  return;
1793
2127
  }
1794
2128
  const obj = val;
1795
- if (["PropertyDecl", "DestructuringDecl", "FunDecl", "ExtensionFunDecl", "ComponentDecl", "ClassDecl", "DataClassDecl", "SealedClassDecl", "EnumClassDecl", "InterfaceDecl", "ObjectDecl", "TypeAliasDecl", "LambdaExpr"].includes(obj.kind) && obj.name)
1796
- names.add(obj.name);
1797
- if (obj.typeParams)
1798
- obj.typeParams.forEach((tp) => names.add(tp.name));
1799
- if (obj.params)
1800
- obj.params.forEach((p) => names.add(p.name));
1801
- if (obj.kind === "ForStmt" && typeof obj.binding === "string")
1802
- names.add(obj.binding);
1803
- if (obj.kind === "TryCatchStmt" && obj.catches)
1804
- obj.catches.forEach((c) => names.add(c.name));
1805
- Object.values(obj).forEach(walk);
2129
+ if (visited.has(obj))
2130
+ return;
2131
+ visited.add(obj);
2132
+ const kind = obj["kind"];
2133
+ // Collect bound names at their declaration sites
2134
+ if (kind === "PropertyDecl" || kind === "DestructuringDecl") {
2135
+ if (typeof obj["name"] === "string")
2136
+ names.add(obj["name"]);
2137
+ const nms = obj["names"];
2138
+ if (Array.isArray(nms)) {
2139
+ for (const n of nms) {
2140
+ if (typeof n === "string")
2141
+ names.add(n);
2142
+ }
2143
+ }
2144
+ }
2145
+ if (kind === "FunDecl" || kind === "ExtensionFunDecl" ||
2146
+ kind === "ComponentDecl" || kind === "ClassDecl" ||
2147
+ kind === "DataClassDecl" || kind === "SealedClassDecl" ||
2148
+ kind === "EnumClassDecl" || kind === "InterfaceDecl" ||
2149
+ kind === "ObjectDecl" || kind === "TypeAliasDecl") {
2150
+ if (typeof obj["name"] === "string" && obj["name"])
2151
+ names.add(obj["name"]);
2152
+ // Type parameters
2153
+ const typeParams = obj["typeParams"];
2154
+ if (Array.isArray(typeParams)) {
2155
+ for (const tp of typeParams) {
2156
+ if (tp.name)
2157
+ names.add(tp.name);
2158
+ }
2159
+ }
2160
+ // Function/method parameters
2161
+ const params = obj["params"];
2162
+ if (Array.isArray(params)) {
2163
+ for (const p of params) {
2164
+ if (p.name)
2165
+ names.add(p.name);
2166
+ }
2167
+ }
2168
+ }
2169
+ if (kind === "LambdaExpr") {
2170
+ const params = obj["params"];
2171
+ if (Array.isArray(params)) {
2172
+ for (const p of params) {
2173
+ if (p.name)
2174
+ names.add(p.name);
2175
+ }
2176
+ }
2177
+ }
2178
+ if (kind === "ForStmt") {
2179
+ const binding = obj["binding"];
2180
+ if (typeof binding === "string") {
2181
+ names.add(binding);
2182
+ }
2183
+ else if (binding && typeof binding === "object") {
2184
+ const b = binding;
2185
+ // TupleDestructure or MapDestructure
2186
+ if (Array.isArray(b["names"])) {
2187
+ for (const n of b["names"]) {
2188
+ if (typeof n === "string")
2189
+ names.add(n);
2190
+ }
2191
+ }
2192
+ if (typeof b["key"] === "string")
2193
+ names.add(b["key"]);
2194
+ if (typeof b["value"] === "string")
2195
+ names.add(b["value"]);
2196
+ }
2197
+ }
2198
+ if (kind === "TryCatchStmt") {
2199
+ const catches = obj["catches"];
2200
+ if (Array.isArray(catches)) {
2201
+ for (const c of catches) {
2202
+ if (c.name)
2203
+ names.add(c.name);
2204
+ }
2205
+ }
2206
+ }
2207
+ if (kind === "WhenStmt" || kind === "WhenExpr") {
2208
+ const subject = obj["subject"];
2209
+ if (typeof subject?.["binding"] === "string")
2210
+ names.add(subject["binding"]);
2211
+ }
2212
+ if (kind === "SecondaryConstructor") {
2213
+ const params = obj["params"];
2214
+ if (Array.isArray(params)) {
2215
+ for (const p of params) {
2216
+ if (p.name)
2217
+ names.add(p.name);
2218
+ }
2219
+ }
2220
+ }
2221
+ for (const propVal of Object.values(obj)) {
2222
+ walk(propVal);
2223
+ }
1806
2224
  };
1807
- program.declarations.forEach(walk);
2225
+ for (const decl of program.declarations)
2226
+ walk(decl);
1808
2227
  return names;
1809
2228
  }
1810
2229
  }
1811
2230
  exports.CodeGenerator = CodeGenerator;
1812
- const PRIMITIVE_TYPE_MAP = { Int: "number", Long: "bigint", Float: "number", Double: "number", Boolean: "boolean", String: "string", Char: "string", Byte: "number", Short: "number", Unit: "void", Any: "unknown", Nothing: "never" };
1813
- const GENERIC_TYPE_MAP = { List: "ReadonlyArray", MutableList: "Array", Set: "ReadonlySet", MutableSet: "Set", Map: "ReadonlyMap", MutableMap: "Map", Array: "Array", Deferred: "Promise", StateFlow: "StateFlow", MutableStateFlow: "MutableStateFlow", Flow: "AsyncIterable" };
2231
+ // ---------------------------------------------------------------------------
2232
+ // Type name mappings (Jalvin TypeScript)
2233
+ // ---------------------------------------------------------------------------
2234
+ const PRIMITIVE_TYPE_MAP = {
2235
+ Int: "number",
2236
+ Long: "bigint",
2237
+ Float: "number",
2238
+ Double: "number",
2239
+ Boolean: "boolean",
2240
+ String: "string",
2241
+ Char: "string",
2242
+ Byte: "number",
2243
+ Short: "number",
2244
+ Unit: "void",
2245
+ Any: "unknown",
2246
+ Nothing: "never",
2247
+ };
2248
+ const GENERIC_TYPE_MAP = {
2249
+ List: "ReadonlyArray",
2250
+ MutableList: "Array",
2251
+ Set: "ReadonlySet",
2252
+ MutableSet: "Set",
2253
+ Map: "ReadonlyMap",
2254
+ MutableMap: "Map",
2255
+ Array: "Array",
2256
+ Pair: "[", // handled specially
2257
+ Triple: "[",
2258
+ Deferred: "Promise",
2259
+ StateFlow: "StateFlow",
2260
+ MutableStateFlow: "MutableStateFlow",
2261
+ Flow: "AsyncIterable",
2262
+ };
1814
2263
  const PRIMITIVE_TYPES = new Set(Object.keys(PRIMITIVE_TYPE_MAP));
1815
- const JS_GLOBAL_NAMES = new Set(["Array", "Map", "Set", "Object", "String", "Number", "Boolean", "Promise", "Error", "console", "Math", "Date", "JSON", "setTimeout", "setInterval", "fetch", "document", "window", "undefined", "null", "NaN", "Infinity", "it", "this", "super", "any", "unknown", "never", "void"]);
1816
- function generate(program, opts, operatorOverloads, typeMap) { return new CodeGenerator(opts).generate(program, operatorOverloads, typeMap); }
2264
+ // ---------------------------------------------------------------------------
2265
+ // Well-known JavaScript / TypeScript global names that must never be emitted
2266
+ // as named imports from a wildcard-imported package.
2267
+ // ---------------------------------------------------------------------------
2268
+ const JS_GLOBAL_NAMES = new Set([
2269
+ // JS built-in constructors and objects
2270
+ "Array", "Map", "Set", "Object", "String", "Number", "Boolean",
2271
+ "Promise", "Error", "TypeError", "RangeError", "SyntaxError", "URIError",
2272
+ "EvalError", "ReferenceError",
2273
+ "console", "Math", "Date", "JSON", "RegExp", "Symbol", "BigInt",
2274
+ "Proxy", "Reflect", "globalThis", "Atomics", "SharedArrayBuffer",
2275
+ "ArrayBuffer", "DataView", "Int8Array", "Uint8Array", "Uint8ClampedArray",
2276
+ "Int16Array", "Uint16Array", "Int32Array", "Uint32Array",
2277
+ "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array",
2278
+ // Global functions
2279
+ "isNaN", "isFinite", "parseInt", "parseFloat", "eval",
2280
+ "encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent",
2281
+ // Browser/Node globals
2282
+ "setTimeout", "clearTimeout", "setInterval", "clearInterval",
2283
+ "queueMicrotask", "requestAnimationFrame", "cancelAnimationFrame",
2284
+ "fetch", "URL", "URLSearchParams", "AbortController", "AbortSignal",
2285
+ "EventTarget", "Event", "CustomEvent", "FormData", "Headers",
2286
+ "Request", "Response", "Blob", "File", "FileReader",
2287
+ "Worker", "SharedWorker", "WebSocket",
2288
+ "ReadableStream", "WritableStream", "TransformStream",
2289
+ "document", "window", "navigator", "location", "history", "screen",
2290
+ "performance", "crypto", "indexedDB", "localStorage", "sessionStorage",
2291
+ "confirm", "alert", "prompt",
2292
+ "process", "Buffer", "global", "require", "module", "exports", "__dirname", "__filename",
2293
+ // Special identifiers
2294
+ "undefined", "null", "NaN", "Infinity",
2295
+ "it", "this", "super", "arguments", "new", "class", "function",
2296
+ // TypeScript primitive type names
2297
+ "any", "unknown", "never", "void", "string", "number", "boolean", "bigint",
2298
+ "object", "symbol",
2299
+ // Jalvin type names that map to TS primitives (never need import)
2300
+ "String", "Boolean", "Any", "Nothing", "Unit", "Char", "Byte", "Short",
2301
+ "Float", "Double",
2302
+ ]);
2303
+ // ---------------------------------------------------------------------------
2304
+ // Public helper
2305
+ // ---------------------------------------------------------------------------
2306
+ function generate(program, opts, operatorOverloads, typeMap) {
2307
+ return new CodeGenerator(opts).generate(program, operatorOverloads, typeMap);
2308
+ }
1817
2309
  //# sourceMappingURL=codegen.js.map