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
14 changes: 10 additions & 4 deletions src/AstValidationSegmenter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DottedGetExpression, TypeExpression, VariableExpression } from './parser/Expression';
import { isAliasStatement, isBinaryExpression, isBlock, isBody, isClassStatement, isConditionalCompileStatement, isDottedGetExpression, isInterfaceStatement, isNamespaceStatement, isTypecastStatement, isTypeExpression, isVariableExpression } from './astUtils/reflection';
import { isAliasStatement, isArrayType, isBinaryExpression, isBlock, isBody, isClassStatement, isConditionalCompileStatement, isDottedGetExpression, isInterfaceStatement, isNamespaceStatement, isTypecastStatement, isTypeExpression, isVariableExpression } from './astUtils/reflection';
import { ChildrenSkipper, WalkMode, createVisitor } from './astUtils/visitors';
import type { ExtraSymbolData, GetTypeOptions, TypeChainEntry } from './interfaces';
import type { AstNode, Expression } from './parser/AstNode';
Expand Down Expand Up @@ -77,8 +77,11 @@ export class AstValidationSegmenter {
this.checkExpressionForUnresolved(segment, expression.expression.right as VariableExpression, assignedSymbolsNames);
}
if (isTypeExpression(expression)) {
const typeIntypeExpression = expression.getType({ flags: SymbolTypeFlag.typetime });
if (typeIntypeExpression.isResolvable()) {
let typeInTypeExpression = expression.getType({ flags: SymbolTypeFlag.typetime });
if (isArrayType(typeInTypeExpression)) {
typeInTypeExpression = typeInTypeExpression.defaultType;
}
if (typeInTypeExpression.isResolvable()) {
return this.handleTypeCastTypeExpression(segment, expression);
}
}
Expand All @@ -90,7 +93,10 @@ export class AstValidationSegmenter {
let typeChain: TypeChainEntry[] = [];
const extraData = {} as ExtraSymbolData;
const options: GetTypeOptions = { flags: flag, onlyCacheResolvedTypes: true, typeChain: typeChain, data: extraData };
const nodeType = expression.getType(options);
let nodeType = expression.getType(options);
if (isArrayType(nodeType)) {
nodeType = nodeType.defaultType;
}
if (!nodeType?.isResolvable()) {
let symbolsSet: Set<UnresolvedSymbol>;
if (!assignedSymbolsNames?.has(typeChain[0].name.toLowerCase())) {
Expand Down
7 changes: 2 additions & 5 deletions src/bscPlugin/validation/BrsFileValidator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isAliasStatement, isArrayType, isBlock, isBody, isClassStatement, isConditionalCompileConstStatement, isConditionalCompileErrorStatement, isConditionalCompileStatement, isConstStatement, isDottedGetExpression, isDottedSetStatement, isEnumStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isImportStatement, isIndexedGetExpression, isIndexedSetStatement, isInterfaceStatement, isInvalidType, isLibraryStatement, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTypecastExpression, isTypecastStatement, isUnaryExpression, isVariableExpression, isVoidType, isWhileStatement } from '../../astUtils/reflection';
import { isAliasStatement, isBlock, isBody, isClassStatement, isConditionalCompileConstStatement, isConditionalCompileErrorStatement, isConditionalCompileStatement, isConstStatement, isDottedGetExpression, isDottedSetStatement, isEnumStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isIfStatement, isImportStatement, isIndexedGetExpression, isIndexedSetStatement, isInterfaceStatement, isInvalidType, isLibraryStatement, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTypecastExpression, isTypecastStatement, isUnaryExpression, isVariableExpression, isVoidType, isWhileStatement } from '../../astUtils/reflection';
import { createVisitor, WalkMode } from '../../astUtils/visitors';
import { DiagnosticMessages } from '../../DiagnosticMessages';
import type { BrsFile } from '../../files/BrsFile';
Expand Down Expand Up @@ -123,11 +123,8 @@ export class BrsFileValidator {
ForEachStatement: (node) => {
//register the for loop variable
const loopTargetType = node.target.getType({ flags: SymbolTypeFlag.runtime });
let loopVarType = isArrayType(loopTargetType) ? loopTargetType.defaultType : DynamicType.instance;
const loopVarType = new ArrayDefaultTypeReferenceType(loopTargetType);

if (!loopTargetType.isResolvable()) {
loopVarType = new ArrayDefaultTypeReferenceType(loopTargetType);
}
node.parent.getSymbolTable()?.addSymbol(node.tokens.item.text, { definingNode: node, isInstance: true, canUseInDefinedAstNode: true }, loopVarType, SymbolTypeFlag.runtime);
},
NamespaceStatement: (node) => {
Expand Down
42 changes: 42 additions & 0 deletions src/bscPlugin/validation/ScopeValidator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4430,6 +4430,48 @@ describe('ScopeValidator', () => {
// there should be no more errors
expectZeroDiagnostics(program);
});

it('recognizes when the type of a for-each loop variable changes', () => {
program.setFile<BrsFile>('source/file1.bs', `
interface FooFace
prop as string
end interface
`);

program.setFile<BrsFile>('source/file2.bs', `
function loopFooFace(input as FooFace[])
out = ""
for each ff in input
out += ff.prop
end for
return out
end function
`);
program.validate();
//currently no error
expectZeroDiagnostics(program);

// change FooFace.prop to not work in other file
program.setFile<BrsFile>('source/file1.bs', `
interface FooFace
prop as integer
end interface
`);
program.validate();
expectDiagnostics(program, [
DiagnosticMessages.operatorTypeMismatch('+=', 'string', 'integer')
]);

// change FooFace.prop to back to working
program.setFile<BrsFile>('source/file1.bs', `
interface FooFace
prop as string
end interface
`);
program.validate();
//currently no error
expectZeroDiagnostics(program);
});
});


Expand Down
36 changes: 14 additions & 22 deletions src/types/ReferenceType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,6 @@ 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
Expand All @@ -506,36 +504,30 @@ export class ArrayDefaultTypeReferenceType extends BscType {

if (propName === 'getTarget') {
return () => {
if (this.cachedType) {
return this.cachedType;
}
if (isReferenceType(this.objType)) {
(this.objType as any).getTarget();
}
return this.objType;
};
}

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;
}
let resultType: BscType = DynamicType.instance;
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 {
if (isArrayType(this.objType)) {
resultType = this.objType.defaultType;
} else {
resultType = DynamicType.instance;
}
this.cachedType = resultType;
resultType = DynamicType.instance;
}

}

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