diff --git a/src/files/BrsFile.Class.spec.ts b/src/files/BrsFile.Class.spec.ts index 63ad7f8fe..6ad265412 100644 --- a/src/files/BrsFile.Class.spec.ts +++ b/src/files/BrsFile.Class.spec.ts @@ -451,7 +451,7 @@ describe('BrsFile BrighterScript classes', () => { `, undefined, 'source/main.bs'); }); - it('works for simple class', () => { + it('works for simple class', () => { testTranspile(` class Duck end class @@ -470,6 +470,77 @@ describe('BrsFile BrighterScript classes', () => { `, undefined, 'source/main.bs'); }); + it('inherits the parameters of the last known constructor', () => { + testTranspile(` + class Animal + sub new(p1) + end sub + end class + class Bird extends Animal + end class + class Duck extends Bird + sub new(p1, p2) + super(p1) + m.p2 = p2 + end sub + end class + class BabyDuck extends Duck + end class + `, ` + function __Animal_builder() + instance = {} + instance.new = sub(p1) + end sub + return instance + end function + function Animal(p1) + instance = __Animal_builder() + instance.new(p1) + return instance + end function + function __Bird_builder() + instance = __Animal_builder() + instance.super0_new = instance.new + instance.new = sub(p1) + m.super0_new(p1) + end sub + return instance + end function + function Bird(p1) + instance = __Bird_builder() + instance.new(p1) + return instance + end function + function __Duck_builder() + instance = __Bird_builder() + instance.super1_new = instance.new + instance.new = sub(p1, p2) + m.super1_new(p1) + m.p2 = p2 + end sub + return instance + end function + function Duck(p1, p2) + instance = __Duck_builder() + instance.new(p1, p2) + return instance + end function + function __BabyDuck_builder() + instance = __Duck_builder() + instance.super2_new = instance.new + instance.new = sub(p1, p2) + m.super2_new(p1, p2) + end sub + return instance + end function + function BabyDuck(p1, p2) + instance = __BabyDuck_builder() + instance.new(p1, p2) + return instance + end function + `); + }); + it('registers the constructor and properly handles its parameters', () => { testTranspile(` class Duck @@ -569,8 +640,8 @@ describe('BrsFile BrighterScript classes', () => { function __Duck_builder() instance = __Creature_builder() instance.super0_new = instance.new - instance.new = sub() - m.super0_new() + instance.new = sub(name as string) + m.super0_new(name) end sub instance.super0_sayHello = instance.sayHello instance.sayHello = function(text) @@ -581,9 +652,9 @@ describe('BrsFile BrighterScript classes', () => { end function return instance end function - function Duck() + function Duck(name as string) instance = __Duck_builder() - instance.new() + instance.new(name) return instance end function `, 'trim', 'source/main.bs' @@ -774,8 +845,8 @@ describe('BrsFile BrighterScript classes', () => { function __Duck_builder() instance = __Animal_builder() instance.super0_new = instance.new - instance.new = sub() - m.super0_new() + instance.new = sub(name as string) + m.super0_new(name) end sub instance.super0_move = instance.move instance.move = sub(distanceInMeters as integer) @@ -784,16 +855,16 @@ describe('BrsFile BrighterScript classes', () => { end sub return instance end function - function Duck() + function Duck(name as string) instance = __Duck_builder() - instance.new() + instance.new(name) return instance end function function __BabyDuck_builder() instance = __Duck_builder() instance.super1_new = instance.new - instance.new = sub() - m.super1_new() + instance.new = sub(name as string) + m.super1_new(name) end sub instance.super1_move = instance.move instance.move = sub(distanceInMeters as integer) @@ -802,9 +873,9 @@ describe('BrsFile BrighterScript classes', () => { end sub return instance end function - function BabyDuck() + function BabyDuck(name as string) instance = __BabyDuck_builder() - instance.new() + instance.new(name) return instance end function diff --git a/src/parser/Statement.ts b/src/parser/Statement.ts index 3b9baf8bb..b0b2011a1 100644 --- a/src/parser/Statement.ts +++ b/src/parser/Statement.ts @@ -1,7 +1,7 @@ /* eslint-disable no-bitwise */ import type { Token, Identifier } from '../lexer/Token'; import { CompoundAssignmentOperators, TokenKind } from '../lexer/TokenKind'; -import type { BinaryExpression, NamespacedVariableNameExpression, FunctionExpression, FunctionParameterExpression, LiteralExpression } from './Expression'; +import { type BinaryExpression, type NamespacedVariableNameExpression, FunctionExpression, type FunctionParameterExpression, type LiteralExpression } from './Expression'; import { CallExpression, VariableExpression } from './Expression'; import { util } from '../util'; import type { Range } from 'vscode-languageserver'; @@ -11,7 +11,7 @@ import type { WalkVisitor, WalkOptions } from '../astUtils/visitors'; import { InternalWalkMode, walk, createVisitor, WalkMode, walkArray } from '../astUtils/visitors'; import { isCallExpression, isCommentStatement, isEnumMemberStatement, isExpression, isExpressionStatement, isFieldStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTypedefProvider, isUnaryExpression, isVoidType } from '../astUtils/reflection'; import type { TranspileResult, TypedefProvider } from '../interfaces'; -import { createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators'; +import { createIdentifier, createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators'; import { DynamicType } from '../types/DynamicType'; import type { BscType } from '../types/BscType'; import type { TranspileState } from './TranspileState'; @@ -2144,7 +2144,7 @@ export class ClassStatement extends Statement implements TypedefProvider { let stmt = this as ClassStatement; while (stmt) { if (stmt.parentClassName) { - const namespace = this.findAncestor(isNamespaceStatement); + const namespace = stmt.findAncestor(isNamespaceStatement); stmt = state.file.getClassFileLink( stmt.parentClassName.getName(ParseMode.BrighterScript), namespace?.getName(ParseMode.BrighterScript) @@ -2173,6 +2173,21 @@ export class ClassStatement extends Statement implements TypedefProvider { }) as MethodStatement; } + /** + * Return the parameters for the first constructor function for this class + * @param ancestors The list of ancestors for this class + * @returns The parameters for the first constructor function for this class + */ + private getConstructorParams(ancestors: ClassStatement[]) { + for (let ancestor of ancestors) { + const ctor = ancestor?.getConstructorFunction(); + if (ctor) { + return ctor.func.parameters; + } + } + return []; + } + /** * Determine if the specified field was declared in one of the ancestor classes */ @@ -2226,10 +2241,38 @@ export class ClassStatement extends Statement implements TypedefProvider { let body = this.body; //inject an empty "new" method if missing if (!this.getConstructorFunction()) { - body = [ - createMethodStatement('new', TokenKind.Sub), - ...this.body - ]; + if (ancestors.length === 0) { + body = [ + createMethodStatement('new', TokenKind.Sub), + ...this.body + ]; + } else { + const params = this.getConstructorParams(ancestors); + const call = new ExpressionStatement( + new CallExpression( + new VariableExpression(createToken(TokenKind.Identifier, 'super')), + createToken(TokenKind.LeftParen), + createToken(TokenKind.RightParen), + params.map(x => new VariableExpression(x.name)) + ) + ); + body = [ + new MethodStatement( + [], + createIdentifier('new'), + new FunctionExpression( + params.map(x => x.clone()), + new Block([call]), + createToken(TokenKind.Sub), + createToken(TokenKind.EndSub), + createToken(TokenKind.LeftParen), + createToken(TokenKind.RightParen) + ), + null + ), + ...this.body + ]; + } } for (let statement of body) { @@ -2289,8 +2332,14 @@ export class ClassStatement extends Statement implements TypedefProvider { */ private getTranspiledClassFunction(state: BrsTranspileState) { let result = [] as TranspileResult; + const constructorFunction = this.getConstructorFunction(); - const constructorParams = constructorFunction ? constructorFunction.func.parameters : []; + let constructorParams = []; + if (constructorFunction) { + constructorParams = constructorFunction.func.parameters; + } else { + constructorParams = this.getConstructorParams(this.getAncestors(state)); + } result.push( state.sourceNode(this.classKeyword, 'function'),