@tsonic/frontend 0.0.72 → 0.0.74

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 (153) 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 +98 -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/binding-resolution.test.js.map +1 -1
  10. package/dist/ir/builder/imports.d.ts.map +1 -1
  11. package/dist/ir/builder/imports.js +48 -8
  12. package/dist/ir/builder/imports.js.map +1 -1
  13. package/dist/ir/builder.test.js +774 -12
  14. package/dist/ir/builder.test.js.map +1 -1
  15. package/dist/ir/converters/expressions/access/access-converter.d.ts.map +1 -1
  16. package/dist/ir/converters/expressions/access/access-converter.js +34 -1
  17. package/dist/ir/converters/expressions/access/access-converter.js.map +1 -1
  18. package/dist/ir/converters/expressions/access/member-resolution.d.ts.map +1 -1
  19. package/dist/ir/converters/expressions/access/member-resolution.js +24 -0
  20. package/dist/ir/converters/expressions/access/member-resolution.js.map +1 -1
  21. package/dist/ir/converters/expressions/calls/call-converter.d.ts.map +1 -1
  22. package/dist/ir/converters/expressions/calls/call-converter.js +14 -13
  23. package/dist/ir/converters/expressions/calls/call-converter.js.map +1 -1
  24. package/dist/ir/converters/expressions/calls/call-site-analysis.d.ts +1 -1
  25. package/dist/ir/converters/expressions/calls/call-site-analysis.d.ts.map +1 -1
  26. package/dist/ir/converters/expressions/calls/call-site-analysis.js +6 -30
  27. package/dist/ir/converters/expressions/calls/call-site-analysis.js.map +1 -1
  28. package/dist/ir/converters/expressions/collections.d.ts.map +1 -1
  29. package/dist/ir/converters/expressions/collections.js +17 -7
  30. package/dist/ir/converters/expressions/collections.js.map +1 -1
  31. package/dist/ir/converters/expressions/dynamic-import.d.ts.map +1 -1
  32. package/dist/ir/converters/expressions/dynamic-import.js.map +1 -1
  33. package/dist/ir/converters/flow-narrowing.d.ts +1 -0
  34. package/dist/ir/converters/flow-narrowing.d.ts.map +1 -1
  35. package/dist/ir/converters/flow-narrowing.js +225 -0
  36. package/dist/ir/converters/flow-narrowing.js.map +1 -1
  37. package/dist/ir/converters/statements/control/blocks.d.ts.map +1 -1
  38. package/dist/ir/converters/statements/control/blocks.js +48 -0
  39. package/dist/ir/converters/statements/control/blocks.js.map +1 -1
  40. package/dist/ir/program-context.d.ts.map +1 -1
  41. package/dist/ir/program-context.js +32 -2
  42. package/dist/ir/program-context.js.map +1 -1
  43. package/dist/ir/program-context.test.d.ts +2 -0
  44. package/dist/ir/program-context.test.d.ts.map +1 -0
  45. package/dist/ir/program-context.test.js +76 -0
  46. package/dist/ir/program-context.test.js.map +1 -0
  47. package/dist/ir/type-system/internal/type-converter/orchestrator.d.ts.map +1 -1
  48. package/dist/ir/type-system/internal/type-converter/orchestrator.js +256 -12
  49. package/dist/ir/type-system/internal/type-converter/orchestrator.js.map +1 -1
  50. package/dist/ir/type-system/internal/type-converter/orchestrator.test.js +19 -0
  51. package/dist/ir/type-system/internal/type-converter/orchestrator.test.js.map +1 -1
  52. package/dist/ir/type-system/internal/type-converter/references.d.ts.map +1 -1
  53. package/dist/ir/type-system/internal/type-converter/references.js +94 -3
  54. package/dist/ir/type-system/internal/type-converter/references.js.map +1 -1
  55. package/dist/ir/type-system/internal/type-converter/utility-types.d.ts.map +1 -1
  56. package/dist/ir/type-system/internal/type-converter/utility-types.js +9 -3
  57. package/dist/ir/type-system/internal/type-converter/utility-types.js.map +1 -1
  58. package/dist/ir/type-system/internal/type-converter/utility-types.test.js +19 -0
  59. package/dist/ir/type-system/internal/type-converter/utility-types.test.js.map +1 -1
  60. package/dist/ir/type-system/internal/universe/clr-catalog.d.ts.map +1 -1
  61. package/dist/ir/type-system/internal/universe/clr-catalog.js +20 -1
  62. package/dist/ir/type-system/internal/universe/clr-catalog.js.map +1 -1
  63. package/dist/ir/type-system/internal/universe/clr-catalog.test.js +43 -0
  64. package/dist/ir/type-system/internal/universe/clr-catalog.test.js.map +1 -1
  65. package/dist/ir/type-system/internal/universe/clr-entry-converter.d.ts.map +1 -1
  66. package/dist/ir/type-system/internal/universe/clr-entry-converter.js +44 -2
  67. package/dist/ir/type-system/internal/universe/clr-entry-converter.js.map +1 -1
  68. package/dist/ir/type-system/internal/universe/types.d.ts +10 -1
  69. package/dist/ir/type-system/internal/universe/types.d.ts.map +1 -1
  70. package/dist/ir/type-system/internal/universe/types.js.map +1 -1
  71. package/dist/ir/type-system/type-system-call-resolution.d.ts +1 -0
  72. package/dist/ir/type-system/type-system-call-resolution.d.ts.map +1 -1
  73. package/dist/ir/type-system/type-system-call-resolution.js +49 -22
  74. package/dist/ir/type-system/type-system-call-resolution.js.map +1 -1
  75. package/dist/ir/type-system/type-system-inference.d.ts.map +1 -1
  76. package/dist/ir/type-system/type-system-inference.js +39 -5
  77. package/dist/ir/type-system/type-system-inference.js.map +1 -1
  78. package/dist/ir/type-system/type-system-utilities.d.ts.map +1 -1
  79. package/dist/ir/type-system/type-system-utilities.js +6 -22
  80. package/dist/ir/type-system/type-system-utilities.js.map +1 -1
  81. package/dist/ir/type-system/type-system.d.ts +16 -0
  82. package/dist/ir/type-system/type-system.d.ts.map +1 -1
  83. package/dist/ir/type-system/type-system.js +3 -1
  84. package/dist/ir/type-system/type-system.js.map +1 -1
  85. package/dist/ir/types/index.d.ts +1 -1
  86. package/dist/ir/types/index.d.ts.map +1 -1
  87. package/dist/ir/types/index.js +1 -1
  88. package/dist/ir/types/index.js.map +1 -1
  89. package/dist/ir/types/type-ops.d.ts +2 -0
  90. package/dist/ir/types/type-ops.d.ts.map +1 -1
  91. package/dist/ir/types/type-ops.js +34 -12
  92. package/dist/ir/types/type-ops.js.map +1 -1
  93. package/dist/ir/types/type-ops.test.js +44 -1
  94. package/dist/ir/types/type-ops.test.js.map +1 -1
  95. package/dist/ir/types.d.ts +1 -1
  96. package/dist/ir/types.d.ts.map +1 -1
  97. package/dist/ir/types.js +1 -1
  98. package/dist/ir/types.js.map +1 -1
  99. package/dist/ir/validation/numeric-proof-pass.d.ts.map +1 -1
  100. package/dist/ir/validation/numeric-proof-pass.js +8 -7
  101. package/dist/ir/validation/numeric-proof-pass.js.map +1 -1
  102. package/dist/program/binding-registry.d.ts +2 -0
  103. package/dist/program/binding-registry.d.ts.map +1 -1
  104. package/dist/program/binding-registry.js +54 -12
  105. package/dist/program/binding-registry.js.map +1 -1
  106. package/dist/program/binding-types.d.ts +21 -1
  107. package/dist/program/binding-types.d.ts.map +1 -1
  108. package/dist/program/binding-types.js +80 -4
  109. package/dist/program/binding-types.js.map +1 -1
  110. package/dist/program/bindings.test.js +146 -2
  111. package/dist/program/bindings.test.js.map +1 -1
  112. package/dist/program/clr-bindings-discovery.d.ts.map +1 -1
  113. package/dist/program/clr-bindings-discovery.js +9 -3
  114. package/dist/program/clr-bindings-discovery.js.map +1 -1
  115. package/dist/program/clr-bindings-discovery.test.js +107 -0
  116. package/dist/program/clr-bindings-discovery.test.js.map +1 -1
  117. package/dist/program/creation.d.ts.map +1 -1
  118. package/dist/program/creation.js +22 -3
  119. package/dist/program/creation.js.map +1 -1
  120. package/dist/program/creation.test.js +34 -0
  121. package/dist/program/creation.test.js.map +1 -1
  122. package/dist/program/package-roots.js +4 -4
  123. package/dist/program/package-roots.js.map +1 -1
  124. package/dist/program/package-roots.test.d.ts +2 -0
  125. package/dist/program/package-roots.test.d.ts.map +1 -0
  126. package/dist/program/package-roots.test.js +42 -0
  127. package/dist/program/package-roots.test.js.map +1 -0
  128. package/dist/program.d.ts +1 -1
  129. package/dist/program.d.ts.map +1 -1
  130. package/dist/program.js +1 -1
  131. package/dist/program.js.map +1 -1
  132. package/dist/resolver/dynamic-import.test.js +20 -11
  133. package/dist/resolver/dynamic-import.test.js.map +1 -1
  134. package/dist/resolver/import-resolution.d.ts.map +1 -1
  135. package/dist/resolver/import-resolution.js +2 -2
  136. package/dist/resolver/import-resolution.js.map +1 -1
  137. package/dist/resolver/source-package-resolution.d.ts +1 -0
  138. package/dist/resolver/source-package-resolution.d.ts.map +1 -1
  139. package/dist/resolver/source-package-resolution.js +6 -1
  140. package/dist/resolver/source-package-resolution.js.map +1 -1
  141. package/dist/resolver/source-package-resolution.test.js +7 -1
  142. package/dist/resolver/source-package-resolution.test.js.map +1 -1
  143. package/dist/resolver.test.js +37 -0
  144. package/dist/resolver.test.js.map +1 -1
  145. package/dist/surface/profiles.d.ts.map +1 -1
  146. package/dist/surface/profiles.js +12 -0
  147. package/dist/surface/profiles.js.map +1 -1
  148. package/dist/surface/profiles.test.js +43 -1
  149. package/dist/surface/profiles.test.js.map +1 -1
  150. package/dist/validation/core-intrinsics.d.ts.map +1 -1
  151. package/dist/validation/core-intrinsics.js +1 -115
  152. package/dist/validation/core-intrinsics.js.map +1 -1
  153. 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,71 @@ 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" &&
