diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 50bc074d5..d8aee1a01 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -1237,55 +1237,29 @@ export class LuaTransformer { return undefined; } - const bodyWithFieldInitializers: tstl.Statement[] = this.transformClassInstanceFields( - classDeclaration, - instanceFields - ); - - // Check for field declarations in constructor - const constructorFieldsDeclarations = statement.parameters.filter(p => p.modifiers !== undefined); - - // Add in instance field declarations - for (const declaration of constructorFieldsDeclarations) { - const declarationName = this.transformIdentifier(declaration.name as ts.Identifier); - if (declaration.initializer) { - // self.declarationName = declarationName or initializer - const assignment = tstl.createAssignmentStatement( - tstl.createTableIndexExpression( - this.createSelfIdentifier(), - tstl.createStringLiteral(declarationName.text) - ), - tstl.createBinaryExpression( - declarationName, - this.transformExpression(declaration.initializer), - tstl.SyntaxKind.OrOperator - ) - ); - bodyWithFieldInitializers.push(assignment); - } else { - // self.declarationName = declarationName - const assignment = tstl.createAssignmentStatement( - tstl.createTableIndexExpression( - this.createSelfIdentifier(), - tstl.createStringLiteral(declarationName.text) - ), - declarationName - ); - bodyWithFieldInitializers.push(assignment); - } - } - - // function className.constructor(self, params) ... end + // Transform body + const [body, scope] = this.transformFunctionBodyStatements(statement.body); const [params, dotsLiteral, restParamName] = this.transformParameters( statement.parameters, this.createSelfIdentifier() ); - const [body] = this.transformFunctionBody(statement.parameters, statement.body, restParamName); + // Make sure default parameters are assigned before fields are initialized + const bodyWithFieldInitializers = this.transformFunctionBodyHeader(scope, statement.parameters, restParamName); - // If there are field initializers and the first statement is a super call, hoist the super call to the top - if (bodyWithFieldInitializers.length > 0 && statement.body && statement.body.statements.length > 0) { + // Check for field declarations in constructor + const constructorFieldsDeclarations = statement.parameters.filter(p => p.modifiers !== undefined); + + const classInstanceFields = this.transformClassInstanceFields(classDeclaration, instanceFields); + + // If there are field initializers and the first statement is a super call, + // move super call between default assignments and initializers + if ( + (constructorFieldsDeclarations.length > 0 || classInstanceFields.length > 0) && + statement.body && + statement.body.statements.length > 0 + ) { const firstStatement = statement.body.statements[0]; if ( ts.isExpressionStatement(firstStatement) && @@ -1294,11 +1268,27 @@ export class LuaTransformer { ) { const superCall = body.shift(); if (superCall) { - bodyWithFieldInitializers.unshift(superCall); + bodyWithFieldInitializers.push(superCall); } } } + // Add in instance field declarations + for (const declaration of constructorFieldsDeclarations) { + const declarationName = this.transformIdentifier(declaration.name as ts.Identifier); + // self.declarationName = declarationName + const assignment = tstl.createAssignmentStatement( + tstl.createTableIndexExpression( + this.createSelfIdentifier(), + tstl.createStringLiteral(declarationName.text) + ), + declarationName + ); + bodyWithFieldInitializers.push(assignment); + } + + bodyWithFieldInitializers.push(...classInstanceFields); + bodyWithFieldInitializers.push(...body); const block: tstl.Block = tstl.createBlock(bodyWithFieldInitializers); @@ -1490,16 +1480,19 @@ export class LuaTransformer { ); } - protected transformFunctionBody( - parameters: ts.NodeArray, - body: ts.Block, - spreadIdentifier?: tstl.Identifier - ): [tstl.Statement[], Scope] { + protected transformFunctionBodyStatements(body: ts.Block): [tstl.Statement[], Scope] { this.pushScope(ScopeType.Function); const bodyStatements = this.performHoisting(this.transformStatements(body.statements)); const scope = this.popScope(); + return [bodyStatements, scope]; + } - const headerStatements = []; + protected transformFunctionBodyHeader( + bodyScope: Scope, + parameters: ts.NodeArray, + spreadIdentifier?: tstl.Identifier + ): tstl.Statement[] { + const headerStatements: tstl.Statement[] = []; // Add default parameters and object binding patterns const bindingPatternDeclarations: tstl.Statement[] = []; @@ -1530,7 +1523,7 @@ export class LuaTransformer { } // Push spread operator here - if (spreadIdentifier && this.isRestParameterReferenced(spreadIdentifier, scope)) { + if (spreadIdentifier && this.isRestParameterReferenced(spreadIdentifier, bodyScope)) { const spreadTable = this.wrapInTable(tstl.createDotsLiteral()); headerStatements.push(tstl.createVariableDeclarationStatement(spreadIdentifier, spreadTable)); } @@ -1538,6 +1531,16 @@ export class LuaTransformer { // Binding pattern statements need to be after spread table is declared headerStatements.push(...bindingPatternDeclarations); + return headerStatements; + } + + protected transformFunctionBody( + parameters: ts.NodeArray, + body: ts.Block, + spreadIdentifier?: tstl.Identifier + ): [tstl.Statement[], Scope] { + const [bodyStatements, scope] = this.transformFunctionBodyStatements(body); + const headerStatements = this.transformFunctionBodyHeader(scope, parameters, spreadIdentifier); return [headerStatements.concat(bodyStatements), scope]; } diff --git a/test/unit/classes/classes.spec.ts b/test/unit/classes/classes.spec.ts index 3f156c93a..343ad75ad 100644 --- a/test/unit/classes/classes.spec.ts +++ b/test/unit/classes/classes.spec.ts @@ -88,6 +88,25 @@ test("ClassConstructorAssignmentDefault", () => { expect(result).toBe(3); }); +test("ClassConstructorPropertyInitiailizationOrder", () => { + util.testFunction` + class Test { + public foo = this.bar; + constructor(public bar: string) {} + } + return (new Test("baz")).foo; + `.expectToMatchJsResult(); +}); + +test("ClassConstructorPropertyInitiailizationFalsey", () => { + util.testFunction` + class Test { + constructor(public foo = true) {} + } + return (new Test(false)).foo; + `.expectToMatchJsResult(); +}); + test("ClassNewNoBrackets", () => { const result = util.transpileAndExecute( `class a {