@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,258 @@
1
+ /**
2
+ * Tests for Statement Emission
3
+ * Tests emission of control flow statements (if, for-of)
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("Statement Emission", () => {
12
+ it("should emit if statements", () => {
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: "check",
24
+ parameters: [
25
+ {
26
+ kind: "parameter",
27
+ pattern: { kind: "identifierPattern", name: "x" },
28
+ type: { kind: "primitiveType", name: "number" },
29
+ isOptional: false,
30
+ isRest: false,
31
+ passing: "value",
32
+ },
33
+ ],
34
+ returnType: { kind: "primitiveType", name: "string" },
35
+ body: {
36
+ kind: "blockStatement",
37
+ statements: [
38
+ {
39
+ kind: "ifStatement",
40
+ condition: {
41
+ kind: "binary",
42
+ operator: ">",
43
+ left: { kind: "identifier", name: "x" },
44
+ right: { kind: "literal", value: 0 },
45
+ },
46
+ thenStatement: {
47
+ kind: "blockStatement",
48
+ statements: [
49
+ {
50
+ kind: "returnStatement",
51
+ expression: { kind: "literal", value: "positive" },
52
+ },
53
+ ],
54
+ },
55
+ elseStatement: {
56
+ kind: "blockStatement",
57
+ statements: [
58
+ {
59
+ kind: "returnStatement",
60
+ expression: {
61
+ kind: "literal",
62
+ value: "negative or zero",
63
+ },
64
+ },
65
+ ],
66
+ },
67
+ },
68
+ ],
69
+ },
70
+ isExported: true,
71
+ isAsync: false,
72
+ isGenerator: false,
73
+ },
74
+ ],
75
+ exports: [],
76
+ };
77
+
78
+ const result = emitModule(module);
79
+
80
+ // Binary comparison emits with truthiness check
81
+ expect(result).to.include("if (x > 0");
82
+ expect(result).to.include('return "positive"');
83
+ expect(result).to.include("else");
84
+ expect(result).to.include('return "negative or zero"');
85
+ });
86
+
87
+ it("should emit canonical for loops with int counter and no cast", () => {
88
+ // Test: for (let i = 0; i < list.Count; i++) { list[i] }
89
+ // Should emit: for (int i = 0; ...) { list[i] } - NO (int) cast
90
+ const module: IrModule = {
91
+ kind: "module",
92
+ filePath: "/src/test.ts",
93
+ namespace: "MyApp",
94
+ className: "test",
95
+ isStaticContainer: true,
96
+ imports: [],
97
+ body: [
98
+ {
99
+ kind: "functionDeclaration",
100
+ name: "process",
101
+ parameters: [
102
+ {
103
+ kind: "parameter",
104
+ pattern: { kind: "identifierPattern", name: "items" },
105
+ type: {
106
+ kind: "referenceType",
107
+ name: "List",
108
+ typeArguments: [{ kind: "primitiveType", name: "number" }],
109
+ },
110
+ isOptional: false,
111
+ isRest: false,
112
+ passing: "value",
113
+ },
114
+ ],
115
+ returnType: { kind: "voidType" },
116
+ body: {
117
+ kind: "blockStatement",
118
+ statements: [
119
+ {
120
+ kind: "forStatement",
121
+ initializer: {
122
+ kind: "variableDeclaration",
123
+ declarationKind: "let",
124
+ declarations: [
125
+ {
126
+ kind: "variableDeclarator",
127
+ name: { kind: "identifierPattern", name: "i" },
128
+ initializer: { kind: "literal", value: 0 },
129
+ },
130
+ ],
131
+ isExported: false,
132
+ },
133
+ condition: {
134
+ kind: "binary",
135
+ operator: "<",
136
+ left: { kind: "identifier", name: "i" },
137
+ right: {
138
+ kind: "memberAccess",
139
+ object: { kind: "identifier", name: "items" },
140
+ property: "Count",
141
+ isComputed: false,
142
+ isOptional: false,
143
+ },
144
+ },
145
+ update: {
146
+ kind: "update",
147
+ operator: "++",
148
+ prefix: false,
149
+ expression: { kind: "identifier", name: "i" },
150
+ },
151
+ body: {
152
+ kind: "blockStatement",
153
+ statements: [
154
+ {
155
+ kind: "expressionStatement",
156
+ expression: {
157
+ kind: "call",
158
+ callee: {
159
+ kind: "memberAccess",
160
+ object: { kind: "identifier", name: "console" },
161
+ property: "log",
162
+ isComputed: false,
163
+ isOptional: false,
164
+ },
165
+ arguments: [
166
+ {
167
+ kind: "memberAccess",
168
+ object: { kind: "identifier", name: "items" },
169
+ property: { kind: "identifier", name: "i" },
170
+ isComputed: true,
171
+ isOptional: false,
172
+ },
173
+ ],
174
+ isOptional: false,
175
+ },
176
+ },
177
+ ],
178
+ },
179
+ },
180
+ ],
181
+ },
182
+ isExported: true,
183
+ isAsync: false,
184
+ isGenerator: false,
185
+ },
186
+ ],
187
+ exports: [],
188
+ };
189
+
190
+ const result = emitModule(module);
191
+
192
+ // Canonical loop: int i = 0 (not var i = 0.0)
193
+ expect(result).to.include("for (int i = 0;");
194
+ // CLR indexer without cast: items[i] (not items[(int)(i)])
195
+ expect(result).to.include("items[i]");
196
+ // Must NOT contain the redundant cast
197
+ expect(result).to.not.include("[(int)");
198
+ });
199
+
200
+ it("should emit for loops", () => {
201
+ const module: IrModule = {
202
+ kind: "module",
203
+ filePath: "/src/test.ts",
204
+ namespace: "MyApp",
205
+ className: "test",
206
+ isStaticContainer: true,
207
+ imports: [],
208
+ body: [
209
+ {
210
+ kind: "functionDeclaration",
211
+ name: "loop",
212
+ parameters: [],
213
+ returnType: { kind: "voidType" },
214
+ body: {
215
+ kind: "blockStatement",
216
+ statements: [
217
+ {
218
+ kind: "forOfStatement",
219
+ variable: { kind: "identifierPattern", name: "item" },
220
+ expression: { kind: "identifier", name: "items" },
221
+ body: {
222
+ kind: "blockStatement",
223
+ statements: [
224
+ {
225
+ kind: "expressionStatement",
226
+ expression: {
227
+ kind: "call",
228
+ callee: {
229
+ kind: "memberAccess",
230
+ object: { kind: "identifier", name: "console" },
231
+ property: "log",
232
+ isComputed: false,
233
+ isOptional: false,
234
+ },
235
+ arguments: [{ kind: "identifier", name: "item" }],
236
+ isOptional: false,
237
+ },
238
+ },
239
+ ],
240
+ },
241
+ },
242
+ ],
243
+ },
244
+ isExported: true,
245
+ isAsync: false,
246
+ isGenerator: false,
247
+ },
248
+ ],
249
+ exports: [],
250
+ };
251
+
252
+ const result = emitModule(module);
253
+
254
+ expect(result).to.include("foreach (var item in items)");
255
+ expect(result).to.include("Tsonic.JSRuntime.console.log(item)");
256
+ expect(result).to.include("using Tsonic.JSRuntime");
257
+ });
258
+ });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Statement emitters barrel exports
3
+ */
4
+
5
+ // Block and simple statements
6
+ export {
7
+ emitBlockStatement,
8
+ emitReturnStatement,
9
+ emitYieldStatement,
10
+ emitExpressionStatement,
11
+ } from "./blocks.js";
12
+
13
+ // Control flow statements
14
+ export {
15
+ emitIfStatement,
16
+ emitWhileStatement,
17
+ emitForStatement,
18
+ emitForOfStatement,
19
+ emitSwitchStatement,
20
+ emitTryStatement,
21
+ emitThrowStatement,
22
+ } from "./control.js";
23
+
24
+ // Class-related helpers
25
+ export {
26
+ emitClassMember,
27
+ emitParameters,
28
+ capitalize,
29
+ extractInlineObjectTypes,
30
+ emitExtractedType,
31
+ emitInterfaceMemberAsProperty,
32
+ type ExtractedType,
33
+ } from "./classes.js";
34
+
35
+ // Declaration emitters
36
+ export {
37
+ emitVariableDeclaration,
38
+ emitFunctionDeclaration,
39
+ emitClassDeclaration,
40
+ emitInterfaceDeclaration,
41
+ emitEnumDeclaration,
42
+ emitTypeAliasDeclaration,
43
+ } from "./declarations.js";
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Tests for type assertion handling
3
+ * Verifies that TypeScript type assertions are stripped during IR conversion
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 Assertion Emission", () => {
12
+ it("should strip 'as' type assertions", () => {
13
+ const module: IrModule = {
14
+ kind: "module",
15
+ filePath: "/test/assertion.ts",
16
+ namespace: "Test",
17
+ className: "assertion",
18
+ isStaticContainer: true,
19
+ imports: [],
20
+ exports: [],
21
+ body: [
22
+ {
23
+ kind: "variableDeclaration",
24
+ declarationKind: "const",
25
+ isExported: false,
26
+ declarations: [
27
+ {
28
+ kind: "variableDeclarator",
29
+ name: { kind: "identifierPattern", name: "value" },
30
+ type: { kind: "primitiveType", name: "string" },
31
+ // In the IR, type assertions are already stripped
32
+ // So this is just the underlying expression
33
+ initializer: { kind: "literal", value: "hello" },
34
+ },
35
+ ],
36
+ },
37
+ ],
38
+ };
39
+
40
+ const code = emitModule(module);
41
+
42
+ // Should emit the value directly without any type assertion
43
+ expect(code).to.include('string value = "hello"');
44
+ // Note: "as" might appear in "class" or "namespace" - check for actual cast syntax
45
+ expect(code).not.to.match(/\sas\s/);
46
+ });
47
+
48
+ it("should handle nested expressions with type assertions", () => {
49
+ const module: IrModule = {
50
+ kind: "module",
51
+ filePath: "/test/nested.ts",
52
+ namespace: "Test",
53
+ className: "nested",
54
+ isStaticContainer: true,
55
+ imports: [],
56
+ exports: [],
57
+ body: [
58
+ {
59
+ kind: "functionDeclaration",
60
+ name: "getValue",
61
+ parameters: [],
62
+ returnType: { kind: "primitiveType", name: "number" },
63
+ body: {
64
+ kind: "blockStatement",
65
+ statements: [
66
+ {
67
+ kind: "returnStatement",
68
+ // Type assertion is stripped, only the literal remains
69
+ expression: { kind: "literal", value: 42 },
70
+ },
71
+ ],
72
+ },
73
+ isAsync: false,
74
+ isGenerator: false,
75
+ isExported: true,
76
+ },
77
+ ],
78
+ };
79
+
80
+ const code = emitModule(module);
81
+
82
+ // Should emit clean C# without type assertions - C# handles implicit conversion
83
+ expect(code).to.include("return 42");
84
+ // Note: "as" might appear in "class" or "namespace" - check for actual cast syntax
85
+ expect(code).not.to.match(/\sas\s/);
86
+ });
87
+
88
+ it("should preserve union types when assertion is stripped", () => {
89
+ const module: IrModule = {
90
+ kind: "module",
91
+ filePath: "/test/unionAssert.ts",
92
+ namespace: "Test",
93
+ className: "unionAssert",
94
+ isStaticContainer: true,
95
+ imports: [],
96
+ exports: [],
97
+ body: [
98
+ {
99
+ kind: "functionDeclaration",
100
+ name: "process",
101
+ parameters: [
102
+ {
103
+ kind: "parameter",
104
+ pattern: { kind: "identifierPattern", name: "input" },
105
+ type: {
106
+ kind: "unionType",
107
+ types: [
108
+ { kind: "primitiveType", name: "string" },
109
+ { kind: "primitiveType", name: "number" },
110
+ ],
111
+ },
112
+ isOptional: false,
113
+ isRest: false,
114
+ passing: "value",
115
+ },
116
+ ],
117
+ returnType: { kind: "primitiveType", name: "string" },
118
+ body: {
119
+ kind: "blockStatement",
120
+ statements: [
121
+ {
122
+ kind: "returnStatement",
123
+ // Even if there was a type assertion in TS, it's stripped
124
+ // Only the underlying expression remains
125
+ expression: { kind: "identifier", name: "input" },
126
+ },
127
+ ],
128
+ },
129
+ isAsync: false,
130
+ isGenerator: false,
131
+ isExported: true,
132
+ },
133
+ ],
134
+ };
135
+
136
+ const code = emitModule(module);
137
+
138
+ // Should preserve union type parameter
139
+ expect(code).to.include("Union<string, double> input");
140
+ // Should return the value directly
141
+ expect(code).to.include("return input");
142
+ });
143
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Type Emitter - IR types to C# types
3
+ * Main dispatcher - re-exports from types/ subdirectory
4
+ */
5
+
6
+ export {
7
+ emitType,
8
+ emitTypeParameters,
9
+ emitParameterType,
10
+ emitPrimitiveType,
11
+ emitReferenceType,
12
+ emitArrayType,
13
+ emitFunctionType,
14
+ emitObjectType,
15
+ emitUnionType,
16
+ emitIntersectionType,
17
+ emitLiteralType,
18
+ } from "./types/index.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Array type emission
3
+ */
4
+
5
+ import { IrType } from "@tsonic/frontend";
6
+ import { EmitterContext } from "../types.js";
7
+ import { emitType } from "./emitter.js";
8
+
9
+ /**
10
+ * Emit array types as global::System.Collections.Generic.List<T>
11
+ */
12
+ export const emitArrayType = (
13
+ type: Extract<IrType, { kind: "arrayType" }>,
14
+ context: EmitterContext
15
+ ): [string, EmitterContext] => {
16
+ const [elementType, newContext] = emitType(type.elementType, context);
17
+ return [
18
+ `global::System.Collections.Generic.List<${elementType}>`,
19
+ newContext,
20
+ ];
21
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Dictionary type emission
3
+ */
4
+
5
+ import { IrDictionaryType } from "@tsonic/frontend";
6
+ import { EmitterContext } from "../types.js";
7
+ import { emitType } from "./emitter.js";
8
+
9
+ /**
10
+ * Emit dictionary type as global::System.Collections.Generic.Dictionary<TKey, TValue>
11
+ *
12
+ * IrDictionaryType represents:
13
+ * - `{ [k: string]: T }` → Dictionary<string, T>
14
+ * - `Record<string, T>` → Dictionary<string, T>
15
+ *
16
+ * Note: Only string keys are supported (TSN7413). Number keys are rejected
17
+ * at validation time because TS number keys have string-ish semantics.
18
+ */
19
+ export const emitDictionaryType = (
20
+ type: IrDictionaryType,
21
+ context: EmitterContext
22
+ ): [string, EmitterContext] => {
23
+ // Emit key type
24
+ const [keyTypeStr, ctx1] = emitDictionaryKeyType(type.keyType, context);
25
+
26
+ // Emit value type
27
+ const [valueTypeStr, ctx2] = emitType(type.valueType, ctx1);
28
+
29
+ return [
30
+ `global::System.Collections.Generic.Dictionary<${keyTypeStr}, ${valueTypeStr}>`,
31
+ ctx2,
32
+ ];
33
+ };
34
+
35
+ /**
36
+ * Emit dictionary key type.
37
+ * Only string keys are supported (enforced by TSN7413).
38
+ * Non-string keys trigger ICE - validation should have caught them.
39
+ */
40
+ const emitDictionaryKeyType = (
41
+ keyType: IrDictionaryType["keyType"],
42
+ context: EmitterContext
43
+ ): [string, EmitterContext] => {
44
+ if (keyType.kind === "primitiveType" && keyType.name === "string") {
45
+ return ["string", context];
46
+ }
47
+
48
+ // ICE: Only string keys allowed (enforced by TSN7413)
49
+ throw new Error(
50
+ `ICE: Non-string dictionary key type reached emitter - validation missed TSN7413. Got: ${keyType.kind}`
51
+ );
52
+ };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Type emission main dispatcher
3
+ */
4
+
5
+ import { IrType } from "@tsonic/frontend";
6
+ import { EmitterContext } from "../types.js";
7
+ import { emitPrimitiveType } from "./primitives.js";
8
+ import { emitReferenceType } from "./references.js";
9
+ import { emitArrayType } from "./arrays.js";
10
+ import { emitFunctionType } from "./functions.js";
11
+ import { emitObjectType } from "./objects.js";
12
+ import { emitDictionaryType } from "./dictionaries.js";
13
+ import { emitUnionType } from "./unions.js";
14
+ import { emitIntersectionType } from "./intersections.js";
15
+ import { emitLiteralType } from "./literals.js";
16
+
17
+ /**
18
+ * Emit a C# type from an IR type
19
+ */
20
+ export const emitType = (
21
+ type: IrType,
22
+ context: EmitterContext
23
+ ): [string, EmitterContext] => {
24
+ switch (type.kind) {
25
+ case "primitiveType":
26
+ return emitPrimitiveType(type, context);
27
+
28
+ case "referenceType":
29
+ return emitReferenceType(type, context);
30
+
31
+ case "arrayType":
32
+ return emitArrayType(type, context);
33
+
34
+ case "functionType":
35
+ return emitFunctionType(type, context);
36
+
37
+ case "objectType":
38
+ return emitObjectType(type, context);
39
+
40
+ case "dictionaryType":
41
+ return emitDictionaryType(type, context);
42
+
43
+ case "unionType":
44
+ return emitUnionType(type, context);
45
+
46
+ case "intersectionType":
47
+ return emitIntersectionType(type, context);
48
+
49
+ case "literalType":
50
+ return emitLiteralType(type, context);
51
+
52
+ case "anyType":
53
+ // ICE: Frontend validation (TSN7401) should have caught this.
54
+ throw new Error(
55
+ "ICE: 'any' type reached emitter - validation missed TSN7401"
56
+ );
57
+
58
+ case "unknownType":
59
+ // 'unknown' is a legitimate type - emit as nullable object
60
+ return ["object?", context];
61
+
62
+ case "voidType":
63
+ return ["void", context];
64
+
65
+ case "neverType":
66
+ return ["void", context];
67
+
68
+ default: {
69
+ // ICE: All IR types should be handled explicitly
70
+ const exhaustiveCheck: never = type;
71
+ throw new Error(
72
+ `ICE: Unhandled IR type kind: ${(exhaustiveCheck as IrType).kind}`
73
+ );
74
+ }
75
+ }
76
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Function type emission (global::System.Func<>, global::System.Action<>)
3
+ */
4
+
5
+ import { IrType } from "@tsonic/frontend";
6
+ import { EmitterContext } from "../types.js";
7
+ import { emitType } from "./emitter.js";
8
+
9
+ /**
10
+ * Emit function types as global::System.Func<> or global::System.Action<> delegates
11
+ */
12
+ export const emitFunctionType = (
13
+ type: Extract<IrType, { kind: "functionType" }>,
14
+ context: EmitterContext
15
+ ): [string, EmitterContext] => {
16
+ // For function types, we'll use Func<> or Action<> delegates
17
+ const paramTypes: string[] = [];
18
+ let currentContext = context;
19
+
20
+ for (const param of type.parameters) {
21
+ const paramType = param.type ?? { kind: "anyType" as const };
22
+ const [typeStr, newContext] = emitType(paramType, currentContext);
23
+ paramTypes.push(typeStr);
24
+ currentContext = newContext;
25
+ }
26
+
27
+ const returnTypeNode = type.returnType ?? { kind: "voidType" as const };
28
+ const [returnType, newContext] = emitType(returnTypeNode, currentContext);
29
+
30
+ if (returnType === "void") {
31
+ if (paramTypes.length === 0) {
32
+ return ["global::System.Action", newContext];
33
+ }
34
+ return [`global::System.Action<${paramTypes.join(", ")}>`, newContext];
35
+ }
36
+
37
+ if (paramTypes.length === 0) {
38
+ return [`global::System.Func<${returnType}>`, newContext];
39
+ }
40
+
41
+ return [
42
+ `global::System.Func<${paramTypes.join(", ")}, ${returnType}>`,
43
+ newContext,
44
+ ];
45
+ };