@jalvin/compiler 2.0.43 → 2.0.45

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/TSX
3
+ // Jalvin Code Generator — AST → TypeScript
4
4
  //
5
5
  // Design principles:
6
- // • component declarations → React functional components (TSX)
6
+ // • component declarations → Functional components (standard TS)
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,7 +89,6 @@ class Writer {
89
89
  setSourceLine(line) { this.currentLine = line + 1; }
90
90
  }
91
91
  exports.DEFAULT_CODEGEN_OPTIONS = {
92
- jsx: false,
93
92
  module: "esm",
94
93
  emitTypes: false,
95
94
  runtimeImport: "@jalvin/runtime",
@@ -99,8 +98,10 @@ class CodeGenerator {
99
98
  opts;
100
99
  hasComponents = false;
101
100
  runtimeSymbolsNeeded = new Set();
102
- /** Names of component functions — used to detect component calls and emit them as JSX */
103
- componentNames = 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();
104
105
  /**
105
106
  * Param names for components resolved from imported local .jalvin files.
106
107
  * Maps component name → ordered list of param names.
@@ -108,6 +109,8 @@ class CodeGenerator {
108
109
  componentParamNames = new Map();
109
110
  /** True when the program contains `import @jalvin/ui.*` — used to detect UI primitive calls */
110
111
  hasUiStarImport = false;
112
+ /** Names of symbols explicitly imported from @jalvin/ui */
113
+ uiNamedImports = new Set();
111
114
  /** Operator overload resolutions from the type checker */
112
115
  operatorOverloadMap = new Map();
113
116
  /** Type map from the type checker */
@@ -131,6 +134,7 @@ class CodeGenerator {
131
134
  * Maps module specifier (e.g. "src/views") → sorted list of exported names.
132
135
  */
133
136
  localStarExports = new Map();
137
+ isInClass = false;
134
138
  constructor(opts = {}) {
135
139
  this.opts = { ...exports.DEFAULT_CODEGEN_OPTIONS, ...opts };
136
140
  }
@@ -141,34 +145,40 @@ class CodeGenerator {
141
145
  this.typeMap = typeMap;
142
146
  // Pre-scan for components (local declarations OR @jalvin/ui imports)
143
147
  this.hasComponents = program.declarations.some((d) => d.kind === "ComponentDecl" ||
144
- (d.kind === "ClassDecl" && d.body?.members.some((m) => m.kind === "ComponentDecl"))) || program.imports.some((imp) => imp.path[0] === "@jalvin" && imp.path[1] === "ui");
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);
145
150
  // Collect component names for component call detection
146
- this.componentNames = new Set();
151
+ this.userComponentNames = new Set();
152
+ this.allComponentLikeNames = new Set();
147
153
  this.componentParamNames = new Map();
148
154
  for (const decl of program.declarations) {
149
155
  if (decl.kind === "ComponentDecl") {
150
- this.componentNames.add(decl.name);
156
+ this.userComponentNames.add(decl.name);
157
+ this.allComponentLikeNames.add(decl.name);
151
158
  this.componentParamNames.set(decl.name, decl.params.map((p) => p.name));
152
159
  }
153
160
  if (decl.kind === "ClassDecl" && decl.body) {
154
161
  for (const m of decl.body.members) {
155
162
  if (m.kind === "ComponentDecl") {
156
- this.componentNames.add(m.name);
163
+ this.userComponentNames.add(m.name);
164
+ this.allComponentLikeNames.add(m.name);
157
165
  this.componentParamNames.set(m.name, m.params.map((p) => p.name));
158
166
  }
159
167
  }
160
168
  }
161
169
  }
162
170
  this.hasUiStarImport = false;
171
+ this.uiNamedImports = new Set();
163
172
  for (const imp of program.imports) {
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;
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
+ }
172
182
  }
173
183
  }
174
184
  // Resolve component fun symbols from locally imported .jalvin files
@@ -202,14 +212,42 @@ class CodeGenerator {
202
212
  return {
203
213
  code,
204
214
  lineMap: this.w.lineMap,
205
- isJsx: this.hasComponents,
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
+ }
206
237
  };
238
+ for (const decl of program.declarations)
239
+ walk(decl);
240
+ return found;
207
241
  }
208
242
  // ── Preamble & imports ─────────────────────────────────────────────────────
209
243
  buildPreamble() {
210
244
  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("// ─────────────────────────────────────────────────────────────────────────────");
211
249
  if (this.hasComponents) {
212
- lines.push('import React from "react";');
250
+ this.runtimeSymbolsNeeded.add("jalvinCreateElement");
213
251
  }
214
252
  // Emit compiler-injected runtime symbols, but only those NOT already covered
215
253
  // by a star-import's named-import expansion (to avoid duplicate imports).
@@ -226,19 +264,6 @@ class CodeGenerator {
226
264
  const sourceRoot = this.opts.sourceRoot ?? process.cwd();
227
265
  // Re-emit user imports as ES imports
228
266
  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"
242
267
  const isScoped = imp.path[0].startsWith("@");
243
268
  let moduleSpecifier;
244
269
  if (imp.star) {
@@ -253,35 +278,28 @@ class CodeGenerator {
253
278
  moduleSpecifier = moduleParts[0] + "/" + moduleParts.slice(1).join("/");
254
279
  }
255
280
  else {
256
- // Non-scoped import: prefer local-file convention unless this looks like
257
- // an installed npm package path (e.g. cubing/twisty.TwistyPlayer).
281
+ // Non-scoped import: prefer local-file convention
258
282
  const precedingParts = imp.path.slice(0, -1);
259
283
  const precedingRelPath = precedingParts.join("/");
260
284
  const fileExts = [".ts", ".tsx", ".jalvin"];
261
285
  const precedingIsFile = precedingParts.length > 0 && fileExts.some((ext) => fs.existsSync(nodePath.join(sourceRoot, precedingRelPath + ext)));
262
286
  const looksLikeNpmPackage = this.isLikelyNodeModuleImport(imp.path, sourceRoot);
263
287
  moduleSpecifier = precedingIsFile
264
- ? precedingRelPath // src.models.css.Css → "src/models/css"
288
+ ? precedingRelPath
265
289
  : looksLikeNpmPackage
266
- ? precedingRelPath // cubing.twisty.TwistyPlayer → "cubing/twisty"
267
- : imp.path.join("/"); // src.models.Rotation → "src/models/Rotation"
290
+ ? precedingRelPath
291
+ : imp.path.join("/");
268
292
  }
269
293
  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.
273
294
  const symbols = [...this.externalStarCandidates].sort();
274
295
  this.w.writeIndentedLine(`import { ${symbols.join(", ")} } from "${moduleSpecifier}";`);
275
296
  this.handledStarImportModules.add(moduleSpecifier);
276
297
  }
277
298
  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).
280
299
  const symbols = this.localStarExports.get(moduleSpecifier).slice().sort();
281
300
  this.w.writeIndentedLine(`import { ${symbols.join(", ")} } from "${moduleSpecifier}";`);
282
301
  }
283
302
  else if (imp.star) {
284
- // Fallback: namespace import (multiple scoped star imports or no candidates).
285
303
  this.w.writeIndentedLine(`import * as ${imp.path[imp.path.length - 1]} from "${moduleSpecifier}";`);
286
304
  }