816
+ decl.name.name === variableName);
817
+ expect(declaration?.initializer?.kind).to.equal(expectedKind);
818
+ };
819
+ it("does not lower locally declared nameof as the compiler intrinsic", () => {
820
+ expectVariableInitializerKind(`
821
+ function nameof(value: string): string {
822
+ return value + "!";
823
+ }
824
+
825
+ export const label = nameof("x");
826
+ `, "label", "call");
827
+ });
828
+ it("does not lower locally declared sizeof as the compiler intrinsic", () => {
829
+ expectVariableInitializerKind(`
830
+ function sizeof<T>(): number {
831
+ return 4;
832
+ }
833
+
834
+ export const bytes = sizeof<number>();
835
+ `, "bytes", "call");
836
+ });
837
+ it("does not lower locally declared defaultof/trycast/stackalloc/asinterface intrinsics", () => {
838
+ const source = `
839
+ function defaultof<T>(): T | undefined {
840
+ return undefined;
841
+ }
842
+ function trycast<T>(value: unknown): T | undefined {
843
+ return value as T | undefined;
844
+ }
845
+ function stackalloc<T>(size: number): T {
846
+ throw new Error(String(size));
847
+ }
848
+ function asinterface<T>(value: unknown): T {
849
+ return value as T;
850
+ }
851
+
852
+ interface Box { value: number; }
853
+
854
+ export const fallback = defaultof<number>();
855
+ export const maybe = trycast<Box>({ value: 1 });
856
+ export const mem = stackalloc<number>(16);
857
+ export const view = asinterface<Box>({ value: 1 });
858
+ `;
859
+ for (const variableName of ["fallback", "maybe", "mem", "view"]) {
860
+ expectVariableInitializerKind(source, variableName, "call");
861
+ }
862
+ });
863
+ });
580
864
  describe("Import Extraction", () => {
581
865
  it("should extract local imports", () => {
582
866
  const source = `
@@ -659,6 +943,57 @@ describe("IR Builder", () => {
659
943
  memberName: "buildSite",
660
944
  });
661
945
  });
946
+ it("should attach resolvedClrType for CLR type imports used as values", () => {
947
+ const source = `
948
+ import { Task as TaskValue } from "@tsonic/dotnet/System.Threading.Tasks.js";
949
+ `;
950
+ const { testProgram, ctx, options } = createTestProgram(source);
951
+ ctx.clrResolver = {
952
+ resolve: (s) => s === "@tsonic/dotnet/System.Threading.Tasks.js"
953
+ ? {
954
+ isClr: true,
955
+ packageName: "@tsonic/dotnet",
956
+ resolvedNamespace: "System.Threading.Tasks",
957
+ bindingsPath: "/x/tasks.bindings.json",
958
+ assembly: "System.Runtime",
959
+ }
960
+ : { isClr: false },
961
+ };
962
+ ctx.bindings.addBindings("/x/tasks.bindings.json", {
963
+ namespace: "System.Threading.Tasks",
964
+ types: [
965
+ {
966
+ alias: "Task",
967
+ clrName: "System.Threading.Tasks.Task",
968
+ assemblyName: "System.Runtime",
969
+ kind: "Class",
970
+ methods: [],
971
+ properties: [],
972
+ fields: [],
973
+ },
974
+ ],
975
+ exports: {},
976
+ });
977
+ const sourceFile = testProgram.sourceFiles[0];
978
+ if (!sourceFile)
979
+ throw new Error("Failed to create source file");
980
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
981
+ expect(result.ok).to.equal(true);
982
+ if (!result.ok)
983
+ return;
984
+ const imp = result.value.imports[0];
985
+ if (!imp)
986
+ throw new Error("Missing imports");
987
+ const spec = imp.specifiers[0];
988
+ if (!spec || spec.kind !== "named") {
989
+ throw new Error("Missing named specifier");
990
+ }
991
+ expect(spec.name).to.equal("Task");
992
+ expect(spec.localName).to.equal("TaskValue");
993
+ expect(spec.isType).to.not.equal(true);
994
+ expect(spec.resolvedClrType).to.equal("System.Threading.Tasks.Task");
995
+ expect(spec.resolvedClrValue).to.equal(undefined);
996
+ });
662
997
  it("should error if a CLR namespace value import lacks tsbindgen exports mapping", () => {
663
998
  const source = `
664
999
  import { buildSite } from "@demo/pkg/Demo.js";
@@ -1307,6 +1642,58 @@ describe("IR Builder", () => {
1307
1642
  expect(returnStmt.expression.inferredType?.kind).to.not.equal("unknownType");
1308
1643
  expect(returnStmt.expression.inferredType?.kind).to.not.equal("anyType");
1309
1644
  });
1645
+ it("synthesizes exact numeric properties after nullish fallback narrowing", () => {
1646
+ const source = `
1647
+ import type { int } from "@tsonic/core/types.js";
1648
+
1649
+ declare function parseRole(raw: string): int | undefined;
1650
+
1651
+ export function run(raw: string): int {
1652
+ const parsedInviteAsRole = parseRole(raw);
1653
+ const inviteAsRole = parsedInviteAsRole ?? (400 as int);
1654
+ const input = {
1655
+ inviteAsRole,
1656
+ };
1657
+ return input.inviteAsRole;
1658
+ }
1659
+ `;
1660
+ const { testProgram, ctx, options } = createTestProgram(source);
1661
+ const sourceFile = testProgram.sourceFiles[0];
1662
+ if (!sourceFile)
1663
+ throw new Error("Failed to create source file");
1664
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
1665
+ expect(result.ok).to.equal(true);
1666
+ expect(ctx.diagnostics.some((diagnostic) => diagnostic.code === "TSN5203" &&
1667
+ diagnostic.message.includes("inviteAsRole"))).to.equal(false);
1668
+ if (!result.ok)
1669
+ return;
1670
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
1671
+ expect(run).to.not.equal(undefined);
1672
+ if (!run)
1673
+ return;
1674
+ const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
1675
+ stmt.declarations.some((declaration) => declaration.name.kind === "identifierPattern" &&
1676
+ declaration.name.name === "input" &&
1677
+ declaration.initializer?.kind === "object"));
1678
+ const initializer = decl?.declarations.find((declaration) => declaration.name.kind === "identifierPattern" &&
1679
+ declaration.name.name === "input")?.initializer;
1680
+ expect(initializer?.kind).to.equal("object");
1681
+ if (!initializer || initializer.kind !== "object")
1682
+ return;
1683
+ const objectType = initializer.inferredType;
1684
+ expect(objectType?.kind).to.equal("objectType");
1685
+ if (!objectType || objectType.kind !== "objectType")
1686
+ return;
1687
+ const inviteAsRoleMember = objectType.members.find((member) => member.kind === "propertySignature" && 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;
@@ -1749,6 +2262,10 @@ describe("IR Builder", () => {
1749
2262
  expect(sourceFile).to.not.equal(undefined);
1750
2263
  if (!sourceFile)
1751
2264
  return;
2265
+ const moduleSourceFile = tsProgram.getSourceFile(path.join(srcDir, "module.ts"));
2266
+ expect(moduleSourceFile).to.not.equal(undefined);
2267
+ if (!moduleSourceFile)
2268
+ return;
1752
2269
  const program = {
1753
2270
  program: tsProgram,
1754
2271
  checker,
@@ -1758,10 +2275,7 @@ describe("IR Builder", () => {
1758
2275
  rootNamespace: "TestApp",
1759
2276
  strict: true,
1760
2277
  },
1761
- sourceFiles: [
1762
- sourceFile,
1763
- tsProgram.getSourceFile(path.join(srcDir, "module.ts")),
1764
- ],
2278
+ sourceFiles: [sourceFile, moduleSourceFile],
1765
2279
  declarationSourceFiles: [],
1766
2280
  metadata: new DotnetMetadataRegistry(),
1767
2281
  bindings: new BindingRegistry(),
@@ -1836,6 +2350,10 @@ describe("IR Builder", () => {
1836
2350
  expect(sourceFile).to.not.equal(undefined);
1837
2351
  if (!sourceFile)
1838
2352
  return;
2353
+ const moduleSourceFile = tsProgram.getSourceFile(path.join(srcDir, "module.ts"));
2354
+ expect(moduleSourceFile).to.not.equal(undefined);
2355
+ if (!moduleSourceFile)
2356
+ return;
1839
2357
  const program = {
1840
2358
  program: tsProgram,
1841
2359
  checker,
@@ -1845,10 +2363,7 @@ describe("IR Builder", () => {
1845
2363
  rootNamespace: "TestApp",
1846
2364
  strict: true,
1847
2365
  },
1848
- sourceFiles: [
1849
- sourceFile,
1850
- tsProgram.getSourceFile(path.join(srcDir, "module.ts")),
1851
- ],
2366
+ sourceFiles: [sourceFile, moduleSourceFile],
1852
2367
  declarationSourceFiles: [],
1853
2368
  metadata: new DotnetMetadataRegistry(),
1854
2369
  bindings: new BindingRegistry(),
@@ -1922,6 +2437,10 @@ describe("IR Builder", () => {
1922
2437
  expect(sourceFile).to.not.equal(undefined);
1923
2438
  if (!sourceFile)
1924
2439
  return;
2440
+ const moduleSourceFile = tsProgram.getSourceFile(path.join(srcDir, "module.ts"));
2441
+ expect(moduleSourceFile).to.not.equal(undefined);
2442
+ if (!moduleSourceFile)
2443
+ return;
1925
2444
  const program = {
1926
2445
  program: tsProgram,
1927
2446
  checker,
@@ -1931,10 +2450,7 @@ describe("IR Builder", () => {
1931
2450
  rootNamespace: "TestApp",
1932
2451
  strict: true,
1933
2452
  },
1934
- sourceFiles: [
1935
- sourceFile,
1936
- tsProgram.getSourceFile(path.join(srcDir, "module.ts")),
1937
- ],
2453
+ sourceFiles: [sourceFile, moduleSourceFile],
1938
2454
  declarationSourceFiles: [],
1939
2455
  metadata: new DotnetMetadataRegistry(),
1940
2456
  bindings: new BindingRegistry(),
@@ -2230,6 +2746,252 @@ describe("IR Builder", () => {
2230
2746
  elementType: { kind: "unknownType" },
2231
2747
  });
2232
2748
  });
2749
+ it("allows arbitrary property access on Record<string, unknown> without unknown poison", () => {
2750
+ const source = `
2751
+ export function fill(): Record<string, unknown> {
2752
+ const state: Record<string, unknown> = {};
2753
+ state.zulip_version = "1.0";
2754
+ state.realm_users = [];
2755
+ return state;
2756
+ }
2757
+ `;
2758
+ const { testProgram, ctx, options } = createTestProgram(source);
2759
+ const sourceFile = testProgram.sourceFiles[0];
2760
+ if (!sourceFile)
2761
+ throw new Error("Failed to create source file");
2762
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2763
+ expect(result.ok).to.equal(true);
2764
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2765
+ });
2766
+ it("allows declared unknown members on structural callback parameters", () => {
2767
+ const source = `
2768
+ export function project(
2769
+ rawUpdates: { stream_id: string; property: string; value: unknown }[]
2770
+ ): string[] {
2771
+ return rawUpdates.map((update) => String(update.value ?? ""));
2772
+ }
2773
+ `;
2774
+ const { testProgram, ctx, options } = createTestProgram(source);
2775
+ const sourceFile = testProgram.sourceFiles[0];
2776
+ if (!sourceFile)
2777
+ throw new Error("Failed to create source file");
2778
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2779
+ expect(result.ok).to.equal(true);
2780
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2781
+ });
2782
+ it("supports indexed access on generic discriminated-union payloads after narrowing", () => {
2783
+ const source = `
2784
+ type Ok<T> = { success: true; data: T };
2785
+ type Err<E> = { success: false; error: E };
2786
+ type Result<T, E> = Ok<T> | Err<E>;
2787
+
2788
+ declare function listTenants(): Result<{ Id: string }[], string>;
2789
+
2790
+ export function run(): string {
2791
+ const result = listTenants();
2792
+ if (!result.success) {
2793
+ return result.error;
2794
+ }
2795
+
2796
+ const data = result.data;
2797
+ return data[0]!.Id;
2798
+ }
2799
+ `;
2800
+ const { testProgram, ctx, options } = createTestProgram(source);
2801
+ const sourceFile = testProgram.sourceFiles[0];
2802
+ if (!sourceFile)
2803
+ throw new Error("Failed to create source file");
2804
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2805
+ expect(result.ok).to.equal(true);
2806
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2807
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5107")).to.equal(false);
2808
+ if (!result.ok)
2809
+ return;
2810
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
2811
+ expect(run).to.not.equal(undefined);
2812
+ if (!run)
2813
+ return;
2814
+ const dataDecl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
2815
+ stmt.declarations.some((declaration) => declaration.name.kind === "identifierPattern" &&
2816
+ declaration.name.name === "data"));
2817
+ expect(dataDecl).to.not.equal(undefined);
2818
+ const dataInit = dataDecl?.declarations[0]?.initializer;
2819
+ expect(dataInit?.kind).to.equal("memberAccess");
2820
+ if (!dataInit || dataInit.kind !== "memberAccess")
2821
+ return;
2822
+ expect(dataInit.inferredType?.kind).to.equal("arrayType");
2823
+ if (!dataInit.inferredType ||
2824
+ dataInit.inferredType.kind !== "arrayType") {
2825
+ return;
2826
+ }
2827
+ expect(dataInit.inferredType.elementType.kind).to.equal("objectType");
2828
+ if (dataInit.inferredType.elementType.kind !== "objectType")
2829
+ return;
2830
+ expect(dataInit.inferredType.elementType.members).to.have.length(1);
2831
+ const idMember = dataInit.inferredType.elementType.members[0];
2832
+ expect(idMember?.kind).to.equal("propertySignature");
2833
+ if (!idMember || idMember.kind !== "propertySignature")
2834
+ return;
2835
+ expect(idMember.name).to.equal("Id");
2836
+ expect(idMember.type).to.deep.equal({
2837
+ kind: "primitiveType",
2838
+ name: "string",
2839
+ });
2840
+ expect(idMember.isOptional).to.equal(false);
2841
+ expect(idMember.isReadonly).to.equal(false);
2842
+ });
2843
+ it("treats string-literal element access on narrowed unions like property access", () => {
2844
+ const source = `
2845
+ type Err = { error: string; code?: string };
2846
+ type Ok = { events: string[] };
2847
+
2848
+ declare function getEvents(): Err | Ok;
2849
+
2850
+ export function run(): string {
2851
+ const result = getEvents();
2852
+ if ("error" in result) {
2853
+ return result["code"] ?? result["error"];
2854
+ }
2855
+ return result["events"][0] ?? "";
2856
+ }
2857
+ `;
2858
+ const { testProgram, ctx, options } = createTestProgram(source);
2859
+ const sourceFile = testProgram.sourceFiles[0];
2860
+ if (!sourceFile)
2861
+ throw new Error("Failed to create source file");
2862
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2863
+ expect(result.ok).to.equal(true);
2864
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2865
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5107")).to.equal(false);
2866
+ if (!result.ok)
2867
+ return;
2868
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
2869
+ expect(run).to.not.equal(undefined);
2870
+ if (!run)
2871
+ return;
2872
+ const ifStmt = run.body.statements.find((stmt) => stmt.kind === "ifStatement");
2873
+ expect(ifStmt).to.not.equal(undefined);
2874
+ if (!ifStmt || ifStmt.kind !== "ifStatement")
2875
+ return;
2876
+ const thenReturn = ifStmt.thenStatement.kind === "blockStatement"
2877
+ ? ifStmt.thenStatement.statements.find((stmt) => stmt.kind === "returnStatement")
2878
+ : undefined;
2879
+ expect(thenReturn).to.not.equal(undefined);
2880
+ if (!thenReturn ||
2881
+ thenReturn.kind !== "returnStatement" ||
2882
+ !thenReturn.expression ||
2883
+ thenReturn.expression.kind !== "logical") {
2884
+ return;
2885
+ }
2886
+ const codeAccess = thenReturn.expression.left;
2887
+ expect(codeAccess.kind).to.equal("memberAccess");
2888
+ if (codeAccess.kind !== "memberAccess")
2889
+ return;
2890
+ expect(codeAccess.accessKind).to.not.equal("unknown");
2891
+ expect(codeAccess.inferredType).to.deep.equal({
2892
+ kind: "unionType",
2893
+ types: [
2894
+ { kind: "primitiveType", name: "string" },
2895
+ { kind: "primitiveType", name: "undefined" },
2896
+ ],
2897
+ });
2898
+ const finalReturn = [...run.body.statements]
2899
+ .reverse()
2900
+ .find((stmt) => stmt.kind === "returnStatement");
2901
+ expect(finalReturn).to.not.equal(undefined);
2902
+ if (!finalReturn ||
2903
+ !finalReturn.expression ||
2904
+ finalReturn.expression.kind !== "logical" ||
2905
+ finalReturn.expression.left.kind !== "memberAccess") {
2906
+ return;
2907
+ }
2908
+ const eventsIndex = finalReturn.expression.left;
2909
+ expect(eventsIndex.accessKind).to.equal("clrIndexer");
2910
+ expect(eventsIndex.inferredType).to.deep.equal({
2911
+ kind: "primitiveType",
2912
+ name: "string",
2913
+ });
2914
+ });
2915
+ it("keeps string-literal element access computed for alias-wrapped string dictionaries", () => {
2916
+ const source = `
2917
+ interface SettingsMap {
2918
+ [key: string]: string;
2919
+ }
2920
+
2921
+ declare function load(): SettingsMap;
2922
+
2923
+ export function run(): string | undefined {
2924
+ const settings = load();
2925
+ return settings["waiting_period_threshold"];
2926
+ }
2927
+ `;
2928
+ const { testProgram, ctx, options } = createTestProgram(source);
2929
+ const sourceFile = testProgram.sourceFiles[0];
2930
+ if (!sourceFile)
2931
+ throw new Error("Failed to create source file");
2932
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2933
+ expect(result.ok).to.equal(true);
2934
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2935
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5107")).to.equal(false);
2936
+ if (!result.ok)
2937
+ return;
2938
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
2939
+ expect(run).to.not.equal(undefined);
2940
+ if (!run)
2941
+ return;
2942
+ const returnStmt = run.body.statements.find((stmt) => stmt.kind === "returnStatement");
2943
+ expect(returnStmt).to.not.equal(undefined);
2944
+ if (!returnStmt?.expression)
2945
+ return;
2946
+ expect(returnStmt.expression.kind).to.equal("memberAccess");
2947
+ if (returnStmt.expression.kind !== "memberAccess")
2948
+ return;
2949
+ expect(returnStmt.expression.isComputed).to.equal(true);
2950
+ expect(returnStmt.expression.accessKind).to.equal("dictionary");
2951
+ });
2952
+ it("keeps string-literal element access computed after generic return narrowing", () => {
2953
+ const source = `
2954
+ type SettingsMap = { [key: string]: string };
2955
+
2956
+ declare const JsonSerializer: {
2957
+ Deserialize<T>(json: string): T | undefined;
2958
+ };
2959
+
2960
+ export function run(json: string): string | undefined {
2961
+ const settingsOrNull = JsonSerializer.Deserialize<SettingsMap>(json);
2962
+ if (settingsOrNull === undefined) {
2963
+ return undefined;
2964
+ }
2965
+ const settings = settingsOrNull;
2966
+ return settings["waiting_period_threshold"];
2967
+ }
2968
+ `;
2969
+ const { testProgram, ctx, options } = createTestProgram(source);
2970
+ const sourceFile = testProgram.sourceFiles[0];
2971
+ if (!sourceFile)
2972
+ throw new Error("Failed to create source file");
2973
+ const result = buildIrModule(sourceFile, testProgram, options, ctx);
2974
+ expect(result.ok).to.equal(true);
2975
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5203")).to.equal(false);
2976
+ expect(ctx.diagnostics.some((d) => d.code === "TSN5107")).to.equal(false);
2977
+ if (!result.ok)
2978
+ return;
2979
+ const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
2980
+ expect(run).to.not.equal(undefined);
2981
+ if (!run)
2982
+ return;
2983
+ const returnStmt = [...run.body.statements]
2984
+ .reverse()
2985
+ .find((stmt) => stmt.kind === "returnStatement");
2986
+ expect(returnStmt).to.not.equal(undefined);
2987
+ if (!returnStmt?.expression)
2988
+ return;
2989
+ expect(returnStmt.expression.kind).to.equal("memberAccess");
2990
+ if (returnStmt.expression.kind !== "memberAccess")
2991
+ return;
2992
+ expect(returnStmt.expression.isComputed).to.equal(true);
2993
+ expect(returnStmt.expression.accessKind).to.equal("dictionary");
2994
+ });
2233
2995
  it("types inferred array Length access as int without unknown poison", () => {
2234
2996
  const source = `
2235
2997
  export function count(items: string[]): int {