@tsonic/emitter 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +34 -0
- package/scripts/update-golden-tests.ts +119 -0
- package/src/adapter-generator.ts +112 -0
- package/src/array.test.ts +301 -0
- package/src/constants.ts +32 -0
- package/src/core/exports.ts +36 -0
- package/src/core/imports.test.ts +83 -0
- package/src/core/imports.ts +243 -0
- package/src/core/index.ts +9 -0
- package/src/core/module-emitter/assembly.ts +83 -0
- package/src/core/module-emitter/header.ts +19 -0
- package/src/core/module-emitter/index.ts +17 -0
- package/src/core/module-emitter/namespace.ts +39 -0
- package/src/core/module-emitter/orchestrator.ts +98 -0
- package/src/core/module-emitter/separation.ts +41 -0
- package/src/core/module-emitter/static-container.ts +75 -0
- package/src/core/module-emitter.test.ts +154 -0
- package/src/core/module-emitter.ts +6 -0
- package/src/core/module-map.ts +218 -0
- package/src/core/options.ts +16 -0
- package/src/core/type-params.ts +34 -0
- package/src/emitter-types/context.ts +91 -0
- package/src/emitter-types/core.ts +138 -0
- package/src/emitter-types/csharp-types.ts +42 -0
- package/src/emitter-types/formatting.ts +13 -0
- package/src/emitter-types/fqn.ts +81 -0
- package/src/emitter-types/index.ts +31 -0
- package/src/emitter.ts +107 -0
- package/src/expression-emitter.ts +112 -0
- package/src/expressions/access.ts +104 -0
- package/src/expressions/calls.ts +264 -0
- package/src/expressions/collections.ts +354 -0
- package/src/expressions/functions.ts +71 -0
- package/src/expressions/identifiers.ts +125 -0
- package/src/expressions/index.test.ts +515 -0
- package/src/expressions/index.ts +38 -0
- package/src/expressions/literals.ts +138 -0
- package/src/expressions/operators.ts +211 -0
- package/src/expressions/other.ts +63 -0
- package/src/generator-exchange.ts +120 -0
- package/src/generator.test.ts +193 -0
- package/src/golden-tests/config-parser.ts +67 -0
- package/src/golden-tests/discovery.ts +61 -0
- package/src/golden-tests/index.ts +10 -0
- package/src/golden-tests/registration.ts +26 -0
- package/src/golden-tests/runner.ts +131 -0
- package/src/golden-tests/tree-builder.ts +43 -0
- package/src/golden-tests/types.ts +21 -0
- package/src/golden.test.ts +40 -0
- package/src/hierarchical-bindings.test.ts +258 -0
- package/src/index.ts +14 -0
- package/src/integration.test.ts +303 -0
- package/src/specialization/call-site-rewriting.test.ts +99 -0
- package/src/specialization/collection/expressions.ts +184 -0
- package/src/specialization/collection/index.ts +7 -0
- package/src/specialization/collection/orchestrator.ts +25 -0
- package/src/specialization/collection/statements.ts +91 -0
- package/src/specialization/collection.ts +10 -0
- package/src/specialization/generation.ts +189 -0
- package/src/specialization/generic-classes.test.ts +59 -0
- package/src/specialization/generic-functions.test.ts +292 -0
- package/src/specialization/helpers.ts +39 -0
- package/src/specialization/index.ts +28 -0
- package/src/specialization/interfaces.test.ts +151 -0
- package/src/specialization/naming.ts +34 -0
- package/src/specialization/substitution.ts +186 -0
- package/src/specialization/type-aliases.test.ts +134 -0
- package/src/specialization/types.ts +19 -0
- package/src/specialization-generator.ts +23 -0
- package/src/statement-emitter.ts +117 -0
- package/src/statements/blocks.ts +115 -0
- package/src/statements/classes/helpers.ts +9 -0
- package/src/statements/classes/index.ts +13 -0
- package/src/statements/classes/inline-types.ts +79 -0
- package/src/statements/classes/members/constructors.ts +123 -0
- package/src/statements/classes/members/index.ts +8 -0
- package/src/statements/classes/members/methods.ts +137 -0
- package/src/statements/classes/members/orchestrator.ts +33 -0
- package/src/statements/classes/members/properties.ts +62 -0
- package/src/statements/classes/members.ts +6 -0
- package/src/statements/classes/parameters.ts +69 -0
- package/src/statements/classes/properties.ts +95 -0
- package/src/statements/classes.ts +14 -0
- package/src/statements/control/conditionals.ts +134 -0
- package/src/statements/control/exceptions.ts +59 -0
- package/src/statements/control/index.ts +11 -0
- package/src/statements/control/loops.ts +250 -0
- package/src/statements/control.ts +14 -0
- package/src/statements/declarations/classes.ts +89 -0
- package/src/statements/declarations/enums.ts +32 -0
- package/src/statements/declarations/functions.ts +147 -0
- package/src/statements/declarations/index.ts +10 -0
- package/src/statements/declarations/interfaces.ts +116 -0
- package/src/statements/declarations/structs.test.ts +182 -0
- package/src/statements/declarations/type-aliases.ts +104 -0
- package/src/statements/declarations/variables.ts +159 -0
- package/src/statements/declarations.ts +13 -0
- package/src/statements/index.test.ts +258 -0
- package/src/statements/index.ts +43 -0
- package/src/type-assertion.test.ts +143 -0
- package/src/type-emitter.ts +18 -0
- package/src/types/arrays.ts +21 -0
- package/src/types/dictionaries.ts +52 -0
- package/src/types/emitter.ts +76 -0
- package/src/types/functions.ts +45 -0
- package/src/types/index.test.ts +116 -0
- package/src/types/index.ts +14 -0
- package/src/types/intersections.ts +19 -0
- package/src/types/literals.ts +26 -0
- package/src/types/objects.ts +27 -0
- package/src/types/parameters.test.ts +146 -0
- package/src/types/parameters.ts +95 -0
- package/src/types/primitives.ts +24 -0
- package/src/types/references.ts +187 -0
- package/src/types/unions.test.ts +397 -0
- package/src/types/unions.ts +62 -0
- package/src/types.ts +33 -0
- package/testcases/README.md +213 -0
- package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
- package/testcases/arrays/basic/config.yaml +1 -0
- package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
- package/testcases/arrays/destructuring/config.yaml +1 -0
- package/testcases/arrays/methods/ArrayMethods.ts +6 -0
- package/testcases/arrays/methods/config.yaml +1 -0
- package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
- package/testcases/arrays/multidimensional/config.yaml +1 -0
- package/testcases/arrays/spread/ArraySpread.ts +3 -0
- package/testcases/arrays/spread/config.yaml +1 -0
- package/testcases/async/basic/AsyncFunction.ts +5 -0
- package/testcases/async/basic/config.yaml +1 -0
- package/testcases/classes/abstract/AbstractClasses.ts +53 -0
- package/testcases/classes/abstract/config.yaml +1 -0
- package/testcases/classes/basic/Person.ts +12 -0
- package/testcases/classes/basic/config.yaml +1 -0
- package/testcases/classes/constructor/User.ts +11 -0
- package/testcases/classes/constructor/config.yaml +1 -0
- package/testcases/classes/field-inference/Counter.ts +11 -0
- package/testcases/classes/field-inference/config.yaml +1 -0
- package/testcases/classes/inheritance/Inheritance.ts +24 -0
- package/testcases/classes/inheritance/config.yaml +1 -0
- package/testcases/classes/static-members/MathHelper.ts +12 -0
- package/testcases/classes/static-members/config.yaml +1 -0
- package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
- package/testcases/control-flow/error-handling/config.yaml +1 -0
- package/testcases/control-flow/loops/Loops.ts +21 -0
- package/testcases/control-flow/loops/config.yaml +1 -0
- package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
- package/testcases/control-flow/switch/config.yaml +1 -0
- package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
- package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
- package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
- package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
- package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
- package/testcases/edge-cases/shadowing/config.yaml +1 -0
- package/testcases/functions/arrow/ArrowFunction.ts +5 -0
- package/testcases/functions/arrow/config.yaml +1 -0
- package/testcases/functions/basic/Greet.ts +3 -0
- package/testcases/functions/basic/config.yaml +1 -0
- package/testcases/functions/closures/Closures.ts +11 -0
- package/testcases/functions/closures/config.yaml +1 -0
- package/testcases/functions/default-params/DefaultParams.ts +7 -0
- package/testcases/functions/default-params/config.yaml +1 -0
- package/testcases/functions/rest-params/RestParams.ts +7 -0
- package/testcases/functions/rest-params/config.yaml +1 -0
- package/testcases/functions/type-guards/TypeGuards.ts +52 -0
- package/testcases/functions/type-guards/config.yaml +1 -0
- package/testcases/operators/logical/LogicalOperators.ts +11 -0
- package/testcases/operators/logical/config.yaml +1 -0
- package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
- package/testcases/operators/nullish-coalescing/config.yaml +1 -0
- package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
- package/testcases/operators/optional-chaining/config.yaml +1 -0
- package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
- package/testcases/real-world/advanced-generics/config.yaml +1 -0
- package/testcases/real-world/async-ops/async-ops.ts +67 -0
- package/testcases/real-world/async-ops/config.yaml +1 -0
- package/testcases/real-world/business-logic/business-logic.ts +215 -0
- package/testcases/real-world/business-logic/config.yaml +1 -0
- package/testcases/real-world/calculator/calculator.ts +29 -0
- package/testcases/real-world/calculator/config.yaml +1 -0
- package/testcases/real-world/data-structures/config.yaml +1 -0
- package/testcases/real-world/data-structures/data-structures.ts +133 -0
- package/testcases/real-world/functional/config.yaml +1 -0
- package/testcases/real-world/functional/functional.ts +116 -0
- package/testcases/real-world/shapes/config.yaml +1 -0
- package/testcases/real-world/shapes/shapes.ts +87 -0
- package/testcases/real-world/string-utils/config.yaml +1 -0
- package/testcases/real-world/string-utils/string-utils.ts +47 -0
- package/testcases/real-world/todo-list/config.yaml +1 -0
- package/testcases/real-world/todo-list/todo-list.ts +52 -0
- package/testcases/real-world/type-guards/config.yaml +1 -0
- package/testcases/real-world/type-guards/type-guards.ts +71 -0
- package/testcases/structs/basic/Point.ts +9 -0
- package/testcases/structs/basic/config.yaml +1 -0
- package/testcases/types/conditional/ConditionalTypes.ts +35 -0
- package/testcases/types/conditional/config.yaml +1 -0
- package/testcases/types/constants/ModuleConstants.ts +6 -0
- package/testcases/types/constants/config.yaml +1 -0
- package/testcases/types/generics/Generics.ts +15 -0
- package/testcases/types/generics/config.yaml +1 -0
- package/testcases/types/interfaces/Interfaces.ts +14 -0
- package/testcases/types/interfaces/config.yaml +1 -0
- package/testcases/types/mapped/MappedTypes.ts +27 -0
- package/testcases/types/mapped/config.yaml +1 -0
- package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
- package/testcases/types/tuples-intersections/config.yaml +1 -0
- package/testcases/types/unions/UnionTypes.ts +11 -0
- package/testcases/types/unions/config.yaml +1 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,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,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
|
+
};
|