287
305
  else if (imp.alias) {
@@ -295,11 +313,6 @@ class CodeGenerator {
295
313
  if (program.imports.length > 0)
296
314
  this.w.writeLine();
297
315
  }
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
- */
303
316
  isLikelyNodeModuleImport(pathParts, sourceRoot) {
304
317
  if (pathParts.length < 2)
305
318
  return false;
@@ -317,14 +330,12 @@ class CodeGenerator {
317
330
  return false;
318
331
  }
319
332
  // ── Top-level declarations ─────────────────────────────────────────────────
320
- /** Emit a @deprecated JSDoc block if the declaration has @Nuked. */
321
333
  emitAnnotations(mods) {
322
334
  for (const ann of mods.annotations) {
323
335
  if (ann.name === "Nuked") {
324
336
  const reason = ann.args ? ` ${ann.args.replace(/^["']|["']$/g, "")}` : "";
325
337
  this.w.writeIndentedLine(`/** @deprecated${reason} */`);
326
338
  }
327
- // Other annotations are silently ignored for now (pass-through model)
328
339
  }
329
340
  }
330
341
  emitTopLevelDecl(decl) {
@@ -379,7 +390,6 @@ class CodeGenerator {
379
390
  ? `: ${this.emitTypeRef(decl.returnType)}`
380
391
  : "";
381
392
  if (!decl.body) {
382
- // Abstract / interface method
383
393
  this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType};`);
384
394
  return;
385
395
  }
@@ -396,7 +406,6 @@ class CodeGenerator {
396
406
  this.w.writeIndentedLine(`}`);
397
407
  }
398
408
  else {
399
- // Expression body
400
409
  if (memberOf) {
401
410
  this.w.writeIndentedLine(`${vis}${asyncKw}${decl.name}${typeParams}(${params})${retType} {`);
402
411
  }
@@ -437,21 +446,19 @@ class CodeGenerator {
437
446
  this.w.writeIndentedLine(`}`);
438
447
  }
439
448
  }
440
- // ── component declarations → React function components ────────────────────
449
+ // ── component declarations → Functional components ────────────────────
441
450
  emitComponentDecl(decl) {
442
451
  this.emitAnnotations(decl.modifiers);
443
452
  this.hasComponents = true;
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.
447
- const propsParams = decl.params.filter((p) => p.name !== "children");
453
+ const vis = this.isInClass ? this.visibilityPrefix(decl.modifiers) : this.exportPrefix(decl.modifiers);
448
454
  const hasChildren = decl.params.some((p) => p.name === "children");
455
+ const propsParams = decl.params.filter((p) => p.name !== "children");
449
456
  // Props interface
450
- if (decl.params.length > 0) {
457
+ if (!this.isInClass && propsParams.length > 0) {
451
458
  this.w.writeIndentedLine(`interface ${decl.name}Props {`);
452
459
  this.w.pushIndent();
453
- for (const p of decl.params) {
454
- const optional = p.defaultValue || p.name === "children" ? "?" : "";
460
+ for (const p of propsParams) {
461
+ const optional = p.defaultValue ? "?" : "";
455
462
  const typeStr = this.opts.emitTypes ? this.emitTypeRef(p.type) : "any";
456
463
  this.w.writeIndentedLine(`readonly ${p.name}${optional}: ${typeStr};`);
457
464
  }
@@ -459,21 +466,32 @@ class CodeGenerator {
459
466
  this.w.writeIndentedLine(`}`);
460
467
  this.w.writeLine();
461
468
  }
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}) {`);
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} {`);
466
490
  this.w.pushIndent();
467
491
  this.emitComponentBlock(decl.body);
468
492
  this.w.popIndent();
469
493
  this.w.writeIndentedLine(`}`);
470
494
  }
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
- */
477
495
  emitComponentBlock(block) {
478
496
  const stmts = block.statements;
479
497
  for (let i = 0; i < stmts.length; i++) {
@@ -486,10 +504,8 @@ class CodeGenerator {
486
504
  }
487
505
  }
488
506
  }
489
- /** Emit a statement in tail position, adding implicit return where applicable. */
490
507
  emitTailStmt(stmt) {
491
508
  if (stmt.kind === "ExprStmt") {
492
- // Implicit return: last expression in a component block
493
509
  this.w.writeIndentedLine(`return ${this.emitExpr(stmt.expr)};`);
494
510
  }
495
511
  else if (stmt.kind === "IfStmt") {
@@ -502,7 +518,6 @@ class CodeGenerator {
502
518
  this.emitStmt(stmt);
503
519
  }
504
520
  }
505
- /** Emit a block in tail position, treating its last statement as a tail expression. */
506
521
  emitTailBlock(block) {
507
522
  const stmts = block.statements;
508
523
  for (let i = 0; i < stmts.length; i++) {
@@ -515,7 +530,6 @@ class CodeGenerator {
515
530
  }
516
531
  }
517
532
  }
518
- /** Like emitIfStmt but with tail-return applied recursively to each branch body. */
519
533
  emitIfStmtWithTailReturn(stmt) {
520
534
  this.w.writeIndentedLine(`if (${this.emitExpr(stmt.condition)}) {`);
521
535
  this.w.pushIndent();
@@ -538,7 +552,6 @@ class CodeGenerator {
538
552
  this.w.writeIndentedLine(`}`);
539
553
  }
540
554
  }
541
- /** Like emitWhenStmt but with tail-return applied recursively to each branch body. */
542
555
  emitWhenStmtWithTailReturn(stmt) {
543
556
  const subjectVar = stmt.subject ? "__when_subject__" : null;
544
557
  if (stmt.subject) {
@@ -568,6 +581,8 @@ class CodeGenerator {
568
581
  }
569
582
  // ── regular class ──────────────────────────────────────────────────────────
570
583
  emitClassDecl(decl) {
584
+ if (decl.modifiers.modifiers.includes("external"))
585
+ return;
571
586
  this.emitAnnotations(decl.modifiers);
572
587
  const vis = this.exportPrefix(decl.modifiers);
573
588
  const abstract = decl.modifiers.modifiers.includes("abstract") ? "abstract " : "";
@@ -575,15 +590,12 @@ class CodeGenerator {
575
590
  const superTypes = this.emitSuperTypesStr(decl.superTypes);
576
591
  this.w.writeIndentedLine(`${vis}${abstract}class ${decl.name}${typeParams}${superTypes} {`);
577
592
  this.w.pushIndent();
578
- // The first supertype's delegateArgs become the `super(args)` call inside the constructor.
579
593
  const superDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
580
594
  if (decl.primaryConstructor) {
581
595
  const initBlocks = decl.body?.members.filter((m) => m.kind === "InitBlock") ?? [];
582
596
  this.emitPrimaryConstructorProps(decl.primaryConstructor.params, initBlocks, superDelegateArgs);
583
597
  }
584
598
  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().
587
599
  const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
588
600
  this.w.writeIndentedLine(`constructor() {`);
589
601
  this.w.pushIndent();
@@ -606,24 +618,20 @@ class CodeGenerator {
606
618
  const props = decl.primaryConstructor.params;
607
619
  this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams}${superTypes} {`);
608
620
  this.w.pushIndent();
609
- // Discriminant for sealed class sub-types
610
621
  if (kindName) {
611
622
  this.w.writeIndentedLine(`readonly __kind = "${kindName}" as const;`);
612
623
  }
613
- // Constructor params → readonly properties
614
624
  for (const p of props) {
615
625
  const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
616
626
  this.w.writeIndentedLine(`readonly ${p.name}${t};`);
617
627
  }
618
628
  this.w.writeLine();
619
- // Constructor
620
629
  const ctorParams = props.map((p) => {
621
630
  const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
622
631
  return `${p.name}${t}`;
623
632
  }).join(", ");
624
633
  this.w.writeIndentedLine(`constructor(${ctorParams}) {`);
625
634
  this.w.pushIndent();
626
- // Emit super() with actual delegation args (or empty call if delegateArgs is []).
627
635
  const dataSuperDelegateArgs = decl.superTypes[0]?.delegateArgs ?? null;
628
636
  if (dataSuperDelegateArgs !== null) {
629
637
  const superArgsStr = dataSuperDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
@@ -635,7 +643,6 @@ class CodeGenerator {
635
643
  this.w.popIndent();
636
644
  this.w.writeIndentedLine(`}`);
637
645
  this.w.writeLine();
638
- // copy()
639
646
  const copyParams = props.map((p) => {
640
647
  const t = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
641
648
  return `${p.name}${t} = this.${p.name}`;
@@ -647,7 +654,6 @@ class CodeGenerator {
647
654
  this.w.popIndent();
648
655
  this.w.writeIndentedLine(`}`);
649
656
  this.w.writeLine();
650
- // equals()
651
657
  this.runtimeSymbolsNeeded.add("jalvinEquals");
652
658
  const eqChecks = props.map((p) => `jalvinEquals(this.${p.name}, other.${p.name})`).join(" && ") || "true";
653
659
  this.w.writeIndentedLine(`equals(other: unknown): boolean {`);
@@ -657,7 +663,6 @@ class CodeGenerator {
657
663
  this.w.popIndent();
658
664
  this.w.writeIndentedLine(`}`);
659
665
  this.w.writeLine();
660
- // toString()
661
666
  const toStringParts = props.map((p) => `${p.name}=\${this.${p.name}}`).join(", ");
662
667
  this.w.writeIndentedLine(`toString(): string {`);
663
668
  this.w.pushIndent();
@@ -665,7 +670,6 @@ class CodeGenerator {
665
670
  this.w.popIndent();
666
671
  this.w.writeIndentedLine(`}`);
667
672
  this.w.writeLine();
668
- // hashCode() — djb2
669
673
  this.w.writeIndentedLine(`hashCode(): number {`);
670
674
  this.w.pushIndent();
671
675
  this.w.writeIndentedLine(`let h = 17;`);
@@ -701,7 +705,6 @@ class CodeGenerator {
701
705
  }
702
706
  }
703
707
  }
704
- // Emit the abstract base class (only non-subtype members in the body)
705
708
  this.w.writeIndentedLine(`${vis}abstract class ${decl.name}${typeParams} {`);
706
709
  this.w.pushIndent();
707
710
  this.w.writeIndentedLine(`abstract readonly __kind: string;`);
@@ -714,7 +717,6 @@ class CodeGenerator {
714
717
  this.w.writeLine();
715
718
  if (subDecls.length === 0)
716
719
  return;
717
- // Emit non-object sub-declarations at module level (before namespace)
718
720
  for (const sub of subDecls) {
719
721
  if (sub.kind === "DataClassDecl") {
720
722
  this.emitDataClassDecl(sub, sub.name);
@@ -725,12 +727,10 @@ class CodeGenerator {
725
727
  this.w.writeLine();
726
728
  }
727
729
  }
728
- // Emit namespace merging so SealedClass.SubType is accessible
729
730
  this.w.writeIndentedLine(`${vis}namespace ${decl.name} {`);
730
731
  this.w.pushIndent();
731
732
  for (const sub of subDecls) {
732
733
  if (sub.kind === "ObjectDecl") {
733
- // Singleton object — emit inline in namespace with __kind discriminant
734
734
  const superStr = sub.superTypes.length > 0
735
735
  ? this.emitSuperTypesStr(sub.superTypes)
736
736
  : ` extends ${decl.name}`;
@@ -746,7 +746,6 @@ class CodeGenerator {
746
746
  this.w.writeIndentedLine(`export const ${sub.name} = new (class${superStr} { ${membersStr} })();`);
747
747
  }
748
748
  else {
749
- // DataClassDecl / ClassDecl — already emitted at module level, re-export
750
749
  this.w.writeIndentedLine(`export { ${sub.name} };`);
751
750
  }
752
751
  }
@@ -757,12 +756,8 @@ class CodeGenerator {
757
756
  emitEnumClassDecl(decl) {
758
757
  const vis = this.exportPrefix(decl.modifiers);
759
758
  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.
763
759
  this.w.writeIndentedLine(`${vis}class ${decl.name}${typeParams} {`);
764
760
  this.w.pushIndent();
765
- // Private constructor so entries are the only instances
766
761
  if (decl.primaryConstructor && decl.primaryConstructor.params.length > 0) {
767
762
  const ctorParams = decl.primaryConstructor.params
768
763
  .map((p) => {
@@ -777,7 +772,6 @@ class CodeGenerator {
777
772
  this.w.writeIndentedLine(`private constructor(readonly name: string, readonly ordinal: number) {}`);
778
773
  }
779
774
  this.w.writeLine();
780
- // Static entry instances
781
775
  for (let i = 0; i < decl.entries.length; i++) {
782
776
  const entry = decl.entries[i];
783
777
  const args = entry.args.length > 0
@@ -787,7 +781,6 @@ class CodeGenerator {
787
781
  }
788
782
  if (decl.entries.length > 0) {
789
783
  this.w.writeLine();
790
- // values() helper
791
784
  const entryList = decl.entries.map((e) => `${decl.name}.${e.name}`).join(", ");
792
785
  this.w.writeIndentedLine(`static values(): ${decl.name}[] { return [${entryList}]; }`);
793
786
  this.w.writeIndentedLine(`static valueOf(name: string): ${decl.name} {`);
@@ -805,14 +798,12 @@ class CodeGenerator {
805
798
  this.w.popIndent();
806
799
  this.w.writeIndentedLine(`}`);
807
800
  }
808
- // ── destructuring declaration — val (a, b) = expr ─────────────────────────
809
801
  emitDestructuringDecl(decl, _memberOf) {
810
802
  const kw = decl.mutable ? "let" : "const";
811
803
  const names = decl.names.map((n) => n ?? "_").join(", ");
812
804
  const init = this.emitExpr(decl.initializer);
813
805
  this.w.writeIndentedLine(`${kw} [${names}] = ${init};`);
814
806
  }
815
- // ── interface ──────────────────────────────────────────────────────────────
816
807
  emitInterfaceDecl(decl) {
817
808
  this.emitAnnotations(decl.modifiers);
818
809
  const vis = this.exportPrefix(decl.modifiers);
@@ -830,12 +821,9 @@ class CodeGenerator {
830
821
  this.w.popIndent();
831
822
  this.w.writeIndentedLine(`}`);
832
823
  }
833
- // ── object declaration → singleton ────────────────────────────────────────
834
824
  emitObjectDecl(decl) {
835
- if (!decl.name) {
836
- // Anonymous object — emitted inline
825
+ if (!decl.name)
837
826
  return;
838
- }
839
827
  this.emitAnnotations(decl.modifiers);
840
828
  const vis = this.exportPrefix(decl.modifiers);
841
829
  const superTypes = this.emitSuperTypesStr(decl.superTypes);
@@ -845,16 +833,11 @@ class CodeGenerator {
845
833
  this.w.popIndent();
846
834
  this.w.writeIndentedLine(`})();`);
847
835
  }
848
- // ── type alias ─────────────────────────────────────────────────────────────
849
836
  emitTypeAliasDecl(decl) {
850
837
  const vis = this.exportPrefix(decl.modifiers);
851
838
  const typeParams = this.emitTypeParamsStr(decl.typeParams);
852
839
  this.w.writeIndentedLine(`${vis}type ${decl.name}${typeParams} = ${this.emitTypeRef(decl.type)};`);
853
840
  }
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`.
858
841
  emitExtensionFunDecl(decl) {
859
842
  const isSuspend = AST.isSuspend(decl.modifiers);
860
843
  const asyncKw = isSuspend ? "async " : "";
@@ -865,9 +848,6 @@ class CodeGenerator {
865
848
  ? `: ${this.emitTypeRef(decl.returnType)}`
866
849
  : "";
867
850
  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.
871
851
  this.w.writeIndentedLine(`${asyncKw}function ${fnName}${typeParams}($receiver: ${receiverType}${params ? ", " + params : ""})${retType} {`);
872
852
  this.w.pushIndent();
873
853
  if (decl.body.kind === "Block") {
@@ -886,14 +866,12 @@ class CodeGenerator {
886
866
  const simpleReceiverName = this.receiverClassName(decl.receiver);
887
867
  if (simpleReceiverName) {
888
868
  if (PRIMITIVE_TYPES.has(simpleReceiverName)) {
889
- // Register for call-site rewriting — cannot patch primitive prototypes
890
869
  if (!this.primitiveExtensions.has(simpleReceiverName)) {
891
870
  this.primitiveExtensions.set(simpleReceiverName, new Map());
892
871
  }
893
872
  this.primitiveExtensions.get(simpleReceiverName).set(decl.name, fnName);
894
873
  }
895
874
  else {
896
- // Class types — prototype monkey-patch for dot-call syntax
897
875
  this.w.writeIndentedLine(`// Extension: ${receiverType}.${decl.name}`);
898
876
  this.w.writeIndentedLine(`(${simpleReceiverName}.prototype as any).${decl.name} = function(this: ${receiverType}, ...args: unknown[]) { return (${fnName} as Function)(this, ...args); };`);
899
877
  }
@@ -906,11 +884,6 @@ class CodeGenerator {
906
884
  return ref.base.name[ref.base.name.length - 1] ?? null;
907
885
  return null;
908
886
  }
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
- */
914
887
  jTypeToReceiverName(type) {
915
888
  const tagToReceiverName = {
916
889
  string: "String",
@@ -930,19 +903,16 @@ class CodeGenerator {
930
903
  m.name === "invoke" &&
931
904
  m.modifiers.modifiers.includes("operator"));
932
905
  }
933
- // ── property declaration ───────────────────────────────────────────────────
934
906
  emitPropertyDecl(decl, member, isLocal = false) {
935
907
  this.emitAnnotations(decl.modifiers);
936
908
  const vis = member ? this.visibilityPrefix(decl.modifiers) : isLocal ? "" : this.exportPrefix(decl.modifiers);
937
909
  const isConst = decl.modifiers.modifiers.includes("const");
938
910
  const isLateinit = decl.modifiers.modifiers.includes("lateinit");
939
- // `const val` → `const` at module level; inside classes treated as `static readonly`
940
911
  const kw = member
941
912
  ? (decl.mutable ? "" : "readonly ")
942
913
  : (isConst ? "const " : decl.mutable ? "let " : "const ");
943
914
  const type = decl.type && this.opts.emitTypes ? `: ${this.emitTypeRef(decl.type)}` : "";
944
915
  if (decl.delegate) {
945
- // Delegated property — wrap in a getter/setter pair using the delegate
946
916
  this.runtimeSymbolsNeeded.add("delegate");
947
917
  const delegateExpr = this.emitExpr(decl.delegate);
948
918
  if (member) {
@@ -952,28 +922,22 @@ class CodeGenerator {
952
922
  }
953
923
  }
954
924
  else {
955
- // Non-member delegate: resolve via delegate().getValue() so the identifier
956
- // holds the delegated value, not the raw delegate wrapper object.
957
925
  this.w.writeIndentedLine(`${kw}${decl.name}${type} = delegate(${delegateExpr}, "${decl.name}", null).getValue();`);
958
926
  }
959
927
  return;
960
928
  }
961
- const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` : (isLateinit ? "" : "");
929
+ const init = decl.initializer ? ` = ${this.emitExpr(decl.initializer)}` : "";
962
930
  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.
965
931
  const modStr = isConst ? "static readonly " : decl.mutable ? "" : "readonly ";
966
932
  this.w.writeIndentedLine(`${vis}${modStr}${decl.name}${type}${init};`);
967
933
  }
968
934
  else if (!member) {
969
935
  this.w.writeIndentedLine(`${vis}${kw}${decl.name}${type}${init};`);
970
936
  }
971
- if (decl.getter) {
937
+ if (decl.getter)
972
938
  this.emitPropertyAccessor("get", decl.name, decl.getter, member, type);
973
- }
974
- if (decl.setter) {
939
+ if (decl.setter)
975
940
  this.emitPropertyAccessor("set", decl.name, decl.setter, member, type);
976
- }
977
941
  }
978
942
  emitPropertyAccessor(kind, name, acc, member, type) {
979
943
  const vis = this.visibilityPrefix(acc.modifiers);
@@ -993,14 +957,19 @@ class CodeGenerator {
993
957
  this.w.popIndent();
994
958
  this.w.writeIndentedLine(`}`);
995
959
  }
996
- // ── class body ─────────────────────────────────────────────────────────────
997
960
  emitClassBody(body) {
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();
961
+ const old = this.isInClass;
962
+ this.isInClass = true;
963
+ try {
964
+ for (const member of body.members) {
965
+ if (member.kind === "InitBlock")
966
+ continue;
967
+ this.emitClassMember(member);
968
+ this.w.writeLine();
969
+ }
970
+ }
971
+ finally {
972
+ this.isInClass = old;
1004
973
  }
1005
974
  }
1006
975
  emitClassMember(member) {
@@ -1032,7 +1001,6 @@ class CodeGenerator {
1032
1001
  case "CompanionObject":
1033
1002
  this.emitCompanionObject(member);
1034
1003
  break;
1035
- case "InitBlock": /* handled in constructor */ break;
1036
1004
  case "SecondaryConstructor":
1037
1005
  this.emitSecondaryConstructor(member);
1038
1006
  break;
@@ -1049,16 +1017,13 @@ class CodeGenerator {
1049
1017
  this.w.writeIndentedLine(`${ro}${p.name}${type};`);
1050
1018
  }
1051
1019
  }
1052
- // Constructor
1053
1020
  const ctorParams = params.map((p) => {
1054
- const ro = p.propertyKind === "val" ? "readonly " : p.propertyKind === "var" ? "" : "";
1055
1021
  const type = this.opts.emitTypes ? `: ${this.emitTypeRef(p.type)}` : "";
1056
1022
  const def = p.defaultValue ? ` = ${this.emitExpr(p.defaultValue)}` : "";
1057
1023
  return `${p.name}${type}${def}`;
1058
1024
  });
1059
1025
  this.w.writeIndentedLine(`constructor(${ctorParams.join(", ")}) {`);
1060
1026
  this.w.pushIndent();
1061
- // super() call must be the first statement if there is a supertype.
1062
1027
  if (superDelegateArgs !== null && superDelegateArgs !== undefined) {
1063
1028
  const superArgsStr = superDelegateArgs.map((a) => this.emitExpr(a.value)).join(", ");
1064
1029
  this.w.writeIndentedLine(`super(${superArgsStr});`);
@@ -1068,17 +1033,13 @@ class CodeGenerator {
1068
1033
  this.w.writeIndentedLine(`this.${p.name} = ${p.name};`);
1069
1034
  }
1070
1035
  }
1071
- // Emit init{} blocks inside the constructor, in declaration order
1072
- for (const ib of (initBlocks ?? [])) {
1036
+ for (const ib of (initBlocks ?? []))
1073
1037
  this.emitBlock(ib.body);
1074
- }
1075
1038
  this.w.popIndent();
1076
1039
  this.w.writeIndentedLine(`}`);
1077
1040
  this.w.writeLine();
1078
1041
  }
1079
1042
  emitCompanionObject(co) {
1080
- // Emit companion object members as `static` members of the outer class so that
1081
- // `MyClass.factoryMethod()` works directly (standard semantics).
1082
1043
  for (const member of co.body.members) {
1083
1044
  if (member.kind === "FunDecl") {
1084
1045
  const vis = this.visibilityPrefix(member.modifiers);
@@ -1123,11 +1084,9 @@ class CodeGenerator {
1123
1084
  this.w.popIndent();
1124
1085
  this.w.writeIndentedLine(`}`);
1125
1086
  }
1126
- // ── Statements ─────────────────────────────────────────────────────────────
1127
1087
  emitBlock(block) {
1128
- for (const stmt of block.statements) {
1088
+ for (const stmt of block.statements)
1129
1089
  this.emitStmt(stmt);
1130
- }
1131
1090
  }
1132
1091
  emitStmt(stmt) {
1133
1092
  switch (stmt.kind) {
@@ -1216,8 +1175,6 @@ class CodeGenerator {
1216
1175
  }
1217
1176
  }
1218
1177
  emitWhenStmt(stmt) {
1219
- // when(subject) { is Foo -> ... else -> ... }
1220
- // Compiles to a series of if/else-if chains
1221
1178
  const subjectVar = stmt.subject ? "__when_subject__" : null;
1222
1179
  if (stmt.subject) {
1223
1180
  const binding = stmt.subject.binding ?? subjectVar;
@@ -1234,12 +1191,10 @@ class CodeGenerator {
1234
1191
  }
1235
1192
  first = false;
1236
1193
  this.w.pushIndent();
1237
- if (branch.body.kind === "Block") {
1194
+ if (branch.body.kind === "Block")
1238
1195
  this.emitBlock(branch.body);
1239
- }
1240
- else {
1196
+ else
1241
1197
  this.w.writeIndentedLine(`${this.emitExpr(branch.body)};`);
1242
- }
1243
1198
  this.w.popIndent();
1244
1199
  }
1245
1200
  this.w.writeIndentedLine(`}`);
@@ -1247,8 +1202,6 @@ class CodeGenerator {
1247
1202
  emitWhenCondition(cond, subject) {
1248
1203
  switch (cond.kind) {
1249
1204
  case "WhenIsCondition": {
1250
- // Qualified name (e.g. UiState.Loading) → use __kind discriminant
1251
- // Single name (e.g. BibiError) → use instanceof
1252
1205
  const isQualified = cond.type.kind === "SimpleTypeRef" && cond.type.name.length > 1;
1253
1206
  const check = isQualified
1254
1207
  ? `(${subject} as any).__kind === "${cond.type.kind === "SimpleTypeRef" ? cond.type.name[cond.type.name.length - 1] : ""}"`
@@ -1278,7 +1231,6 @@ class CodeGenerator {
1278
1231
  }
1279
1232
  else {
1280
1233
  const { key, value } = stmt.binding;
1281
- // Use .entries() for JS Map (Map<K,V>), Object.entries() for plain objects
1282
1234
  const iterableType = this.typeMap.get(stmt.iterable);
1283
1235
  const isMap = iterableType?.tag === "class" && iterableType.name === "Map";
1284
1236
  const entries = isMap ? `${iter}.entries()` : `Object.entries(${iter})`;
@@ -1297,9 +1249,6 @@ class CodeGenerator {
1297
1249
  for (const c of stmt.catches) {
1298
1250
  this.w.writeIndentedLine(`} catch (${c.name}: unknown) {`);
1299
1251
  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).
1303
1252
  this.w.writeIndentedLine(`if (!(${c.name} instanceof ${this.emitTypeRef(c.type)})) throw ${c.name};`);
1304
1253
  this.emitBlock(c.body);
1305
1254
  this.w.popIndent();
@@ -1312,7 +1261,6 @@ class CodeGenerator {
1312
1261
  }
1313
1262
  this.w.writeIndentedLine(`}`);
1314
1263
  }
1315
- // ── Expressions ────────────────────────────────────────────────────────────
1316
1264
  emitExpr(expr) {
1317
1265
  switch (expr.kind) {
1318
1266
  case "IntLiteralExpr": return String(expr.value);
@@ -1322,17 +1270,12 @@ class CodeGenerator {
1322
1270
  case "BooleanLiteralExpr": return String(expr.value);
1323
1271
  case "NullLiteralExpr": return "null";
1324
1272
  case "StringLiteralExpr":
1325
- return expr.raw
1326
- ? `\`${expr.value}\``
1327
- : JSON.stringify(expr.value);
1273
+ return expr.raw ? `\`${expr.value}\`` : JSON.stringify(expr.value);
1328
1274
  case "StringTemplateExpr":
1329
1275
  return this.emitStringTemplate(expr);
1330
1276
  case "NameExpr":
1331
- // Ensure Int/Long companion objects are imported from the runtime
1332
- if (expr.name === "Int" || expr.name === "Long") {
1277
+ if (expr.name === "Int" || expr.name === "Long")
1333
1278
  this.runtimeSymbolsNeeded.add(expr.name);
1334
- }
1335
- // Unit singleton emits as undefined (void semantics)
1336
1279
  if (expr.name === "Unit")
1337
1280
  return "undefined";
1338
1281
  return expr.name;
@@ -1348,34 +1291,23 @@ class CodeGenerator {
1348
1291
  if (opMethod) {
1349
1292
  const l = this.emitExpr(expr.left);
1350
1293
  const r = this.emitExpr(expr.right);
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") {
1294
+ if (opMethod === "compareTo")
1295
+ return `(${l}.compareTo(${r}) ${expr.op} 0)`;
1296
+ if (opMethod === "contains")
1358
1297
  return `${r}.contains(${l})`;
1359
- }
1360
- if (opMethod === "!contains") {
1298
+ if (opMethod === "!contains")
1361
1299
  return `!${r}.contains(${l})`;
1362
- }
1363
- // Generic operator overload: emit as method call `left.method(right)`
1364
1300
  return `${l}.${opMethod}(${r})`;
1365
1301
  }
1366
- // `==` → structural equality, `!=` → negation
1367
1302
  if (expr.op === "==" || expr.op === "!=") {
1368
1303
  this.runtimeSymbolsNeeded.add("jalvinEquals");
1369
1304
  const eq = `jalvinEquals(${this.emitExpr(expr.left)}, ${this.emitExpr(expr.right)})`;
1370
1305
  return expr.op === "==" ? eq : `!${eq}`;
1371
1306
  }
1372
- // `in` / `!in` without a user-defined operator: use JS `in` / array includes
1373
- if (expr.op === "in") {
1307
+ if (expr.op === "in")
1374
1308
  return `${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)})`;
1375
- }
1376
- if (expr.op === "!in") {
1309
+ if (expr.op === "!in")
1377
1310
  return `!(${this.emitExpr(expr.right)}.includes?.(${this.emitExpr(expr.left)}) ?? (${this.emitExpr(expr.left)} in ${this.emitExpr(expr.right)}))`;
1378
- }
1379
1311
  return `(${this.emitExpr(expr.left)} ${this.binaryOpStr(expr.op)} ${this.emitExpr(expr.right)})`;
1380
1312
  }
1381
1313
  case "AssignExpr":
@@ -1386,124 +1318,81 @@ class CodeGenerator {
1386
1318
  return expr.prefix
1387
1319
  ? `${expr.op}${this.emitExpr(expr.target)}`
1388
1320
  : `${this.emitExpr(expr.target)}${expr.op}`;
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)}]`;
1321
+ case "MemberExpr": return `${this.emitExpr(expr.target)}.${expr.member}`;
1322
+ case "SafeMemberExpr": return `${this.emitExpr(expr.target)}?.${expr.member}`;
1323
+ case "IndexExpr": return `${this.emitExpr(expr.target)}[${this.emitExpr(expr.index)}]`;
1395
1324
  case "NotNullExpr":
1396
- // Runtime check
1397
1325
  this.runtimeSymbolsNeeded.add("notNull");
1398
1326
  return `notNull(${this.emitExpr(expr.expr)})`;
1399
1327
  case "SafeCallExpr": {
1400
- // x?.() — safe invocation of a nullable function value
1401
1328
  const callee = this.emitExpr(expr.callee);
1402
- const restArgs = [];
1403
- for (const a of expr.args) {
1404
- restArgs.push(a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1405
- }
1329
+ const restArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1406
1330
  if (expr.trailingLambda)
1407
1331
  restArgs.push(this.emitLambdaExpr(expr.trailingLambda));
1408
1332
  return `${callee}?.(${restArgs.join(", ")})`;
1409
1333
  }
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": {
1334
+ case "ElvisExpr": return `(${this.emitExpr(expr.left)} ?? ${this.emitExpr(expr.right)})`;
1335
+ case "CallExpr": return this.emitCallExpr(expr);
1336
+ case "LambdaExpr": return this.emitLambdaExpr(expr);
1337
+ case "IfExpr": return this.emitIfExpr(expr);
1338
+ case "WhenExpr": return this.emitWhenExpr(expr);
1339
+ case "TryCatchExpr": return this.emitTryCatchExpr(expr);
1340
+ case "TypeCheckExpr": return `${expr.negated ? "!(" : ""}${this.emitExpr(expr.expr)} instanceof ${this.emitTypeRef(expr.type)}${expr.negated ? ")" : ""}`;
1341
+ case "TypeCastExpr": return `(${this.emitExpr(expr.expr)} as unknown as ${this.emitTypeRef(expr.type)})`;
1342
+ case "SafeCastExpr":
1430
1343
  this.runtimeSymbolsNeeded.add("safeCast");
1431
1344
  return `safeCast(${this.emitExpr(expr.expr)}, ${this.emitTypeRef(expr.type)})`;
1432
- }
1433
- case "RangeExpr": {
1345
+ case "RangeExpr":
1434
1346
  this.runtimeSymbolsNeeded.add("range");
1435
1347
  return `range(${this.emitExpr(expr.from)}, ${this.emitExpr(expr.to)}, ${expr.inclusive})`;
1436
- }
1437
1348
  case "LaunchExpr": {
1438
- // fire-and-forget → void IIFE
1439
1349
  const stmts = this.captureBlock(expr.body);
1440
1350
  return `(async () => { ${stmts} })()`;
1441
1351
  }
1442
1352
  case "AsyncExpr": {
1443
- // returns Promise<T>
1444
1353
  const stmts = this.captureBlock(expr.body);
1445
1354
  return `(async () => { ${stmts} })()`;
1446
1355
  }
1447
- case "CollectionLiteralExpr":
1448
- return this.emitCollectionLiteral(expr);
1449
- case "ObjectExpr":
1450
- return this.emitObjectExpr(expr);
1356
+ case "CollectionLiteralExpr": return this.emitCollectionLiteral(expr);
1357
+ case "ObjectExpr": return this.emitObjectExpr(expr);
1451
1358
  case "ReturnExpr": {
1452
1359
  const val = expr.value ? ` ${this.emitExpr(expr.value)}` : "";
1453
1360
  return `((() => { return${val}; })())`;
1454
1361
  }
1455
1362
  case "BreakExpr": return "undefined /* break */";
1456
1363
  case "ContinueExpr": return "undefined /* continue */";
1457
- case "JsxElement":
1458
- return this.emitJsxElement(expr);
1459
- default:
1460
- return "undefined";
1364
+ case "JsxElement": return this.emitJsxElement(expr);
1365
+ default: return "undefined";
1461
1366
  }
1462
1367
  }
1463
- // ── JSX ──────────────────────────────────────────────────────────────────
1464
1368
  emitJsxElement(expr) {
1369
+ this.runtimeSymbolsNeeded.add("jalvinCreateElement");
1465
1370
  const attrsStr = expr.attrs.length > 0
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}>`;
1371
+ ? "{ " + expr.attrs.map((a) => this.emitJsxAttr(a)).join(", ") + " }"
1372
+ : "{}";
1373
+ const childrenArr = expr.children.length > 0
1374
+ ? "[" + expr.children.map((c) => this.emitJsxChild(c)).join(", ") + "]"
1375
+ : "[]";
1376
+ return `jalvinCreateElement(${JSON.stringify(expr.tag)}, ${attrsStr}, ${childrenArr})`;
1473
1377
  }
1474
1378
  emitJsxAttr(attr) {
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)}}`;
1379
+ const name = attr.name === "class" ? "className" : attr.name === "for" ? "htmlFor" : attr.name;
1380
+ const val = attr.value === null ? "true" : typeof attr.value === "string" ? JSON.stringify(attr.value) : this.emitExpr(attr.value);
1381
+ return `${name}: ${val}`;
1484
1382
  }
1485
1383
  emitJsxChild(child) {
1486
1384
  switch (child.kind) {
1487
1385
  case "JsxElement": return this.emitJsxElement(child);
1488
- case "JsxExprChild": return `{${this.emitExpr(child.expr)}}`;
1489
- case "JsxTextChild": return child.text;
1386
+ case "JsxExprChild": return this.emitExpr(child.expr);
1387
+ case "JsxTextChild": return `document.createTextNode(${JSON.stringify(child.text)})`;
1490
1388
  }
1491
1389
  }
1492
1390
  emitStringTemplate(expr) {
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("");
1391
+ const inner = expr.parts.map((p) => p.kind === "LiteralPart" ? p.value.replace(/`/g, "\\`").replace(/\\/g, "\\\\") : `\${${this.emitExpr(p.expr)}}`).join("");
1499
1392
  return `\`${inner}\``;
1500
1393
  }
1501
1394
  emitCallExpr(expr) {
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")) {
1395
+ if (expr.callee.kind === "MemberExpr" && (expr.callee.member === "downTo" || expr.callee.member === "until" || expr.callee.member === "step")) {
1507
1396
  const obj = this.emitExpr(expr.callee.target);
1508
1397
  const arg = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "0";
1509
1398
  if (expr.callee.member === "downTo") {
@@ -1519,51 +1408,39 @@ class CodeGenerator {
1519
1408
  return `step(${obj}, ${arg})`;
1520
1409
  }
1521
1410
  }
1522
- // Rewrite calls on primitive-receiver extension functions.
1523
- // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1524
1411
  if (expr.callee.kind === "MemberExpr") {
1525
1412
  const scopeMember = expr.callee.member;
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
- }
1413
+ const receiver = this.emitExpr(expr.callee.target);
1414
+ const lambda = expr.trailingLambda ?? (expr.args.length === 1 && expr.args[0].value.kind === "LambdaExpr" ? expr.args[0].value : null);
1415
+ if (lambda) {
1416
+ if (scopeMember === "let") {
1417
+ this.runtimeSymbolsNeeded.add("let_");
1418
+ return `let_(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1419
+ }
1420
+ if (scopeMember === "also") {
1421
+ this.runtimeSymbolsNeeded.add("also");
1422
+ return `also(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1423
+ }
1424
+ if (scopeMember === "apply") {
1425
+ this.runtimeSymbolsNeeded.add("apply");
1426
+ const body = this.captureBlockStatements(lambda.body);
1427
+ return `apply(${receiver}, function(this: any) { ${body} })`;
1428
+ }
1429
+ if (scopeMember === "run") {
1430
+ this.runtimeSymbolsNeeded.add("run_");
1431
+ const body = this.captureBlockStatements(lambda.body);
1432
+ return `run_(${receiver}, function(this: any) { ${body} })`;
1433
+ }
1434
+ if (scopeMember === "takeIf") {
1435
+ this.runtimeSymbolsNeeded.add("takeIf");
1436
+ return `takeIf(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1437
+ }
1438
+ if (scopeMember === "takeUnless") {
1439
+ this.runtimeSymbolsNeeded.add("takeUnless");
1440
+ return `takeUnless(${receiver}, ${this.emitLambdaExpr(lambda)})`;
1561
1441
  }
1562
1442
  }
1563
- // `with(x) { ... }` is a free function handled via seedBuiltins; but rewrite if emitted as member
1564
1443
  }
1565
- // Rewrite calls on primitive-receiver extension functions.
1566
- // `str.truncate(30)` where `String.truncate` is a user extension → `__ext_string_truncate(str, 30)`
1567
1444
  if (expr.callee.kind === "MemberExpr") {
1568
1445
  const receiverType = this.typeMap.get(expr.callee.target);
1569
1446
  const memberName = expr.callee.member;
@@ -1583,105 +1460,69 @@ class CodeGenerator {
1583
1460
  }
1584
1461
  }
1585
1462
  const callee = this.emitExpr(expr.callee);
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
+ const typeArgs = expr.typeArgs.length > 0 ? `<${expr.typeArgs.map((t) => t.star ? "*" : this.emitTypeRef(t.type)).join(", ")}>` : "";
1464
+ if (expr.callee.kind === "NameExpr" && expr.callee.name === "with" && (expr.trailingLambda || (expr.args.length === 2 && expr.args[1].value.kind === "LambdaExpr"))) {
1592
1465
  const obj = expr.args.length > 0 ? this.emitExpr(expr.args[0].value) : "undefined";
1593
- const lambda = expr.trailingLambda ??
1594
- expr.args[1].value;
1466
+ const lambda = expr.trailingLambda ?? expr.args[1].value;
1595
1467
  this.runtimeSymbolsNeeded.add("with_");
1596
1468
  const body = this.captureBlockStatements(lambda.body);
1597
1469
  return `with_(${obj}, function(this: any) { ${body} })`;
1598
1470
  }
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) {
1471
+ if (expr.callee.kind === "NameExpr" && expr.callee.name === "run" && expr.args.length === 0 && expr.trailingLambda) {
1602
1472
  return `(${this.emitLambdaExpr(expr.trailingLambda)})()`;
1603
1473
  }
1604
- // Handle named arguments by reordering them to match positional parameters
1605
1474
  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.
1612
1475
  const isKnownPositionalFunc = calleeType?.tag === "func" && calleeType.paramNames !== undefined;
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)))) {
1615
- return this.emitComponentCall(expr);
1476
+ let isComponentLike = false;
1477
+ if (expr.callee.kind === "NameExpr") {
1478
+ isComponentLike = this.allComponentLikeNames.has(expr.callee.name) || (this.hasUiStarImport && (!calleeType || calleeType.tag === "unknown") && /^[A-Z]/.test(expr.callee.name));
1479
+ }
1480
+ else if (expr.callee.kind === "MemberExpr" || expr.callee.kind === "SafeMemberExpr") {
1481
+ isComponentLike = this.allComponentLikeNames.has(expr.callee.member);
1616
1482
  }
1483
+ if (!isKnownPositionalFunc && isComponentLike)
1484
+ return this.emitComponentCall(expr);
1617
1485
  let finalArgs = [];
1618
1486
  if (calleeType && calleeType.tag === "func" && calleeType.paramNames) {
1619
1487
  const paramNames = calleeType.paramNames;
1620
1488
  const argsByName = new Map();
1621
1489
  const positionalArgs = [];
1622
1490
  for (const arg of expr.args) {
1623
- if (arg.name) {
1491
+ if (arg.name)
1624
1492
  argsByName.set(arg.name, arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1625
- }
1626
- else {
1493
+ else
1627
1494
  positionalArgs.push(arg.spread ? `...${this.emitExpr(arg.value)}` : this.emitExpr(arg.value));
1628
- }
1629
1495
  }
1630
- // Reconstruct args based on paramNames
1631
1496
  for (let i = 0; i < paramNames.length; i++) {
1632
1497
  const name = paramNames[i];
1633
- if (argsByName.has(name)) {
1498
+ if (argsByName.has(name))
1634
1499
  finalArgs.push(argsByName.get(name));
1635
- }
1636
- else if (i < positionalArgs.length) {
1500
+ else if (i < positionalArgs.length)
1637
1501
  finalArgs.push(positionalArgs[i]);
1638
- }
1639
- else {
1640
- // Missing argument - JS default value logic will handle it if we pass undefined
1502
+ else
1641
1503
  finalArgs.push("undefined");
1642
- }
1643
1504
  }
1644
- // Handle trailing positional args if any
1645
- if (positionalArgs.length > paramNames.length) {
1505
+ if (positionalArgs.length > paramNames.length)
1646
1506
  finalArgs.push(...positionalArgs.slice(paramNames.length));
1647
- }
1648
1507
  }
1649
1508
  else {
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
- });
1509
+ finalArgs = expr.args.map((a) => a.spread ? `...${this.emitExpr(a.value)}` : this.emitExpr(a.value));
1654
1510
  }
1655
- if (expr.trailingLambda) {
1511
+ if (expr.trailingLambda)
1656
1512
  finalArgs.push(this.emitLambdaExpr(expr.trailingLambda));
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)) {
1513
+ if (calleeType && calleeType.tag === "class" && calleeType.decl && this.classHasInvokeOperator(calleeType.decl)) {
1663
1514
  return `${callee}.invoke(${finalArgs.join(", ")})`;
1664
1515
  }
1665
- // Constructor calls: class names start with uppercase by convention
1666
- if (this.isConstructorCall(expr.callee)) {
1516
+ if (this.isConstructorCall(expr.callee))
1667
1517
  return `new ${callee}${typeArgs}(${finalArgs.join(", ")})`;
1668
- }
1669
1518
  return `${callee}${typeArgs}(${finalArgs.join(", ")})`;
1670
1519
  }
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
- */
1677
1520
  resolveLocalComponentImports(program, sourceRoot) {
1678
1521
  this.localStarExports.clear();
1679
1522
  for (const imp of program.imports) {
1680
- // Skip scoped packages (@org/pkg)
1681
1523
  if (imp.path[0]?.startsWith("@"))
1682
1524
  continue;
1683
1525
  if (imp.star) {
1684
- // Local wildcard import: import src.module.* — load all exports from src/module.jalvin
1685
1526
  if (imp.path.length < 1)
1686
1527
  continue;
1687
1528
  const candidatePath = nodePath.join(sourceRoot, imp.path.join("/") + ".jalvin");
@@ -1704,21 +1545,27 @@ class CodeGenerator {
1704
1545
  continue;
1705
1546
  exportedNames.push(name);
1706
1547
  if (d.kind === "ComponentDecl") {
1707
- this.componentNames.add(name);
1548
+ this.userComponentNames.add(name);
1549
+ this.allComponentLikeNames.add(name);
1708
1550
  this.componentParamNames.set(name, d.params.map((p) => p.name));
1709
1551
  this.hasComponents = true;
1710
1552
  }
1553
+ if (d.kind === "ClassDecl" && d.body) {
1554
+ for (const m of d.body.members) {
1555
+ if (m.kind === "ComponentDecl") {
1556
+ this.allComponentLikeNames.add(m.name);
1557
+ this.componentParamNames.set(m.name, m.params.map((p) => p.name));
1558
+ this.hasComponents = true;
1559
+ }
1560
+ }
1561
+ }
1711
1562
  }
1712
- if (exportedNames.length > 0) {
1563
+ if (exportedNames.length > 0)
1713
1564
  this.localStarExports.set(moduleSpecifier, exportedNames);
1714
- }
1715
- }
1716
- catch {
1717
- // Silently skip if the file cannot be read or parsed
1718
1565
  }
1566
+ catch { }
1719
1567
  continue;
1720
1568
  }
1721
- // Named import: last path segment is the symbol, preceding segments form the file path
1722
1569
  if (imp.path.length < 2)
1723
1570
  continue;
1724
1571
  const rawSymbolName = imp.path[imp.path.length - 1];
@@ -1736,574 +1583,232 @@ class CodeGenerator {
1736
1583
  const ast = (0, parser_js_1.parse)(tokens, candidatePath, diag, source);
1737
1584
  if (diag.hasErrors)
1738
1585
  continue;
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));
1586
+ const decl = ast.declarations.find((d) => ((d.kind === "ComponentDecl" || d.kind === "ClassDecl") && d.name === rawSymbolName));
1587
+ if (decl?.kind === "ComponentDecl") {
1588
+ this.userComponentNames.add(symbolName);
1589
+ this.allComponentLikeNames.add(symbolName);
1590
+ this.componentParamNames.set(symbolName, decl.params.map((p) => p.name));
1744
1591
  this.hasComponents = true;
1745
1592
  }
1593
+ else if (decl?.kind === "ClassDecl" && decl.body) {
1594
+ for (const m of decl.body.members) {
1595
+ if (m.kind === "ComponentDecl") {
1596
+ this.userComponentNames.add(m.name);
1597
+ this.allComponentLikeNames.add(m.name);
1598
+ this.componentParamNames.set(m.name, m.params.map((p) => p.name));
1599
+ this.hasComponents = true;
1600
+ }
1601
+ }
1602
+ }
1746
1603
  }
1747
- catch {
1748
- // Silently skip if the file cannot be read or parsed
1749
- }
1604
+ catch { }
1750
1605
  }
1751
1606
  }
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. */
1754
1607
  isConstructorCall(calleeExpr) {
1755
- if (calleeExpr.kind === "NameExpr") {
1756
- // Names in componentNames are factory functions (UI primitives, components), not constructors
1757
- if (this.componentNames.has(calleeExpr.name))
1758
- return false;
1759
- // Class names start with uppercase; function names start with lowercase
1760
- return /^[A-Z]/.test(calleeExpr.name);
1761
- }
1762
- if (calleeExpr.kind === "MemberExpr") {
1763
- // e.g. UiState.Success(data), Discount.Percentage(0.1)
1764
- return /^[A-Z]/.test(calleeExpr.member);
1765
- }
1766
- return false;
1608
+ const name = calleeExpr.kind === "NameExpr" ? calleeExpr.name :
1609
+ calleeExpr.kind === "MemberExpr" ? calleeExpr.member :
1610
+ null;
1611
+ if (name !== null && this.allComponentLikeNames.has(name))
1612
+ return false;
1613
+ return this.typeMap.get(calleeExpr)?.tag === "class";
1767
1614
  }
1768
- // ── Component call → props object ──────────────────────────────────────
1769
- /** Emit `Column(modifier = ...) { ... }` as `React.createElement(Column, { modifier: ... }, [children])` */
1770
1615
  emitComponentCall(expr) {
1771
- const tag = expr.callee.name;
1616
+ const callee = this.emitExpr(expr.callee);
1617
+ const tagName = expr.callee.kind === "NameExpr" ? expr.callee.name : expr.callee.member;
1772
1618
  const calleeType = this.typeMap.get(expr.callee);
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
1619
+ const paramNames = (calleeType?.tag === "func" ? calleeType.paramNames : undefined) ?? (tagName ? this.componentParamNames.get(tagName) : undefined);
1777
1620
  const props = [];
1778
1621
  for (let i = 0; i < expr.args.length; i++) {
1779
1622
  const arg = expr.args[i];
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);
1623
+ const propName = arg.name ?? paramNames?.[i] ?? (arg.value.kind === "NameExpr" ? arg.value.name : undefined);
1784
1624
  if (!propName)
1785
1625
  continue;
1786
1626
  const val = this.emitExpr(arg.value);
1787
- // Use JS shorthand `{ key }` when key and value are the same identifier
1788
1627
  props.push(propName === val ? propName : `${propName}: ${val}`);
1789
1628
  }
1790
1629
  const propsStr = props.length > 0 ? `{ ${props.join(", ")} }` : "{}";
1791
1630
  if (expr.trailingLambda) {
1792
1631
  const children = this.emitLambdaBodyAsDomChildren(expr.trailingLambda);
1793
- return `React.createElement(${tag}, ${propsStr}, [${children}])`;
1632
+ return `${callee}(${propsStr}, [${children}])`;
1794
1633
  }
1795
- return `React.createElement(${tag}, ${propsStr})`;
1634
+ return `${callee}(${propsStr})`;
1796
1635
  }
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. */
1799
1636
  emitLambdaBodyAsDomChildren(lambda) {
1800
1637
  const parts = [];
1801
1638
  for (const stmt of lambda.body) {
1802
1639
  if (stmt.kind === "ExprStmt") {
1803
1640
  parts.push(this.emitExpr(stmt.expr));
1804
1641
  }
1642
+ else if (stmt.kind === "IfStmt") {
1643
+ const s = stmt;
1644
+ const cond = this.emitExpr(s.condition);
1645
+ const thenExprs = s.then.statements.filter((st) => st.kind === "ExprStmt").map((st) => this.emitExpr(st.expr));
1646
+ const elseExprs = s.else && s.else.kind === "Block" ? s.else.statements.filter((st) => st.kind === "ExprStmt").map((st) => this.emitExpr(st.expr)) : [];
1647
+ const thenStr = thenExprs.length > 0 ? `...(${cond} ? [${thenExprs.join(", ")}] : [])` : "";
1648
+ const elseStr = elseExprs.length > 0 ? `...(!(${cond}) ? [${elseExprs.join(", ")}] : [])` : "";
1649
+ if (thenStr)
1650
+ parts.push(thenStr);
1651
+ if (elseStr)
1652
+ parts.push(elseStr);
1653
+ }
1805
1654
  else if (stmt.kind === "ForStmt") {
1806
1655
  const s = stmt;
1807
1656
  const iter = this.emitExpr(s.iterable);
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));
1657
+ 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}]`;
1658
+ const innerExprs = s.body.statements.filter((inner) => inner.kind === "ExprStmt").map((inner) => this.emitExpr(inner.expr));
1822
1659
  if (innerExprs.length > 0) {
1823
1660
  const pushes = innerExprs.map((e) => `__c.push(${e});`).join(" ");
1824
1661
  parts.push(`...(() => { const __c: any[] = []; for (${bindingStr} of ${iter}) { ${pushes} } return __c; })()`);
1825
1662
  }
1826
1663
  }
1827
- // Other statement kinds (if, while, etc.) are not renderable as UI children inline
1828
1664
  }
1829
1665
  return parts.join(", ");
1830
1666
  }
1831
1667
  emitLambdaExpr(expr) {
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(" ")} }`;
1668
+ const params = expr.params.length === 0 ? "it" : expr.params.map((p) => p.name ?? "_").join(", ");
1669
+ if (expr.body.length === 1 && expr.body[0].kind === "ExprStmt")
1670
+ return `(${params}) => ${this.emitExpr(expr.body[0].expr)}`;
1671
+ return `(${params}) => { ${expr.body.map((s) => this.captureStmt(s)).join(" ")} }`;
1842
1672
  }
1843
1673
  emitIfExpr(expr) {
1844
1674
  const cond = this.emitExpr(expr.condition);
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);
1675
+ const thenStr = expr.then.kind === "Block" ? `(() => { ${this.captureBlockStatements(expr.then.statements)} })()` : this.emitExpr(expr.then);
1676
+ const elseStr = expr.else.kind === "Block" ? `(() => { ${this.captureBlockStatements(expr.else.statements)} })()` : expr.else.kind === "IfExpr" ? this.emitIfExpr(expr.else) : this.emitExpr(expr.else);
1854
1677
  return `(${cond} ? ${thenStr} : ${elseStr})`;
1855
1678
  }
1856
1679
  emitWhenExpr(expr) {
1857
- // Compile as an IIFE with if/else chain
1858
1680
  const parts = [];
1859
1681
  const subject = expr.subject ? `const __s = ${this.emitExpr(expr.subject.expr)};` : "";
1860
1682
  const subjectRef = expr.subject ? (expr.subject.binding ?? "__s") : "";
1861
1683
  for (const branch of expr.branches) {
1862
- if (branch.isElse) {
1863
- const body = branch.body.kind === "Block"
1864
- ? this.captureBlock(branch.body)
1865
- : `return ${this.emitExpr(branch.body)};`;
1684
+ const body = branch.body.kind === "Block" ? this.captureBlock(branch.body) : `return ${this.emitExpr(branch.body)};`;
1685
+ if (branch.isElse)
1866
1686
  parts.push(`{ ${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
- }
1687
+ else
1688
+ parts.push(`if (${branch.conditions.map((c) => this.emitWhenCondition(c, subjectRef)).join(" || ")}) { ${body} }`);
1875
1689
  }
1876
1690
  return `(() => { ${subject} ${parts.join(" else ")} })()`;
1877
1691
  }
1878
1692
  emitTryCatchExpr(expr) {
1879
1693
  const body = this.captureBlock(expr.body);
1880
- const catches = expr.catches.map((c) => {
1881
- const cb = this.captureBlock(c.body);
1882
- return `catch (${c.name}) { ${cb} }`;
1883
- }).join(" ");
1694
+ const catches = expr.catches.map((c) => `catch (${c.name}) { ${this.captureBlock(c.body)} }`).join(" ");
1884
1695
  const fin = expr.finally ? `finally { ${this.captureBlock(expr.finally)} }` : "";
1885
1696
  return `(() => { try { ${body} } ${catches} ${fin} })()`;
1886
1697
  }
1887
1698
  emitCollectionLiteral(expr) {
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(", ")}]`;
1699
+ if (expr.collectionKind === "map")
1700
+ return `new Map([${expr.elements.map((e) => ("kind" in e && e.kind === "MapEntry") ? `[${this.emitExpr(e.key)}, ${this.emitExpr(e.value)}]` : "null").join(", ")}])`;
1701
+ if (expr.collectionKind === "set")
1702
+ return `new Set([${expr.elements.map((e) => this.emitExpr(e)).join(", ")}])`;
1703
+ return `[${expr.elements.map((e) => this.emitExpr(e)).join(", ")}]`;
1903
1704
  }
1904
1705
  emitObjectExpr(expr) {
1905
1706
  const superType = expr.superTypes[0];
1906
1707
  const ext = superType ? ` extends ${this.emitTypeRef(superType.type)}` : "";
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 ────────────────────────────────────────────────
1708
+ return `(new (class${ext} { ${expr.body.members.map((m) => this.captureClassMember(m)).join(" ")} })())`;
1709
+ }
1710
+ captureBlock(block) { return block.statements.map((s) => this.captureStmt(s)).join(" "); }
1711
+ captureBlockStatements(stmts) { return stmts.map((s) => this.captureStmt(s)).join(" "); }
1712
+ captureStmt(stmt) { const saved = this.w; const tmp = new Writer(); this.w = tmp; this.emitStmt(stmt); this.w = saved; return tmp.output.trim(); }
1713
+ captureClassMember(member) { const saved = this.w; const tmp = new Writer(); this.w = tmp; this.emitClassMember(member); this.w = saved; return tmp.output.trim(); }
1934
1714
  emitTypeRef(ref) {
1935
1715
  switch (ref.kind) {
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
- */
1716
+ case "SimpleTypeRef": return PRIMITIVE_TYPE_MAP[ref.name.join(".")] ?? ref.name.join(".");
1717
+ case "NullableTypeRef": return `${this.emitTypeRef(ref.base)} | null | undefined`;
1718
+ 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(", ")}>`;
1719
+ case "FunctionTypeRef": return `(${ref.params.map((p, i) => `p${i}: ${this.emitTypeRef(p)}`).join(", ")}) => ${this.emitTypeRef(ref.returnType)}`;
1720
+ case "StarProjection": return "any";
1721
+ }
1722
+ }
1723
+ emitTypeParamsStr(params) { return params.length === 0 ? "" : `<${params.map((p) => `${p.name}${p.upperBound ? ` extends ${this.emitTypeRef(p.upperBound)}` : ""}`).join(", ")}>`; }
1724
+ 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(", "); }
1725
+ emitSuperTypesStr(superTypes) { return superTypes.length === 0 ? "" : superTypes.map((s, i) => i === 0 ? ` extends ${this.emitTypeRef(s.type)}` : ` implements ${this.emitTypeRef(s.type)}`).join(""); }
1726
+ exportPrefix(mods) { return (mods.visibility === "private" || mods.visibility === "internal") ? "" : "export "; }
1727
+ visibilityPrefix(mods) { switch (mods.visibility) {
1728
+ case "private": return "private ";
1729
+ case "protected": return "protected ";
1730
+ case "internal": return "/* internal */ ";
1731
+ default: return "";
1732
+ } }
1733
+ unaryOpStr(op) { return op === "not" ? "!" : op; }
1734
+ binaryOpStr(op) { switch (op) {
1735
+ case "and": return "&&";
1736
+ case "or": return "||";
1737
+ case "xor": return "^";
1738
+ case "shl": return "<<";
1739
+ case "shr": return ">>";
1740
+ case "ushr": return ">>>";
1741
+ case "===": return "===";
1742
+ case "!==": return "!==";
1743
+ default: return op;
1744
+ } }
2047
1745
  gatherReferencedNames(program) {
2048
1746
  const names = new Set();
2049
1747
  const visited = new WeakSet();
2050
1748
  const walk = (val) => {
2051
- if (!val || typeof val !== "object")
1749
+ if (!val || typeof val !== "object" || visited.has(val))
2052
1750
  return;
1751
+ visited.add(val);
2053
1752
  if (Array.isArray(val)) {
2054
- for (const item of val)
2055
- walk(item);
1753
+ val.forEach(walk);
2056
1754
  return;
2057
1755
  }
2058
1756
  const obj = val;
2059
- if (visited.has(obj))
2060
- return;
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
- }
1757
+ if (obj.kind === "NameExpr") {
1758
+ if (typeof obj.name === "string")
1759
+ names.add(obj.name);
2077
1760
  return;
2078
1761
  }
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
1762
+ if (obj.kind === "SimpleTypeRef") {
1763
+ if (obj.name?.[0] && !(obj.name[0] in PRIMITIVE_TYPE_MAP))
1764
+ names.add(obj.name[0]);
1765
+ return;
2088
1766
  }
2089
- for (const propVal of Object.values(obj)) {
2090
- walk(propVal);
1767
+ if (obj.kind === "GenericTypeRef") {
1768
+ if (obj.base?.name?.[0] && !(obj.base.name[0] in PRIMITIVE_TYPE_MAP))
1769
+ names.add(obj.base.name[0]);
2091
1770
  }
1771
+ Object.values(obj).forEach(walk);
2092
1772
  };
2093
- for (const decl of program.declarations)
2094
- walk(decl);
1773
+ program.declarations.forEach(walk);
2095
1774
  return names;
2096
1775
  }
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
- */
2103
1776
  gatherAllLocalBindings(program) {
2104
1777
  const names = new Set();
2105
1778
  const visited = new WeakSet();
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
- }
1779
+ program.declarations.forEach((d) => { if (d.name)
1780
+ names.add(d.name); });
1781
+ program.imports.forEach((i) => { if (!i.star)
1782
+ names.add(i.alias ?? i.path[i.path.length - 1]); });
2120
1783
  const walk = (val) => {
2121
- if (!val || typeof val !== "object")
1784
+ if (!val || typeof val !== "object" || visited.has(val))
2122
1785
  return;
1786
+ visited.add(val);
2123
1787
  if (Array.isArray(val)) {
2124
- for (const item of val)
2125
- walk(item);
1788
+ val.forEach(walk);
2126
1789
  return;
2127
1790
  }
2128
1791
  const obj = val;
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
- }
1792
+ if (["PropertyDecl", "DestructuringDecl", "FunDecl", "ExtensionFunDecl", "ComponentDecl", "ClassDecl", "DataClassDecl", "SealedClassDecl", "EnumClassDecl", "InterfaceDecl", "ObjectDecl", "TypeAliasDecl", "LambdaExpr"].includes(obj.kind) && obj.name)
1793
+ names.add(obj.name);
1794
+ if (obj.typeParams)
1795
+ obj.typeParams.forEach((tp) => names.add(tp.name));
1796
+ if (obj.params)
1797
+ obj.params.forEach((p) => names.add(p.name));
1798
+ if (obj.kind === "ForStmt" && typeof obj.binding === "string")
1799
+ names.add(obj.binding);
1800
+ if (obj.kind === "TryCatchStmt" && obj.catches)
1801
+ obj.catches.forEach((c) => names.add(c.name));
1802
+ Object.values(obj).forEach(walk);
2224
1803
  };
2225
- for (const decl of program.declarations)
2226
- walk(decl);
1804
+ program.declarations.forEach(walk);
2227
1805
  return names;
2228
1806
  }
2229
1807
  }
2230
1808
  exports.CodeGenerator = CodeGenerator;
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
- };
1809
+ 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" };
1810
+ 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" };
2263
1811
  const PRIMITIVE_TYPES = new Set(Object.keys(PRIMITIVE_TYPE_MAP));
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
- }
1812
+ 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"]);
1813
+ function generate(program, opts, operatorOverloads, typeMap) { return new CodeGenerator(opts).generate(program, operatorOverloads, typeMap); }
2309
1814
  //# sourceMappingURL=codegen.js.map