@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,123 @@
1
+ /**
2
+ * Constructor member emission
3
+ */
4
+
5
+ import { IrClassMember, IrStatement } from "@tsonic/frontend";
6
+ import { EmitterContext, getIndent, indent, dedent } from "../../../types.js";
7
+ import { emitExpression } from "../../../expression-emitter.js";
8
+ import { emitBlockStatement } from "../../blocks.js";
9
+ import { emitParameters } from "../parameters.js";
10
+
11
+ /**
12
+ * Emit a constructor declaration
13
+ */
14
+ export const emitConstructorMember = (
15
+ member: IrClassMember & { kind: "constructorDeclaration" },
16
+ context: EmitterContext
17
+ ): [string, EmitterContext] => {
18
+ const ind = getIndent(context);
19
+ let currentContext = context;
20
+ const parts: string[] = [];
21
+
22
+ // Access modifier
23
+ const accessibility = member.accessibility ?? "public";
24
+ parts.push(accessibility);
25
+
26
+ // Constructor name (same as class name)
27
+ const constructorName = context.className ?? "UnknownClass";
28
+ parts.push(constructorName);
29
+
30
+ // Parameters
31
+ const params = emitParameters(member.parameters, currentContext);
32
+ currentContext = params[1];
33
+
34
+ // Constructor body
35
+ if (!member.body) {
36
+ // Abstract or interface constructor without body
37
+ const signature = parts.join(" ");
38
+ const code = `${ind}${signature}(${params[0]});`;
39
+ return [code, currentContext];
40
+ }
41
+
42
+ // Check for super() call - MUST be the first statement if present
43
+ // C# base() calls execute before the constructor body, so we can't preserve
44
+ // TypeScript semantics if there are statements before super()
45
+ const [baseCall, bodyStatements, baseCallContext] = extractSuperCall(
46
+ member.body.statements,
47
+ currentContext
48
+ );
49
+ currentContext = baseCallContext;
50
+
51
+ // Check if super() appears later in the body (not supported)
52
+ const hasLaterSuperCall = bodyStatements.some(
53
+ (stmt) =>
54
+ stmt.kind === "expressionStatement" &&
55
+ stmt.expression.kind === "call" &&
56
+ stmt.expression.callee.kind === "identifier" &&
57
+ stmt.expression.callee.name === "super"
58
+ );
59
+
60
+ if (hasLaterSuperCall) {
61
+ // TODO: This should be a compile error in the IR builder
62
+ // For now, emit a comment noting the issue
63
+ const signature = parts.join(" ");
64
+ const errorComment = `${ind}// ERROR: super() must be the first statement in constructor`;
65
+ const code = `${errorComment}\n${ind}${signature}(${params[0]})\n${ind}{\n${ind} // Constructor body omitted due to error\n${ind}}`;
66
+ return [code, currentContext];
67
+ }
68
+
69
+ // Emit body without the super() call
70
+ const bodyContext = indent(currentContext);
71
+ const modifiedBody: typeof member.body = {
72
+ ...member.body,
73
+ statements: bodyStatements,
74
+ };
75
+ const [bodyCode, finalContext] = emitBlockStatement(
76
+ modifiedBody,
77
+ bodyContext
78
+ );
79
+
80
+ const signature = parts.join(" ");
81
+ const code = `${ind}${signature}(${params[0]})${baseCall}\n${bodyCode}`;
82
+
83
+ return [code, dedent(finalContext)];
84
+ };
85
+
86
+ /**
87
+ * Extract super() call from first statement if present
88
+ * Returns [baseCall, remainingStatements, context]
89
+ */
90
+ const extractSuperCall = (
91
+ statements: readonly IrStatement[],
92
+ context: EmitterContext
93
+ ): [string, readonly IrStatement[], EmitterContext] => {
94
+ let currentContext = context;
95
+
96
+ if (statements.length === 0) {
97
+ return ["", statements, currentContext];
98
+ }
99
+
100
+ const firstStmt = statements[0];
101
+ if (
102
+ firstStmt &&
103
+ firstStmt.kind === "expressionStatement" &&
104
+ firstStmt.expression.kind === "call" &&
105
+ firstStmt.expression.callee.kind === "identifier" &&
106
+ firstStmt.expression.callee.name === "super"
107
+ ) {
108
+ // Found super() call as first statement - convert to : base(...)
109
+ const superCall = firstStmt.expression;
110
+ const argFrags: string[] = [];
111
+ for (const arg of superCall.arguments) {
112
+ const [argFrag, newContext] = emitExpression(arg, currentContext);
113
+ argFrags.push(argFrag.text);
114
+ currentContext = newContext;
115
+ }
116
+ const baseCall = ` : base(${argFrags.join(", ")})`;
117
+ // Remove super() call from body statements
118
+ const remainingStatements = statements.slice(1);
119
+ return [baseCall, remainingStatements, currentContext];
120
+ }
121
+
122
+ return ["", statements, currentContext];
123
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Class member emission - Public API
3
+ */
4
+
5
+ export { emitClassMember } from "./orchestrator.js";
6
+ export { emitPropertyMember } from "./properties.js";
7
+ export { emitMethodMember } from "./methods.js";
8
+ export { emitConstructorMember } from "./constructors.js";
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Method member emission
3
+ */
4
+
5
+ import { IrClassMember } from "@tsonic/frontend";
6
+ import {
7
+ EmitterContext,
8
+ getIndent,
9
+ indent,
10
+ dedent,
11
+ withAsync,
12
+ } from "../../../types.js";
13
+ import { emitType, emitTypeParameters } from "../../../type-emitter.js";
14
+ import { emitBlockStatement } from "../../blocks.js";
15
+ import { emitParameters } from "../parameters.js";
16
+
17
+ /**
18
+ * Emit a method declaration
19
+ */
20
+ export const emitMethodMember = (
21
+ member: IrClassMember & { kind: "methodDeclaration" },
22
+ context: EmitterContext
23
+ ): [string, EmitterContext] => {
24
+ const ind = getIndent(context);
25
+ let currentContext = context;
26
+ const parts: string[] = [];
27
+
28
+ // Access modifier
29
+ const accessibility = member.accessibility ?? "public";
30
+ parts.push(accessibility);
31
+
32
+ if (member.isStatic) {
33
+ parts.push("static");
34
+ }
35
+
36
+ // Override modifier (from metadata or TS base class detection)
37
+ if (member.isOverride) {
38
+ parts.push("override");
39
+ }
40
+
41
+ if (member.isAsync) {
42
+ parts.push("async");
43
+ }
44
+
45
+ // Return type
46
+ if (member.returnType) {
47
+ const [returnType, newContext] = emitType(
48
+ member.returnType,
49
+ currentContext
50
+ );
51
+ currentContext = newContext;
52
+ // If async and return type is Promise, it's already converted to Task
53
+ // Don't wrap it again
54
+ if (
55
+ member.isAsync &&
56
+ member.returnType.kind === "referenceType" &&
57
+ member.returnType.name === "Promise"
58
+ ) {
59
+ parts.push(returnType); // Already Task<T> from emitType
60
+ } else {
61
+ parts.push(
62
+ member.isAsync
63
+ ? `global::System.Threading.Tasks.Task<${returnType}>`
64
+ : returnType
65
+ );
66
+ }
67
+ } else {
68
+ parts.push(member.isAsync ? "global::System.Threading.Tasks.Task" : "void");
69
+ }
70
+
71
+ // Method name
72
+ parts.push(member.name);
73
+
74
+ // Type parameters
75
+ const [typeParamsStr, whereClauses, typeParamContext] = emitTypeParameters(
76
+ member.typeParameters,
77
+ currentContext
78
+ );
79
+ currentContext = typeParamContext;
80
+
81
+ // Parameters
82
+ const params = emitParameters(member.parameters, currentContext);
83
+ currentContext = params[1];
84
+
85
+ const whereClause =
86
+ whereClauses.length > 0
87
+ ? `\n${ind} ${whereClauses.join(`\n${ind} `)}`
88
+ : "";
89
+
90
+ // Method body
91
+ const bodyContext = withAsync(indent(currentContext), member.isAsync);
92
+
93
+ if (!member.body) {
94
+ // Abstract method without body
95
+ const signature = parts.join(" ");
96
+ const code = `${ind}${signature}${typeParamsStr}(${params[0]})${whereClause};`;
97
+ return [code, currentContext];
98
+ }
99
+
100
+ const [bodyCode, finalContext] = emitBlockStatement(member.body, bodyContext);
101
+
102
+ // Collect out parameters that need initialization
103
+ const outParams: Array<{ name: string; type: string }> = [];
104
+ for (const param of member.parameters) {
105
+ // Use param.passing to detect out parameters (type is already unwrapped by frontend)
106
+ if (param.passing === "out" && param.pattern.kind === "identifierPattern") {
107
+ // Get the type for default value
108
+ let typeName = "object";
109
+ if (param.type) {
110
+ const [typeStr] = emitType(param.type, currentContext);
111
+ typeName = typeStr;
112
+ }
113
+ outParams.push({ name: param.pattern.name, type: typeName });
114
+ }
115
+ }
116
+
117
+ // Inject out parameter initializations
118
+ let finalBodyCode = bodyCode;
119
+ if (outParams.length > 0) {
120
+ const bodyInd = getIndent(bodyContext);
121
+ const injectLines: string[] = [];
122
+ for (const outParam of outParams) {
123
+ injectLines.push(`${bodyInd}${outParam.name} = default;`);
124
+ }
125
+
126
+ const lines = bodyCode.split("\n");
127
+ if (lines.length > 1) {
128
+ lines.splice(1, 0, ...injectLines, "");
129
+ finalBodyCode = lines.join("\n");
130
+ }
131
+ }
132
+
133
+ const signature = parts.join(" ");
134
+ const code = `${ind}${signature}${typeParamsStr}(${params[0]})${whereClause}\n${finalBodyCode}`;
135
+
136
+ return [code, dedent(finalContext)];
137
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Class member emission orchestrator
3
+ */
4
+
5
+ import { IrClassMember } from "@tsonic/frontend";
6
+ import { EmitterContext, getIndent } from "../../../types.js";
7
+ import { emitPropertyMember } from "./properties.js";
8
+ import { emitMethodMember } from "./methods.js";
9
+ import { emitConstructorMember } from "./constructors.js";
10
+
11
+ /**
12
+ * Emit a class member (property, method, or constructor)
13
+ */
14
+ export const emitClassMember = (
15
+ member: IrClassMember,
16
+ context: EmitterContext
17
+ ): [string, EmitterContext] => {
18
+ const ind = getIndent(context);
19
+
20
+ switch (member.kind) {
21
+ case "propertyDeclaration":
22
+ return emitPropertyMember(member, context);
23
+
24
+ case "methodDeclaration":
25
+ return emitMethodMember(member, context);
26
+
27
+ case "constructorDeclaration":
28
+ return emitConstructorMember(member, context);
29
+
30
+ default:
31
+ return [`${ind}// TODO: unhandled class member`, context];
32
+ }
33
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Property member emission
3
+ */
4
+
5
+ import { IrClassMember } from "@tsonic/frontend";
6
+ import { EmitterContext, getIndent } from "../../../types.js";
7
+ import { emitExpression } from "../../../expression-emitter.js";
8
+ import { emitType } from "../../../type-emitter.js";
9
+
10
+ /**
11
+ * Emit a property declaration
12
+ */
13
+ export const emitPropertyMember = (
14
+ member: IrClassMember & { kind: "propertyDeclaration" },
15
+ context: EmitterContext
16
+ ): [string, EmitterContext] => {
17
+ const ind = getIndent(context);
18
+ let currentContext = context;
19
+ const parts: string[] = [];
20
+
21
+ // Access modifier
22
+ const accessibility = member.accessibility ?? "public";
23
+ parts.push(accessibility);
24
+
25
+ if (member.isStatic) {
26
+ parts.push("static");
27
+ }
28
+
29
+ // Override modifier (from metadata or TS base class detection)
30
+ if (member.isOverride) {
31
+ parts.push("override");
32
+ }
33
+
34
+ if (member.isReadonly) {
35
+ parts.push("readonly");
36
+ }
37
+
38
+ // Property type - uses standard type emission pipeline
39
+ // Note: type is always set for class fields (from annotation or inference)
40
+ if (member.type) {
41
+ const [typeName, newContext] = emitType(member.type, currentContext);
42
+ currentContext = newContext;
43
+ parts.push(typeName);
44
+ } else {
45
+ parts.push("object");
46
+ }
47
+
48
+ // Property name
49
+ parts.push(member.name);
50
+
51
+ // Emit as field (TypeScript class fields map to C# fields, not properties)
52
+ let code = `${ind}${parts.join(" ")}`;
53
+ if (member.initializer) {
54
+ const [initFrag, finalContext] = emitExpression(
55
+ member.initializer,
56
+ currentContext
57
+ );
58
+ code += ` = ${initFrag.text}`;
59
+ currentContext = finalContext;
60
+ }
61
+ return [`${code};`, currentContext];
62
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Class member emission (properties, methods, constructors)
3
+ * Main dispatcher - re-exports from members/ subdirectory
4
+ */
5
+
6
+ export { emitClassMember } from "./members/index.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Parameter emission for functions and methods
3
+ */
4
+
5
+ import { IrParameter, IrType } from "@tsonic/frontend";
6
+ import { EmitterContext } from "../../types.js";
7
+ import { emitExpression } from "../../expression-emitter.js";
8
+ import { emitParameterType } from "../../type-emitter.js";
9
+
10
+ /**
11
+ * Emit parameters for functions and methods
12
+ */
13
+ export const emitParameters = (
14
+ parameters: readonly IrParameter[],
15
+ context: EmitterContext
16
+ ): [string, EmitterContext] => {
17
+ let currentContext = context;
18
+ const params: string[] = [];
19
+
20
+ for (const param of parameters) {
21
+ const isRest = param.isRest;
22
+ const isOptional = param.isOptional;
23
+
24
+ // Use the passing mode from IR (frontend already unwrapped ref<T>/out<T>/in<T>)
25
+ const paramModifier = param.passing !== "value" ? param.passing : "";
26
+ const actualType: IrType | undefined = param.type;
27
+
28
+ // Parameter type
29
+ let paramType = "object";
30
+ if (actualType) {
31
+ const [typeName, newContext] = emitParameterType(
32
+ actualType,
33
+ isOptional,
34
+ currentContext
35
+ );
36
+ currentContext = newContext;
37
+ paramType = typeName;
38
+ // TODO: Rest parameters currently map to Tsonic.Runtime.Array<T> to preserve
39
+ // JavaScript semantics (reduce, join, etc.). In future, could optimize to
40
+ // params T[] and wrap with Array.from() at call sites.
41
+ }
42
+
43
+ // Parameter name
44
+ let paramName = "param";
45
+ if (param.pattern.kind === "identifierPattern") {
46
+ paramName = param.pattern.name;
47
+ }
48
+
49
+ // Construct parameter string with modifier if present
50
+ let paramStr = paramModifier
51
+ ? `${paramModifier} ${paramType} ${paramName}`
52
+ : `${paramType} ${paramName}`;
53
+ if (param.initializer) {
54
+ // Emit the default value directly
55
+ const [defaultExpr, newContext] = emitExpression(
56
+ param.initializer,
57
+ currentContext
58
+ );
59
+ currentContext = newContext;
60
+ paramStr = `${paramType} ${paramName} = ${defaultExpr.text}`;
61
+ } else if (isOptional && !isRest) {
62
+ paramStr += " = default";
63
+ }
64
+
65
+ params.push(paramStr);
66
+ }
67
+
68
+ return [params.join(", "), currentContext];
69
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Interface member to property emission
3
+ */
4
+
5
+ import { IrInterfaceMember } from "@tsonic/frontend";
6
+ import { EmitterContext, getIndent } from "../../types.js";
7
+ import { emitType } from "../../type-emitter.js";
8
+ import { capitalize } from "./helpers.js";
9
+ import { emitParameters } from "./parameters.js";
10
+
11
+ /**
12
+ * Emit interface member as C# auto-property (for classes)
13
+ * Per spec/16-types-and-interfaces.md §2.1
14
+ */
15
+ export const emitInterfaceMemberAsProperty = (
16
+ member: IrInterfaceMember,
17
+ context: EmitterContext
18
+ ): [string, EmitterContext] => {
19
+ const ind = getIndent(context);
20
+
21
+ switch (member.kind) {
22
+ case "propertySignature": {
23
+ let currentContext = context;
24
+ const parts: string[] = [];
25
+
26
+ parts.push("public"); // All properties public
27
+
28
+ // Property type
29
+ if (member.type) {
30
+ // If this is an inline object type, use the extracted class name
31
+ let typeName: string;
32
+ if (member.type.kind === "objectType") {
33
+ // Use capitalized property name as the class name
34
+ typeName = capitalize(member.name);
35
+ } else {
36
+ const [emittedType, newContext] = emitType(
37
+ member.type,
38
+ currentContext
39
+ );
40
+ currentContext = newContext;
41
+ typeName = emittedType;
42
+ }
43
+ // Optional members become nullable (spec §2.1)
44
+ const typeStr = member.isOptional ? `${typeName}?` : typeName;
45
+ parts.push(typeStr);
46
+ } else {
47
+ const typeStr = member.isOptional ? "object?" : "object";
48
+ parts.push(typeStr);
49
+ }
50
+
51
+ // Property name
52
+ parts.push(member.name);
53
+
54
+ // Getter/setter (readonly is get-only)
55
+ const accessors = member.isReadonly ? "{ get; }" : "{ get; set; }";
56
+
57
+ return [`${ind}${parts.join(" ")} ${accessors}`, currentContext];
58
+ }
59
+
60
+ case "methodSignature": {
61
+ let currentContext = context;
62
+ const parts: string[] = [];
63
+
64
+ parts.push("public"); // All methods public
65
+
66
+ // Return type
67
+ if (member.returnType) {
68
+ const [returnType, newContext] = emitType(
69
+ member.returnType,
70
+ currentContext
71
+ );
72
+ currentContext = newContext;
73
+ parts.push(returnType);
74
+ } else {
75
+ parts.push("void");
76
+ }
77
+
78
+ // Method name
79
+ parts.push(member.name);
80
+
81
+ // Parameters
82
+ const params = emitParameters(member.parameters, currentContext);
83
+ currentContext = params[1];
84
+
85
+ // Methods in interfaces are abstract declarations
86
+ return [
87
+ `${ind}${parts.join(" ")}(${params[0]}) => throw new NotImplementedException();`,
88
+ currentContext,
89
+ ];
90
+ }
91
+
92
+ default:
93
+ return [`${ind}// TODO: unhandled interface member`, context];
94
+ }
95
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Class-related helpers (members, constructors, parameters, interface members)
3
+ * Main dispatcher - re-exports from classes/ subdirectory
4
+ */
5
+
6
+ export {
7
+ emitClassMember,
8
+ emitInterfaceMemberAsProperty,
9
+ extractInlineObjectTypes,
10
+ emitExtractedType,
11
+ type ExtractedType,
12
+ emitParameters,
13
+ capitalize,
14
+ } from "./classes/index.js";
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Conditional statement emitters (if, switch)
3
+ */
4
+
5
+ import { IrExpression, IrStatement } from "@tsonic/frontend";
6
+ import { EmitterContext, getIndent, indent, dedent } from "../../types.js";
7
+ import { emitExpression } from "../../expression-emitter.js";
8
+ import { emitStatement } from "../../statement-emitter.js";
9
+
10
+ /**
11
+ * Check if an expression's inferred type is boolean
12
+ */
13
+ const isBooleanCondition = (expr: IrExpression): boolean => {
14
+ const type = expr.inferredType;
15
+ if (!type) return false;
16
+ return type.kind === "primitiveType" && type.name === "boolean";
17
+ };
18
+
19
+ /**
20
+ * Convert an expression to a valid C# boolean condition.
21
+ * In TypeScript, any value can be used in a boolean context (truthy/falsy).
22
+ * In C#, only boolean expressions are valid conditions.
23
+ *
24
+ * For non-boolean expressions:
25
+ * - Reference types (objects, arrays): emit `expr != null`
26
+ * - Numbers: could emit `expr != 0` (not implemented yet)
27
+ * - Strings: could emit `!string.IsNullOrEmpty(expr)` (not implemented yet)
28
+ */
29
+ const toBooleanCondition = (
30
+ expr: IrExpression,
31
+ emittedText: string
32
+ ): string => {
33
+ // If already boolean, use as-is
34
+ if (isBooleanCondition(expr)) {
35
+ return emittedText;
36
+ }
37
+
38
+ // For reference types (non-primitive), add != null check
39
+ const type = expr.inferredType;
40
+ if (type && type.kind !== "primitiveType") {
41
+ return `${emittedText} != null`;
42
+ }
43
+
44
+ // Default: assume it's a reference type and add null check
45
+ // This handles cases where type inference didn't work
46
+ if (!type) {
47
+ return `${emittedText} != null`;
48
+ }
49
+
50
+ // For primitives that are not boolean, just use as-is for now
51
+ // TODO: Handle number truthiness (x != 0) and string truthiness
52
+ return emittedText;
53
+ };
54
+
55
+ /**
56
+ * Emit an if statement
57
+ */
58
+ export const emitIfStatement = (
59
+ stmt: Extract<IrStatement, { kind: "ifStatement" }>,
60
+ context: EmitterContext
61
+ ): [string, EmitterContext] => {
62
+ const ind = getIndent(context);
63
+ const [condFrag, condContext] = emitExpression(stmt.condition, context);
64
+
65
+ // Convert to boolean condition if needed
66
+ const condText = toBooleanCondition(stmt.condition, condFrag.text);
67
+
68
+ const [thenCode, thenContext] = emitStatement(
69
+ stmt.thenStatement,
70
+ indent(condContext)
71
+ );
72
+
73
+ let code = `${ind}if (${condText})\n${thenCode}`;
74
+ let finalContext = dedent(thenContext);
75
+
76
+ if (stmt.elseStatement) {
77
+ const [elseCode, elseContext] = emitStatement(
78
+ stmt.elseStatement,
79
+ indent(finalContext)
80
+ );
81
+ code += `\n${ind}else\n${elseCode}`;
82
+ finalContext = dedent(elseContext);
83
+ }
84
+
85
+ return [code, finalContext];
86
+ };
87
+
88
+ /**
89
+ * Emit a switch statement
90
+ */
91
+ export const emitSwitchStatement = (
92
+ stmt: Extract<IrStatement, { kind: "switchStatement" }>,
93
+ context: EmitterContext
94
+ ): [string, EmitterContext] => {
95
+ const ind = getIndent(context);
96
+ const [exprFrag, exprContext] = emitExpression(stmt.expression, context);
97
+
98
+ let currentContext = indent(exprContext);
99
+ const caseInd = getIndent(currentContext);
100
+ const cases: string[] = [];
101
+
102
+ for (const switchCase of stmt.cases) {
103
+ if (switchCase.test) {
104
+ const [testFrag, testContext] = emitExpression(
105
+ switchCase.test,
106
+ currentContext
107
+ );
108
+ currentContext = testContext;
109
+ cases.push(`${caseInd}case ${testFrag.text}:`);
110
+ } else {
111
+ cases.push(`${caseInd}default:`);
112
+ }
113
+
114
+ const stmtContext = indent(currentContext);
115
+ for (const s of switchCase.statements) {
116
+ const [code, newContext] = emitStatement(s, stmtContext);
117
+ cases.push(code);
118
+ currentContext = newContext;
119
+ }
120
+
121
+ // Add break if not already present
122
+ const lastStmt = switchCase.statements[switchCase.statements.length - 1];
123
+ if (
124
+ !lastStmt ||
125
+ (lastStmt.kind !== "breakStatement" &&
126
+ lastStmt.kind !== "returnStatement")
127
+ ) {
128
+ cases.push(`${getIndent(stmtContext)}break;`);
129
+ }
130
+ }
131
+
132
+ const code = `${ind}switch (${exprFrag.text})\n${ind}{\n${cases.join("\n")}\n${ind}}`;
133
+ return [code, dedent(currentContext)];
134
+ };