Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/files/BrsFile.Class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,132 @@ describe('BrsFile BrighterScript classes', () => {
`, undefined, 'source/main.bs');
});

it('does not crash synthesizing an implicit constructor whose inherited param is a class type declared in a different namespace', async () => {
await testTranspile(`
namespace Foo
class Thing
end class

class Base
sub new(t as Thing)
m.thing = t
end sub
thing as Thing
end class
end namespace

namespace Bar
class DerivedImplicitCtor extends Foo.Base
extra as integer = 2
end class
end namespace
`, `
sub __Foo_Thing_method_new()
end sub
function __Foo_Thing_builder()
instance = {}
instance.new = __Foo_Thing_method_new
return instance
end function
function Foo_Thing()
instance = __Foo_Thing_builder()
instance.new()
return instance
end function
sub __Foo_Base_method_new(t as dynamic)
m.thing = invalid
m.thing = t
end sub
function __Foo_Base_builder()
instance = {}
instance.new = __Foo_Base_method_new
return instance
end function
function Foo_Base(t as dynamic)
instance = __Foo_Base_builder()
instance.new(t)
return instance
end function
sub __Bar_DerivedImplicitCtor_method_new(t as dynamic)
m.super0_new(t)
m.extra = 2
end sub
function __Bar_DerivedImplicitCtor_builder()
instance = __Foo_Base_builder()
instance.super0_new = instance.new
instance.new = __Bar_DerivedImplicitCtor_method_new
return instance
end function
function Bar_DerivedImplicitCtor(t as dynamic)
instance = __Bar_DerivedImplicitCtor_builder()
instance.new(t)
return instance
end function
`, undefined, 'source/main.bs');
});

it('does not crash synthesizing an implicit constructor whose inherited param is a class type declared in the same namespace', async () => {
await testTranspile(`
namespace Foo
class Thing
end class

class Base
sub new(t as Thing)
m.thing = t
end sub
thing as Thing
end class

class DerivedSameNs extends Base
extra as integer = 2
end class
end namespace
`, `
sub __Foo_Thing_method_new()
end sub
function __Foo_Thing_builder()
instance = {}
instance.new = __Foo_Thing_method_new
return instance
end function
function Foo_Thing()
instance = __Foo_Thing_builder()
instance.new()
return instance
end function
sub __Foo_Base_method_new(t as dynamic)
m.thing = invalid
m.thing = t
end sub
function __Foo_Base_builder()
instance = {}
instance.new = __Foo_Base_method_new
return instance
end function
function Foo_Base(t as dynamic)
instance = __Foo_Base_builder()
instance.new(t)
return instance
end function
sub __Foo_DerivedSameNs_method_new(t as dynamic)
m.super0_new(t)
m.extra = 2
end sub
function __Foo_DerivedSameNs_builder()
instance = __Foo_Base_builder()
instance.super0_new = instance.new
instance.new = __Foo_DerivedSameNs_method_new
return instance
end function
function Foo_DerivedSameNs(t as dynamic)
instance = __Foo_DerivedSameNs_builder()
instance.new(t)
return instance
end function
`, undefined, 'source/main.bs');
});

it('properly handles child class constructor override and super calls', async () => {
await testTranspile(`
class Animal
Expand Down
18 changes: 16 additions & 2 deletions src/parser/Expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2693,9 +2693,16 @@ export class TypeExpression extends Expression implements TypedefProvider {
* The standard AST expression that represents the type for this TypeExpression.
*/
expression: Expression;
/**
* An already-known type for this TypeExpression, bypassing resolution of `expression`
* via symbol table lookup. Useful when `expression` is not attached to (or can't resolve
* against) a real symbol table - e.g. a type reference synthesized for a detached AST node.
*/
resolvedType?: BscType;
}) {
super();
this.expression = options.expression;
this.resolvedType = options.resolvedType;
this.location = util.cloneLocation(this.expression?.location);
}

Expand All @@ -2706,6 +2713,12 @@ export class TypeExpression extends Expression implements TypedefProvider {
*/
public readonly expression: Expression;

/**
* An already-known type for this TypeExpression. When set, `getType()` returns this
* directly instead of resolving `expression` via symbol table lookup.
*/
public readonly resolvedType?: BscType;

public readonly location: Location;

public transpile(state: BrsTranspileState): TranspileResult {
Expand All @@ -2729,7 +2742,7 @@ export class TypeExpression extends Expression implements TypedefProvider {
}

public getType(options: GetTypeOptions): BscType {
return this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
return this.resolvedType ?? this.expression.getType({ ...options, flags: SymbolTypeFlag.typetime });
}

getTypedef(state: TranspileState): TranspileResult {
Expand Down Expand Up @@ -2760,7 +2773,8 @@ export class TypeExpression extends Expression implements TypedefProvider {
public clone() {
return this.finalizeClone(
new TypeExpression({
expression: this.expression?.clone()
expression: this.expression?.clone(),
resolvedType: this.resolvedType
}),
['expression']
);
Expand Down
35 changes: 30 additions & 5 deletions src/parser/Statement.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
/* eslint-disable no-bitwise */
import type { Token, Identifier } from '../lexer/Token';
import { TokenKind } from '../lexer/TokenKind';
import type { DottedGetExpression, FunctionParameterExpression, LiteralExpression, TypecastExpression, TypeExpression } from './Expression';
import { FunctionExpression } from './Expression';
import type { DottedGetExpression, LiteralExpression, TypecastExpression } from './Expression';
import { FunctionExpression, FunctionParameterExpression, TypeExpression } from './Expression';
import { CallExpression, VariableExpression } from './Expression';
import { util } from '../util';
import type { Location } from 'vscode-languageserver';
import type { BrsTranspileState } from './BrsTranspileState';
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, isEnumStatement, isExpressionStatement, isFieldStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isPrintSeparatorExpression, isTryCatchStatement, isTypedefProvider, isUnaryExpression, isUninitializedType, isVoidType, isWhileStatement } from '../astUtils/reflection';
import { isCallExpression, isCatchStatement, isClassType, isConditionalCompileStatement, isEnumMemberStatement, isEnumType, isEnumStatement, isExpressionStatement, isFieldStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isMethodStatement, isNamespaceStatement, isPrintSeparatorExpression, isTryCatchStatement, isTypedefProvider, isUnaryExpression, isUninitializedType, isVoidType, isWhileStatement } from '../astUtils/reflection';
import type { GetTypeOptions } from '../interfaces';
import { TypeChainEntry, type TranspileResult, type TypedefProvider } from '../interfaces';
import { createIdentifier, createInvalidLiteral, createMethodStatement, createToken } from '../astUtils/creators';
import { createDottedIdentifier, createIdentifier, createInvalidLiteral, createMethodStatement, createToken, createVariableExpression } from '../astUtils/creators';
import { DynamicType } from '../types/DynamicType';
import type { BscType } from '../types/BscType';
import { SymbolTable } from '../SymbolTable';
Expand Down Expand Up @@ -2867,6 +2867,31 @@ export class ClassStatement extends Statement implements TypedefProvider {
return [];
}

/**
* Clone a parent constructor's parameter for use in a synthesized subclass constructor.
* The clone is detached from the AST (no parent), so if the parameter's type is a class
* or enum, its type reference can't be re-resolved by symbol name once cloned - especially
* since the subclass may live in a different namespace than the one the parameter's type
* was originally declared in, where even a fully-qualified reference has no symbol table
* to resolve against. Resolve the type now (while the original is still properly attached),
* and bake the fully-qualified name/type directly into the clone.
*/
private cloneConstructorParam(param: FunctionParameterExpression): FunctionParameterExpression {
const exprType = param.typeExpression?.getType({ flags: SymbolTypeFlag.typetime });
if (!isClassType(exprType) && !isEnumType(exprType)) {
return param.clone();
}
const nameParts = exprType.toString().split('.');
const qualifiedExpression = nameParts.length > 1 ? createDottedIdentifier(nameParts) : createVariableExpression(nameParts[0]);
return new FunctionParameterExpression({
name: param.tokens.name,
equals: param.tokens.equals,
defaultValue: param.defaultValue?.clone(),
as: param.tokens.as,
typeExpression: new TypeExpression({ expression: qualifiedExpression, resolvedType: exprType })
});
}

/**
* Determine if the specified field was declared in one of the ancestor classes
*/
Expand Down Expand Up @@ -2999,7 +3024,7 @@ export class ClassStatement extends Statement implements TypedefProvider {
modifiers: [],
name: createIdentifier('new'),
func: new FunctionExpression({
parameters: params.map(x => x.clone()),
parameters: params.map(x => this.cloneConstructorParam(x)),
body: new Block({ statements: [call] }),
functionType: createToken(TokenKind.Sub),
endFunctionType: createToken(TokenKind.EndSub),
Expand Down
Loading