@tsonic/emitter 0.0.1
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/package.json +34 -0
- package/scripts/update-golden-tests.ts +119 -0
- package/src/adapter-generator.ts +112 -0
- package/src/array.test.ts +301 -0
- package/src/constants.ts +32 -0
- package/src/core/exports.ts +36 -0
- package/src/core/imports.test.ts +83 -0
- package/src/core/imports.ts +243 -0
- package/src/core/index.ts +9 -0
- package/src/core/module-emitter/assembly.ts +83 -0
- package/src/core/module-emitter/header.ts +19 -0
- package/src/core/module-emitter/index.ts +17 -0
- package/src/core/module-emitter/namespace.ts +39 -0
- package/src/core/module-emitter/orchestrator.ts +98 -0
- package/src/core/module-emitter/separation.ts +41 -0
- package/src/core/module-emitter/static-container.ts +75 -0
- package/src/core/module-emitter.test.ts +154 -0
- package/src/core/module-emitter.ts +6 -0
- package/src/core/module-map.ts +218 -0
- package/src/core/options.ts +16 -0
- package/src/core/type-params.ts +34 -0
- package/src/emitter-types/context.ts +91 -0
- package/src/emitter-types/core.ts +138 -0
- package/src/emitter-types/csharp-types.ts +42 -0
- package/src/emitter-types/formatting.ts +13 -0
- package/src/emitter-types/fqn.ts +81 -0
- package/src/emitter-types/index.ts +31 -0
- package/src/emitter.ts +107 -0
- package/src/expression-emitter.ts +112 -0
- package/src/expressions/access.ts +104 -0
- package/src/expressions/calls.ts +264 -0
- package/src/expressions/collections.ts +354 -0
- package/src/expressions/functions.ts +71 -0
- package/src/expressions/identifiers.ts +125 -0
- package/src/expressions/index.test.ts +515 -0
- package/src/expressions/index.ts +38 -0
- package/src/expressions/literals.ts +138 -0
- package/src/expressions/operators.ts +211 -0
- package/src/expressions/other.ts +63 -0
- package/src/generator-exchange.ts +120 -0
- package/src/generator.test.ts +193 -0
- package/src/golden-tests/config-parser.ts +67 -0
- package/src/golden-tests/discovery.ts +61 -0
- package/src/golden-tests/index.ts +10 -0
- package/src/golden-tests/registration.ts +26 -0
- package/src/golden-tests/runner.ts +131 -0
- package/src/golden-tests/tree-builder.ts +43 -0
- package/src/golden-tests/types.ts +21 -0
- package/src/golden.test.ts +40 -0
- package/src/hierarchical-bindings.test.ts +258 -0
- package/src/index.ts +14 -0
- package/src/integration.test.ts +303 -0
- package/src/specialization/call-site-rewriting.test.ts +99 -0
- package/src/specialization/collection/expressions.ts +184 -0
- package/src/specialization/collection/index.ts +7 -0
- package/src/specialization/collection/orchestrator.ts +25 -0
- package/src/specialization/collection/statements.ts +91 -0
- package/src/specialization/collection.ts +10 -0
- package/src/specialization/generation.ts +189 -0
- package/src/specialization/generic-classes.test.ts +59 -0
- package/src/specialization/generic-functions.test.ts +292 -0
- package/src/specialization/helpers.ts +39 -0
- package/src/specialization/index.ts +28 -0
- package/src/specialization/interfaces.test.ts +151 -0
- package/src/specialization/naming.ts +34 -0
- package/src/specialization/substitution.ts +186 -0
- package/src/specialization/type-aliases.test.ts +134 -0
- package/src/specialization/types.ts +19 -0
- package/src/specialization-generator.ts +23 -0
- package/src/statement-emitter.ts +117 -0
- package/src/statements/blocks.ts +115 -0
- package/src/statements/classes/helpers.ts +9 -0
- package/src/statements/classes/index.ts +13 -0
- package/src/statements/classes/inline-types.ts +79 -0
- package/src/statements/classes/members/constructors.ts +123 -0
- package/src/statements/classes/members/index.ts +8 -0
- package/src/statements/classes/members/methods.ts +137 -0
- package/src/statements/classes/members/orchestrator.ts +33 -0
- package/src/statements/classes/members/properties.ts +62 -0
- package/src/statements/classes/members.ts +6 -0
- package/src/statements/classes/parameters.ts +69 -0
- package/src/statements/classes/properties.ts +95 -0
- package/src/statements/classes.ts +14 -0
- package/src/statements/control/conditionals.ts +134 -0
- package/src/statements/control/exceptions.ts +59 -0
- package/src/statements/control/index.ts +11 -0
- package/src/statements/control/loops.ts +250 -0
- package/src/statements/control.ts +14 -0
- package/src/statements/declarations/classes.ts +89 -0
- package/src/statements/declarations/enums.ts +32 -0
- package/src/statements/declarations/functions.ts +147 -0
- package/src/statements/declarations/index.ts +10 -0
- package/src/statements/declarations/interfaces.ts +116 -0
- package/src/statements/declarations/structs.test.ts +182 -0
- package/src/statements/declarations/type-aliases.ts +104 -0
- package/src/statements/declarations/variables.ts +159 -0
- package/src/statements/declarations.ts +13 -0
- package/src/statements/index.test.ts +258 -0
- package/src/statements/index.ts +43 -0
- package/src/type-assertion.test.ts +143 -0
- package/src/type-emitter.ts +18 -0
- package/src/types/arrays.ts +21 -0
- package/src/types/dictionaries.ts +52 -0
- package/src/types/emitter.ts +76 -0
- package/src/types/functions.ts +45 -0
- package/src/types/index.test.ts +116 -0
- package/src/types/index.ts +14 -0
- package/src/types/intersections.ts +19 -0
- package/src/types/literals.ts +26 -0
- package/src/types/objects.ts +27 -0
- package/src/types/parameters.test.ts +146 -0
- package/src/types/parameters.ts +95 -0
- package/src/types/primitives.ts +24 -0
- package/src/types/references.ts +187 -0
- package/src/types/unions.test.ts +397 -0
- package/src/types/unions.ts +62 -0
- package/src/types.ts +33 -0
- package/testcases/README.md +213 -0
- package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
- package/testcases/arrays/basic/config.yaml +1 -0
- package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
- package/testcases/arrays/destructuring/config.yaml +1 -0
- package/testcases/arrays/methods/ArrayMethods.ts +6 -0
- package/testcases/arrays/methods/config.yaml +1 -0
- package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
- package/testcases/arrays/multidimensional/config.yaml +1 -0
- package/testcases/arrays/spread/ArraySpread.ts +3 -0
- package/testcases/arrays/spread/config.yaml +1 -0
- package/testcases/async/basic/AsyncFunction.ts +5 -0
- package/testcases/async/basic/config.yaml +1 -0
- package/testcases/classes/abstract/AbstractClasses.ts +53 -0
- package/testcases/classes/abstract/config.yaml +1 -0
- package/testcases/classes/basic/Person.ts +12 -0
- package/testcases/classes/basic/config.yaml +1 -0
- package/testcases/classes/constructor/User.ts +11 -0
- package/testcases/classes/constructor/config.yaml +1 -0
- package/testcases/classes/field-inference/Counter.ts +11 -0
- package/testcases/classes/field-inference/config.yaml +1 -0
- package/testcases/classes/inheritance/Inheritance.ts +24 -0
- package/testcases/classes/inheritance/config.yaml +1 -0
- package/testcases/classes/static-members/MathHelper.ts +12 -0
- package/testcases/classes/static-members/config.yaml +1 -0
- package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
- package/testcases/control-flow/error-handling/config.yaml +1 -0
- package/testcases/control-flow/loops/Loops.ts +21 -0
- package/testcases/control-flow/loops/config.yaml +1 -0
- package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
- package/testcases/control-flow/switch/config.yaml +1 -0
- package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
- package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
- package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
- package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
- package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
- package/testcases/edge-cases/shadowing/config.yaml +1 -0
- package/testcases/functions/arrow/ArrowFunction.ts +5 -0
- package/testcases/functions/arrow/config.yaml +1 -0
- package/testcases/functions/basic/Greet.ts +3 -0
- package/testcases/functions/basic/config.yaml +1 -0
- package/testcases/functions/closures/Closures.ts +11 -0
- package/testcases/functions/closures/config.yaml +1 -0
- package/testcases/functions/default-params/DefaultParams.ts +7 -0
- package/testcases/functions/default-params/config.yaml +1 -0
- package/testcases/functions/rest-params/RestParams.ts +7 -0
- package/testcases/functions/rest-params/config.yaml +1 -0
- package/testcases/functions/type-guards/TypeGuards.ts +52 -0
- package/testcases/functions/type-guards/config.yaml +1 -0
- package/testcases/operators/logical/LogicalOperators.ts +11 -0
- package/testcases/operators/logical/config.yaml +1 -0
- package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
- package/testcases/operators/nullish-coalescing/config.yaml +1 -0
- package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
- package/testcases/operators/optional-chaining/config.yaml +1 -0
- package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
- package/testcases/real-world/advanced-generics/config.yaml +1 -0
- package/testcases/real-world/async-ops/async-ops.ts +67 -0
- package/testcases/real-world/async-ops/config.yaml +1 -0
- package/testcases/real-world/business-logic/business-logic.ts +215 -0
- package/testcases/real-world/business-logic/config.yaml +1 -0
- package/testcases/real-world/calculator/calculator.ts +29 -0
- package/testcases/real-world/calculator/config.yaml +1 -0
- package/testcases/real-world/data-structures/config.yaml +1 -0
- package/testcases/real-world/data-structures/data-structures.ts +133 -0
- package/testcases/real-world/functional/config.yaml +1 -0
- package/testcases/real-world/functional/functional.ts +116 -0
- package/testcases/real-world/shapes/config.yaml +1 -0
- package/testcases/real-world/shapes/shapes.ts +87 -0
- package/testcases/real-world/string-utils/config.yaml +1 -0
- package/testcases/real-world/string-utils/string-utils.ts +47 -0
- package/testcases/real-world/todo-list/config.yaml +1 -0
- package/testcases/real-world/todo-list/todo-list.ts +52 -0
- package/testcases/real-world/type-guards/config.yaml +1 -0
- package/testcases/real-world/type-guards/type-guards.ts +71 -0
- package/testcases/structs/basic/Point.ts +9 -0
- package/testcases/structs/basic/config.yaml +1 -0
- package/testcases/types/conditional/ConditionalTypes.ts +35 -0
- package/testcases/types/conditional/config.yaml +1 -0
- package/testcases/types/constants/ModuleConstants.ts +6 -0
- package/testcases/types/constants/config.yaml +1 -0
- package/testcases/types/generics/Generics.ts +15 -0
- package/testcases/types/generics/config.yaml +1 -0
- package/testcases/types/interfaces/Interfaces.ts +14 -0
- package/testcases/types/interfaces/config.yaml +1 -0
- package/testcases/types/mapped/MappedTypes.ts +27 -0
- package/testcases/types/mapped/config.yaml +1 -0
- package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
- package/testcases/types/tuples-intersections/config.yaml +1 -0
- package/testcases/types/unions/UnionTypes.ts +11 -0
- package/testcases/types/unions/config.yaml +1 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Type Emission
|
|
3
|
+
* Tests emission of primitive types and async/Task types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it } from "mocha";
|
|
7
|
+
import { expect } from "chai";
|
|
8
|
+
import { emitModule } from "../emitter.js";
|
|
9
|
+
import { IrModule } from "@tsonic/frontend";
|
|
10
|
+
|
|
11
|
+
describe("Type Emission", () => {
|
|
12
|
+
it("should emit primitive types correctly", () => {
|
|
13
|
+
const module: IrModule = {
|
|
14
|
+
kind: "module",
|
|
15
|
+
filePath: "/src/test.ts",
|
|
16
|
+
namespace: "MyApp",
|
|
17
|
+
className: "test",
|
|
18
|
+
isStaticContainer: true,
|
|
19
|
+
imports: [],
|
|
20
|
+
body: [
|
|
21
|
+
{
|
|
22
|
+
kind: "functionDeclaration",
|
|
23
|
+
name: "test",
|
|
24
|
+
parameters: [
|
|
25
|
+
{
|
|
26
|
+
kind: "parameter",
|
|
27
|
+
pattern: { kind: "identifierPattern", name: "a" },
|
|
28
|
+
type: { kind: "primitiveType", name: "string" },
|
|
29
|
+
isOptional: false,
|
|
30
|
+
isRest: false,
|
|
31
|
+
passing: "value",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
kind: "parameter",
|
|
35
|
+
pattern: { kind: "identifierPattern", name: "b" },
|
|
36
|
+
type: { kind: "primitiveType", name: "number" },
|
|
37
|
+
isOptional: false,
|
|
38
|
+
isRest: false,
|
|
39
|
+
passing: "value",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
kind: "parameter",
|
|
43
|
+
pattern: { kind: "identifierPattern", name: "c" },
|
|
44
|
+
type: { kind: "primitiveType", name: "boolean" },
|
|
45
|
+
isOptional: false,
|
|
46
|
+
isRest: false,
|
|
47
|
+
passing: "value",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
returnType: { kind: "voidType" },
|
|
51
|
+
body: { kind: "blockStatement", statements: [] },
|
|
52
|
+
isExported: true,
|
|
53
|
+
isAsync: false,
|
|
54
|
+
isGenerator: false,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
exports: [],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const result = emitModule(module);
|
|
61
|
+
|
|
62
|
+
expect(result).to.include("string a");
|
|
63
|
+
expect(result).to.include("double b");
|
|
64
|
+
expect(result).to.include("bool c");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should emit async functions with Task types", () => {
|
|
68
|
+
const module: IrModule = {
|
|
69
|
+
kind: "module",
|
|
70
|
+
filePath: "/src/test.ts",
|
|
71
|
+
namespace: "MyApp",
|
|
72
|
+
className: "test",
|
|
73
|
+
isStaticContainer: true,
|
|
74
|
+
imports: [],
|
|
75
|
+
body: [
|
|
76
|
+
{
|
|
77
|
+
kind: "functionDeclaration",
|
|
78
|
+
name: "fetchData",
|
|
79
|
+
parameters: [],
|
|
80
|
+
returnType: {
|
|
81
|
+
kind: "referenceType",
|
|
82
|
+
name: "Promise",
|
|
83
|
+
typeArguments: [{ kind: "primitiveType", name: "string" }],
|
|
84
|
+
},
|
|
85
|
+
body: {
|
|
86
|
+
kind: "blockStatement",
|
|
87
|
+
statements: [
|
|
88
|
+
{
|
|
89
|
+
kind: "returnStatement",
|
|
90
|
+
expression: {
|
|
91
|
+
kind: "await",
|
|
92
|
+
expression: {
|
|
93
|
+
kind: "call",
|
|
94
|
+
callee: { kind: "identifier", name: "getData" },
|
|
95
|
+
arguments: [],
|
|
96
|
+
isOptional: false,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
isExported: true,
|
|
103
|
+
isAsync: true,
|
|
104
|
+
isGenerator: false,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
exports: [],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const result = emitModule(module);
|
|
111
|
+
|
|
112
|
+
expect(result).to.include("public static async Task<string> fetchData()");
|
|
113
|
+
expect(result).to.include("await getData()");
|
|
114
|
+
expect(result).to.include("using System.Threading.Tasks");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type emitter - Public API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { emitType } from "./emitter.js";
|
|
6
|
+
export { emitTypeParameters, emitParameterType } from "./parameters.js";
|
|
7
|
+
export { emitPrimitiveType } from "./primitives.js";
|
|
8
|
+
export { emitReferenceType } from "./references.js";
|
|
9
|
+
export { emitArrayType } from "./arrays.js";
|
|
10
|
+
export { emitFunctionType } from "./functions.js";
|
|
11
|
+
export { emitObjectType } from "./objects.js";
|
|
12
|
+
export { emitUnionType } from "./unions.js";
|
|
13
|
+
export { emitIntersectionType } from "./intersections.js";
|
|
14
|
+
export { emitLiteralType } from "./literals.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intersection type emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrType } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext } from "../types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Emit intersection types as object (C# doesn't have intersection types)
|
|
10
|
+
*/
|
|
11
|
+
export const emitIntersectionType = (
|
|
12
|
+
_type: Extract<IrType, { kind: "intersectionType" }>,
|
|
13
|
+
context: EmitterContext
|
|
14
|
+
): [string, EmitterContext] => {
|
|
15
|
+
// C# doesn't have intersection types
|
|
16
|
+
// For MVP, we'll use object
|
|
17
|
+
// In a more complete implementation, we might generate an interface
|
|
18
|
+
return ["object", context];
|
|
19
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Literal type emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrType } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext } from "../types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Emit literal types (string, number, boolean literals)
|
|
10
|
+
*/
|
|
11
|
+
export const emitLiteralType = (
|
|
12
|
+
type: Extract<IrType, { kind: "literalType" }>,
|
|
13
|
+
context: EmitterContext
|
|
14
|
+
): [string, EmitterContext] => {
|
|
15
|
+
// For literal types, we emit the base type
|
|
16
|
+
if (typeof type.value === "string") {
|
|
17
|
+
return ["string", context];
|
|
18
|
+
}
|
|
19
|
+
if (typeof type.value === "number") {
|
|
20
|
+
return ["double", context];
|
|
21
|
+
}
|
|
22
|
+
if (typeof type.value === "boolean") {
|
|
23
|
+
return ["bool", context];
|
|
24
|
+
}
|
|
25
|
+
return ["object", context];
|
|
26
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object type emission
|
|
3
|
+
*
|
|
4
|
+
* IrObjectType represents anonymous object types like `{ x: number }`.
|
|
5
|
+
* These should be caught by frontend validation (TSN7403) and never reach the emitter.
|
|
6
|
+
*
|
|
7
|
+
* Named types (interfaces, type aliases, classes) become IrReferenceType instead.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { IrType } from "@tsonic/frontend";
|
|
11
|
+
import { EmitterContext } from "../types.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Emit object types
|
|
15
|
+
*
|
|
16
|
+
* ICE: This should never be called. Frontend validation (TSN7403) should
|
|
17
|
+
* reject anonymous object types. If we reach here, validation has a gap.
|
|
18
|
+
*/
|
|
19
|
+
export const emitObjectType = (
|
|
20
|
+
_type: Extract<IrType, { kind: "objectType" }>,
|
|
21
|
+
_context: EmitterContext
|
|
22
|
+
): [string, EmitterContext] => {
|
|
23
|
+
// ICE: Frontend validation (TSN7403) should have caught this.
|
|
24
|
+
throw new Error(
|
|
25
|
+
"ICE: Anonymous object type reached emitter - validation missed TSN7403"
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test ref/out/in parameter emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it } from "mocha";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import { emitParameters } from "../statements/classes/parameters.js";
|
|
8
|
+
import type { IrParameter } from "@tsonic/frontend";
|
|
9
|
+
import type { EmitterContext } from "../types.js";
|
|
10
|
+
|
|
11
|
+
describe("Parameter modifiers (ref/out/in)", () => {
|
|
12
|
+
const baseContext: EmitterContext = {
|
|
13
|
+
indentLevel: 0,
|
|
14
|
+
isStatic: false,
|
|
15
|
+
isAsync: false,
|
|
16
|
+
options: { runtime: "dotnet", rootNamespace: "Test" },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
it("should emit out parameter with out modifier", () => {
|
|
20
|
+
const params: IrParameter[] = [
|
|
21
|
+
{
|
|
22
|
+
kind: "parameter",
|
|
23
|
+
pattern: { kind: "identifierPattern", name: "result" },
|
|
24
|
+
type: {
|
|
25
|
+
kind: "referenceType",
|
|
26
|
+
name: "out",
|
|
27
|
+
typeArguments: [
|
|
28
|
+
{
|
|
29
|
+
kind: "referenceType",
|
|
30
|
+
name: "int",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
isOptional: false,
|
|
35
|
+
isRest: false,
|
|
36
|
+
passing: "out",
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const [emitted, _context] = emitParameters(params, baseContext);
|
|
41
|
+
expect(emitted).to.equal("out int result");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should emit ref parameter with ref modifier", () => {
|
|
45
|
+
const params: IrParameter[] = [
|
|
46
|
+
{
|
|
47
|
+
kind: "parameter",
|
|
48
|
+
pattern: { kind: "identifierPattern", name: "value" },
|
|
49
|
+
type: {
|
|
50
|
+
kind: "referenceType",
|
|
51
|
+
name: "ref",
|
|
52
|
+
typeArguments: [
|
|
53
|
+
{
|
|
54
|
+
kind: "referenceType",
|
|
55
|
+
name: "int",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
isOptional: false,
|
|
60
|
+
isRest: false,
|
|
61
|
+
passing: "ref",
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const [emitted, _context] = emitParameters(params, baseContext);
|
|
66
|
+
expect(emitted).to.equal("ref int value");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should emit in parameter with in modifier", () => {
|
|
70
|
+
const params: IrParameter[] = [
|
|
71
|
+
{
|
|
72
|
+
kind: "parameter",
|
|
73
|
+
pattern: { kind: "identifierPattern", name: "value" },
|
|
74
|
+
type: {
|
|
75
|
+
kind: "referenceType",
|
|
76
|
+
name: "In",
|
|
77
|
+
typeArguments: [
|
|
78
|
+
{
|
|
79
|
+
kind: "referenceType",
|
|
80
|
+
name: "int",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
isOptional: false,
|
|
85
|
+
isRest: false,
|
|
86
|
+
passing: "in",
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const [emitted, _context] = emitParameters(params, baseContext);
|
|
91
|
+
expect(emitted).to.equal("in int value");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should emit multiple parameters with mixed modifiers", () => {
|
|
95
|
+
const params: IrParameter[] = [
|
|
96
|
+
{
|
|
97
|
+
kind: "parameter",
|
|
98
|
+
pattern: { kind: "identifierPattern", name: "input" },
|
|
99
|
+
type: {
|
|
100
|
+
kind: "referenceType",
|
|
101
|
+
name: "int",
|
|
102
|
+
},
|
|
103
|
+
isOptional: false,
|
|
104
|
+
isRest: false,
|
|
105
|
+
passing: "value",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
kind: "parameter",
|
|
109
|
+
pattern: { kind: "identifierPattern", name: "output" },
|
|
110
|
+
type: {
|
|
111
|
+
kind: "referenceType",
|
|
112
|
+
name: "out",
|
|
113
|
+
typeArguments: [
|
|
114
|
+
{
|
|
115
|
+
kind: "referenceType",
|
|
116
|
+
name: "int",
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
isOptional: false,
|
|
121
|
+
isRest: false,
|
|
122
|
+
passing: "out",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
kind: "parameter",
|
|
126
|
+
pattern: { kind: "identifierPattern", name: "counter" },
|
|
127
|
+
type: {
|
|
128
|
+
kind: "referenceType",
|
|
129
|
+
name: "ref",
|
|
130
|
+
typeArguments: [
|
|
131
|
+
{
|
|
132
|
+
kind: "referenceType",
|
|
133
|
+
name: "int",
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
isOptional: false,
|
|
138
|
+
isRest: false,
|
|
139
|
+
passing: "ref",
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const [emitted, _context] = emitParameters(params, baseContext);
|
|
144
|
+
expect(emitted).to.equal("int input, out int output, ref int counter");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type parameter and parameter type emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrType, IrTypeParameter } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext } from "../types.js";
|
|
7
|
+
import { emitType } from "./emitter.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Emit C# type parameters with constraints
|
|
11
|
+
* Example: <T, U extends Foo> → <T, U> with where clauses
|
|
12
|
+
*/
|
|
13
|
+
export const emitTypeParameters = (
|
|
14
|
+
typeParams: readonly IrTypeParameter[] | undefined,
|
|
15
|
+
context: EmitterContext
|
|
16
|
+
): [string, string[], EmitterContext] => {
|
|
17
|
+
if (!typeParams || typeParams.length === 0) {
|
|
18
|
+
return ["", [], context];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const paramNames = typeParams.map((tp) => tp.name).join(", ");
|
|
22
|
+
const typeParamsStr = `<${paramNames}>`;
|
|
23
|
+
|
|
24
|
+
// Build where clauses for constraints
|
|
25
|
+
const whereClauses: string[] = [];
|
|
26
|
+
let currentContext = context;
|
|
27
|
+
|
|
28
|
+
for (const tp of typeParams) {
|
|
29
|
+
if (tp.constraint) {
|
|
30
|
+
// Handle structural constraints specially - they generate adapter interfaces
|
|
31
|
+
// Don't call emitType on objectType constraints (would trigger ICE)
|
|
32
|
+
if (tp.isStructuralConstraint) {
|
|
33
|
+
// Structural constraints generate interfaces - reference them
|
|
34
|
+
whereClauses.push(`where ${tp.name} : __Constraint_${tp.name}`);
|
|
35
|
+
} else {
|
|
36
|
+
const [constraintStr, newContext] = emitType(
|
|
37
|
+
tp.constraint,
|
|
38
|
+
currentContext
|
|
39
|
+
);
|
|
40
|
+
currentContext = newContext;
|
|
41
|
+
whereClauses.push(`where ${tp.name} : ${constraintStr}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return [typeParamsStr, whereClauses, currentContext];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if a type is a ref/out/in wrapper type and return the inner type
|
|
51
|
+
*/
|
|
52
|
+
const unwrapParameterModifierType = (type: IrType): IrType | null => {
|
|
53
|
+
if (type.kind !== "referenceType") {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const name = type.name;
|
|
58
|
+
// Check for wrapper types: out<T>, ref<T>, In<T>
|
|
59
|
+
if (
|
|
60
|
+
(name === "out" || name === "ref" || name === "In") &&
|
|
61
|
+
type.typeArguments &&
|
|
62
|
+
type.typeArguments.length === 1
|
|
63
|
+
) {
|
|
64
|
+
const innerType = type.typeArguments[0];
|
|
65
|
+
return innerType ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Emit a parameter type with optional and default value handling
|
|
73
|
+
*/
|
|
74
|
+
export const emitParameterType = (
|
|
75
|
+
type: IrType | undefined,
|
|
76
|
+
isOptional: boolean,
|
|
77
|
+
context: EmitterContext
|
|
78
|
+
): [string, EmitterContext] => {
|
|
79
|
+
const typeNode = type ?? { kind: "anyType" as const };
|
|
80
|
+
|
|
81
|
+
// Unwrap ref/out/in wrapper types - the modifier is handled separately
|
|
82
|
+
const unwrapped = unwrapParameterModifierType(typeNode);
|
|
83
|
+
const actualType = unwrapped ?? typeNode;
|
|
84
|
+
|
|
85
|
+
const [baseType, newContext] = emitType(actualType, context);
|
|
86
|
+
|
|
87
|
+
// For optional parameters, add ? suffix for nullable types
|
|
88
|
+
// This includes both value types (double?, int?) and reference types (string?)
|
|
89
|
+
// per spec/04-type-mappings.md:21-78
|
|
90
|
+
if (isOptional) {
|
|
91
|
+
return [`${baseType}?`, newContext];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return [baseType, newContext];
|
|
95
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitive type emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrType } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext } from "../types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Emit primitive types (number, string, boolean, null, undefined)
|
|
10
|
+
*/
|
|
11
|
+
export const emitPrimitiveType = (
|
|
12
|
+
type: Extract<IrType, { kind: "primitiveType" }>,
|
|
13
|
+
context: EmitterContext
|
|
14
|
+
): [string, EmitterContext] => {
|
|
15
|
+
const typeMap: Record<string, string> = {
|
|
16
|
+
number: "double",
|
|
17
|
+
string: "string",
|
|
18
|
+
boolean: "bool",
|
|
19
|
+
null: "object",
|
|
20
|
+
undefined: "object",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return [typeMap[type.name] ?? "object", context];
|
|
24
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference type emission (Array, Promise, Error, etc.)
|
|
3
|
+
*
|
|
4
|
+
* All types are emitted with global:: prefix for unambiguous resolution.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { IrType } from "@tsonic/frontend";
|
|
8
|
+
import { EmitterContext } from "../types.js";
|
|
9
|
+
import { emitType } from "./emitter.js";
|
|
10
|
+
import {
|
|
11
|
+
isNestedType,
|
|
12
|
+
tsCSharpNestedTypeName,
|
|
13
|
+
} from "@tsonic/frontend/types/nested-types.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a type name indicates an unsupported support type.
|
|
17
|
+
*
|
|
18
|
+
* TODO: This is a basic check. Full implementation requires:
|
|
19
|
+
* 1. Access to TypeScript type checker
|
|
20
|
+
* 2. Integration with support-types.ts helpers
|
|
21
|
+
* 3. Proper diagnostic reporting
|
|
22
|
+
*
|
|
23
|
+
* For now, we check the type name string.
|
|
24
|
+
*/
|
|
25
|
+
const checkUnsupportedSupportType = (typeName: string): string | undefined => {
|
|
26
|
+
if (
|
|
27
|
+
typeName === "TSUnsafePointer" ||
|
|
28
|
+
typeName.startsWith("TSUnsafePointer<")
|
|
29
|
+
) {
|
|
30
|
+
return "Unsafe pointers are not supported in Tsonic. Use IntPtr for opaque handles.";
|
|
31
|
+
}
|
|
32
|
+
if (typeName === "TSFixed" || typeName.startsWith("TSFixed<")) {
|
|
33
|
+
return "Fixed-size buffers (unsafe feature) are not supported. Use arrays or Span<T> instead.";
|
|
34
|
+
}
|
|
35
|
+
if (typeName === "TSStackAlloc" || typeName.startsWith("TSStackAlloc<")) {
|
|
36
|
+
return "stackalloc is not supported in Tsonic. Use heap-allocated arrays instead.";
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Emit reference types with type arguments
|
|
43
|
+
*/
|
|
44
|
+
export const emitReferenceType = (
|
|
45
|
+
type: Extract<IrType, { kind: "referenceType" }>,
|
|
46
|
+
context: EmitterContext
|
|
47
|
+
): [string, EmitterContext] => {
|
|
48
|
+
const { name, typeArguments, resolvedClrType } = type;
|
|
49
|
+
|
|
50
|
+
// If the type has a pre-resolved CLR type (from IR), use it
|
|
51
|
+
if (resolvedClrType) {
|
|
52
|
+
if (typeArguments && typeArguments.length > 0) {
|
|
53
|
+
const typeParams: string[] = [];
|
|
54
|
+
let currentContext = context;
|
|
55
|
+
for (const typeArg of typeArguments) {
|
|
56
|
+
const [paramType, newContext] = emitType(typeArg, currentContext);
|
|
57
|
+
typeParams.push(paramType);
|
|
58
|
+
currentContext = newContext;
|
|
59
|
+
}
|
|
60
|
+
return [`${resolvedClrType}<${typeParams.join(", ")}>`, currentContext];
|
|
61
|
+
}
|
|
62
|
+
return [resolvedClrType, context];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check if this type is imported - use pre-computed CLR name directly
|
|
66
|
+
const importBinding = context.importBindings?.get(name);
|
|
67
|
+
if (importBinding) {
|
|
68
|
+
// Use clrName directly - all resolution was done when building the binding
|
|
69
|
+
// For type imports: clrName is the type's FQN (e.g., "MultiFileTypes.models.User")
|
|
70
|
+
// For value imports: clrName is container, member is the export name
|
|
71
|
+
// Note: Type references should only match type bindings; value bindings
|
|
72
|
+
// appearing here would be a bug (referencing a function as a type)
|
|
73
|
+
const qualifiedName =
|
|
74
|
+
importBinding.kind === "type"
|
|
75
|
+
? importBinding.clrName
|
|
76
|
+
: importBinding.member
|
|
77
|
+
? `${importBinding.clrName}.${importBinding.member}`
|
|
78
|
+
: importBinding.clrName;
|
|
79
|
+
|
|
80
|
+
if (typeArguments && typeArguments.length > 0) {
|
|
81
|
+
const typeParams: string[] = [];
|
|
82
|
+
let currentContext = context;
|
|
83
|
+
for (const typeArg of typeArguments) {
|
|
84
|
+
const [paramType, newContext] = emitType(typeArg, currentContext);
|
|
85
|
+
typeParams.push(paramType);
|
|
86
|
+
currentContext = newContext;
|
|
87
|
+
}
|
|
88
|
+
return [`${qualifiedName}<${typeParams.join(", ")}>`, currentContext];
|
|
89
|
+
}
|
|
90
|
+
return [qualifiedName, context];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check for unsupported support types
|
|
94
|
+
const unsupportedError = checkUnsupportedSupportType(name);
|
|
95
|
+
if (unsupportedError) {
|
|
96
|
+
// TODO: Report diagnostic error instead of throwing
|
|
97
|
+
// For now, emit a comment to make the error visible in generated code
|
|
98
|
+
console.warn(`[Tsonic] ${unsupportedError}`);
|
|
99
|
+
return [`/* ERROR: ${unsupportedError} */ object`, context];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Handle built-in types
|
|
103
|
+
if (name === "Array" && typeArguments && typeArguments.length > 0) {
|
|
104
|
+
const firstArg = typeArguments[0];
|
|
105
|
+
if (!firstArg) {
|
|
106
|
+
return [`global::System.Collections.Generic.List<object>`, context];
|
|
107
|
+
}
|
|
108
|
+
const [elementType, newContext] = emitType(firstArg, context);
|
|
109
|
+
return [
|
|
110
|
+
`global::System.Collections.Generic.List<${elementType}>`,
|
|
111
|
+
newContext,
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (name === "Promise" && typeArguments && typeArguments.length > 0) {
|
|
116
|
+
const firstArg = typeArguments[0];
|
|
117
|
+
if (!firstArg) {
|
|
118
|
+
return [`global::System.Threading.Tasks.Task`, context];
|
|
119
|
+
}
|
|
120
|
+
const [elementType, newContext] = emitType(firstArg, context);
|
|
121
|
+
// Promise<void> should map to Task (not Task<void>)
|
|
122
|
+
if (elementType === "void") {
|
|
123
|
+
return [`global::System.Threading.Tasks.Task`, newContext];
|
|
124
|
+
}
|
|
125
|
+
return [`global::System.Threading.Tasks.Task<${elementType}>`, newContext];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (name === "Promise") {
|
|
129
|
+
return ["global::System.Threading.Tasks.Task", context];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Map common JS types to .NET equivalents
|
|
133
|
+
// Note: Date, RegExp, Map, Set are NOT SUPPORTED in MVP
|
|
134
|
+
// Users should use System.DateTime, System.Text.RegularExpressions.Regex,
|
|
135
|
+
// Dictionary<K,V>, and HashSet<T> directly
|
|
136
|
+
const runtimeTypes: Record<string, string> = {
|
|
137
|
+
Error: "System.Exception",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (name in runtimeTypes) {
|
|
141
|
+
const csharpType = runtimeTypes[name];
|
|
142
|
+
if (!csharpType) {
|
|
143
|
+
return [name, context];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Always emit with global:: prefix for unambiguous resolution
|
|
147
|
+
const fqnType = `global::${csharpType}`;
|
|
148
|
+
|
|
149
|
+
if (typeArguments && typeArguments.length > 0) {
|
|
150
|
+
const typeParams: string[] = [];
|
|
151
|
+
let currentContext = context;
|
|
152
|
+
|
|
153
|
+
for (const typeArg of typeArguments) {
|
|
154
|
+
const [paramType, newContext] = emitType(typeArg, currentContext);
|
|
155
|
+
typeParams.push(paramType);
|
|
156
|
+
currentContext = newContext;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return [`${fqnType}<${typeParams.join(", ")}>`, currentContext];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return [fqnType, context];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Handle type arguments for other reference types
|
|
166
|
+
if (typeArguments && typeArguments.length > 0) {
|
|
167
|
+
const typeParams: string[] = [];
|
|
168
|
+
let currentContext = context;
|
|
169
|
+
|
|
170
|
+
for (const typeArg of typeArguments) {
|
|
171
|
+
const [paramType, newContext] = emitType(typeArg, currentContext);
|
|
172
|
+
typeParams.push(paramType);
|
|
173
|
+
currentContext = newContext;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Convert nested type names (Outer$Inner → Outer.Inner)
|
|
177
|
+
const csharpName = isNestedType(name) ? tsCSharpNestedTypeName(name) : name;
|
|
178
|
+
|
|
179
|
+
return [`${csharpName}<${typeParams.join(", ")}>`, currentContext];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Convert nested type names (Outer$Inner → Outer.Inner)
|
|
183
|
+
// before returning the name
|
|
184
|
+
const csharpName = isNestedType(name) ? tsCSharpNestedTypeName(name) : name;
|
|
185
|
+
|
|
186
|
+
return [csharpName, context];
|
|
187
|
+
};
|