@tsonic/frontend 0.0.71 → 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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/core-intrinsics/provenance.d.ts +11 -0
- package/dist/core-intrinsics/provenance.d.ts.map +1 -0
- package/dist/core-intrinsics/provenance.js +101 -0
- package/dist/core-intrinsics/provenance.js.map +1 -0
- package/dist/ir/binding/binding-factory.d.ts.map +1 -1
- package/dist/ir/binding/binding-factory.js +42 -6
- package/dist/ir/binding/binding-factory.js.map +1 -1
- package/dist/ir/binding-resolution.test.js +97 -0
- package/dist/ir/binding-resolution.test.js.map +1 -1
- package/dist/ir/builder/imports.d.ts.map +1 -1
- package/dist/ir/builder/imports.js +51 -8
- package/dist/ir/builder/imports.js.map +1 -1
- package/dist/ir/builder.test.js +1010 -53
- package/dist/ir/builder.test.js.map +1 -1
- package/dist/ir/converters/expressions/access/access-converter.d.ts.map +1 -1
- package/dist/ir/converters/expressions/access/access-converter.js +35 -1
- package/dist/ir/converters/expressions/access/access-converter.js.map +1 -1
- package/dist/ir/converters/expressions/access/member-resolution.d.ts.map +1 -1
- package/dist/ir/converters/expressions/access/member-resolution.js +24 -0
- package/dist/ir/converters/expressions/access/member-resolution.js.map +1 -1
- package/dist/ir/converters/expressions/calls/call-converter.d.ts.map +1 -1
- package/dist/ir/converters/expressions/calls/call-converter.js +38 -14
- package/dist/ir/converters/expressions/calls/call-converter.js.map +1 -1
- package/dist/ir/converters/expressions/calls/call-site-analysis.d.ts +1 -1
- package/dist/ir/converters/expressions/calls/call-site-analysis.d.ts.map +1 -1
- package/dist/ir/converters/expressions/calls/call-site-analysis.js +4 -30
- package/dist/ir/converters/expressions/calls/call-site-analysis.js.map +1 -1
- package/dist/ir/converters/expressions/collections.d.ts.map +1 -1
- package/dist/ir/converters/expressions/collections.js +154 -12
- package/dist/ir/converters/expressions/collections.js.map +1 -1
- package/dist/ir/converters/expressions/dynamic-import.d.ts.map +1 -1
- package/dist/ir/converters/expressions/dynamic-import.js +41 -7
- package/dist/ir/converters/expressions/dynamic-import.js.map +1 -1
- package/dist/ir/converters/flow-narrowing.d.ts +1 -0
- package/dist/ir/converters/flow-narrowing.d.ts.map +1 -1
- package/dist/ir/converters/flow-narrowing.js +223 -0
- package/dist/ir/converters/flow-narrowing.js.map +1 -1
- package/dist/ir/converters/statements/control/blocks.d.ts.map +1 -1
- package/dist/ir/converters/statements/control/blocks.js +46 -0
- package/dist/ir/converters/statements/control/blocks.js.map +1 -1
- package/dist/ir/program-context.d.ts.map +1 -1
- package/dist/ir/program-context.js +32 -2
- package/dist/ir/program-context.js.map +1 -1
- package/dist/ir/program-context.test.d.ts +2 -0
- package/dist/ir/program-context.test.d.ts.map +1 -0
- package/dist/ir/program-context.test.js +76 -0
- package/dist/ir/program-context.test.js.map +1 -0
- package/dist/ir/type-system/internal/type-converter/orchestrator.d.ts.map +1 -1
- package/dist/ir/type-system/internal/type-converter/orchestrator.js +250 -12
- package/dist/ir/type-system/internal/type-converter/orchestrator.js.map +1 -1
- package/dist/ir/type-system/internal/type-converter/orchestrator.test.js +19 -0
- package/dist/ir/type-system/internal/type-converter/orchestrator.test.js.map +1 -1
- package/dist/ir/type-system/internal/type-converter/references.d.ts.map +1 -1
- package/dist/ir/type-system/internal/type-converter/references.js +93 -3
- package/dist/ir/type-system/internal/type-converter/references.js.map +1 -1
- package/dist/ir/type-system/internal/type-converter/utility-types.d.ts.map +1 -1
- package/dist/ir/type-system/internal/type-converter/utility-types.js +9 -3
- package/dist/ir/type-system/internal/type-converter/utility-types.js.map +1 -1
- package/dist/ir/type-system/internal/type-converter/utility-types.test.js +19 -0
- package/dist/ir/type-system/internal/type-converter/utility-types.test.js.map +1 -1
- package/dist/ir/type-system/internal/universe/clr-catalog.d.ts.map +1 -1
- package/dist/ir/type-system/internal/universe/clr-catalog.js +20 -1
- package/dist/ir/type-system/internal/universe/clr-catalog.js.map +1 -1
- package/dist/ir/type-system/internal/universe/clr-catalog.test.js +43 -0
- package/dist/ir/type-system/internal/universe/clr-catalog.test.js.map +1 -1
- package/dist/ir/type-system/internal/universe/clr-entry-converter.d.ts.map +1 -1
- package/dist/ir/type-system/internal/universe/clr-entry-converter.js +43 -2
- package/dist/ir/type-system/internal/universe/clr-entry-converter.js.map +1 -1
- package/dist/ir/type-system/internal/universe/types.d.ts +10 -1
- package/dist/ir/type-system/internal/universe/types.d.ts.map +1 -1
- package/dist/ir/type-system/internal/universe/types.js.map +1 -1
- package/dist/ir/type-system/type-system-call-resolution.d.ts +1 -0
- package/dist/ir/type-system/type-system-call-resolution.d.ts.map +1 -1
- package/dist/ir/type-system/type-system-call-resolution.js +49 -22
- package/dist/ir/type-system/type-system-call-resolution.js.map +1 -1
- package/dist/ir/type-system/type-system-inference.d.ts.map +1 -1
- package/dist/ir/type-system/type-system-inference.js +39 -5
- package/dist/ir/type-system/type-system-inference.js.map +1 -1
- package/dist/ir/type-system/type-system-utilities.d.ts.map +1 -1
- package/dist/ir/type-system/type-system-utilities.js +4 -22
- package/dist/ir/type-system/type-system-utilities.js.map +1 -1
- package/dist/ir/type-system/type-system.d.ts +16 -0
- package/dist/ir/type-system/type-system.d.ts.map +1 -1
- package/dist/ir/type-system/type-system.js +3 -1
- package/dist/ir/type-system/type-system.js.map +1 -1
- package/dist/ir/types/index.d.ts +1 -1
- package/dist/ir/types/index.d.ts.map +1 -1
- package/dist/ir/types/index.js +1 -1
- package/dist/ir/types/index.js.map +1 -1
- package/dist/ir/types/type-ops.d.ts +2 -0
- package/dist/ir/types/type-ops.d.ts.map +1 -1
- package/dist/ir/types/type-ops.js +34 -12
- package/dist/ir/types/type-ops.js.map +1 -1
- package/dist/ir/types/type-ops.test.js +44 -1
- package/dist/ir/types/type-ops.test.js.map +1 -1
- package/dist/ir/types.d.ts +1 -1
- package/dist/ir/types.d.ts.map +1 -1
- package/dist/ir/types.js +1 -1
- package/dist/ir/types.js.map +1 -1
- package/dist/program/binding-registry.d.ts +2 -0
- package/dist/program/binding-registry.d.ts.map +1 -1
- package/dist/program/binding-registry.js +54 -12
- package/dist/program/binding-registry.js.map +1 -1
- package/dist/program/binding-types.d.ts +21 -1
- package/dist/program/binding-types.d.ts.map +1 -1
- package/dist/program/binding-types.js +70 -0
- package/dist/program/binding-types.js.map +1 -1
- package/dist/program/bindings.test.js +145 -0
- package/dist/program/bindings.test.js.map +1 -1
- package/dist/program/clr-bindings-discovery.d.ts.map +1 -1
- package/dist/program/clr-bindings-discovery.js +9 -3
- package/dist/program/clr-bindings-discovery.js.map +1 -1
- package/dist/program/clr-bindings-discovery.test.js +107 -0
- package/dist/program/clr-bindings-discovery.test.js.map +1 -1
- package/dist/program/creation.d.ts.map +1 -1
- package/dist/program/creation.js +22 -3
- package/dist/program/creation.js.map +1 -1
- package/dist/program/creation.test.js +37 -0
- package/dist/program/creation.test.js.map +1 -1
- package/dist/program/package-roots.js +4 -4
- package/dist/program/package-roots.js.map +1 -1
- package/dist/program/package-roots.test.d.ts +2 -0
- package/dist/program/package-roots.test.d.ts.map +1 -0
- package/dist/program/package-roots.test.js +42 -0
- package/dist/program/package-roots.test.js.map +1 -0
- package/dist/program.d.ts +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +1 -1
- package/dist/program.js.map +1 -1
- package/dist/resolver/import-resolution.d.ts.map +1 -1
- package/dist/resolver/import-resolution.js +2 -2
- package/dist/resolver/import-resolution.js.map +1 -1
- package/dist/resolver/source-package-resolution.d.ts +1 -0
- package/dist/resolver/source-package-resolution.d.ts.map +1 -1
- package/dist/resolver/source-package-resolution.js +6 -1
- package/dist/resolver/source-package-resolution.js.map +1 -1
- package/dist/resolver/source-package-resolution.test.js +7 -1
- package/dist/resolver/source-package-resolution.test.js.map +1 -1
- package/dist/resolver.test.js +37 -0
- package/dist/resolver.test.js.map +1 -1
- package/dist/surface/profiles.d.ts.map +1 -1
- package/dist/surface/profiles.js +12 -0
- package/dist/surface/profiles.js.map +1 -1
- package/dist/surface/profiles.test.js +43 -1
- package/dist/surface/profiles.test.js.map +1 -1
- package/dist/validation/core-intrinsics.d.ts.map +1 -1
- package/dist/validation/core-intrinsics.js +1 -115
- package/dist/validation/core-intrinsics.js.map +1 -1
- package/dist/validator.test.js +14 -0
- package/dist/validator.test.js.map +1 -1
- package/package.json +1 -1
package/dist/ir/builder.test.js
CHANGED
|
@@ -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";
|
|
@@ -1198,14 +1532,16 @@ describe("IR Builder", () => {
|
|
|
1198
1532
|
const doubledAccessor = initializer.behaviorMembers?.find((member) => member.kind === "propertyDeclaration" && member.name === "doubled");
|
|
1199
1533
|
expect(doubledAccessor).to.not.equal(undefined);
|
|
1200
1534
|
});
|
|
1201
|
-
it("
|
|
1535
|
+
it("infers unannotated object literal getter types from deterministic bodies", () => {
|
|
1202
1536
|
const source = `
|
|
1203
1537
|
export function run(): number {
|
|
1204
|
-
const slot = 1;
|
|
1205
1538
|
const obj = {
|
|
1206
|
-
|
|
1539
|
+
value: 21,
|
|
1540
|
+
get doubled() {
|
|
1541
|
+
return this.value * 2;
|
|
1542
|
+
},
|
|
1207
1543
|
};
|
|
1208
|
-
return obj
|
|
1544
|
+
return obj.doubled;
|
|
1209
1545
|
}
|
|
1210
1546
|
`;
|
|
1211
1547
|
const { testProgram, ctx, options } = createTestProgram(source);
|
|
@@ -1224,36 +1560,36 @@ describe("IR Builder", () => {
|
|
|
1224
1560
|
const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
|
|
1225
1561
|
stmt.declarations.some((declaration) => declaration.name.kind === "identifierPattern" &&
|
|
1226
1562
|
declaration.name.name === "obj" &&
|
|
1227
|
-
declaration.initializer
|
|
1563
|
+
declaration.initializer?.kind === "object"));
|
|
1228
1564
|
const initializer = decl?.declarations.find((declaration) => declaration.name.kind === "identifierPattern" &&
|
|
1229
1565
|
declaration.name.name === "obj")?.initializer;
|
|
1230
1566
|
expect(initializer?.kind).to.equal("object");
|
|
1231
1567
|
if (!initializer || initializer.kind !== "object")
|
|
1232
1568
|
return;
|
|
1233
|
-
const
|
|
1234
|
-
expect(
|
|
1569
|
+
const objectType = initializer.inferredType;
|
|
1570
|
+
expect(objectType?.kind).to.equal("objectType");
|
|
1571
|
+
if (!objectType || objectType.kind !== "objectType")
|
|
1572
|
+
return;
|
|
1573
|
+
const doubledMember = objectType.members.find((member) => member.kind === "propertySignature" && member.name === "doubled");
|
|
1574
|
+
expect(doubledMember?.kind).to.equal("propertySignature");
|
|
1575
|
+
if (!doubledMember || doubledMember.kind !== "propertySignature")
|
|
1576
|
+
return;
|
|
1577
|
+
expect(doubledMember.type.kind).to.equal("primitiveType");
|
|
1578
|
+
if (doubledMember.type.kind !== "primitiveType")
|
|
1579
|
+
return;
|
|
1580
|
+
expect(doubledMember.type.name).to.equal("number");
|
|
1235
1581
|
});
|
|
1236
|
-
it("
|
|
1582
|
+
it("infers unannotated object literal method return types from deterministic bodies", () => {
|
|
1237
1583
|
const source = `
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
foundAnchor: boolean;
|
|
1248
|
-
foundNewest: boolean;
|
|
1249
|
-
foundOldest: boolean;
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
export function run(anchor: string): Result<Payload, string> {
|
|
1253
|
-
const foundAnchor = anchor !== "newest" && anchor !== "oldest";
|
|
1254
|
-
const foundNewest = anchor === "newest";
|
|
1255
|
-
const foundOldest = anchor === "oldest";
|
|
1256
|
-
return ok({ foundAnchor, foundNewest, foundOldest });
|
|
1584
|
+
export function run(): number {
|
|
1585
|
+
const obj = {
|
|
1586
|
+
value: 21,
|
|
1587
|
+
inc() {
|
|
1588
|
+
this.value += 1;
|
|
1589
|
+
return this.value;
|
|
1590
|
+
},
|
|
1591
|
+
};
|
|
1592
|
+
return obj.inc();
|
|
1257
1593
|
}
|
|
1258
1594
|
`;
|
|
1259
1595
|
const { testProgram, ctx, options } = createTestProgram(source);
|
|
@@ -1262,45 +1598,205 @@ describe("IR Builder", () => {
|
|
|
1262
1598
|
throw new Error("Failed to create source file");
|
|
1263
1599
|
const result = buildIrModule(sourceFile, testProgram, options, ctx);
|
|
1264
1600
|
expect(result.ok).to.equal(true);
|
|
1601
|
+
expect(ctx.diagnostics.some((d) => d.code === "TSN7403")).to.equal(false);
|
|
1265
1602
|
if (!result.ok)
|
|
1266
1603
|
return;
|
|
1267
1604
|
const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
|
|
1268
1605
|
expect(run).to.not.equal(undefined);
|
|
1269
1606
|
if (!run)
|
|
1270
1607
|
return;
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1273
|
-
|
|
1608
|
+
const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
|
|
1609
|
+
stmt.declarations.some((declaration) => declaration.name.kind === "identifierPattern" &&
|
|
1610
|
+
declaration.name.name === "obj" &&
|
|
1611
|
+
declaration.initializer?.kind === "object"));
|
|
1612
|
+
const initializer = decl?.declarations.find((declaration) => declaration.name.kind === "identifierPattern" &&
|
|
1613
|
+
declaration.name.name === "obj")?.initializer;
|
|
1614
|
+
expect(initializer?.kind).to.equal("object");
|
|
1615
|
+
if (!initializer || initializer.kind !== "object")
|
|
1274
1616
|
return;
|
|
1275
|
-
const
|
|
1276
|
-
expect(
|
|
1277
|
-
if (!
|
|
1617
|
+
const objectType = initializer.inferredType;
|
|
1618
|
+
expect(objectType?.kind).to.equal("objectType");
|
|
1619
|
+
if (!objectType || objectType.kind !== "objectType")
|
|
1278
1620
|
return;
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1621
|
+
const incMember = objectType.members.find((member) => member.kind === "propertySignature" && member.name === "inc");
|
|
1622
|
+
expect(incMember?.kind).to.equal("propertySignature");
|
|
1623
|
+
if (!incMember || incMember.kind !== "propertySignature")
|
|
1624
|
+
return;
|
|
1625
|
+
expect(incMember.type.kind).to.equal("functionType");
|
|
1626
|
+
if (incMember.type.kind !== "functionType")
|
|
1627
|
+
return;
|
|
1628
|
+
expect(incMember.type.returnType.kind).to.not.equal("voidType");
|
|
1629
|
+
expect(incMember.type.returnType.kind).to.not.equal("unknownType");
|
|
1630
|
+
expect(incMember.type.returnType.kind).to.not.equal("anyType");
|
|
1631
|
+
const returnStmt = run.body.statements.find((stmt) => stmt.kind === "returnStatement");
|
|
1632
|
+
expect(returnStmt).to.not.equal(undefined);
|
|
1633
|
+
if (!returnStmt ||
|
|
1634
|
+
returnStmt.kind !== "returnStatement" ||
|
|
1635
|
+
!returnStmt.expression)
|
|
1636
|
+
return;
|
|
1637
|
+
expect(returnStmt.expression.kind).to.equal("call");
|
|
1638
|
+
if (returnStmt.expression.kind !== "call")
|
|
1639
|
+
return;
|
|
1640
|
+
expect(returnStmt.expression.inferredType?.kind).to.not.equal("voidType");
|
|
1641
|
+
expect(returnStmt.expression.inferredType?.kind).to.not.equal("unknownType");
|
|
1642
|
+
expect(returnStmt.expression.inferredType?.kind).to.not.equal("anyType");
|
|
1283
1643
|
});
|
|
1284
|
-
it("
|
|
1644
|
+
it("synthesizes exact numeric properties after nullish fallback narrowing", () => {
|
|
1285
1645
|
const source = `
|
|
1286
|
-
type
|
|
1287
|
-
type Err<E> = { success: false; error: E };
|
|
1288
|
-
type Result<T, E> = Ok<T> | Err<E>;
|
|
1646
|
+
import type { int } from "@tsonic/core/types.js";
|
|
1289
1647
|
|
|
1290
|
-
function
|
|
1291
|
-
return { success: true, data };
|
|
1292
|
-
}
|
|
1648
|
+
declare function parseRole(raw: string): int | undefined;
|
|
1293
1649
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
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;
|
|
1298
1657
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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
|
+
});
|
|
1697
|
+
it("normalizes computed const-literal numeric keys during synthesis", () => {
|
|
1698
|
+
const source = `
|
|
1699
|
+
export function run(): number {
|
|
1700
|
+
const slot = 1;
|
|
1701
|
+
const obj = {
|
|
1702
|
+
[slot]: 7,
|
|
1703
|
+
};
|
|
1704
|
+
return obj["1"];
|
|
1705
|
+
}
|
|
1706
|
+
`;
|
|
1707
|
+
const { testProgram, ctx, options } = createTestProgram(source);
|
|
1708
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
1709
|
+
if (!sourceFile)
|
|
1710
|
+
throw new Error("Failed to create source file");
|
|
1711
|
+
const result = buildIrModule(sourceFile, testProgram, options, ctx);
|
|
1712
|
+
expect(result.ok).to.equal(true);
|
|
1713
|
+
expect(ctx.diagnostics.some((d) => d.code === "TSN7403")).to.equal(false);
|
|
1714
|
+
if (!result.ok)
|
|
1715
|
+
return;
|
|
1716
|
+
const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
|
|
1717
|
+
expect(run).to.not.equal(undefined);
|
|
1718
|
+
if (!run)
|
|
1719
|
+
return;
|
|
1720
|
+
const decl = run.body.statements.find((stmt) => stmt.kind === "variableDeclaration" &&
|
|
1721
|
+
stmt.declarations.some((declaration) => declaration.name.kind === "identifierPattern" &&
|
|
1722
|
+
declaration.name.name === "obj" &&
|
|
1723
|
+
declaration.initializer !== undefined));
|
|
1724
|
+
const initializer = decl?.declarations.find((declaration) => declaration.name.kind === "identifierPattern" &&
|
|
1725
|
+
declaration.name.name === "obj")?.initializer;
|
|
1726
|
+
expect(initializer?.kind).to.equal("object");
|
|
1727
|
+
if (!initializer || initializer.kind !== "object")
|
|
1728
|
+
return;
|
|
1729
|
+
const slotProp = initializer.properties.find((prop) => prop.kind === "property" && prop.key === "1");
|
|
1730
|
+
expect(slotProp).to.not.equal(undefined);
|
|
1731
|
+
});
|
|
1732
|
+
it("threads expected return generic context into call argument typing", () => {
|
|
1733
|
+
const source = `
|
|
1734
|
+
type Ok<T> = { success: true; data: T };
|
|
1735
|
+
type Err<E> = { success: false; error: E };
|
|
1736
|
+
type Result<T, E> = Ok<T> | Err<E>;
|
|
1737
|
+
|
|
1738
|
+
function ok<T>(data: T): Ok<T> {
|
|
1739
|
+
return { success: true, data };
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
interface Payload {
|
|
1743
|
+
foundAnchor: boolean;
|
|
1744
|
+
foundNewest: boolean;
|
|
1745
|
+
foundOldest: boolean;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
export function run(anchor: string): Result<Payload, string> {
|
|
1749
|
+
const foundAnchor = anchor !== "newest" && anchor !== "oldest";
|
|
1750
|
+
const foundNewest = anchor === "newest";
|
|
1751
|
+
const foundOldest = anchor === "oldest";
|
|
1752
|
+
return ok({ foundAnchor, foundNewest, foundOldest });
|
|
1753
|
+
}
|
|
1754
|
+
`;
|
|
1755
|
+
const { testProgram, ctx, options } = createTestProgram(source);
|
|
1756
|
+
const sourceFile = testProgram.sourceFiles[0];
|
|
1757
|
+
if (!sourceFile)
|
|
1758
|
+
throw new Error("Failed to create source file");
|
|
1759
|
+
const result = buildIrModule(sourceFile, testProgram, options, ctx);
|
|
1760
|
+
expect(result.ok).to.equal(true);
|
|
1761
|
+
if (!result.ok)
|
|
1762
|
+
return;
|
|
1763
|
+
const run = result.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "run");
|
|
1764
|
+
expect(run).to.not.equal(undefined);
|
|
1765
|
+
if (!run)
|
|
1766
|
+
return;
|
|
1767
|
+
const retStmt = run.body.statements.find((stmt) => stmt.kind === "returnStatement");
|
|
1768
|
+
expect(retStmt?.expression?.kind).to.equal("call");
|
|
1769
|
+
if (!retStmt?.expression || retStmt.expression.kind !== "call")
|
|
1770
|
+
return;
|
|
1771
|
+
const arg0 = retStmt.expression.arguments[0];
|
|
1772
|
+
expect(arg0?.kind).to.equal("object");
|
|
1773
|
+
if (!arg0 || arg0.kind !== "object")
|
|
1774
|
+
return;
|
|
1775
|
+
expect(arg0.inferredType?.kind).to.equal("referenceType");
|
|
1776
|
+
if (arg0.inferredType?.kind === "referenceType") {
|
|
1777
|
+
expect(arg0.inferredType.name).to.equal("Payload");
|
|
1778
|
+
}
|
|
1779
|
+
});
|
|
1780
|
+
it("threads expected return generic context through async Promise wrappers", () => {
|
|
1781
|
+
const source = `
|
|
1782
|
+
type Ok<T> = { success: true; data: T };
|
|
1783
|
+
type Err<E> = { success: false; error: E };
|
|
1784
|
+
type Result<T, E> = Ok<T> | Err<E>;
|
|
1785
|
+
|
|
1786
|
+
function ok<T>(data: T): Ok<T> {
|
|
1787
|
+
return { success: true, data };
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
interface Payload {
|
|
1791
|
+
foundAnchor: boolean;
|
|
1792
|
+
foundNewest: boolean;
|
|
1793
|
+
foundOldest: boolean;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
export async function run(anchor: string): Promise<Result<Payload, string>> {
|
|
1797
|
+
const foundAnchor = anchor !== "newest" && anchor !== "oldest";
|
|
1798
|
+
const foundNewest = anchor === "newest";
|
|
1799
|
+
const foundOldest = anchor === "oldest";
|
|
1304
1800
|
return ok({ foundAnchor, foundNewest, foundOldest });
|
|
1305
1801
|
}
|
|
1306
1802
|
`;
|
|
@@ -1329,6 +1825,132 @@ describe("IR Builder", () => {
|
|
|
1329
1825
|
expect(arg0.inferredType.name).to.equal("Payload");
|
|
1330
1826
|
}
|
|
1331
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
|
+
});
|
|
1332
1954
|
it("does not leak type-alias cache entries across program contexts", () => {
|
|
1333
1955
|
const sourceA = `
|
|
1334
1956
|
export type UserId = string;
|
|
@@ -1691,12 +2313,102 @@ describe("IR Builder", () => {
|
|
|
1691
2313
|
return;
|
|
1692
2314
|
expect(property.key).to.equal("value");
|
|
1693
2315
|
expect(property.value.kind).to.equal("memberAccess");
|
|
2316
|
+
expect(property.value.inferredType?.kind).to.not.equal("voidType");
|
|
2317
|
+
expect(property.value.inferredType?.kind).to.not.equal("unknownType");
|
|
2318
|
+
expect(property.value.inferredType?.kind).to.not.equal("anyType");
|
|
1694
2319
|
expect(call.inferredType?.kind).to.equal("referenceType");
|
|
1695
2320
|
}
|
|
1696
2321
|
finally {
|
|
1697
2322
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1698
2323
|
}
|
|
1699
2324
|
});
|
|
2325
|
+
it("should preserve callable exports as function-valued namespace members", () => {
|
|
2326
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-dynamic-import-fn-"));
|
|
2327
|
+
try {
|
|
2328
|
+
fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ name: "app", version: "1.0.0", type: "module" }, null, 2));
|
|
2329
|
+
const srcDir = path.join(tempDir, "src");
|
|
2330
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
2331
|
+
const entryPath = path.join(srcDir, "index.ts");
|
|
2332
|
+
fs.writeFileSync(entryPath, [
|
|
2333
|
+
"export async function load() {",
|
|
2334
|
+
' const module = await import("./module.js");',
|
|
2335
|
+
" return module.value;",
|
|
2336
|
+
"}",
|
|
2337
|
+
].join("\n"));
|
|
2338
|
+
fs.writeFileSync(path.join(srcDir, "module.ts"), "export function value(): number { return 42; }\n");
|
|
2339
|
+
const tsProgram = ts.createProgram([entryPath, path.join(srcDir, "module.ts")], {
|
|
2340
|
+
target: ts.ScriptTarget.ES2022,
|
|
2341
|
+
module: ts.ModuleKind.NodeNext,
|
|
2342
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
2343
|
+
strict: true,
|
|
2344
|
+
noEmit: true,
|
|
2345
|
+
skipLibCheck: true,
|
|
2346
|
+
});
|
|
2347
|
+
const checker = tsProgram.getTypeChecker();
|
|
2348
|
+
const sourceFile = tsProgram.getSourceFile(entryPath);
|
|
2349
|
+
expect(sourceFile).to.not.equal(undefined);
|
|
2350
|
+
if (!sourceFile)
|
|
2351
|
+
return;
|
|
2352
|
+
const program = {
|
|
2353
|
+
program: tsProgram,
|
|
2354
|
+
checker,
|
|
2355
|
+
options: {
|
|
2356
|
+
projectRoot: tempDir,
|
|
2357
|
+
sourceRoot: srcDir,
|
|
2358
|
+
rootNamespace: "TestApp",
|
|
2359
|
+
strict: true,
|
|
2360
|
+
},
|
|
2361
|
+
sourceFiles: [
|
|
2362
|
+
sourceFile,
|
|
2363
|
+
tsProgram.getSourceFile(path.join(srcDir, "module.ts")),
|
|
2364
|
+
],
|
|
2365
|
+
declarationSourceFiles: [],
|
|
2366
|
+
metadata: new DotnetMetadataRegistry(),
|
|
2367
|
+
bindings: new BindingRegistry(),
|
|
2368
|
+
clrResolver: createClrBindingsResolver(tempDir),
|
|
2369
|
+
binding: createBinding(checker),
|
|
2370
|
+
};
|
|
2371
|
+
const options = { sourceRoot: srcDir, rootNamespace: "TestApp" };
|
|
2372
|
+
const ctx = createProgramContext(program, options);
|
|
2373
|
+
const moduleResult = buildIrModule(sourceFile, program, options, ctx);
|
|
2374
|
+
expect(moduleResult.ok).to.equal(true);
|
|
2375
|
+
if (!moduleResult.ok)
|
|
2376
|
+
return;
|
|
2377
|
+
const loadFn = moduleResult.value.body.find((stmt) => stmt.kind === "functionDeclaration" && stmt.name === "load");
|
|
2378
|
+
expect(loadFn).to.not.equal(undefined);
|
|
2379
|
+
if (!loadFn)
|
|
2380
|
+
return;
|
|
2381
|
+
const declStmt = loadFn.body.statements[0];
|
|
2382
|
+
expect(declStmt?.kind).to.equal("variableDeclaration");
|
|
2383
|
+
if (!declStmt || declStmt.kind !== "variableDeclaration")
|
|
2384
|
+
return;
|
|
2385
|
+
const initializer = declStmt.declarations[0]?.initializer;
|
|
2386
|
+
expect(initializer?.kind).to.equal("await");
|
|
2387
|
+
if (!initializer || initializer.kind !== "await")
|
|
2388
|
+
return;
|
|
2389
|
+
const call = initializer.expression;
|
|
2390
|
+
expect(call.kind).to.equal("call");
|
|
2391
|
+
if (call.kind !== "call" || !call.dynamicImportNamespace)
|
|
2392
|
+
return;
|
|
2393
|
+
const property = call.dynamicImportNamespace.properties[0];
|
|
2394
|
+
expect(property?.kind).to.equal("property");
|
|
2395
|
+
if (!property || property.kind !== "property")
|
|
2396
|
+
return;
|
|
2397
|
+
expect(property.value.inferredType?.kind).to.equal("functionType");
|
|
2398
|
+
const namespaceType = call.dynamicImportNamespace.inferredType;
|
|
2399
|
+
expect(namespaceType?.kind).to.equal("objectType");
|
|
2400
|
+
if (!namespaceType || namespaceType.kind !== "objectType")
|
|
2401
|
+
return;
|
|
2402
|
+
const valueMember = namespaceType.members.find((member) => member.kind === "propertySignature" && member.name === "value");
|
|
2403
|
+
expect(valueMember?.kind).to.equal("propertySignature");
|
|
2404
|
+
if (!valueMember || valueMember.kind !== "propertySignature")
|
|
2405
|
+
return;
|
|
2406
|
+
expect(valueMember.type.kind).to.equal("functionType");
|
|
2407
|
+
}
|
|
2408
|
+
finally {
|
|
2409
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
1700
2412
|
it("should represent empty closed-world dynamic import namespaces as Promise<object>", () => {
|
|
1701
2413
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsonic-builder-dynamic-import-empty-"));
|
|
1702
2414
|
try {
|
|
@@ -2031,6 +2743,251 @@ describe("IR Builder", () => {
|
|
|
2031
2743
|
elementType: { kind: "unknownType" },
|
|
2032
2744
|
});
|
|
2033
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
|
+
});
|
|
2034
2991
|
it("types inferred array Length access as int without unknown poison", () => {
|
|
2035
2992
|
const source = `
|
|
2036
2993
|
export function count(items: string[]): int {
|