@tsonic/frontend 0.0.72 → 0.0.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/core-intrinsics/provenance.d.ts +11 -0
  3. package/dist/core-intrinsics/provenance.d.ts.map +1 -0
  4. package/dist/core-intrinsics/provenance.js +101 -0
  5. package/dist/core-intrinsics/provenance.js.map +1 -0
  6. package/dist/ir/binding/binding-factory.d.ts.map +1 -1
  7. package/dist/ir/binding/binding-factory.js +42 -6
  8. package/dist/ir/binding/binding-factory.js.map +1 -1
  9. package/dist/ir/builder/imports.d.ts.map +1 -1
  10. package/dist/ir/builder/imports.js +51 -8
  11. package/dist/ir/builder/imports.js.map +1 -1
  12. package/dist/ir/builder.test.js +758 -0
  13. package/dist/ir/builder.test.js.map +1 -1
  14. package/dist/ir/converters/expressions/access/access-converter.d.ts.map +1 -1
  15. package/dist/ir/converters/expressions/access/access-converter.js +35 -1
  16. package/dist/ir/converters/expressions/access/access-converter.js.map +1 -1
  17. package/dist/ir/converters/expressions/access/member-resolution.d.ts.map +1 -1
  18. package/dist/ir/converters/expressions/access/member-resolution.js +24 -0
  19. package/dist/ir/converters/expressions/access/member-resolution.js.map +1 -1
  20. package/dist/ir/converters/expressions/calls/call-converter.d.ts.map +1 -1
  21. package/dist/ir/converters/expressions/calls/call-converter.js +14 -13
  22. package/dist/ir/converters/expressions/calls/call-converter.js.map +1 -1
  23. package/dist/ir/converters/expressions/calls/call-site-analysis.d.ts +1 -1
  24. package/dist/ir/converters/expressions/calls/call-site-analysis.d.ts.map +1 -1
  25. package/dist/ir/converters/expressions/calls/call-site-analysis.js +4 -30
  26. package/dist/ir/converters/expressions/calls/call-site-analysis.js.map +1 -1
  27. package/dist/ir/converters/expressions/collections.d.ts.map +1 -1
  28. package/dist/ir/converters/expressions/collections.js +13 -6
  29. package/dist/ir/converters/expressions/collections.js.map +1 -1
  30. package/dist/ir/converters/flow-narrowing.d.ts +1 -0
  31. package/dist/ir/converters/flow-narrowing.d.ts.map +1 -1
  32. package/dist/ir/converters/flow-narrowing.js +223 -0
  33. package/dist/ir/converters/flow-narrowing.js.map +1 -1
  34. package/dist/ir/converters/statements/control/blocks.d.ts.map +1 -1
  35. package/dist/ir/converters/statements/control/blocks.js +46 -0
  36. package/dist/ir/converters/statements/control/blocks.js.map +1 -1
  37. package/dist/ir/program-context.d.ts.map +1 -1
  38. package/dist/ir/program-context.js +32 -2
  39. package/dist/ir/program-context.js.map +1 -1
  40. package/dist/ir/program-context.test.d.ts +2 -0
  41. package/dist/ir/program-context.test.d.ts.map +1 -0
  42. package/dist/ir/program-context.test.js +76 -0
  43. package/dist/ir/program-context.test.js.map +1 -0
  44. package/dist/ir/type-system/internal/type-converter/orchestrator.d.ts.map +1 -1
  45. package/dist/ir/type-system/internal/type-converter/orchestrator.js +250 -12
  46. package/dist/ir/type-system/internal/type-converter/orchestrator.js.map +1 -1
  47. package/dist/ir/type-system/internal/type-converter/orchestrator.test.js +19 -0
  48. package/dist/ir/type-system/internal/type-converter/orchestrator.test.js.map +1 -1
  49. package/dist/ir/type-system/internal/type-converter/references.d.ts.map +1 -1
  50. package/dist/ir/type-system/internal/type-converter/references.js +93 -3
  51. package/dist/ir/type-system/internal/type-converter/references.js.map +1 -1
  52. package/dist/ir/type-system/internal/type-converter/utility-types.d.ts.map +1 -1
  53. package/dist/ir/type-system/internal/type-converter/utility-types.js +9 -3
  54. package/dist/ir/type-system/internal/type-converter/utility-types.js.map +1 -1
  55. package/dist/ir/type-system/internal/type-converter/utility-types.test.js +19 -0
  56. package/dist/ir/type-system/internal/type-converter/utility-types.test.js.map +1 -1
  57. package/dist/ir/type-system/internal/universe/clr-catalog.d.ts.map +1 -1
  58. package/dist/ir/type-system/internal/universe/clr-catalog.js +20 -1
  59. package/dist/ir/type-system/internal/universe/clr-catalog.js.map +1 -1
  60. package/dist/ir/type-system/internal/universe/clr-catalog.test.js +43 -0
  61. package/dist/ir/type-system/internal/universe/clr-catalog.test.js.map +1 -1
  62. package/dist/ir/type-system/internal/universe/clr-entry-converter.d.ts.map +1 -1
  63. package/dist/ir/type-system/internal/universe/clr-entry-converter.js +43 -2
  64. package/dist/ir/type-system/internal/universe/clr-entry-converter.js.map +1 -1
  65. package/dist/ir/type-system/internal/universe/types.d.ts +10 -1
  66. package/dist/ir/type-system/internal/universe/types.d.ts.map +1 -1
  67. package/dist/ir/type-system/internal/universe/types.js.map +1 -1
  68. package/dist/ir/type-system/type-system-call-resolution.d.ts +1 -0
  69. package/dist/ir/type-system/type-system-call-resolution.d.ts.map +1 -1
  70. package/dist/ir/type-system/type-system-call-resolution.js +49 -22
  71. package/dist/ir/type-system/type-system-call-resolution.js.map +1 -1
  72. package/dist/ir/type-system/type-system-inference.d.ts.map +1 -1
  73. package/dist/ir/type-system/type-system-inference.js +39 -5
  74. package/dist/ir/type-system/type-system-inference.js.map +1 -1
  75. package/dist/ir/type-system/type-system-utilities.d.ts.map +1 -1
  76. package/dist/ir/type-system/type-system-utilities.js +4 -22
  77. package/dist/ir/type-system/type-system-utilities.js.map +1 -1
  78. package/dist/ir/type-system/type-system.d.ts +16 -0
  79. package/dist/ir/type-system/type-system.d.ts.map +1 -1
  80. package/dist/ir/type-system/type-system.js +3 -1
  81. package/dist/ir/type-system/type-system.js.map +1 -1
  82. package/dist/ir/types/index.d.ts +1 -1
  83. package/dist/ir/types/index.d.ts.map +1 -1
  84. package/dist/ir/types/index.js +1 -1
  85. package/dist/ir/types/index.js.map +1 -1
  86. package/dist/ir/types/type-ops.d.ts +2 -0
  87. package/dist/ir/types/type-ops.d.ts.map +1 -1
  88. package/dist/ir/types/type-ops.js +34 -12
  89. package/dist/ir/types/type-ops.js.map +1 -1
  90. package/dist/ir/types/type-ops.test.js +44 -1
  91. package/dist/ir/types/type-ops.test.js.map +1 -1
  92. package/dist/ir/types.d.ts +1 -1
  93. package/dist/ir/types.d.ts.map +1 -1
  94. package/dist/ir/types.js +1 -1
  95. package/dist/ir/types.js.map +1 -1
  96. package/dist/program/binding-registry.d.ts +2 -0
  97. package/dist/program/binding-registry.d.ts.map +1 -1
  98. package/dist/program/binding-registry.js +54 -12
  99. package/dist/program/binding-registry.js.map +1 -1
  100. package/dist/program/binding-types.d.ts +21 -1
  101. package/dist/program/binding-types.d.ts.map +1 -1
  102. package/dist/program/binding-types.js +70 -0
  103. package/dist/program/binding-types.js.map +1 -1
  104. package/dist/program/bindings.test.js +145 -0
  105. package/dist/program/bindings.test.js.map +1 -1
  106. package/dist/program/clr-bindings-discovery.d.ts.map +1 -1
  107. package/dist/program/clr-bindings-discovery.js +9 -3
  108. package/dist/program/clr-bindings-discovery.js.map +1 -1
  109. package/dist/program/clr-bindings-discovery.test.js +107 -0
  110. package/dist/program/clr-bindings-discovery.test.js.map +1 -1
  111. package/dist/program/creation.d.ts.map +1 -1
  112. package/dist/program/creation.js +22 -3
  113. package/dist/program/creation.js.map +1 -1
  114. package/dist/program/creation.test.js +37 -0
  115. package/dist/program/creation.test.js.map +1 -1
  116. package/dist/program/package-roots.js +4 -4
  117. package/dist/program/package-roots.js.map +1 -1
  118. package/dist/program/package-roots.test.d.ts +2 -0
  119. package/dist/program/package-roots.test.d.ts.map +1 -0
  120. package/dist/program/package-roots.test.js +42 -0
  121. package/dist/program/package-roots.test.js.map +1 -0
  122. package/dist/program.d.ts +1 -1
  123. package/dist/program.d.ts.map +1 -1
  124. package/dist/program.js +1 -1
  125. package/dist/program.js.map +1 -1
  126. package/dist/resolver/import-resolution.d.ts.map +1 -1
  127. package/dist/resolver/import-resolution.js +2 -2
  128. package/dist/resolver/import-resolution.js.map +1 -1
  129. package/dist/resolver/source-package-resolution.d.ts +1 -0
  130. package/dist/resolver/source-package-resolution.d.ts.map +1 -1
  131. package/dist/resolver/source-package-resolution.js +6 -1
  132. package/dist/resolver/source-package-resolution.js.map +1 -1
  133. package/dist/resolver/source-package-resolution.test.js +7 -1
  134. package/dist/resolver/source-package-resolution.test.js.map +1 -1
  135. package/dist/resolver.test.js +37 -0
  136. package/dist/resolver.test.js.map +1 -1
  137. package/dist/surface/profiles.d.ts.map +1 -1
  138. package/dist/surface/profiles.js +12 -0
  139. package/dist/surface/profiles.js.map +1 -1
  140. package/dist/surface/profiles.test.js +43 -1
  141. package/dist/surface/profiles.test.js.map +1 -1
  142. package/dist/validation/core-intrinsics.d.ts.map +1 -1
  143. package/dist/validation/core-intrinsics.js +1 -115
  144. package/dist/validation/core-intrinsics.js.map +1 -1
  145. package/package.json +1 -1
