@tsonic/emitter 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +34 -0
- package/scripts/update-golden-tests.ts +119 -0
- package/src/adapter-generator.ts +112 -0
- package/src/array.test.ts +301 -0
- package/src/constants.ts +32 -0
- package/src/core/exports.ts +36 -0
- package/src/core/imports.test.ts +83 -0
- package/src/core/imports.ts +243 -0
- package/src/core/index.ts +9 -0
- package/src/core/module-emitter/assembly.ts +83 -0
- package/src/core/module-emitter/header.ts +19 -0
- package/src/core/module-emitter/index.ts +17 -0
- package/src/core/module-emitter/namespace.ts +39 -0
- package/src/core/module-emitter/orchestrator.ts +98 -0
- package/src/core/module-emitter/separation.ts +41 -0
- package/src/core/module-emitter/static-container.ts +75 -0
- package/src/core/module-emitter.test.ts +154 -0
- package/src/core/module-emitter.ts +6 -0
- package/src/core/module-map.ts +218 -0
- package/src/core/options.ts +16 -0
- package/src/core/type-params.ts +34 -0
- package/src/emitter-types/context.ts +91 -0
- package/src/emitter-types/core.ts +138 -0
- package/src/emitter-types/csharp-types.ts +42 -0
- package/src/emitter-types/formatting.ts +13 -0
- package/src/emitter-types/fqn.ts +81 -0
- package/src/emitter-types/index.ts +31 -0
- package/src/emitter.ts +107 -0
- package/src/expression-emitter.ts +112 -0
- package/src/expressions/access.ts +104 -0
- package/src/expressions/calls.ts +264 -0
- package/src/expressions/collections.ts +354 -0
- package/src/expressions/functions.ts +71 -0
- package/src/expressions/identifiers.ts +125 -0
- package/src/expressions/index.test.ts +515 -0
- package/src/expressions/index.ts +38 -0
- package/src/expressions/literals.ts +138 -0
- package/src/expressions/operators.ts +211 -0
- package/src/expressions/other.ts +63 -0
- package/src/generator-exchange.ts +120 -0
- package/src/generator.test.ts +193 -0
- package/src/golden-tests/config-parser.ts +67 -0
- package/src/golden-tests/discovery.ts +61 -0
- package/src/golden-tests/index.ts +10 -0
- package/src/golden-tests/registration.ts +26 -0
- package/src/golden-tests/runner.ts +131 -0
- package/src/golden-tests/tree-builder.ts +43 -0
- package/src/golden-tests/types.ts +21 -0
- package/src/golden.test.ts +40 -0
- package/src/hierarchical-bindings.test.ts +258 -0
- package/src/index.ts +14 -0
- package/src/integration.test.ts +303 -0
- package/src/specialization/call-site-rewriting.test.ts +99 -0
- package/src/specialization/collection/expressions.ts +184 -0
- package/src/specialization/collection/index.ts +7 -0
- package/src/specialization/collection/orchestrator.ts +25 -0
- package/src/specialization/collection/statements.ts +91 -0
- package/src/specialization/collection.ts +10 -0
- package/src/specialization/generation.ts +189 -0
- package/src/specialization/generic-classes.test.ts +59 -0
- package/src/specialization/generic-functions.test.ts +292 -0
- package/src/specialization/helpers.ts +39 -0
- package/src/specialization/index.ts +28 -0
- package/src/specialization/interfaces.test.ts +151 -0
- package/src/specialization/naming.ts +34 -0
- package/src/specialization/substitution.ts +186 -0
- package/src/specialization/type-aliases.test.ts +134 -0
- package/src/specialization/types.ts +19 -0
- package/src/specialization-generator.ts +23 -0
- package/src/statement-emitter.ts +117 -0
- package/src/statements/blocks.ts +115 -0
- package/src/statements/classes/helpers.ts +9 -0
- package/src/statements/classes/index.ts +13 -0
- package/src/statements/classes/inline-types.ts +79 -0
- package/src/statements/classes/members/constructors.ts +123 -0
- package/src/statements/classes/members/index.ts +8 -0
- package/src/statements/classes/members/methods.ts +137 -0
- package/src/statements/classes/members/orchestrator.ts +33 -0
- package/src/statements/classes/members/properties.ts +62 -0
- package/src/statements/classes/members.ts +6 -0
- package/src/statements/classes/parameters.ts +69 -0
- package/src/statements/classes/properties.ts +95 -0
- package/src/statements/classes.ts +14 -0
- package/src/statements/control/conditionals.ts +134 -0
- package/src/statements/control/exceptions.ts +59 -0
- package/src/statements/control/index.ts +11 -0
- package/src/statements/control/loops.ts +250 -0
- package/src/statements/control.ts +14 -0
- package/src/statements/declarations/classes.ts +89 -0
- package/src/statements/declarations/enums.ts +32 -0
- package/src/statements/declarations/functions.ts +147 -0
- package/src/statements/declarations/index.ts +10 -0
- package/src/statements/declarations/interfaces.ts +116 -0
- package/src/statements/declarations/structs.test.ts +182 -0
- package/src/statements/declarations/type-aliases.ts +104 -0
- package/src/statements/declarations/variables.ts +159 -0
- package/src/statements/declarations.ts +13 -0
- package/src/statements/index.test.ts +258 -0
- package/src/statements/index.ts +43 -0
- package/src/type-assertion.test.ts +143 -0
- package/src/type-emitter.ts +18 -0
- package/src/types/arrays.ts +21 -0
- package/src/types/dictionaries.ts +52 -0
- package/src/types/emitter.ts +76 -0
- package/src/types/functions.ts +45 -0
- package/src/types/index.test.ts +116 -0
- package/src/types/index.ts +14 -0
- package/src/types/intersections.ts +19 -0
- package/src/types/literals.ts +26 -0
- package/src/types/objects.ts +27 -0
- package/src/types/parameters.test.ts +146 -0
- package/src/types/parameters.ts +95 -0
- package/src/types/primitives.ts +24 -0
- package/src/types/references.ts +187 -0
- package/src/types/unions.test.ts +397 -0
- package/src/types/unions.ts +62 -0
- package/src/types.ts +33 -0
- package/testcases/README.md +213 -0
- package/testcases/arrays/basic/ArrayLiteral.ts +4 -0
- package/testcases/arrays/basic/config.yaml +1 -0
- package/testcases/arrays/destructuring/ArrayDestructure.ts +4 -0
- package/testcases/arrays/destructuring/config.yaml +1 -0
- package/testcases/arrays/methods/ArrayMethods.ts +6 -0
- package/testcases/arrays/methods/config.yaml +1 -0
- package/testcases/arrays/multidimensional/MultiDimensional.ts +10 -0
- package/testcases/arrays/multidimensional/config.yaml +1 -0
- package/testcases/arrays/spread/ArraySpread.ts +3 -0
- package/testcases/arrays/spread/config.yaml +1 -0
- package/testcases/async/basic/AsyncFunction.ts +5 -0
- package/testcases/async/basic/config.yaml +1 -0
- package/testcases/classes/abstract/AbstractClasses.ts +53 -0
- package/testcases/classes/abstract/config.yaml +1 -0
- package/testcases/classes/basic/Person.ts +12 -0
- package/testcases/classes/basic/config.yaml +1 -0
- package/testcases/classes/constructor/User.ts +11 -0
- package/testcases/classes/constructor/config.yaml +1 -0
- package/testcases/classes/field-inference/Counter.ts +11 -0
- package/testcases/classes/field-inference/config.yaml +1 -0
- package/testcases/classes/inheritance/Inheritance.ts +24 -0
- package/testcases/classes/inheritance/config.yaml +1 -0
- package/testcases/classes/static-members/MathHelper.ts +12 -0
- package/testcases/classes/static-members/config.yaml +1 -0
- package/testcases/control-flow/error-handling/ErrorHandling.ts +13 -0
- package/testcases/control-flow/error-handling/config.yaml +1 -0
- package/testcases/control-flow/loops/Loops.ts +21 -0
- package/testcases/control-flow/loops/config.yaml +1 -0
- package/testcases/control-flow/switch/SwitchStatement.ts +15 -0
- package/testcases/control-flow/switch/config.yaml +1 -0
- package/testcases/edge-cases/complex-expressions/ComplexExpressions.ts +10 -0
- package/testcases/edge-cases/complex-expressions/config.yaml +1 -0
- package/testcases/edge-cases/nested-scopes/NestedScopes.ts +10 -0
- package/testcases/edge-cases/nested-scopes/config.yaml +1 -0
- package/testcases/edge-cases/shadowing/Shadowing.ts +16 -0
- package/testcases/edge-cases/shadowing/config.yaml +1 -0
- package/testcases/functions/arrow/ArrowFunction.ts +5 -0
- package/testcases/functions/arrow/config.yaml +1 -0
- package/testcases/functions/basic/Greet.ts +3 -0
- package/testcases/functions/basic/config.yaml +1 -0
- package/testcases/functions/closures/Closures.ts +11 -0
- package/testcases/functions/closures/config.yaml +1 -0
- package/testcases/functions/default-params/DefaultParams.ts +7 -0
- package/testcases/functions/default-params/config.yaml +1 -0
- package/testcases/functions/rest-params/RestParams.ts +7 -0
- package/testcases/functions/rest-params/config.yaml +1 -0
- package/testcases/functions/type-guards/TypeGuards.ts +52 -0
- package/testcases/functions/type-guards/config.yaml +1 -0
- package/testcases/operators/logical/LogicalOperators.ts +11 -0
- package/testcases/operators/logical/config.yaml +1 -0
- package/testcases/operators/nullish-coalescing/NullishCoalescing.ts +7 -0
- package/testcases/operators/nullish-coalescing/config.yaml +1 -0
- package/testcases/operators/optional-chaining/OptionalChaining.ts +15 -0
- package/testcases/operators/optional-chaining/config.yaml +1 -0
- package/testcases/real-world/advanced-generics/advanced-generics.ts +116 -0
- package/testcases/real-world/advanced-generics/config.yaml +1 -0
- package/testcases/real-world/async-ops/async-ops.ts +67 -0
- package/testcases/real-world/async-ops/config.yaml +1 -0
- package/testcases/real-world/business-logic/business-logic.ts +215 -0
- package/testcases/real-world/business-logic/config.yaml +1 -0
- package/testcases/real-world/calculator/calculator.ts +29 -0
- package/testcases/real-world/calculator/config.yaml +1 -0
- package/testcases/real-world/data-structures/config.yaml +1 -0
- package/testcases/real-world/data-structures/data-structures.ts +133 -0
- package/testcases/real-world/functional/config.yaml +1 -0
- package/testcases/real-world/functional/functional.ts +116 -0
- package/testcases/real-world/shapes/config.yaml +1 -0
- package/testcases/real-world/shapes/shapes.ts +87 -0
- package/testcases/real-world/string-utils/config.yaml +1 -0
- package/testcases/real-world/string-utils/string-utils.ts +47 -0
- package/testcases/real-world/todo-list/config.yaml +1 -0
- package/testcases/real-world/todo-list/todo-list.ts +52 -0
- package/testcases/real-world/type-guards/config.yaml +1 -0
- package/testcases/real-world/type-guards/type-guards.ts +71 -0
- package/testcases/structs/basic/Point.ts +9 -0
- package/testcases/structs/basic/config.yaml +1 -0
- package/testcases/types/conditional/ConditionalTypes.ts +35 -0
- package/testcases/types/conditional/config.yaml +1 -0
- package/testcases/types/constants/ModuleConstants.ts +6 -0
- package/testcases/types/constants/config.yaml +1 -0
- package/testcases/types/generics/Generics.ts +15 -0
- package/testcases/types/generics/config.yaml +1 -0
- package/testcases/types/interfaces/Interfaces.ts +14 -0
- package/testcases/types/interfaces/config.yaml +1 -0
- package/testcases/types/mapped/MappedTypes.ts +27 -0
- package/testcases/types/mapped/config.yaml +1 -0
- package/testcases/types/tuples-intersections/TuplesAndIntersections.ts +46 -0
- package/testcases/types/tuples-intersections/config.yaml +1 -0
- package/testcases/types/unions/UnionTypes.ts +11 -0
- package/testcases/types/unions/config.yaml +1 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface declaration emission (as C# classes)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrStatement } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext, getIndent, indent } from "../../types.js";
|
|
7
|
+
import { emitType, emitTypeParameters } from "../../type-emitter.js";
|
|
8
|
+
import {
|
|
9
|
+
extractInlineObjectTypes,
|
|
10
|
+
emitExtractedType,
|
|
11
|
+
emitInterfaceMemberAsProperty,
|
|
12
|
+
} from "../classes.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Emit an interface declaration (as C# class)
|
|
16
|
+
*/
|
|
17
|
+
export const emitInterfaceDeclaration = (
|
|
18
|
+
stmt: Extract<IrStatement, { kind: "interfaceDeclaration" }>,
|
|
19
|
+
context: EmitterContext
|
|
20
|
+
): [string, EmitterContext] => {
|
|
21
|
+
// Per spec/16-types-and-interfaces.md §2.1:
|
|
22
|
+
// TypeScript interfaces map to C# classes (not C# interfaces)
|
|
23
|
+
// because TS interfaces are structural and we need nominal types in C#
|
|
24
|
+
|
|
25
|
+
const ind = getIndent(context);
|
|
26
|
+
let currentContext = context;
|
|
27
|
+
|
|
28
|
+
// Extract inline object types and emit them as separate classes
|
|
29
|
+
const extractedTypes = extractInlineObjectTypes(stmt.members);
|
|
30
|
+
const extractedClassCodes: string[] = [];
|
|
31
|
+
|
|
32
|
+
for (const extracted of extractedTypes) {
|
|
33
|
+
const [classCode, newContext] = emitExtractedType(
|
|
34
|
+
extracted,
|
|
35
|
+
currentContext
|
|
36
|
+
);
|
|
37
|
+
extractedClassCodes.push(classCode);
|
|
38
|
+
currentContext = newContext;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const parts: string[] = [];
|
|
42
|
+
|
|
43
|
+
// Access modifier
|
|
44
|
+
const accessibility = stmt.isExported ? "public" : "internal";
|
|
45
|
+
parts.push(accessibility);
|
|
46
|
+
// Emit struct or class based on isStruct flag
|
|
47
|
+
parts.push(stmt.isStruct ? "struct" : "class");
|
|
48
|
+
parts.push(stmt.name);
|
|
49
|
+
|
|
50
|
+
// Type parameters (if any)
|
|
51
|
+
if (stmt.typeParameters && stmt.typeParameters.length > 0) {
|
|
52
|
+
const [typeParamsStr, whereClauses, typeParamContext] = emitTypeParameters(
|
|
53
|
+
stmt.typeParameters,
|
|
54
|
+
currentContext
|
|
55
|
+
);
|
|
56
|
+
parts.push(typeParamsStr);
|
|
57
|
+
currentContext = typeParamContext;
|
|
58
|
+
|
|
59
|
+
// Extended interfaces/classes
|
|
60
|
+
if (stmt.extends && stmt.extends.length > 0) {
|
|
61
|
+
const extended: string[] = [];
|
|
62
|
+
for (const ext of stmt.extends) {
|
|
63
|
+
const [extType, newContext] = emitType(ext, currentContext);
|
|
64
|
+
currentContext = newContext;
|
|
65
|
+
extended.push(extType);
|
|
66
|
+
}
|
|
67
|
+
parts.push(":");
|
|
68
|
+
parts.push(extended.join(", "));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Where clauses for type parameters
|
|
72
|
+
if (whereClauses.length > 0) {
|
|
73
|
+
parts.push("\n" + ind + " " + whereClauses.join("\n" + ind + " "));
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
// Extended interfaces/classes (no generics)
|
|
77
|
+
if (stmt.extends && stmt.extends.length > 0) {
|
|
78
|
+
const extended: string[] = [];
|
|
79
|
+
for (const ext of stmt.extends) {
|
|
80
|
+
const [extType, newContext] = emitType(ext, currentContext);
|
|
81
|
+
currentContext = newContext;
|
|
82
|
+
extended.push(extType);
|
|
83
|
+
}
|
|
84
|
+
parts.push(":");
|
|
85
|
+
parts.push(extended.join(", "));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Class body with auto-properties
|
|
90
|
+
const bodyContext = indent(currentContext);
|
|
91
|
+
const members: string[] = [];
|
|
92
|
+
|
|
93
|
+
for (const member of stmt.members) {
|
|
94
|
+
const [memberCode, newContext] = emitInterfaceMemberAsProperty(
|
|
95
|
+
member,
|
|
96
|
+
bodyContext
|
|
97
|
+
);
|
|
98
|
+
members.push(memberCode);
|
|
99
|
+
currentContext = newContext;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const signature = parts.join(" ");
|
|
103
|
+
const memberCode = members.join("\n\n");
|
|
104
|
+
const mainClassCode = `${ind}${signature}\n${ind}{\n${memberCode}\n${ind}}`;
|
|
105
|
+
|
|
106
|
+
// Combine main interface and extracted classes (extracted classes come after)
|
|
107
|
+
const allParts: string[] = [];
|
|
108
|
+
allParts.push(mainClassCode);
|
|
109
|
+
if (extractedClassCodes.length > 0) {
|
|
110
|
+
allParts.push(...extractedClassCodes);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const code = allParts.join("\n");
|
|
114
|
+
|
|
115
|
+
return [code, currentContext];
|
|
116
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for struct emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it } from "mocha";
|
|
6
|
+
import { expect } from "chai";
|
|
7
|
+
import { emitCSharpFile } from "../../index.js";
|
|
8
|
+
import {
|
|
9
|
+
IrModule,
|
|
10
|
+
IrClassDeclaration,
|
|
11
|
+
IrInterfaceDeclaration,
|
|
12
|
+
} from "@tsonic/frontend";
|
|
13
|
+
|
|
14
|
+
describe("Struct Emission", () => {
|
|
15
|
+
it("should emit struct for class with isStruct flag", () => {
|
|
16
|
+
const classDecl: IrClassDeclaration = {
|
|
17
|
+
kind: "classDeclaration",
|
|
18
|
+
name: "Point",
|
|
19
|
+
members: [
|
|
20
|
+
{
|
|
21
|
+
kind: "propertyDeclaration",
|
|
22
|
+
name: "x",
|
|
23
|
+
type: { kind: "primitiveType", name: "number" },
|
|
24
|
+
accessibility: "public",
|
|
25
|
+
isStatic: false,
|
|
26
|
+
isReadonly: false,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
kind: "propertyDeclaration",
|
|
30
|
+
name: "y",
|
|
31
|
+
type: { kind: "primitiveType", name: "number" },
|
|
32
|
+
accessibility: "public",
|
|
33
|
+
isStatic: false,
|
|
34
|
+
isReadonly: false,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
implements: [],
|
|
38
|
+
isExported: true,
|
|
39
|
+
isStruct: true,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const module: IrModule = {
|
|
43
|
+
kind: "module",
|
|
44
|
+
filePath: "/src/Point.ts",
|
|
45
|
+
namespace: "Geometry",
|
|
46
|
+
className: "Point",
|
|
47
|
+
isStaticContainer: false,
|
|
48
|
+
imports: [],
|
|
49
|
+
body: [classDecl],
|
|
50
|
+
exports: [],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const result = emitCSharpFile(module);
|
|
54
|
+
expect(result).to.include("public struct Point");
|
|
55
|
+
expect(result).to.include("public double x");
|
|
56
|
+
expect(result).to.include("public double y");
|
|
57
|
+
expect(result).not.to.include("class Point");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should emit class for class without isStruct flag", () => {
|
|
61
|
+
const classDecl: IrClassDeclaration = {
|
|
62
|
+
kind: "classDeclaration",
|
|
63
|
+
name: "RegularClass",
|
|
64
|
+
members: [
|
|
65
|
+
{
|
|
66
|
+
kind: "propertyDeclaration",
|
|
67
|
+
name: "value",
|
|
68
|
+
type: { kind: "primitiveType", name: "number" },
|
|
69
|
+
accessibility: "public",
|
|
70
|
+
isStatic: false,
|
|
71
|
+
isReadonly: false,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
implements: [],
|
|
75
|
+
isExported: true,
|
|
76
|
+
isStruct: false,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const module: IrModule = {
|
|
80
|
+
kind: "module",
|
|
81
|
+
filePath: "/src/RegularClass.ts",
|
|
82
|
+
namespace: "App",
|
|
83
|
+
className: "RegularClass",
|
|
84
|
+
isStaticContainer: false,
|
|
85
|
+
imports: [],
|
|
86
|
+
body: [classDecl],
|
|
87
|
+
exports: [],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const result = emitCSharpFile(module);
|
|
91
|
+
expect(result).to.include("public class RegularClass");
|
|
92
|
+
expect(result).not.to.include("struct RegularClass");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should emit struct for interface with isStruct flag", () => {
|
|
96
|
+
const interfaceDecl: IrInterfaceDeclaration = {
|
|
97
|
+
kind: "interfaceDeclaration",
|
|
98
|
+
name: "Vector3D",
|
|
99
|
+
members: [
|
|
100
|
+
{
|
|
101
|
+
kind: "propertySignature",
|
|
102
|
+
name: "x",
|
|
103
|
+
type: { kind: "primitiveType", name: "number" },
|
|
104
|
+
isOptional: false,
|
|
105
|
+
isReadonly: false,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
kind: "propertySignature",
|
|
109
|
+
name: "y",
|
|
110
|
+
type: { kind: "primitiveType", name: "number" },
|
|
111
|
+
isOptional: false,
|
|
112
|
+
isReadonly: false,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
kind: "propertySignature",
|
|
116
|
+
name: "z",
|
|
117
|
+
type: { kind: "primitiveType", name: "number" },
|
|
118
|
+
isOptional: false,
|
|
119
|
+
isReadonly: false,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
extends: [],
|
|
123
|
+
isExported: true,
|
|
124
|
+
isStruct: true,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const module: IrModule = {
|
|
128
|
+
kind: "module",
|
|
129
|
+
filePath: "/src/Vector3D.ts",
|
|
130
|
+
namespace: "Geometry",
|
|
131
|
+
className: "Vector3D",
|
|
132
|
+
isStaticContainer: false,
|
|
133
|
+
imports: [],
|
|
134
|
+
body: [interfaceDecl],
|
|
135
|
+
exports: [],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const result = emitCSharpFile(module);
|
|
139
|
+
expect(result).to.include("public struct Vector3D");
|
|
140
|
+
expect(result).to.include("public double x");
|
|
141
|
+
expect(result).to.include("public double y");
|
|
142
|
+
expect(result).to.include("public double z");
|
|
143
|
+
expect(result).not.to.include("class Vector3D");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should not emit __brand property for struct", () => {
|
|
147
|
+
const classDecl: IrClassDeclaration = {
|
|
148
|
+
kind: "classDeclaration",
|
|
149
|
+
name: "Coord",
|
|
150
|
+
members: [
|
|
151
|
+
{
|
|
152
|
+
kind: "propertyDeclaration",
|
|
153
|
+
name: "x",
|
|
154
|
+
type: { kind: "primitiveType", name: "number" },
|
|
155
|
+
accessibility: "public",
|
|
156
|
+
isStatic: false,
|
|
157
|
+
isReadonly: false,
|
|
158
|
+
},
|
|
159
|
+
// __brand should already be filtered by the IR builder,
|
|
160
|
+
// but test that emitter handles it gracefully
|
|
161
|
+
],
|
|
162
|
+
implements: [],
|
|
163
|
+
isExported: true,
|
|
164
|
+
isStruct: true,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const module: IrModule = {
|
|
168
|
+
kind: "module",
|
|
169
|
+
filePath: "/src/Coord.ts",
|
|
170
|
+
namespace: "Geometry",
|
|
171
|
+
className: "Coord",
|
|
172
|
+
isStaticContainer: false,
|
|
173
|
+
imports: [],
|
|
174
|
+
body: [classDecl],
|
|
175
|
+
exports: [],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const result = emitCSharpFile(module);
|
|
179
|
+
expect(result).to.include("public struct Coord");
|
|
180
|
+
expect(result).not.to.include("__brand");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type alias declaration emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrStatement } from "@tsonic/frontend";
|
|
6
|
+
import { EmitterContext, getIndent, indent } from "../../types.js";
|
|
7
|
+
import { emitType, emitTypeParameters } from "../../type-emitter.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Emit a type alias declaration
|
|
11
|
+
*/
|
|
12
|
+
export const emitTypeAliasDeclaration = (
|
|
13
|
+
stmt: Extract<IrStatement, { kind: "typeAliasDeclaration" }>,
|
|
14
|
+
context: EmitterContext
|
|
15
|
+
): [string, EmitterContext] => {
|
|
16
|
+
// Per spec/16-types-and-interfaces.md §3:
|
|
17
|
+
// - Structural type aliases generate C# classes with __Alias suffix
|
|
18
|
+
// - Simple aliases (primitives, references) emit as comments or using aliases
|
|
19
|
+
|
|
20
|
+
const ind = getIndent(context);
|
|
21
|
+
let currentContext = context;
|
|
22
|
+
|
|
23
|
+
// Check if this is a structural (object) type alias
|
|
24
|
+
if (stmt.type.kind === "objectType") {
|
|
25
|
+
// Generate a sealed class (or struct) for structural type alias
|
|
26
|
+
const parts: string[] = [];
|
|
27
|
+
|
|
28
|
+
const accessibility = stmt.isExported ? "public" : "internal";
|
|
29
|
+
parts.push(accessibility);
|
|
30
|
+
// Emit struct or sealed class based on isStruct flag
|
|
31
|
+
if (stmt.isStruct) {
|
|
32
|
+
parts.push("struct");
|
|
33
|
+
} else {
|
|
34
|
+
parts.push("sealed");
|
|
35
|
+
parts.push("class");
|
|
36
|
+
}
|
|
37
|
+
parts.push(`${stmt.name}__Alias`); // Add __Alias suffix per spec §3.4
|
|
38
|
+
|
|
39
|
+
// Type parameters (if any)
|
|
40
|
+
if (stmt.typeParameters && stmt.typeParameters.length > 0) {
|
|
41
|
+
const [typeParamsStr, whereClauses, typeParamContext] =
|
|
42
|
+
emitTypeParameters(stmt.typeParameters, currentContext);
|
|
43
|
+
parts.push(typeParamsStr);
|
|
44
|
+
currentContext = typeParamContext;
|
|
45
|
+
|
|
46
|
+
if (whereClauses.length > 0) {
|
|
47
|
+
parts.push(
|
|
48
|
+
"\n" + ind + " " + whereClauses.join("\n" + ind + " ")
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Generate properties from object type members
|
|
54
|
+
const bodyContext = indent(currentContext);
|
|
55
|
+
const properties: string[] = [];
|
|
56
|
+
|
|
57
|
+
if (stmt.type.kind === "objectType") {
|
|
58
|
+
for (const member of stmt.type.members) {
|
|
59
|
+
if (member.kind === "propertySignature") {
|
|
60
|
+
const propParts: string[] = [];
|
|
61
|
+
propParts.push("public");
|
|
62
|
+
|
|
63
|
+
// Property type
|
|
64
|
+
if (member.type) {
|
|
65
|
+
const [propType, newContext] = emitType(
|
|
66
|
+
member.type,
|
|
67
|
+
currentContext
|
|
68
|
+
);
|
|
69
|
+
currentContext = newContext;
|
|
70
|
+
// Optional members become nullable
|
|
71
|
+
const typeStr = member.isOptional ? `${propType}?` : propType;
|
|
72
|
+
propParts.push(typeStr);
|
|
73
|
+
} else {
|
|
74
|
+
propParts.push(member.isOptional ? "object?" : "object");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
propParts.push(member.name);
|
|
78
|
+
|
|
79
|
+
// Readonly uses private set
|
|
80
|
+
const accessors = member.isReadonly
|
|
81
|
+
? "{ get; private set; }"
|
|
82
|
+
: "{ get; set; }";
|
|
83
|
+
propParts.push(accessors);
|
|
84
|
+
|
|
85
|
+
// Default initializer
|
|
86
|
+
propParts.push("= default!;");
|
|
87
|
+
|
|
88
|
+
properties.push(`${getIndent(bodyContext)}${propParts.join(" ")}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const signature = parts.join(" ");
|
|
94
|
+
const propsCode = properties.join("\n");
|
|
95
|
+
const code = `${ind}${signature}\n${ind}{\n${propsCode}\n${ind}}`;
|
|
96
|
+
|
|
97
|
+
return [code, currentContext];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// For non-structural aliases, emit as comment (C# using aliases are limited)
|
|
101
|
+
const [typeName, newContext] = emitType(stmt.type, context);
|
|
102
|
+
const code = `${ind}// type ${stmt.name} = ${typeName}`;
|
|
103
|
+
return [code, newContext];
|
|
104
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variable declaration emission
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IrStatement, IrArrayPattern } 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 variable declaration
|
|
12
|
+
*/
|
|
13
|
+
export const emitVariableDeclaration = (
|
|
14
|
+
stmt: Extract<IrStatement, { kind: "variableDeclaration" }>,
|
|
15
|
+
context: EmitterContext
|
|
16
|
+
): [string, EmitterContext] => {
|
|
17
|
+
const ind = getIndent(context);
|
|
18
|
+
let currentContext = context;
|
|
19
|
+
const declarations: string[] = [];
|
|
20
|
+
|
|
21
|
+
for (const decl of stmt.declarations) {
|
|
22
|
+
let varDecl = "";
|
|
23
|
+
|
|
24
|
+
// In static contexts, variable declarations become fields with modifiers
|
|
25
|
+
if (context.isStatic && stmt.isExported) {
|
|
26
|
+
varDecl = "public static ";
|
|
27
|
+
if (stmt.declarationKind === "const") {
|
|
28
|
+
varDecl += "readonly ";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Determine the C# type
|
|
33
|
+
// Priority: 1) Explicit/inferred IR type, 2) Arrow function inference, 3) var
|
|
34
|
+
if (
|
|
35
|
+
decl.type &&
|
|
36
|
+
!(decl.type.kind === "functionType" && !context.isStatic)
|
|
37
|
+
) {
|
|
38
|
+
// Emit explicit type UNLESS it's a function type in a non-static context
|
|
39
|
+
// (let C# infer lambda types in local contexts)
|
|
40
|
+
// Note: For module-level exports, type is always set (from annotation or inference)
|
|
41
|
+
const [typeName, newContext] = emitType(decl.type, currentContext);
|
|
42
|
+
currentContext = newContext;
|
|
43
|
+
varDecl += `${typeName} `;
|
|
44
|
+
} else if (
|
|
45
|
+
decl.initializer &&
|
|
46
|
+
decl.initializer.kind === "arrowFunction" &&
|
|
47
|
+
context.isStatic
|
|
48
|
+
) {
|
|
49
|
+
// For arrow functions in static context without explicit type, infer Func<> type
|
|
50
|
+
const arrowFunc = decl.initializer;
|
|
51
|
+
const paramTypes: string[] = [];
|
|
52
|
+
|
|
53
|
+
for (const param of arrowFunc.parameters) {
|
|
54
|
+
if (param.type) {
|
|
55
|
+
const [paramType, newCtx] = emitType(param.type, currentContext);
|
|
56
|
+
paramTypes.push(paramType);
|
|
57
|
+
currentContext = newCtx;
|
|
58
|
+
} else {
|
|
59
|
+
// ICE: Frontend validation (TSN7405) should have caught this.
|
|
60
|
+
const paramName =
|
|
61
|
+
param.pattern.kind === "identifierPattern"
|
|
62
|
+
? param.pattern.name
|
|
63
|
+
: "unknown";
|
|
64
|
+
throw new Error(
|
|
65
|
+
`ICE: Untyped parameter '${paramName}' reached emitter - validation missed TSN7405`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get return type: explicit annotation, or infer from TS checker
|
|
71
|
+
const arrowReturnType =
|
|
72
|
+
arrowFunc.returnType ??
|
|
73
|
+
(arrowFunc.inferredType?.kind === "functionType"
|
|
74
|
+
? arrowFunc.inferredType.returnType
|
|
75
|
+
: undefined);
|
|
76
|
+
|
|
77
|
+
if (!arrowReturnType) {
|
|
78
|
+
// ICE: Neither explicit nor inferred return type available
|
|
79
|
+
throw new Error(
|
|
80
|
+
"ICE: Arrow function without return type reached emitter - neither explicit nor inferred type available"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const [returnType, retCtx] = emitType(arrowReturnType, currentContext);
|
|
85
|
+
currentContext = retCtx;
|
|
86
|
+
|
|
87
|
+
const allTypes = [...paramTypes, returnType];
|
|
88
|
+
const funcType = `global::System.Func<${allTypes.join(", ")}>`;
|
|
89
|
+
varDecl += `${funcType} `;
|
|
90
|
+
} else {
|
|
91
|
+
varDecl += "var ";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Handle different pattern types
|
|
95
|
+
if (decl.name.kind === "identifierPattern") {
|
|
96
|
+
// Simple identifier pattern
|
|
97
|
+
varDecl += decl.name.name;
|
|
98
|
+
|
|
99
|
+
// Add initializer if present
|
|
100
|
+
if (decl.initializer) {
|
|
101
|
+
const [initFrag, newContext] = emitExpression(
|
|
102
|
+
decl.initializer,
|
|
103
|
+
currentContext,
|
|
104
|
+
decl.type // Pass expected type for contextual typing (e.g., array literals)
|
|
105
|
+
);
|
|
106
|
+
currentContext = newContext;
|
|
107
|
+
varDecl += ` = ${initFrag.text}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
declarations.push(`${ind}${varDecl};`);
|
|
111
|
+
} else if (decl.name.kind === "arrayPattern") {
|
|
112
|
+
// Array destructuring: const [a, b] = arr; -> var a = arr[0]; var b = arr[1];
|
|
113
|
+
if (!decl.initializer) {
|
|
114
|
+
// Array destructuring requires an initializer
|
|
115
|
+
declarations.push(
|
|
116
|
+
`${ind}${varDecl}/* array destructuring without initializer */;`
|
|
117
|
+
);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const [initFrag, newContext] = emitExpression(
|
|
122
|
+
decl.initializer,
|
|
123
|
+
currentContext,
|
|
124
|
+
decl.type
|
|
125
|
+
);
|
|
126
|
+
currentContext = newContext;
|
|
127
|
+
|
|
128
|
+
const arrayPattern = decl.name as IrArrayPattern;
|
|
129
|
+
// Use global:: prefix for Tsonic.Runtime.Array static helpers
|
|
130
|
+
for (let i = 0; i < arrayPattern.elements.length; i++) {
|
|
131
|
+
const element = arrayPattern.elements[i];
|
|
132
|
+
if (element && element.kind === "identifierPattern") {
|
|
133
|
+
// Use double literal for index (JavaScript uses doubles for all numbers)
|
|
134
|
+
const elementVarDecl = `${varDecl}${element.name} = global::Tsonic.Runtime.Array.get(${initFrag.text}, ${i}.0);`;
|
|
135
|
+
declarations.push(`${ind}${elementVarDecl}`);
|
|
136
|
+
}
|
|
137
|
+
// Skip undefined elements (holes in array pattern)
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// Object destructuring or other patterns - not yet supported
|
|
141
|
+
varDecl += "/* destructuring */";
|
|
142
|
+
|
|
143
|
+
// Add initializer if present
|
|
144
|
+
if (decl.initializer) {
|
|
145
|
+
const [initFrag, newContext] = emitExpression(
|
|
146
|
+
decl.initializer,
|
|
147
|
+
currentContext,
|
|
148
|
+
decl.type
|
|
149
|
+
);
|
|
150
|
+
currentContext = newContext;
|
|
151
|
+
varDecl += ` = ${initFrag.text}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
declarations.push(`${ind}${varDecl};`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return [declarations.join("\n"), currentContext];
|
|
159
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declaration emitters (variables, functions, classes, interfaces, enums, type aliases)
|
|
3
|
+
* Main dispatcher - re-exports from declarations/ subdirectory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
emitVariableDeclaration,
|
|
8
|
+
emitFunctionDeclaration,
|
|
9
|
+
emitClassDeclaration,
|
|
10
|
+
emitInterfaceDeclaration,
|
|
11
|
+
emitEnumDeclaration,
|
|
12
|
+
emitTypeAliasDeclaration,
|
|
13
|
+
} from "./declarations/index.js";
|