diff --git a/src/bscPlugin/validation/BrsFileValidator.spec.ts b/src/bscPlugin/validation/BrsFileValidator.spec.ts index c17f01369..7e5c9cfcc 100644 --- a/src/bscPlugin/validation/BrsFileValidator.spec.ts +++ b/src/bscPlugin/validation/BrsFileValidator.spec.ts @@ -770,6 +770,58 @@ describe('BrsFileValidator', () => { expectZeroDiagnostics(program); }); }); + + describe('types', () => { + it('sets assignments of invalid as dynamic', () => { + const file = program.setFile('source/main.bs', ` + sub test() + channel = invalid + if true + channel = { + height: 123 + } + end if + + height = 0 + if channel <> invalid then + height += channel.height + end if + end sub + `); + program.validate(); + expectZeroDiagnostics(program); + const func = file.ast.statements[0].findChild(isFunctionExpression, { walkMode: WalkMode.visitAllRecursive }); + const table = func.body.getSymbolTable(); + const data = {} as ExtraSymbolData; + const channelType = table.getSymbolType('channel', { flags: SymbolTypeFlag.runtime, data: data }); + expectTypeToBe(channelType, DynamicType); + }); + + it('sets default arg of invalid as dynamic', () => { + const file = program.setFile('source/main.bs', ` + sub test(channel = invalid) + if true + channel = { + height: 123 + } + end if + + height = 0 + if channel <> invalid then + height += channel.height + end if + end sub + `); + program.validate(); + expectZeroDiagnostics(program); + const func = file.ast.statements[0].findChild(isFunctionExpression, { walkMode: WalkMode.visitAllRecursive }); + const table = func.body.getSymbolTable(); + const data = {} as ExtraSymbolData; + const channelType = table.getSymbolType('channel', { flags: SymbolTypeFlag.runtime, data: data }); + expectTypeToBe(channelType, DynamicType); + }); + }); + describe('instances of types', () => { it('sets assigned variables as instances', () => { const file = program.setFile('source/main.bs', ` diff --git a/src/bscPlugin/validation/BrsFileValidator.ts b/src/bscPlugin/validation/BrsFileValidator.ts index e4c3ff0f4..1f050c7dc 100644 --- a/src/bscPlugin/validation/BrsFileValidator.ts +++ b/src/bscPlugin/validation/BrsFileValidator.ts @@ -1,4 +1,4 @@ -import { isAliasStatement, isArrayType, isBlock, isBody, isClassStatement, isConditionalCompileConstStatement, isConditionalCompileErrorStatement, isConditionalCompileStatement, isConstStatement, isDottedGetExpression, isDottedSetStatement, isEnumStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isImportStatement, isIndexedGetExpression, isIndexedSetStatement, isInterfaceStatement, isLibraryStatement, isLiteralExpression, isMethodStatement, isNamespaceStatement, isTypecastExpression, isTypecastStatement, isUnaryExpression, isVariableExpression, isWhileStatement } from '../../astUtils/reflection'; +import { isAliasStatement, isArrayType, isBlock, isBody, isClassStatement, isConditionalCompileConstStatement, isConditionalCompileErrorStatement, isConditionalCompileStatement, isConstStatement, isDottedGetExpression, isDottedSetStatement, isEnumStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, 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'; @@ -108,7 +108,10 @@ export class BrsFileValidator { AssignmentStatement: (node) => { const data: ExtraSymbolData = {}; //register this variable - const nodeType = node.getType({ flags: SymbolTypeFlag.runtime, data: data }); + let nodeType = node.getType({ flags: SymbolTypeFlag.runtime, data: data }); + if (isInvalidType(nodeType) || isVoidType(nodeType)) { + nodeType = DynamicType.instance; + } node.parent.getSymbolTable()?.addSymbol(node.tokens.name.text, { definingNode: node, isInstance: true, isFromDocComment: data.isFromDocComment }, nodeType, SymbolTypeFlag.runtime); }, DottedSetStatement: (node) => { diff --git a/src/bscPlugin/validation/ScopeValidator.spec.ts b/src/bscPlugin/validation/ScopeValidator.spec.ts index c70b97275..69ad1601c 100644 --- a/src/bscPlugin/validation/ScopeValidator.spec.ts +++ b/src/bscPlugin/validation/ScopeValidator.spec.ts @@ -2189,6 +2189,70 @@ describe('ScopeValidator', () => { expectZeroDiagnostics(program); }); + it('allows access of properties of union with invalid', () => { + program.setFile('source/main.bs', ` + sub test() + channel = invalid + if true + channel = { + height: 123 + } + end if + + height = 0 + if channel <> invalid then + height += channel.height + end if + end sub + `); + program.validate(); + expectZeroDiagnostics(program); + + }); + + it('sets default arg of invalid as dynamic', () => { + program.setFile('source/main.bs', ` + sub test(channel = invalid) + if true + channel = { + height: 123 + } + end if + + height = 0 + if channel <> invalid then + height += channel.height + end if + end sub + `); + program.validate(); + expectZeroDiagnostics(program); + + }); + + it('sets assignment of function returning invalid as dynamic', () => { + program.setFile('source/main.bs', ` + sub test() + channel = noReturn() + if true + channel = { + height: 123 + } + end if + + height = 0 + if channel <> invalid then + height += channel.height + end if + end sub + + sub noReturn() + print "hello" + end sub + `); + program.validate(); + expectZeroDiagnostics(program); + }); }); describe('itemCannotBeUsedAsVariable', () => { diff --git a/src/types/VoidType.ts b/src/types/VoidType.ts index 8d9288c86..c1bcd2e13 100644 --- a/src/types/VoidType.ts +++ b/src/types/VoidType.ts @@ -3,7 +3,8 @@ import { BscType } from './BscType'; import { BscTypeKind } from './BscTypeKind'; import { isUnionTypeCompatible } from './helpers'; import { BuiltInInterfaceAdder } from './BuiltInInterfaceAdder'; -import type { TypeCompatibilityData } from '../interfaces'; +import type { GetTypeOptions, TypeCompatibilityData } from '../interfaces'; +import { DynamicType } from './DynamicType'; export class VoidType extends BscType { constructor( @@ -36,6 +37,10 @@ export class VoidType extends BscType { public isEqual(targetType: BscType) { return isVoidType(targetType); } + + getMemberType(memberName: string, options: GetTypeOptions) { + return DynamicType.instance; + } } BuiltInInterfaceAdder.primitiveTypeInstanceCache.set('void', VoidType.instance); diff --git a/src/types/helpers.ts b/src/types/helpers.ts index e2710969f..531e7dadd 100644 --- a/src/types/helpers.ts +++ b/src/types/helpers.ts @@ -1,5 +1,5 @@ import type { TypeCompatibilityData } from '../interfaces'; -import { isAnyReferenceType, isDynamicType, isEnumMemberType, isEnumType, isInheritableType, isInterfaceType, isReferenceType, isUnionType } from '../astUtils/reflection'; +import { isAnyReferenceType, isDynamicType, isEnumMemberType, isEnumType, isInheritableType, isInterfaceType, isReferenceType, isUnionType, isVoidType } from '../astUtils/reflection'; import type { BscType } from './BscType'; import type { UnionType } from './UnionType'; @@ -126,7 +126,7 @@ export function getUniqueType(types: BscType[], unionTypeFactory: (types: BscTyp if (!types || types.length === 0) { return undefined; } - const dynType = types.find((x) => !isAnyReferenceType(x) && isDynamicType(x)); + const dynType = types.find((x) => !isAnyReferenceType(x) && (isDynamicType(x) || isVoidType(x))); if (dynType) { return dynType; }