@@ -54,6 +54,66 @@ describe("IR Builder", () => {
54
54
  const ctx = createProgramContext(testProgram, options);
55
55
  return { testProgram, ctx, options };
56
56
  };
57
+ const createFilesystemTestProgram = (files, entryRelativePath) => {
58
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-filesystem-"));
59
+ for (const [relativePath, contents] of Object.entries(files)) {
60
+ const absolutePath = path.join(tempDir, relativePath);
61
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
62
+ fs.writeFileSync(absolutePath, contents);
63
+ }
64
+ const rootNames = Object.keys(files)
65
+ .filter((relativePath) => /\.(?:ts|mts|cts|d\.ts)$/.test(relativePath))
66
+ .map((relativePath) => path.join(tempDir, relativePath));
67
+ const tsProgram = ts.createProgram(rootNames, {
68
+ target: ts.ScriptTarget.ES2022,
69
+ module: ts.ModuleKind.NodeNext,
70
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
71
+ strict: true,
72
+ noEmit: true,
73
+ skipLibCheck: true,
74
+ });
75
+ const checker = tsProgram.getTypeChecker();
76
+ const entryPath = path.join(tempDir, entryRelativePath);
77
+ const sourceFile = tsProgram.getSourceFile(entryPath);
78
+ if (!sourceFile) {
79
+ throw new Error(`Failed to create source file for ${entryRelativePath}`);
80
+ }
81
+ const testProgram = {
82
+ program: tsProgram,
83
+ checker,
84
+ options: {
85
+ projectRoot: tempDir,
86
+ sourceRoot: path.join(tempDir, "src"),
87
+ rootNamespace: "TestApp",
88
+ strict: true,
89
+ },
90
+ sourceFiles: rootNames
91
+ .filter((filePath) => !filePath.endsWith(".d.ts"))
92
+ .map((filePath) => tsProgram.getSourceFile(filePath))
93
+ .filter((candidate) => candidate !== undefined),
94
+ declarationSourceFiles: rootNames
95
+ .filter((filePath) => filePath.endsWith(".d.ts"))
96
+ .map((filePath) => tsProgram.getSourceFile(filePath))
97
+ .filter((candidate) => candidate !== undefined),
98
+ metadata: new DotnetMetadataRegistry(),
99
+ bindings: new BindingRegistry(),
100
+ clrResolver: createClrBindingsResolver(tempDir),
101
+ binding: createBinding(checker),
102
+ };
103
+ const options = {
104
+ sourceRoot: path.join(tempDir, "src"),
105
+ rootNamespace: "TestApp",
106
+ };
107
+ const ctx = createProgramContext(testProgram, options);
108
+ return {
109
+ tempDir,
110
+ sourceFile,
111
+ testProgram,
112
+ ctx,
113
+ options,
114
+ cleanup: () => fs.rmSync(tempDir, { recursive: true, force: true }),
115
+ };
116
+ };
57
117
  describe("Module Structure", () => {
58
118
  it("should create IR module with correct namespace and class name", () => {
59
119
  const source = `
@@ -476,6 +536,165 @@ describe("IR Builder", () => {
476
536
  fs.rmSync(tempDir, { recursive: true, force: true });
477
537
  }
478
538
  });
539
+ it("preserves canonical CLR identity for array elements from source-binding declarations", function () {
540
+ this.timeout(30000);
541
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-source-array-identity-"));
542
+ try {
543
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
544
+ const srcDir = path.join(tempDir, "src");
545
+ fs.mkdirSync(srcDir, { recursive: true });
546
+ const bindingsRoot = path.join(tempDir, "tsonic", "bindings");
547
+ fs.mkdirSync(path.join(bindingsRoot, "Acme.Core", "internal"), {
548
+ recursive: true,
549
+ });
550
+ fs.writeFileSync(path.join(bindingsRoot, "Acme.Core", "internal", "index.d.ts"), [
551
+ "export interface Attachment$instance {",
552
+ ' readonly "__tsonic_binding_alias_Acme.Core.Attachment"?: never;',
553
+ " readonly __tsonic_type_Acme_Core_Attachment?: never;",
554
+ " Id: string;",
555
+ "}",
556
+ "export type Attachment = Attachment$instance;",
557
+ ].join("\n"));
558
+ fs.writeFileSync(path.join(bindingsRoot, "Acme.Core.d.ts"), [
559
+ 'import type { Attachment } from "./Acme.Core/internal/index.js";',
560
+ "export declare function getAttachments(): Attachment[];",
561
+ ].join("\n"));
562
+ const entryPath = path.join(srcDir, "index.ts");
563
+ fs.writeFileSync(entryPath, [
564
+ 'import { getAttachments } from "../tsonic/bindings/Acme.Core.js";',
565
+ "const attachments = getAttachments();",
566
+ "export const attachmentCount = attachments.length;",
567
+ ].join("\n"));
568
+ const programResult = createProgram([entryPath], {
569
+ projectRoot: tempDir,
570
+ sourceRoot: srcDir,
571
+ rootNamespace: "TestApp",
572
+ useStandardLib: true,
573
+ });
574
+ expect(programResult.ok).to.equal(true);
575
+ if (!programResult.ok)
576
+ return;
577
+ const program = programResult.value;
578
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
579
+ expect(sourceFile).to.not.equal(undefined);
580
+ if (!sourceFile)
581
+ return;
582
+ const ctx = createProgramContext(program, {
583
+ sourceRoot: srcDir,
584
+ rootNamespace: "TestApp",
585
+ });
586
+ const moduleResult = buildIrModule(sourceFile, program, {
587
+ sourceRoot: srcDir,
588
+ rootNamespace: "TestApp",
589
+ }, ctx);
590
+ expect(moduleResult.ok).to.equal(true);
591
+ if (!moduleResult.ok)
592
+ return;
593
+ const attachmentsDecl = moduleResult.value.body.find((stmt) => stmt.kind === "variableDeclaration" &&
594
+ stmt.declarations[0]?.name.kind === "identifierPattern" &&
595
+ stmt.declarations[0]?.name.name === "attachments");
596
+ expect(attachmentsDecl).to.not.equal(undefined);
597
+ if (!attachmentsDecl)
598
+ return;
599
+ const attachmentsType = attachmentsDecl.declarations[0]?.type;
600
+ expect(attachmentsType?.kind).to.equal("arrayType");
601
+ if (!attachmentsType || attachmentsType.kind !== "arrayType")
602
+ return;
603
+ expect(attachmentsType.elementType.kind).to.equal("referenceType");
604
+ if (attachmentsType.elementType.kind !== "referenceType")
605
+ return;
606
+ expect(attachmentsType.elementType.name).to.equal("Acme.Core.Attachment");
607
+ expect(attachmentsType.elementType.resolvedClrType).to.equal("Acme.Core.Attachment");
608
+ }
609
+ finally {
610
+ fs.rmSync(tempDir, { recursive: true, force: true });
611
+ }
612
+ });
613
+ it("preserves CLR identity for generic structural aliases from source-binding declarations", function () {
614
+ this.timeout(30000);
615
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-source-generic-alias-"));
616
+ try {
617
+ fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
618
+ const srcDir = path.join(tempDir, "src");
619
+ fs.mkdirSync(srcDir, { recursive: true });
620
+ const bindingsRoot = path.join(tempDir, "tsonic", "bindings");
621
+ fs.mkdirSync(path.join(bindingsRoot, "Acme.Core", "internal"), {
622
+ recursive: true,
623
+ });
624
+ fs.writeFileSync(path.join(bindingsRoot, "Acme.Core", "bindings.json"), JSON.stringify({
625
+ namespace: "Acme.Core",
626
+ types: [
627
+ {
628
+ clrName: "Acme.Core.Ok__Alias`1",
629
+ assemblyName: "Acme.Core",
630
+ methods: [],
631
+ properties: [],
632
+ fields: [],
633
+ },
634
+ ],
635
+ }, null, 2));
636
+ fs.writeFileSync(path.join(bindingsRoot, "Acme.Core", "internal", "index.d.ts"), [
637
+ "export interface Ok__Alias_1$instance<T> {",
638
+ ' readonly "__tsonic_binding_alias_Acme.Core.Ok__Alias_1"?: never;',
639
+ " readonly value: T;",
640
+ "}",
641
+ "export type Ok__Alias_1<T> = Ok__Alias_1$instance<T>;",
642
+ ].join("\n"));
643
+ fs.writeFileSync(path.join(bindingsRoot, "Acme.Core.d.ts"), [
644
+ 'import type { Ok__Alias_1 } from "./Acme.Core/internal/index.js";',
645
+ "export type Ok<T> = Ok__Alias_1<T>;",
646
+ ].join("\n"));
647
+ const entryPath = path.join(srcDir, "index.ts");
648
+ fs.writeFileSync(entryPath, [
649
+ 'import type { Ok } from "../tsonic/bindings/Acme.Core.js";',
650
+ 'export const value: Ok<string> | undefined = undefined;',
651
+ ].join("\n"));
652
+ const programResult = createProgram([entryPath], {
653
+ projectRoot: tempDir,
654
+ sourceRoot: srcDir,
655
+ rootNamespace: "TestApp",
656
+ useStandardLib: true,
657
+ });
658
+ expect(programResult.ok).to.equal(true);
659
+ if (!programResult.ok)
660
+ return;
661
+ const program = programResult.value;
662
+ const sourceFile = program.sourceFiles.find((file) => path.resolve(file.fileName) === path.resolve(entryPath));
663
+ expect(sourceFile).to.not.equal(undefined);
664
+ if (!sourceFile)
665
+ return;
666
+ const ctx = createProgramContext(program, {
667
+ sourceRoot: srcDir,
668
+ rootNamespace: "TestApp",
669
+ });
670
+ const moduleResult = buildIrModule(sourceFile, program, {
671
+ sourceRoot: srcDir,
672
+ rootNamespace: "TestApp",
673
+ }, ctx);
674
+ expect(moduleResult.ok).to.equal(true);
675
+ if (!moduleResult.ok)
676
+ return;
677
+ const valueDecl = moduleResult.value.body.find((stmt) => stmt.kind === "variableDeclaration" &&
678
+ stmt.declarations[0]?.name.kind === "identifierPattern" &&
679
+ stmt.declarations[0]?.name.name === "value");
680
+ expect(valueDecl).to.not.equal(undefined);
681
+ if (!valueDecl)
682
+ return;
683
+ const declaredType = valueDecl.declarations[0]?.type;
684
+ expect(declaredType?.kind).to.equal("unionType");
685
+ if (!declaredType || declaredType.kind !== "unionType")
686
+ return;
687
+ const okType = declaredType.types.find((type) => type.kind === "referenceType");
688
+ expect(okType).to.not.equal(undefined);
689
+ if (!okType || okType.kind !== "referenceType")
690
+ return;
691
+ expect(okType.name).to.equal("Acme.Core.Ok__Alias_1");
692
+ expect(okType.resolvedClrType).to.equal("Acme.Core.Ok__Alias`1");
693
+ }
694
+ finally {
695
+ fs.rmSync(tempDir, { recursive: true, force: true });
696
+ }
697
+ });
479
698
  it("narrows typeof checks in js-surface branches to the matching primitive type", () => {
480
699
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-typeof-narrowing-"));
481
700
  try {
@@ -577,6 +796,70 @@ describe("IR Builder", () => {
577
796
  }
578
797
  });
579
798
  });
