Skip to content

Commit c94e070

Browse files
tomblindPerryvw
authored andcommitted
class field initialization order (#700)
* fixed order of class property initialization fixes #697 * fixed constructor field initialization to falsey values * using new test builder * updated comment
1 parent 532d09d commit c94e070

File tree

2 files changed

+72
-50
lines changed

2 files changed

+72
-50
lines changed

src/LuaTransformer.ts

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,55 +1237,29 @@ export class LuaTransformer {
12371237
return undefined;
12381238
}
12391239

1240-
const bodyWithFieldInitializers: tstl.Statement[] = this.transformClassInstanceFields(
1241-
classDeclaration,
1242-
instanceFields
1243-
);
1244-
1245-
// Check for field declarations in constructor
1246-
const constructorFieldsDeclarations = statement.parameters.filter(p => p.modifiers !== undefined);
1247-
1248-
// Add in instance field declarations
1249-
for (const declaration of constructorFieldsDeclarations) {
1250-
const declarationName = this.transformIdentifier(declaration.name as ts.Identifier);
1251-
if (declaration.initializer) {
1252-
// self.declarationName = declarationName or initializer
1253-
const assignment = tstl.createAssignmentStatement(
1254-
tstl.createTableIndexExpression(
1255-
this.createSelfIdentifier(),
1256-
tstl.createStringLiteral(declarationName.text)
1257-
),
1258-
tstl.createBinaryExpression(
1259-
declarationName,
1260-
this.transformExpression(declaration.initializer),
1261-
tstl.SyntaxKind.OrOperator
1262-
)
1263-
);
1264-
bodyWithFieldInitializers.push(assignment);
1265-
} else {
1266-
// self.declarationName = declarationName
1267-
const assignment = tstl.createAssignmentStatement(
1268-
tstl.createTableIndexExpression(
1269-
this.createSelfIdentifier(),
1270-
tstl.createStringLiteral(declarationName.text)
1271-
),
1272-
declarationName
1273-
);
1274-
bodyWithFieldInitializers.push(assignment);
1275-
}
1276-
}
1277-
1278-
// function className.constructor(self, params) ... end
1240+
// Transform body
1241+
const [body, scope] = this.transformFunctionBodyStatements(statement.body);
12791242

12801243
const [params, dotsLiteral, restParamName] = this.transformParameters(
12811244
statement.parameters,
12821245
this.createSelfIdentifier()
12831246
);
12841247

1285-
const [body] = this.transformFunctionBody(statement.parameters, statement.body, restParamName);
1248+
// Make sure default parameters are assigned before fields are initialized
1249+
const bodyWithFieldInitializers = this.transformFunctionBodyHeader(scope, statement.parameters, restParamName);
12861250

1287-
// If there are field initializers and the first statement is a super call, hoist the super call to the top
1288-
if (bodyWithFieldInitializers.length > 0 && statement.body && statement.body.statements.length > 0) {
1251+
// Check for field declarations in constructor
1252+
const constructorFieldsDeclarations = statement.parameters.filter(p => p.modifiers !== undefined);
1253+
1254+
const classInstanceFields = this.transformClassInstanceFields(classDeclaration, instanceFields);
1255+
1256+
// If there are field initializers and the first statement is a super call,
1257+
// move super call between default assignments and initializers
1258+
if (
1259+
(constructorFieldsDeclarations.length > 0 || classInstanceFields.length > 0) &&
1260+
statement.body &&
1261+
statement.body.statements.length > 0
1262+
) {
12891263
const firstStatement = statement.body.statements[0];
12901264
if (
12911265
ts.isExpressionStatement(firstStatement) &&
@@ -1294,11 +1268,27 @@ export class LuaTransformer {
12941268
) {
12951269
const superCall = body.shift();
12961270
if (superCall) {
1297-
bodyWithFieldInitializers.unshift(superCall);
1271+
bodyWithFieldInitializers.push(superCall);
12981272
}
12991273
}
13001274
}
13011275

1276+
// Add in instance field declarations
1277+
for (const declaration of constructorFieldsDeclarations) {
1278+
const declarationName = this.transformIdentifier(declaration.name as ts.Identifier);
1279+
// self.declarationName = declarationName
1280+
const assignment = tstl.createAssignmentStatement(
1281+
tstl.createTableIndexExpression(
1282+
this.createSelfIdentifier(),
1283+
tstl.createStringLiteral(declarationName.text)
1284+
),
1285+
declarationName
1286+
);
1287+
bodyWithFieldInitializers.push(assignment);
1288+
}
1289+
1290+
bodyWithFieldInitializers.push(...classInstanceFields);
1291+
13021292
bodyWithFieldInitializers.push(...body);
13031293

13041294
const block: tstl.Block = tstl.createBlock(bodyWithFieldInitializers);
@@ -1490,16 +1480,19 @@ export class LuaTransformer {
14901480
);
14911481
}
14921482

1493-
protected transformFunctionBody(
1494-
parameters: ts.NodeArray<ts.ParameterDeclaration>,
1495-
body: ts.Block,
1496-
spreadIdentifier?: tstl.Identifier
1497-
): [tstl.Statement[], Scope] {
1483+
protected transformFunctionBodyStatements(body: ts.Block): [tstl.Statement[], Scope] {
14981484
this.pushScope(ScopeType.Function);
14991485
const bodyStatements = this.performHoisting(this.transformStatements(body.statements));
15001486
const scope = this.popScope();
1487+
return [bodyStatements, scope];
1488+
}
15011489

1502-
const headerStatements = [];
1490+
protected transformFunctionBodyHeader(
1491+
bodyScope: Scope,
1492+
parameters: ts.NodeArray<ts.ParameterDeclaration>,
1493+
spreadIdentifier?: tstl.Identifier
1494+
): tstl.Statement[] {
1495+
const headerStatements: tstl.Statement[] = [];
15031496

15041497
// Add default parameters and object binding patterns
15051498
const bindingPatternDeclarations: tstl.Statement[] = [];
@@ -1530,14 +1523,24 @@ export class LuaTransformer {
15301523
}
15311524

15321525
// Push spread operator here
1533-
if (spreadIdentifier && this.isRestParameterReferenced(spreadIdentifier, scope)) {
1526+
if (spreadIdentifier && this.isRestParameterReferenced(spreadIdentifier, bodyScope)) {
15341527
const spreadTable = this.wrapInTable(tstl.createDotsLiteral());
15351528
headerStatements.push(tstl.createVariableDeclarationStatement(spreadIdentifier, spreadTable));
15361529
}
15371530

15381531
// Binding pattern statements need to be after spread table is declared
15391532
headerStatements.push(...bindingPatternDeclarations);
15401533

1534+
return headerStatements;
1535+
}
1536+
1537+
protected transformFunctionBody(
1538+
parameters: ts.NodeArray<ts.ParameterDeclaration>,
1539+
body: ts.Block,
1540+
spreadIdentifier?: tstl.Identifier
1541+
): [tstl.Statement[], Scope] {
1542+
const [bodyStatements, scope] = this.transformFunctionBodyStatements(body);
1543+
const headerStatements = this.transformFunctionBodyHeader(scope, parameters, spreadIdentifier);
15411544
return [headerStatements.concat(bodyStatements), scope];
15421545
}
15431546

test/unit/classes/classes.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,25 @@ test("ClassConstructorAssignmentDefault", () => {
8888
expect(result).toBe(3);
8989
});
9090

91+
test("ClassConstructorPropertyInitiailizationOrder", () => {
92+
util.testFunction`
93+
class Test {
94+
public foo = this.bar;
95+
constructor(public bar: string) {}
96+
}
97+
return (new Test("baz")).foo;
98+
`.expectToMatchJsResult();
99+
});
100+
101+
test("ClassConstructorPropertyInitiailizationFalsey", () => {
102+
util.testFunction`
103+
class Test {
104+
constructor(public foo = true) {}
105+
}
106+
return (new Test(false)).foo;
107+
`.expectToMatchJsResult();
108+
});
109+
91110
test("ClassNewNoBrackets", () => {
92111
const result = util.transpileAndExecute(
93112
`class a {

0 commit comments

Comments
 (0)