@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.
Files changed (209) hide show
  1. package/package.json +34 -0
  2. package/scripts/update-golden-tests.ts +119 -0
  3. package/src/adapter-generator.ts +112 -0
  4. package/src/array.test.ts +301 -0
  5. package/src/constants.ts +32 -0
  6. package/src/core/exports.ts +36 -0
  7. package/src/core/imports.test.ts +83 -0
  8. package/src/core/imports.ts +243 -0
  9. package/src/core/index.ts +9 -0
  10. package/src/core/module-emitter/assembly.ts +83 -0
  11. package/src/core/module-emitter/header.ts +19 -0
  12. package/src/core/module-emitter/index.ts +17 -0
  13. package/src/core/module-emitter/namespace.ts +39 -0
  14. package/src/core/module-emitter/orchestrator.ts +98 -0
  15. package/src/core/module-emitter/separation.ts +41 -0
  16. package/src/core/module-emitter/static-container.ts +75 -0
  17. package/src/core/module-emitter.test.ts +154 -0
  18. package/src/core/module-emitter.ts +6 -0
  19. package/src/core/module-map.ts +218 -0
  20. package/src/core/options.ts +16 -0
  21. package/src/core/type-params.ts +34 -0
  22. package/src/emitter-types/context.ts +91 -0
  23. package/src/emitter-types/core.ts +138 -0
  24. package/src/emitter-types/csharp-types.ts +42 -0
  25. package/src/emitter-types/formatting.ts +13 -0
  26. package/src/emitter-types/fqn.ts +81 -0
  27. package/src/emitter-types/index.ts +31 -0
  28. package/src/emitter.ts +107 -0
  29. package/src/expression-emitter.ts +112 -0
  30. package/src/expressions/access.ts +104 -0
  31. package/src/expressions/calls.ts +264 -0
  32. package/src/expressions/collections.ts +354 -0
  33. package/src/expressions/functions.ts +71 -0
  34. package/src/expressions/identifiers.ts +125 -0
  35. package/src/expressions/index.test.ts +515 -0
  36. package/src/expressions/index.ts +38 -0
  37. package/src/expressions/literals.ts +138 -0
  38. package/src/expressions/operators.ts +211 -0
  39. package/src/expressions/other.ts +63 -0
  40. package/src/generator-exchange.ts +120 -0
  41. package/src/generator.test.ts +193 -0
  42. package/src/golden-tests/config-parser.ts +67 -0
  43. package/src/golden-tests/discovery.ts +61 -0
  44. package/src/golden-tests/index.ts +10 -0
  45. package/src/golden-tests/registration.ts +26 -0
  46. package/src/golden-tests/runner.ts +131 -0
  47. package/src/golden-tests/tree-builder.ts +43 -0
  48. package/src/golden-tests/types.ts +21 -0
  49. package/src/golden.test.ts +40 -0
  50. package/src/hierarchical-bindings.test.ts +258 -0
  51. package/src/index.ts +14 -0
  52. package/src/integration.test.ts +303 -0
  53. package/src/specialization/call-site-rewriting.test.ts +99 -0
  54. package/src/specialization/collection/expressions.ts +184 -0
  55. package/src/specialization/collection/index.ts +7 -0
  56. package/src/specialization/collection/orchestrator.ts +25 -0
  57. package/src/specialization/collection/statements.ts +91 -0
  58. package/src/specialization/collection.ts +10 -0
  59. package/src/specialization/generation.ts +189 -0
  60. package/src/specialization/generic-classes.test.ts +59 -0
  61. package/src/specialization/generic-functions.test.ts +292 -0
  62. package/src/specialization/helpers.ts +39 -0
  63. package/src/specialization/index.ts +28 -0
  64. package/src/specialization/interfaces.test.ts +151 -0
  65. package/src/specialization/naming.ts +34 -0
  66. package/src/specialization/substitution.ts +186 -0
  67. package/src/specialization/type-aliases.test.ts +134 -0
  68. package/src/specialization/types.ts +19 -0
  69. package/src/specialization-generator.ts +23 -0
  70. package/src/statement-emitter.ts +117 -0
  71. package/src/statements/blocks.ts +115 -0
  72. package/src/statements/classes/helpers.ts +9 -0
  73. package/src/statements/classes/index.ts +13 -0
  74. package/src/statements/classes/inline-types.ts +79 -0
  75. package/src/statements/classes/members/constructors.ts +123 -0
  76. package/src/statements/classes/members/index.ts +8 -0
  77. package/src/statements/classes/members/methods.ts +137 -0
  78. package/src/statements/classes/members/orchestrator.ts +33 -0
  79. package/src/statements/classes/members/properties.ts +62 -0
  80. package/src/statements/classes/members.ts +6 -0
  81. package/src/statements/classes/parameters.ts +69 -0
  82. package/src/statements/classes/properties.ts +95 -0
  83. package/src/statements/classes.ts +14 -0
  84. package/src/statements/control/conditionals.ts +134 -0
  85. package/src/statements/control/exceptions.ts +59 -0
  86. package/src/statements/control/index.ts +11 -0
  87. package/src/statements/control/loops.ts +250 -0
  88. package/src/statements/control.ts +14 -0
  89. package/src/statements/declarations/classes.ts +89 -0
  90. package/src/statements/declarations/enums.ts +32 -0
  91. package/src/statements/declarations/functions.ts +147 -0
  92. package/src/statements/declarations/index.ts +10 -0
  93. package/src/statements/declarations/interfaces.ts +116 -0
  94. package/src/statements/declarations/structs.test.ts +182 -0
  95. package/src/statements/declarations/type-aliases.ts +104 -0
  96. package/src/statements/declarations/variables.ts +159 -0
  97. package/src/statements/declarations.ts +13 -0
  98. package/src/statements/index.test.ts +258 -0
  99. package/src/statements/index.ts +43 -0
  100. package/src/type-assertion.test.ts +143 -0
  101. package/src/type-emitter.ts +18 -0
  102. package/src/types/arrays.ts +21 -0
  103. package/src/types/dictionaries.ts +52 -0
  104. package/src/types/emitter.ts +76 -0
  105. package/src/types/functions.ts +45 -0
  106. package/src/types/index.test.ts +116 -0
  107. package/src/types/index.ts +14 -0
  108. package/src/types/intersections.ts +19 -0
  109. package/src/types/literals.ts +26 -0
  110. package/src/types/objects.ts +27 -0
  111. package/src/types/parameters.test.ts +146 -0
  112. package/src/types/parameters.ts +95 -0
  113. package/src/types/primitives.ts +24 -0
  114. package/src/types/references.ts +187 -0
  115. package/src/types/unions.test.ts +397 -0
  116. package/src/types/unions.ts +62 -0
  117. package/src/types.ts +33 -0
  118. package/testcases/README.md +213 -0
  119. package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
  120. package/testcases/arrays/basic/config.yaml +1 -0
  121. package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
  122. package/testcases/arrays/destructuring/config.yaml +1 -0
  123. package/testcases/arrays/methods/ArrayMethods.ts +6 -0
  124. package/testcases/arrays/methods/config.yaml +1 -0
  125. package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
  126. package/testcases/arrays/multidimensional/config.yaml +1 -0
  127. package/testcases/arrays/spread/ArraySpread.ts +3 -0
  128. package/testcases/arrays/spread/config.yaml +1 -0
  129. package/testcases/async/basic/AsyncFunction.ts +5 -0
  130. package/testcases/async/basic/config.yaml +1 -0
  131. package/testcases/classes/abstract/AbstractClasses.ts +53 -0
  132. package/testcases/classes/abstract/config.yaml +1 -0
  133. package/testcases/classes/basic/Person.ts +12 -0
  134. package/testcases/classes/basic/config.yaml +1 -0
  135. package/testcases/classes/constructor/User.ts +11 -0
  136. package/testcases/classes/constructor/config.yaml +1 -0
  137. package/testcases/classes/field-inference/Counter.ts +11 -0
  138. package/testcases/classes/field-inference/config.yaml +1 -0
  139. package/testcases/classes/inheritance/Inheritance.ts +24 -0
  140. package/testcases/classes/inheritance/config.yaml +1 -0
  141. package/testcases/classes/static-members/MathHelper.ts +12 -0
  142. package/testcases/classes/static-members/config.yaml +1 -0
  143. package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
  144. package/testcases/control-flow/error-handling/config.yaml +1 -0
  145. package/testcases/control-flow/loops/Loops.ts +21 -0
  146. package/testcases/control-flow/loops/config.yaml +1 -0
  147. package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
  148. package/testcases/control-flow/switch/config.yaml +1 -0
  149. package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
  150. package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
  151. package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
  152. package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
  153. package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
  154. package/testcases/edge-cases/shadowing/config.yaml +1 -0
  155. package/testcases/functions/arrow/ArrowFunction.ts +5 -0
  156. package/testcases/functions/arrow/config.yaml +1 -0
  157. package/testcases/functions/basic/Greet.ts +3 -0
  158. package/testcases/functions/basic/config.yaml +1 -0
  159. package/testcases/functions/closures/Closures.ts +11 -0
  160. package/testcases/functions/closures/config.yaml +1 -0
  161. package/testcases/functions/default-params/DefaultParams.ts +7 -0
  162. package/testcases/functions/default-params/config.yaml +1 -0
  163. package/testcases/functions/rest-params/RestParams.ts +7 -0
  164. package/testcases/functions/rest-params/config.yaml +1 -0
  165. package/testcases/functions/type-guards/TypeGuards.ts +52 -0
  166. package/testcases/functions/type-guards/config.yaml +1 -0
  167. package/testcases/operators/logical/LogicalOperators.ts +11 -0
  168. package/testcases/operators/logical/config.yaml +1 -0
  169. package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
  170. package/testcases/operators/nullish-coalescing/config.yaml +1 -0
  171. package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
  172. package/testcases/operators/optional-chaining/config.yaml +1 -0
  173. package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
  174. package/testcases/real-world/advanced-generics/config.yaml +1 -0
  175. package/testcases/real-world/async-ops/async-ops.ts +67 -0
  176. package/testcases/real-world/async-ops/config.yaml +1 -0
  177. package/testcases/real-world/business-logic/business-logic.ts +215 -0
  178. package/testcases/real-world/business-logic/config.yaml +1 -0
  179. package/testcases/real-world/calculator/calculator.ts +29 -0
  180. package/testcases/real-world/calculator/config.yaml +1 -0
  181. package/testcases/real-world/data-structures/config.yaml +1 -0
  182. package/testcases/real-world/data-structures/data-structures.ts +133 -0
  183. package/testcases/real-world/functional/config.yaml +1 -0
  184. package/testcases/real-world/functional/functional.ts +116 -0
  185. package/testcases/real-world/shapes/config.yaml +1 -0
  186. package/testcases/real-world/shapes/shapes.ts +87 -0
  187. package/testcases/real-world/string-utils/config.yaml +1 -0
  188. package/testcases/real-world/string-utils/string-utils.ts +47 -0
  189. package/testcases/real-world/todo-list/config.yaml +1 -0
  190. package/testcases/real-world/todo-list/todo-list.ts +52 -0
  191. package/testcases/real-world/type-guards/config.yaml +1 -0
  192. package/testcases/real-world/type-guards/type-guards.ts +71 -0
  193. package/testcases/structs/basic/Point.ts +9 -0
  194. package/testcases/structs/basic/config.yaml +1 -0
  195. package/testcases/types/conditional/ConditionalTypes.ts +35 -0
  196. package/testcases/types/conditional/config.yaml +1 -0
  197. package/testcases/types/constants/ModuleConstants.ts +6 -0
  198. package/testcases/types/constants/config.yaml +1 -0
  199. package/testcases/types/generics/Generics.ts +15 -0
  200. package/testcases/types/generics/config.yaml +1 -0
  201. package/testcases/types/interfaces/Interfaces.ts +14 -0
  202. package/testcases/types/interfaces/config.yaml +1 -0
  203. package/testcases/types/mapped/MappedTypes.ts +27 -0
  204. package/testcases/types/mapped/config.yaml +1 -0
  205. package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
  206. package/testcases/types/tuples-intersections/config.yaml +1 -0
  207. package/testcases/types/unions/UnionTypes.ts +11 -0
  208. package/testcases/types/unions/config.yaml +1 -0
  209. 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
+ };