799
+ describe("Core intrinsic provenance", () => {
800
+ const expectVariableInitializerKind = (source, variableName, expectedKind) => {
801
+ const { testProgram, ctx, options } = createTestProgram(source);
802
+ const sourceFile = testProgram.sourceFiles[0];
803
+ if (!sourceFile)
804
+ throw new Error("Failed to create source file");
805
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
806
+ expect(result.ok).to.equal(true);
807
+ if (!result.ok)
808
+ return;
809
+ const variableStmt = result.value.body.find((stmt) => stmt.kind === "variableDeclaration" &&
810
+ stmt.declarations.some((decl) => decl.name.kind === "identifierPattern" &&
811
+ decl.name.name === variableName));
812
+ expect(variableStmt).to.not.equal(undefined);
813
+ if (!variableStmt)
814
+ return;
815
+ const declaration = variableStmt.declarations.find((decl) => decl.name.kind === "identifierPattern" && decl.name.name === variableName);
816
+ expect(declaration?.initializer?.kind).to.equal(expectedKind);
817
+ };
818
+ it("does not lower locally declared nameof as the compiler intrinsic", () => {
819
+ expectVariableInitializerKind(`
820
+ function nameof(value: string): string {
821
+ return value + "!";
822
+ }
823
+
824
+ export const label = nameof("x");
825
+ `, "label", "call");
826
+ });
827
+ it("does not lower locally declared sizeof as the compiler intrinsic", () => {
828
+ expectVariableInitializerKind(`
829
+ function sizeof<T>(): number {
830
+ return 4;
831
+ }
832
+
833
+ export const bytes = sizeof<number>();
834
+ `, "bytes", "call");
835
+ });
836
+ it("does not lower locally declared defaultof/trycast/stackalloc/asinterface intrinsics", () => {
837
+ const source = `
838
+ function defaultof<T>(): T | undefined {
839
+ return undefined;
840
+ }
841
+ function trycast<T>(value: unknown): T | undefined {
842
+ return value as T | undefined;
843
+ }
844
+ function stackalloc<T>(size: number): T {
845
+ throw new Error(String(size));
846
+ }
847
+ function asinterface<T>(value: unknown): T {
848
+ return value as T;
849
+ }
850
+
851
+ interface Box { value: number; }
852
+
853
+ export const fallback = defaultof<number>();
854
+ export const maybe = trycast<Box>({ value: 1 });
855
+ export const mem = stackalloc<number>(16);
856
+ export const view = asinterface<Box>({ value: 1 });
857
+ `;
858
+ for (const variableName of ["fallback", "maybe", "mem", "view"]) {
859
+ expectVariableInitializerKind(source, variableName, "call");
860
+ }
861
+ });
862
+ });
580
863
  describe("Import Extraction", () => {
581
864
  it("should extract local imports", () => {
582
865
  const source = `
@@ -659,6 +942,57 @@ describe("IR Builder", () => {
659
942
  memberName: "buildSite",
660
943
  });
661
944
  });
945
+ it("should attach resolvedClrType for CLR type imports used as values", () => {
946
+ const source = `
947
+ import { Task as TaskValue } from "@tsonic/dotnet/System.Threading.Tasks.js";
948
+ `;
949
+ const { testProgram, ctx, options } = createTestProgram(source);
950
+ ctx.clrResolver = {
951
+ resolve: (s) => s === "@tsonic/dotnet/System.Threading.Tasks.js"
952
+ ? {
953
+ isClr: true,
954
+ packageName: "@tsonic/dotnet",
955
+ resolvedNamespace: "System.Threading.Tasks",
956
+ bindingsPath: "/x/tasks.bindings.json",
957
+ assembly: "System.Runtime",
958
+ }
959
+ : { isClr: false },
960
+ };
961
+ ctx.bindings.addBindings("/x/tasks.bindings.json", {
962
+ namespace: "System.Threading.Tasks",
963
+ types: [
964
+ {
965
+ alias: "Task",
966
+ clrName: "System.Threading.Tasks.Task",
967
+ assemblyName: "System.Runtime",
968
+ kind: "Class",
969
+ methods: [],
970
+ properties: [],
971
+ fields: [],
972
+ },
973
+ ],
974
+ exports: {},
975
+ });
976
+ const sourceFile = testProgram.sourceFiles[0];
977
+ if (!sourceFile)
978
+ throw new Error("Failed to create source file");
979
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
980
+ expect(result.ok).to.equal(true);
981
+ if (!result.ok)
982
+ return;
983
+ const imp = result.value.imports[0];
984
+ if (!imp)
985
+ throw new Error("Missing imports");
986
+ const spec = imp.specifiers[0];
987
+ if (!spec || spec.kind !== "named") {
988
+ throw new Error("Missing named specifier");
989
+ }
990
+ expect(spec.name).to.equal("Task");
991
+ expect(spec.localName).to.equal("TaskValue");
992
+ expect(spec.isType).to.not.equal(true);
993
+ expect(spec.resolvedClrType).to.equal("System.Threading.Tasks.Task");
994
+ expect(spec.resolvedClrValue).to.equal(undefined);
995
+ });
662
996
  it("should error if a CLR namespace value import lacks tsbindgen exports mapping", () => {
663
997
  const source = `
664
998
  import { buildSite } from "@demo/pkg/Demo.js";
@@ -1307,6 +1641,59 @@ describe("IR Builder", () => {
1307
1641
  expect(returnStmt.expression.inferredType?.kind).to.not.equal("unknownType");
1308
1642
  expect(returnStmt.expression.inferredType?.kind).to.not.equal("anyType");
1309
1643
  });
1644
+ it("synthesizes exact numeric properties after nullish fallback narrowing", () => {
1645
+ const source = `
1646
+ import type { int } from "@tsonic/core/types.js";
1647
+
1648
+ declare function parseRole(raw: string): int | undefined;
1649
+
1650
+ export function run(raw: string): int {
1651
+ const parsedInviteAsRole = parseRole(raw);
1652
+ const inviteAsRole = parsedInviteAsRole ?? (400 as int);
1653
+ const input = {
1654
+ inviteAsRole,
1655
+ };
1656
+ return input.inviteAsRole;
1657
+ }
1658
+ `;
1659
+ const { testProgram, ctx, options } = createTestProgram(source);
1660
+ const sourceFile = testProgram.sourceFiles[0];
1661
+ if (!sourceFile)
1662
+ throw new Error("Failed to create source file");
1663
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
1664
+ expect(result.ok).to.equal(true);
1665
+ expect(ctx.diagnostics.some((diagnostic) => diagnostic.code === "TSN5203" &&
1666
+ diagnostic.message.includes("inviteAsRole"))).to.equal(false);
1667
+ if (!result.ok)
1668
+ return;
1669
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
1670
+ expect(run).to.not.equal(undefined);
1671
+ if (!run)
1672
+ return;
1673
+ const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
1674
+ stmt.declarations.some((declaration) => declaration.name.kind === "identifierPattern" &&
1675
+ declaration.name.name === "input" &&
1676
+ declaration.initializer?.kind === "object"));
1677
+ const initializer = decl?.declarations.find((declaration) => declaration.name.kind === "identifierPattern" &&
1678
+ declaration.name.name === "input")?.initializer;
1679
+ expect(initializer?.kind).to.equal("object");
1680
+ if (!initializer || initializer.kind !== "object")
1681
+ return;
1682
+ const objectType = initializer.inferredType;
1683
+ expect(objectType?.kind).to.equal("objectType");
1684
+ if (!objectType || objectType.kind !== "objectType")
1685
+ return;
1686
+ const inviteAsRoleMember = objectType.members.find((member) => member.kind === "propertySignature" &&
1687
+ member.name === "inviteAsRole");
1688
+ expect(inviteAsRoleMember?.kind).to.equal("propertySignature");
1689
+ if (!inviteAsRoleMember ||
1690
+ inviteAsRoleMember.kind !== "propertySignature")
1691
+ return;
1692
+ expect(inviteAsRoleMember.type.kind).to.equal("primitiveType");
1693
+ if (inviteAsRoleMember.type.kind !== "primitiveType")
1694
+ return;
1695
+ expect(inviteAsRoleMember.type.name).to.equal("int");
1696
+ });
1310
1697
  it("normalizes computed const-literal numeric keys during synthesis", () => {
1311
1698
  const source = `
1312
1699
  export function run(): number {
@@ -1438,6 +1825,132 @@ describe("IR Builder", () => {
1438
1825
  expect(arg0.inferredType.name).to.equal("Payload");
1439
1826
  }
1440
1827
  });
1828
+ it("threads expected return generic context through imported declaration aliases", () => {
1829
+ const testFiles = {
1830
+ "package.json": JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2),
1831
+ "src/index.ts": `
1832
+ import type { Result } from "./core.js";
1833
+ import { ok } from "./core.js";
1834
+
1835
+ interface Payload {
1836
+ foundAnchor: boolean;
1837
+ foundNewest: boolean;
1838
+ foundOldest: boolean;
1839
+ }
1840
+
1841
+ export function run(anchor: string): Result<Payload, string> {
1842
+ const foundAnchor = anchor !== "newest" && anchor !== "oldest";
1843
+ const foundNewest = anchor === "newest";
1844
+ const foundOldest = anchor === "oldest";
1845
+ return ok({ foundAnchor, foundNewest, foundOldest });
1846
+ }
1847
+ `,
1848
+ "src/core.d.ts": `
1849
+ export interface Ok<T> {
1850
+ readonly success: true;
1851
+ readonly data: T;
1852
+ }
1853
+
1854
+ export interface Err<E> {
1855
+ readonly success: false;
1856
+ readonly error: E;
1857
+ }
1858
+
1859
+ export type Result<T, E> = Ok<T> | Err<E>;
1860
+
1861
+ export declare function ok<T>(data: T): Ok<T>;
1862
+ `,
1863
+ };
1864
+ const { sourceFile, testProgram, ctx, options, cleanup } = createFilesystemTestProgram(testFiles, "src/index.ts");
1865
+ try {
1866
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
1867
+ expect(result.ok).to.equal(true);
1868
+ if (!result.ok)
1869
+ return;
1870
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
1871
+ expect(run).to.not.equal(undefined);
1872
+ if (!run)
1873
+ return;
1874
+ const retStmt = run.body.statements.find((stmt) => stmt.kind === "returnStatement");
1875
+ expect(retStmt?.expression?.kind).to.equal("call");
1876
+ if (!retStmt?.expression || retStmt.expression.kind !== "call")
1877
+ return;
1878
+ const arg0 = retStmt.expression.arguments[0];
1879
+ expect(arg0?.kind).to.equal("object");
1880
+ if (!arg0 || arg0.kind !== "object")
1881
+ return;
1882
+ expect(arg0.inferredType?.kind).to.equal("referenceType");
1883
+ if (arg0.inferredType?.kind === "referenceType") {
1884
+ expect(arg0.inferredType.name).to.equal("Payload");
1885
+ }
1886
+ }
1887
+ finally {
1888
+ cleanup();
1889
+ }
1890
+ });
1891
+ it("threads expected return generic context through imported declaration aliases inside Promise wrappers", () => {
1892
+ const testFiles = {
1893
+ "package.json": JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2),
1894
+ "src/index.ts": `
1895
+ import type { Result } from "./core.js";
1896
+ import { ok } from "./core.js";
1897
+
1898
+ interface Payload {
1899
+ foundAnchor: boolean;
1900
+ foundNewest: boolean;
1901
+ foundOldest: boolean;
1902
+ }
1903
+
1904
+ export async function run(anchor: string): Promise<Result<Payload, string>> {
1905
+ const foundAnchor = anchor !== "newest" && anchor !== "oldest";
1906
+ const foundNewest = anchor === "newest";
1907
+ const foundOldest = anchor === "oldest";
1908
+ return ok({ foundAnchor, foundNewest, foundOldest });
1909
+ }
1910
+ `,
1911
+ "src/core.d.ts": `
1912
+ export interface Ok<T> {
1913
+ readonly success: true;
1914
+ readonly data: T;
1915
+ }
1916
+
1917
+ export interface Err<E> {
1918
+ readonly success: false;
1919
+ readonly error: E;
1920
+ }
1921
+
1922
+ export type Result<T, E> = Ok<T> | Err<E>;
1923
+
1924
+ export declare function ok<T>(data: T): Ok<T>;
1925
+ `,
1926
+ };
1927
+ const { sourceFile, testProgram, ctx, options, cleanup } = createFilesystemTestProgram(testFiles, "src/index.ts");
1928
+ try {
1929
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
1930
+ expect(result.ok).to.equal(true);
1931
+ if (!result.ok)
1932
+ return;
1933
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
1934
+ expect(run).to.not.equal(undefined);
1935
+ if (!run)
1936
+ return;
1937
+ const retStmt = run.body.statements.find((stmt) => stmt.kind === "returnStatement");
1938
+ expect(retStmt?.expression?.kind).to.equal("call");
1939
+ if (!retStmt?.expression || retStmt.expression.kind !== "call")
1940
+ return;
1941
+ const arg0 = retStmt.expression.arguments[0];
1942
+ expect(arg0?.kind).to.equal("object");
1943
+ if (!arg0 || arg0.kind !== "object")
1944
+ return;
1945
+ expect(arg0.inferredType?.kind).to.equal("referenceType");
1946
+ if (arg0.inferredType?.kind === "referenceType") {
1947
+ expect(arg0.inferredType.name).to.equal("Payload");
1948
+ }
1949
+ }
1950
+ finally {
1951
+ cleanup();
1952
+ }
1953
+ });
1441
1954
  it("does not leak type-alias cache entries across program contexts", () => {
1442
1955
  const sourceA = `
1443
1956
  export type UserId = string;
@@ -2230,6 +2743,251 @@ describe("IR Builder", () => {
2230
2743
  elementType: { kind: "unknownType" },
2231
2744
  });
2232
2745
  });
2746
+ it("allows arbitrary property access on Record<string, unknown> without unknown poison", () => {
2747
+ const source = `
2748
+ export function fill(): Record<string, unknown> {
2749
+ const state: Record<string, unknown> = {};
2750
+ state.zulip_version = "1.0";
2751
+ state.realm_users = [];
2752
+ return state;
2753
+ }
2754
+ `;
2755
+ const { testProgram, ctx, options } = createTestProgram(source);
2756
+ const sourceFile = testProgram.sourceFiles[0];
2757
+ if (!sourceFile)
2758
+ throw new Error("Failed to create source file");
2759
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2760
+ expect(result.ok).to.equal(true);
2761
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2762
+ });
2763
+ it("allows declared unknown members on structural callback parameters", () => {
2764
+ const source = `
2765
+ export function project(
2766
+ rawUpdates: { stream_id: string; property: string; value: unknown }[]
2767
+ ): string[] {
2768
+ return rawUpdates.map((update) => String(update.value ?? ""));
2769
+ }
2770
+ `;
2771
+ const { testProgram, ctx, options } = createTestProgram(source);
2772
+ const sourceFile = testProgram.sourceFiles[0];
2773
+ if (!sourceFile)
2774
+ throw new Error("Failed to create source file");
2775
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2776
+ expect(result.ok).to.equal(true);
2777
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2778
+ });
2779
+ it("supports indexed access on generic discriminated-union payloads after narrowing", () => {
2780
+ const source = `
2781
+ type Ok<T> = { success: true; data: T };
2782
+ type Err<E> = { success: false; error: E };
2783
+ type Result<T, E> = Ok<T> | Err<E>;
2784
+
2785
+ declare function listTenants(): Result<{ Id: string }[], string>;
2786
+
2787
+ export function run(): string {
2788
+ const result = listTenants();
2789
+ if (!result.success) {
2790
+ return result.error;
2791
+ }
2792
+
2793
+ const data = result.data;
2794
+ return data[0]!.Id;
2795
+ }
2796
+ `;
2797
+ const { testProgram, ctx, options } = createTestProgram(source);
2798
+ const sourceFile = testProgram.sourceFiles[0];
2799
+ if (!sourceFile)
2800
+ throw new Error("Failed to create source file");
2801
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2802
+ expect(result.ok).to.equal(true);
2803
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2804
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5107")).to.equal(false);
2805
+ if (!result.ok)
2806
+ return;
2807
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
2808
+ expect(run).to.not.equal(undefined);
2809
+ if (!run)
2810
+ return;
2811
+ const dataDecl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
2812
+ stmt.declarations.some((declaration) => declaration.name.kind === "identifierPattern" &&
2813
+ declaration.name.name === "data"));
2814
+ expect(dataDecl).to.not.equal(undefined);
2815
+ const dataInit = dataDecl?.declarations[0]?.initializer;
2816
+ expect(dataInit?.kind).to.equal("memberAccess");
2817
+ if (!dataInit || dataInit.kind !== "memberAccess")
2818
+ return;
2819
+ expect(dataInit.inferredType?.kind).to.equal("arrayType");
2820
+ if (!dataInit.inferredType || dataInit.inferredType.kind !== "arrayType") {
2821
+ return;
2822
+ }
2823
+ expect(dataInit.inferredType.elementType.kind).to.equal("objectType");
2824
+ if (dataInit.inferredType.elementType.kind !== "objectType")
2825
+ return;
2826
+ expect(dataInit.inferredType.elementType.members).to.have.length(1);
2827
+ const idMember = dataInit.inferredType.elementType.members[0];
2828
+ expect(idMember?.kind).to.equal("propertySignature");
2829
+ if (!idMember || idMember.kind !== "propertySignature")
2830
+ return;
2831
+ expect(idMember.name).to.equal("Id");
2832
+ expect(idMember.type).to.deep.equal({
2833
+ kind: "primitiveType",
2834
+ name: "string",
2835
+ });
2836
+ expect(idMember.isOptional).to.equal(false);
2837
+ expect(idMember.isReadonly).to.equal(false);
2838
+ });
2839
+ it("treats string-literal element access on narrowed unions like property access", () => {
2840
+ const source = `
2841
+ type Err = { error: string; code?: string };
2842
+ type Ok = { events: string[] };
2843
+
2844
+ declare function getEvents(): Err | Ok;
2845
+
2846
+ export function run(): string {
2847
+ const result = getEvents();
2848
+ if ("error" in result) {
2849
+ return result["code"] ?? result["error"];
2850
+ }
2851
+ return result["events"][0] ?? "";
2852
+ }
2853
+ `;
2854
+ const { testProgram, ctx, options } = createTestProgram(source);
2855
+ const sourceFile = testProgram.sourceFiles[0];
2856
+ if (!sourceFile)
2857
+ throw new Error("Failed to create source file");
2858
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2859
+ expect(result.ok).to.equal(true);
2860
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2861
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5107")).to.equal(false);
2862
+ if (!result.ok)
2863
+ return;
2864
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
2865
+ expect(run).to.not.equal(undefined);
2866
+ if (!run)
2867
+ return;
2868
+ const ifStmt = run.body.statements.find((stmt) => stmt.kind === "ifStatement");
2869
+ expect(ifStmt).to.not.equal(undefined);
2870
+ if (!ifStmt || ifStmt.kind !== "ifStatement")
2871
+ return;
2872
+ const thenReturn = ifStmt.thenStatement.kind === "blockStatement"
2873
+ ? ifStmt.thenStatement.statements.find((stmt) => stmt.kind === "returnStatement")
2874
+ : undefined;
2875
+ expect(thenReturn).to.not.equal(undefined);
2876
+ if (!thenReturn ||
2877
+ thenReturn.kind !== "returnStatement" ||
2878
+ !thenReturn.expression ||
2879
+ thenReturn.expression.kind !== "logical") {
2880
+ return;
2881
+ }
2882
+ const codeAccess = thenReturn.expression.left;
2883
+ expect(codeAccess.kind).to.equal("memberAccess");
2884
+ if (codeAccess.kind !== "memberAccess")
2885
+ return;
2886
+ expect(codeAccess.accessKind).to.not.equal("unknown");
2887
+ expect(codeAccess.inferredType).to.deep.equal({
2888
+ kind: "unionType",
2889
+ types: [
2890
+ { kind: "primitiveType", name: "string" },
2891
+ { kind: "primitiveType", name: "undefined" },
2892
+ ],
2893
+ });
2894
+ const finalReturn = [...run.body.statements]
2895
+ .reverse()
2896
+ .find((stmt) => stmt.kind === "returnStatement");
2897
+ expect(finalReturn).to.not.equal(undefined);
2898
+ if (!finalReturn ||
2899
+ !finalReturn.expression ||
2900
+ finalReturn.expression.kind !== "logical" ||
2901
+ finalReturn.expression.left.kind !== "memberAccess") {
2902
+ return;
2903
+ }
2904
+ const eventsIndex = finalReturn.expression.left;
2905
+ expect(eventsIndex.accessKind).to.equal("clrIndexer");
2906
+ expect(eventsIndex.inferredType).to.deep.equal({
2907
+ kind: "primitiveType",
2908
+ name: "string",
2909
+ });
2910
+ });
2911
+ it("keeps string-literal element access computed for alias-wrapped string dictionaries", () => {
2912
+ const source = `
2913
+ interface SettingsMap {
2914
+ [key: string]: string;
2915
+ }
2916
+
2917
+ declare function load(): SettingsMap;
2918
+
2919
+ export function run(): string | undefined {
2920
+ const settings = load();
2921
+ return settings["waiting_period_threshold"];
2922
+ }
2923
+ `;
2924
+ const { testProgram, ctx, options } = createTestProgram(source);
2925
+ const sourceFile = testProgram.sourceFiles[0];
2926
+ if (!sourceFile)
2927
+ throw new Error("Failed to create source file");
2928
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2929
+ expect(result.ok).to.equal(true);
2930
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2931
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5107")).to.equal(false);
2932
+ if (!result.ok)
2933
+ return;
2934
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
2935
+ expect(run).to.not.equal(undefined);
2936
+ if (!run)
2937
+ return;
2938
+ const returnStmt = run.body.statements.find((stmt) => stmt.kind === "returnStatement");
2939
+ expect(returnStmt).to.not.equal(undefined);
2940
+ if (!returnStmt?.expression)
2941
+ return;
2942
+ expect(returnStmt.expression.kind).to.equal("memberAccess");
2943
+ if (returnStmt.expression.kind !== "memberAccess")
2944
+ return;
2945
+ expect(returnStmt.expression.isComputed).to.equal(true);
2946
+ expect(returnStmt.expression.accessKind).to.equal("dictionary");
2947
+ });
2948
+ it("keeps string-literal element access computed after generic return narrowing", () => {
2949
+ const source = `
2950
+ type SettingsMap = { [key: string]: string };
2951
+
2952
+ declare const JsonSerializer: {
2953
+ Deserialize<T>(json: string): T | undefined;
2954
+ };
2955
+
2956
+ export function run(json: string): string | undefined {
2957
+ const settingsOrNull = JsonSerializer.Deserialize<SettingsMap>(json);
2958
+ if (settingsOrNull === undefined) {
2959
+ return undefined;
2960
+ }
2961
+ const settings = settingsOrNull;
2962
+ return settings["waiting_period_threshold"];
2963
+ }
2964
+ `;
2965
+ const { testProgram, ctx, options } = createTestProgram(source);
2966
+ const sourceFile = testProgram.sourceFiles[0];
2967
+ if (!sourceFile)
2968
+ throw new Error("Failed to create source file");
2969
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2970
+ expect(result.ok).to.equal(true);
2971
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2972
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5107")).to.equal(false);
2973
+ if (!result.ok)
2974
+ return;
2975
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
2976
+ expect(run).to.not.equal(undefined);
2977
+ if (!run)
2978
+ return;
2979
+ const returnStmt = [...run.body.statements]
2980
+ .reverse()
2981
+ .find((stmt) => stmt.kind === "returnStatement");
2982
+ expect(returnStmt).to.not.equal(undefined);
2983
+ if (!returnStmt?.expression)
2984
+ return;
2985
+ expect(returnStmt.expression.kind).to.equal("memberAccess");
2986
+ if (returnStmt.expression.kind !== "memberAccess")
2987
+ return;
2988
+ expect(returnStmt.expression.isComputed).to.equal(true);
2989
+ expect(returnStmt.expression.accessKind).to.equal("dictionary");
2990
+ });
2233
2991
  it("types inferred array Length access as int without unknown poison", () => {
2234
2992
  const source = `
2235
2993
  export function count(items: string[]): int {