Skip to content
Merged
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
33 changes: 30 additions & 3 deletions src/Scope.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3167,7 +3167,7 @@ describe('Scope', () => {
});


it('should set correct type on for loops', () => {
it('should set correct type on for each loop items', () => {
let mainFile = program.setFile<BrsFile>('source/main.bs', `
sub sum(nums as integer[]) as integer
total = 0
Expand All @@ -3179,14 +3179,41 @@ describe('Scope', () => {
`);
program.validate();
expectZeroDiagnostics(program);
const processFnScope = mainFile.getFunctionScopeAtPosition(util.createPosition(2, 24));
const symbolTable = processFnScope.symbolTable;
const forEachStmt = mainFile.ast.findChild<ForEachStatement>(isForEachStatement);
const symbolTable = forEachStmt.getSymbolTable();
const opts = { flags: SymbolTypeFlag.runtime };
expectTypeToBe(symbolTable.getSymbolType('total', opts), IntegerType);
expectTypeToBe(symbolTable.getSymbolType('num', opts), IntegerType);
expectTypeToBe(symbolTable.getSymbolType('nums', opts), ArrayType);
});

it('should set correct type on for each loop items when looping a const array in a namespace', () => {
let mainFile = program.setFile<BrsFile>('source/main.bs', `
namespace Alpha
const data = [1,2,3]

function printData()
for each item in Alpha.data
print item
end for
end function
end namespace
`);
program.validate();
expectZeroDiagnostics(program);
const forEachStmt = mainFile.ast.findChild<ForEachStatement>(isForEachStatement);
const symbolTable = forEachStmt.getSymbolTable();
const opts = { flags: SymbolTypeFlag.runtime };
expectTypeToBe(symbolTable.getSymbolType('data', opts), ArrayType);
expectTypeToBe((symbolTable.getSymbolType('data', opts) as ArrayType).defaultType, IntegerType);

expectTypeToBe(symbolTable.getSymbolType('Alpha', opts).getMemberType('data', opts), ArrayType);
expectTypeToBe(((symbolTable.getSymbolType('Alpha', opts).getMemberType('data', opts)) as ArrayType).defaultType, IntegerType);

expectTypeToBe(symbolTable.getSymbolType('item', opts), IntegerType);

});

it('should set correct type on array literals', () => {
let mainFile = program.setFile<BrsFile>('source/main.bs', `
sub process()
Expand Down
9 changes: 6 additions & 3 deletions src/astUtils/reflection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type { ObjectType } from '../types/ObjectType';
import type { AstNode, Expression, Statement } from '../parser/AstNode';
import type { AssetFile } from '../files/AssetFile';
import { AstNodeKind } from '../parser/AstNode';
import type { TypePropertyReferenceType, ReferenceType, BinaryOperatorReferenceType } from '../types/ReferenceType';
import type { TypePropertyReferenceType, ReferenceType, BinaryOperatorReferenceType, ArrayDefaultTypeReferenceType } from '../types/ReferenceType';
import type { EnumMemberType, EnumType } from '../types/EnumType';
import type { UnionType } from '../types/UnionType';
import type { UninitializedType } from '../types/UninitializedType';
Expand Down Expand Up @@ -337,6 +337,9 @@ export function isTypePropertyReferenceType(value: any): value is TypePropertyRe
export function isBinaryOperatorReferenceType(value: any): value is BinaryOperatorReferenceType {
return value?.__reflection?.name === 'BinaryOperatorReferenceType';
}
export function isArrayDefaultTypeReferenceType(value: any): value is ArrayDefaultTypeReferenceType {
return value?.__reflection?.name === 'ArrayDefaultTypeReferenceType';
}
export function isNamespaceType(value: any): value is NamespaceType {
return value?.kind === BscTypeKind.NamespaceType;
}
Expand All @@ -360,9 +363,9 @@ export function isCallableType(target): target is BaseFunctionType {
return isFunctionType(target) || isTypedFunctionType(target);
}

export function isAnyReferenceType(target): target is ReferenceType | TypePropertyReferenceType | BinaryOperatorReferenceType {
export function isAnyReferenceType(target): target is ReferenceType | TypePropertyReferenceType | BinaryOperatorReferenceType | ArrayDefaultTypeReferenceType {
const name = target?.__reflection?.name;
return name === 'ReferenceType' || name === 'TypePropertyReferenceType' || name === 'BinaryOperatorReferenceType';
return name === 'ReferenceType' || name === 'TypePropertyReferenceType' || name === 'BinaryOperatorReferenceType' || name === 'ArrayDefaultTypeReferenceType';
}

const numberTypeKinds = [
Expand Down
6 changes: 5 additions & 1 deletion src/bscPlugin/validation/BrsFileValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CallExpression, type FunctionExpression, type LiteralExpression } from
import { ParseMode } from '../../parser/Parser';
import type { ContinueStatement, EnumMemberStatement, EnumStatement, ForEachStatement, ForStatement, ImportStatement, LibraryStatement, WhileStatement } from '../../parser/Statement';
import { SymbolTypeFlag } from '../../SymbolTypeFlag';
import { ArrayDefaultTypeReferenceType } from '../../types/ReferenceType';
import { AssociativeArrayType } from '../../types/AssociativeArrayType';
import { DynamicType } from '../../types/DynamicType';
import util from '../../util';
Expand Down Expand Up @@ -97,7 +98,10 @@ export class BrsFileValidator {
ForEachStatement: (node) => {
//register the for loop variable
const loopTargetType = node.target.getType({ flags: SymbolTypeFlag.runtime });
const loopVarType = isArrayType(loopTargetType) ? loopTargetType.defaultType : DynamicType.instance;
let loopVarType = isArrayType(loopTargetType) ? loopTargetType.defaultType : DynamicType.instance;
if (!loopTargetType.isResolvable()) {
loopVarType = new ArrayDefaultTypeReferenceType(loopTargetType);
}
node.parent.getSymbolTable()?.addSymbol(node.tokens.item.text, { definingNode: node }, loopVarType, SymbolTypeFlag.runtime);
},
NamespaceStatement: (node) => {
Expand Down
21 changes: 20 additions & 1 deletion src/types/ReferenceType.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SymbolTypeFlag } from '../SymbolTypeFlag';
import { expectTypeToBe } from '../testHelpers.spec';
import { DynamicType } from './DynamicType';
import { IntegerType } from './IntegerType';
import { TypePropertyReferenceType, ReferenceType, BinaryOperatorReferenceType } from './ReferenceType';
import { TypePropertyReferenceType, ReferenceType, BinaryOperatorReferenceType, ArrayDefaultTypeReferenceType } from './ReferenceType';
import { StringType } from './StringType';
import { FloatType } from './FloatType';
import { ClassType } from './ClassType';
Expand All @@ -14,6 +14,7 @@ import { NamespaceType } from './NamespaceType';
import { createToken } from '../astUtils/creators';
import { TokenKind } from '../lexer/TokenKind';
import { util } from '../util';
import { ArrayType } from './ArrayType';

const runtimeFlag = SymbolTypeFlag.runtime;

Expand Down Expand Up @@ -159,3 +160,21 @@ describe('PropertyReferenceType', () => {
});

});

describe('ArrayDefaultTypeReferenceType', () => {

it('should resolve the default type of an array, which is defined in the future', () => {
const table = new SymbolTable('test');
const futureArray = new ReferenceType('futureArray', 'futureArray', runtimeFlag, () => table);

const myArrayDefaultType = new ArrayDefaultTypeReferenceType(futureArray);
expect(myArrayDefaultType.isResolvable()).to.be.false;
expect((myArrayDefaultType as any).kind).to.equal(DynamicType.instance.kind);

//Define the symbol
table.addSymbol('futureArray', {}, new ArrayType(IntegerType.instance), runtimeFlag);
//myArrayDefaultType is now treated as integer
expect(myArrayDefaultType.isResolvable()).to.be.true;
expect((myArrayDefaultType as any).kind).to.equal(IntegerType.instance.kind);
});
});
49 changes: 48 additions & 1 deletion src/types/ReferenceType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { GetTypeOptions, TypeChainEntry, TypeCompatibilityData } from '../interfaces';
import type { GetSymbolTypeOptions, SymbolTypeGetterProvider } from '../SymbolTable';
import type { SymbolTypeFlag } from '../SymbolTypeFlag';
import { isAnyReferenceType, isComponentType, isDynamicType, isReferenceType } from '../astUtils/reflection';
import { isAnyReferenceType, isArrayType, isComponentType, isDynamicType, isReferenceType } from '../astUtils/reflection';
import { BscType } from './BscType';
import { DynamicType } from './DynamicType';
import { BscTypeKind } from './BscTypeKind';
Expand Down Expand Up @@ -424,3 +424,50 @@ export class BinaryOperatorReferenceType extends BscType {
});
}
}


/**
* Use this class for when there is a presumable array type that is a referenceType
*/
export class ArrayDefaultTypeReferenceType extends BscType {
cachedType: BscType;

constructor(public objType: BscType) {
super('ArrayDefaultType');
// eslint-disable-next-line no-constructor-return
return new Proxy(this, {
get: (target, propName, receiver) => {

if (propName === '__reflection') {
// Cheeky way to get `ArrayDefaultTypeReferenceType` reflection to work
return { name: 'ArrayDefaultTypeReferenceType' };
}

let resultType: BscType = this.cachedType ?? DynamicType.instance;
if (!this.cachedType) {
if ((isAnyReferenceType(this.objType) && !this.objType.isResolvable())
) {
if (propName === 'isResolvable') {
return () => false;
}
if (propName === 'getTarget') {
return () => undefined;
}
} else {
if (isArrayType(this.objType)) {
resultType = this.objType.defaultType;
} else {
resultType = DynamicType.instance;
}
this.cachedType = resultType;
}

}
if (resultType) {
const result = Reflect.get(resultType, propName, resultType);
return result;
}
}
});
}
}