Skip to content

Commit ccb4649

Browse files
Fix class transpile issue with child class constructor not inherriting parent params (#1390)
* Add tests * Fix test * Make sure the builder works * Make sure the class function works too * Fix bug with nested classes which don't contain "new" * Fix some test cases which expected the bug to happen * Fix crash, update test case * Adjusted the test to account for mid-level ctor change * Fix lint issues * Fix issue with missing ancestors when crossing different namespaces * Update src/parser/Statement.ts --------- Co-authored-by: Luis Soares <luis.soares@sky.uk>
1 parent 3a5ce8c commit ccb4649

File tree

2 files changed

+142
-21
lines changed

2 files changed

+142
-21
lines changed

src/files/BrsFile.Class.spec.ts

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ describe('BrsFile BrighterScript classes', () => {
451451
`, undefined, 'source/main.bs');
452452
});
453453

454-
it('works for simple class', () => {
454+
it('works for simple class', () => {
455455
testTranspile(`
456456
class Duck
457457
end class
@@ -470,6 +470,77 @@ describe('BrsFile BrighterScript classes', () => {
470470
`, undefined, 'source/main.bs');
471471
});
472472

473+
it('inherits the parameters of the last known constructor', () => {
474+
testTranspile(`
475+
class Animal
476+
sub new(p1)
477+
end sub
478+
end class
479+
class Bird extends Animal
480+
end class
481+
class Duck extends Bird
482+
sub new(p1, p2)
483+
super(p1)
484+
m.p2 = p2
485+
end sub
486+
end class
487+
class BabyDuck extends Duck
488+
end class
489+
`, `
490+
function __Animal_builder()
491+
instance = {}
492+
instance.new = sub(p1)
493+
end sub
494+
return instance
495+
end function
496+
function Animal(p1)
497+
instance = __Animal_builder()
498+
instance.new(p1)
499+
return instance
500+
end function
501+
function __Bird_builder()
502+
instance = __Animal_builder()
503+
instance.super0_new = instance.new
504+
instance.new = sub(p1)
505+
m.super0_new(p1)
506+
end sub
507+
return instance
508+
end function
509+
function Bird(p1)
510+
instance = __Bird_builder()
511+
instance.new(p1)
512+
return instance
513+
end function
514+
function __Duck_builder()
515+
instance = __Bird_builder()
516+
instance.super1_new = instance.new
517+
instance.new = sub(p1, p2)
518+
m.super1_new(p1)
519+
m.p2 = p2
520+
end sub
521+
return instance
522+
end function
523+
function Duck(p1, p2)
524+
instance = __Duck_builder()
525+
instance.new(p1, p2)
526+
return instance
527+
end function
528+
function __BabyDuck_builder()
529+
instance = __Duck_builder()
530+
instance.super2_new = instance.new
531+
instance.new = sub(p1, p2)
532+
m.super2_new(p1, p2)
533+
end sub
534+
return instance
535+
end function
536+
function BabyDuck(p1, p2)
537+
instance = __BabyDuck_builder()
538+
instance.new(p1, p2)
539+
return instance
540+
end function
541+
`);
542+
});
543+
473544
it('registers the constructor and properly handles its parameters', () => {
474545
testTranspile(`
475546
class Duck
@@ -569,8 +640,8 @@ describe('BrsFile BrighterScript classes', () => {
569640
function __Duck_builder()
570641
instance = __Creature_builder()
571642
instance.super0_new = instance.new
572-
instance.new = sub()
573-
m.super0_new()
643+
instance.new = sub(name as string)
644+
m.super0_new(name)
574645
end sub
575646
instance.super0_sayHello = instance.sayHello
576647
instance.sayHello = function(text)
@@ -581,9 +652,9 @@ describe('BrsFile BrighterScript classes', () => {
581652
end function
582653
return instance
583654
end function
584-
function Duck()
655+
function Duck(name as string)
585656
instance = __Duck_builder()
586-
instance.new()
657+
instance.new(name)
587658
return instance
588659
end function
589660
`, 'trim', 'source/main.bs'
@@ -774,8 +845,8 @@ describe('BrsFile BrighterScript classes', () => {
774845
function __Duck_builder()
775846
instance = __Animal_builder()
776847
instance.super0_new = instance.new
777-
instance.new = sub()
778-
m.super0_new()
848+
instance.new = sub(name as string)
849+
m.super0_new(name)
779850
end sub
780851
instance.super0_move = instance.move
781852
instance.move = sub(distanceInMeters as integer)
@@ -784,16 +855,16 @@ describe('BrsFile BrighterScript classes', () => {
784855
end sub
785856
return instance
786857
end function
787-
function Duck()
858+
function Duck(name as string)
788859
instance = __Duck_builder()
789-
instance.new()
860+
instance.new(name)
790861
return instance
791862
end function
792863
function __BabyDuck_builder()
793864
instance = __Duck_builder()
794865
instance.super1_new = instance.new
795-
instance.new = sub()
796-
m.super1_new()
866+
instance.new = sub(name as string)
867+
m.super1_new(name)
797868
end sub
798869
instance.super1_move = instance.move
799870
instance.move = sub(distanceInMeters as integer)
@@ -802,9 +873,9 @@ describe('BrsFile BrighterScript classes', () => {
802873
end sub
803874
return instance
804875
end function
805-
function BabyDuck()
876+
function BabyDuck(name as string)
806877
instance = __BabyDuck_builder()
807-
instance.new()
878+
instance.new(name)
808879
return instance
809880
end function
810881

src/parser/Statement.ts

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/* eslint-disable no-bitwise */
22
import type { Token, Identifier } from '../lexer/Token';
33
import { CompoundAssignmentOperators, TokenKind } from '../lexer/TokenKind';
4-
import type { BinaryExpression, NamespacedVariableNameExpression, FunctionExpression, FunctionParameterExpression, LiteralExpression } from './Expression';
4+
import type { BinaryExpression, NamespacedVariableNameExpression, FunctionParameterExpression, LiteralExpression } from './Expression';
5+
import { FunctionExpression } from './Expression';
56
import { CallExpression, VariableExpression } from './Expression';
67
import { util } from '../util';
78
import type { Range } from 'vscode-languageserver';
@@ -11,7 +12,7 @@ import type { WalkVisitor, WalkOptions } from '../astUtils/visitors';
1112
import { InternalWalkMode, walk, createVisitor, WalkMode, walkArray } from '../astUtils/visitors';
1213
import { isCallExpression, isCommentStatement, isEnumMemberStatement, isExpression, isExpressionStatement, isFieldStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTypedefProvider, isUnaryExpression, isVoidType } from '../astUtils/reflection';
1314
import type { TranspileResult, TypedefProvider } from '../interfaces';
14-
import { createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators';
15+
import { createIdentifier, createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators';
1516
import { DynamicType } from '../types/DynamicType';
1617
import type { BscType } from '../types/BscType';
1718
import type { TranspileState } from './TranspileState';
@@ -2144,7 +2145,7 @@ export class ClassStatement extends Statement implements TypedefProvider {
21442145
let stmt = this as ClassStatement;
21452146
while (stmt) {
21462147
if (stmt.parentClassName) {
2147-
const namespace = this.findAncestor<NamespaceStatement>(isNamespaceStatement);
2148+
const namespace = stmt.findAncestor<NamespaceStatement>(isNamespaceStatement);
21482149
stmt = state.file.getClassFileLink(
21492150
stmt.parentClassName.getName(ParseMode.BrighterScript),
21502151
namespace?.getName(ParseMode.BrighterScript)
@@ -2173,6 +2174,21 @@ export class ClassStatement extends Statement implements TypedefProvider {
21732174
}) as MethodStatement;
21742175
}
21752176

2177+
/**
2178+
* Return the parameters for the first constructor function for this class
2179+
* @param ancestors The list of ancestors for this class
2180+
* @returns The parameters for the first constructor function for this class
2181+
*/
2182+
private getConstructorParams(ancestors: ClassStatement[]) {
2183+
for (let ancestor of ancestors) {
2184+
const ctor = ancestor?.getConstructorFunction();
2185+
if (ctor) {
2186+
return ctor.func.parameters;
2187+
}
2188+
}
2189+
return [];
2190+
}
2191+
21762192
/**
21772193
* Determine if the specified field was declared in one of the ancestor classes
21782194
*/
@@ -2226,10 +2242,38 @@ export class ClassStatement extends Statement implements TypedefProvider {
22262242
let body = this.body;
22272243
//inject an empty "new" method if missing
22282244
if (!this.getConstructorFunction()) {
2229-
body = [
2230-
createMethodStatement('new', TokenKind.Sub),
2231-
...this.body
2232-
];
2245+
if (ancestors.length === 0) {
2246+
body = [
2247+
createMethodStatement('new', TokenKind.Sub),
2248+
...this.body
2249+
];
2250+
} else {
2251+
const params = this.getConstructorParams(ancestors);
2252+
const call = new ExpressionStatement(
2253+
new CallExpression(
2254+
new VariableExpression(createToken(TokenKind.Identifier, 'super')),
2255+
createToken(TokenKind.LeftParen),
2256+
createToken(TokenKind.RightParen),
2257+
params.map(x => new VariableExpression(x.name))
2258+
)
2259+
);
2260+
body = [
2261+
new MethodStatement(
2262+
[],
2263+
createIdentifier('new'),
2264+
new FunctionExpression(
2265+
params.map(x => x.clone()),
2266+
new Block([call]),
2267+
createToken(TokenKind.Sub),
2268+
createToken(TokenKind.EndSub),
2269+
createToken(TokenKind.LeftParen),
2270+
createToken(TokenKind.RightParen)
2271+
),
2272+
null
2273+
),
2274+
...this.body
2275+
];
2276+
}
22332277
}
22342278

22352279
for (let statement of body) {
@@ -2289,8 +2333,14 @@ export class ClassStatement extends Statement implements TypedefProvider {
22892333
*/
22902334
private getTranspiledClassFunction(state: BrsTranspileState) {
22912335
let result = [] as TranspileResult;
2336+
22922337
const constructorFunction = this.getConstructorFunction();
2293-
const constructorParams = constructorFunction ? constructorFunction.func.parameters : [];
2338+
let constructorParams = [];
2339+
if (constructorFunction) {
2340+
constructorParams = constructorFunction.func.parameters;
2341+
} else {
2342+
constructorParams = this.getConstructorParams(this.getAncestors(state));
2343+
}
22942344

22952345
result.push(
22962346
state.sourceNode(this.classKeyword, 'function'),

0 commit comments

Comments
 (0)