diff --git a/CHANGELOG.md b/CHANGELOG.md index f1912f9b4..c21dfdfae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -358,6 +358,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.66.0-alpha.0](https://github.com/rokucommunity/brighterscript/compare/v0.65.1...v0.66.0-alpha.0) - 2023-06-09 ### Changed - all the type tracking stuff! +## [0.68.3](https://github.com/rokucommunity/brighterscript/compare/v0.68.2...v0.68.3) - 2025-01-13 +### Changed + - Export more items ([#1394](https://github.com/rokucommunity/brighterscript/pull/1394)) +### Fixed + - Fix class transpile issue with child class constructor not inherriting parent params ([#1390](https://github.com/rokucommunity/brighterscript/pull/1390)) + + + ## [0.68.2](https://github.com/rokucommunity/brighterscript/compare/v0.68.1...v0.68.2) - 2024-12-06 ### Changed - Add more convenience exports from vscode-languageserver ([#1359](https://github.com/rokucommunity/brighterscript/pull/1359)) diff --git a/src/files/BrsFile.Class.spec.ts b/src/files/BrsFile.Class.spec.ts index 2227241e6..0284f152f 100644 --- a/src/files/BrsFile.Class.spec.ts +++ b/src/files/BrsFile.Class.spec.ts @@ -456,7 +456,7 @@ describe('BrsFile BrighterScript classes', () => { `, undefined, 'source/main.bs'); }); - it('works for simple class', async () => { + it('works for simple class', async () => { await testTranspile(` class Duck end class @@ -475,6 +475,79 @@ describe('BrsFile BrighterScript classes', () => { `, undefined, 'source/main.bs'); }); + it('inherits the parameters of the last known constructor', async () => { + await 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 + private p2 as dynamic + 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 = invalid + 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', async () => { await testTranspile(` class Duck @@ -579,8 +652,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) @@ -591,9 +664,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' @@ -784,8 +857,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) @@ -794,16 +867,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) @@ -812,9 +885,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 d85420454..4e8efbfc0 100644 --- a/src/parser/Statement.ts +++ b/src/parser/Statement.ts @@ -1,7 +1,8 @@ /* eslint-disable no-bitwise */ import type { Token, Identifier } from '../lexer/Token'; import { TokenKind } from '../lexer/TokenKind'; -import type { DottedGetExpression, FunctionExpression, FunctionParameterExpression, LiteralExpression, TypeExpression, TypecastExpression } from './Expression'; +import type { DottedGetExpression, FunctionParameterExpression, LiteralExpression, TypecastExpression, TypeExpression } from './Expression'; +import { FunctionExpression } from './Expression'; import { CallExpression, VariableExpression } from './Expression'; import { util } from '../util'; import type { Location } from 'vscode-languageserver'; @@ -10,8 +11,9 @@ import { ParseMode } from './Parser'; import type { WalkVisitor, WalkOptions } from '../astUtils/visitors'; import { InternalWalkMode, walk, createVisitor, WalkMode, walkArray } from '../astUtils/visitors'; import { isCallExpression, isCatchStatement, isConditionalCompileStatement, isEnumMemberStatement, isExpressionStatement, isFieldStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isPrintSeparatorExpression, isTryCatchStatement, isTypedefProvider, isUnaryExpression, isUninitializedType, isVoidType, isWhileStatement } from '../astUtils/reflection'; -import { TypeChainEntry, type GetTypeOptions, type TranspileResult, type TypedefProvider } from '../interfaces'; -import { createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators'; +import type { GetTypeOptions } from '../interfaces'; +import { TypeChainEntry, type TranspileResult, type TypedefProvider } from '../interfaces'; +import { createIdentifier, createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators'; import { DynamicType } from '../types/DynamicType'; import type { BscType } from '../types/BscType'; import { SymbolTable } from '../SymbolTable'; @@ -2781,7 +2783,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(), namespace?.getName(ParseMode.BrighterScript) @@ -2816,6 +2818,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 */ @@ -2869,10 +2886,42 @@ 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({ + expression: new CallExpression({ + callee: new VariableExpression({ + name: createToken(TokenKind.Identifier, 'super') + }), + openingParen: createToken(TokenKind.LeftParen), + args: params.map(x => new VariableExpression({ + name: util.cloneToken(x.tokens.name) + })), + closingParen: createToken(TokenKind.RightParen) + }) + }); + body = [ + new MethodStatement({ + name: createIdentifier('new'), + func: new FunctionExpression({ + parameters: params.map(x => x.clone()), + body: new Block({ + statements: [call] + }), + functionType: createToken(TokenKind.Sub), + endFunctionType: createToken(TokenKind.EndSub), + leftParen: createToken(TokenKind.LeftParen), + rightParen: createToken(TokenKind.RightParen) + }) + }), + ...this.body + ]; + } } for (let statement of body) { @@ -2942,7 +2991,12 @@ export class ClassStatement extends Statement implements TypedefProvider { private getTranspiledClassFunction(state: BrsTranspileState) { let result: TranspileResult = state.transpileAnnotations(this); 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.transpileLeadingComments(this.tokens.class),