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
99 changes: 99 additions & 0 deletions src/Scope.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2222,6 +2222,65 @@ describe('Scope', () => {
expectZeroDiagnostics(program);
});

it('allows interfaces to extend components', () => {
program.setFile('components/MyNode.xml', trim`
<?xml version="1.0" encoding="utf-8" ?>
<component name="MyNode" extends="Group">
<script uri="MyNode.bs"/>
<interface>
<field id="myProp" type="integer" />
</interface>
</component>
`);

program.setFile(s`components/MyNode.bs`, `
`);

program.setFile(`source/main.bs`, `
interface MyIFace extends roSGNodeMyNode
end interface

sub foo(iface as MyIFace)
value = iface.myProp
print value
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

it('allows interfaces to have callfunc members from parent components', () => {
program.setFile('components/MyNode.xml', trim`
<?xml version="1.0" encoding="utf-8" ?>
<component name="MyNode" extends="Group">
<script uri="MyNode.bs"/>
<interface>
<field id="myProp" type="integer" />
<function name="someCallfunc" />
</interface>
</component>
`);

program.setFile(s`components/MyNode.bs`, `
function someCallfunc() as integer
return m.top.myProp
end function
`);

program.setFile(`source/main.bs`, `
interface MyIFace extends roSGNodeMyNode
end interface


sub foo(iface as MyIFace)
value = [email protected]()
print value
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

});


Expand Down Expand Up @@ -3717,6 +3776,46 @@ describe('Scope', () => {
expect(thingType.name).to.eq('WidgetInternal');
sourceScope.unlinkSymbolTable();
});

it('should correctly get return value of interfaces with callfunc members', () => {
program.setFile('components/MyNode.xml', trim`
<?xml version="1.0" encoding="utf-8" ?>
<component name="MyNode" extends="Group">
<script uri="MyNode.bs"/>
<interface>
<field id="myProp" type="integer" />
<function name="someCallfunc" />
</interface>
</component>
`);

program.setFile(s`components/MyNode.bs`, `
function someCallfunc() as integer
return m.top.myProp
end function
`);

let utilFile = program.setFile<BrsFile>(`source/util.bs`, `
interface MyIFace extends roSGNodeMyNode
end interface

sub foo(iface as MyIFace)
value = [email protected]()
print value
end sub
`);
program.validate();
expectZeroDiagnostics(program);
const processFnScope = utilFile.getFunctionScopeAtPosition(util.createPosition(5, 31));
const symbolTable = processFnScope.symbolTable;
const opts = { flags: SymbolTypeFlag.runtime };
const sourceScope = program.getScopeByName('source');
sourceScope.linkSymbolTable();
const thingType = symbolTable.getSymbolType('value', opts) as IntegerType;

expectTypeToBe(thingType, IntegerType);
sourceScope.unlinkSymbolTable();
});
});


Expand Down
8 changes: 7 additions & 1 deletion src/astUtils/reflection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type { UnionType } from '../types/UnionType';
import type { UninitializedType } from '../types/UninitializedType';
import type { ArrayType } from '../types/ArrayType';
import type { InheritableType } from '../types/InheritableType';
import type { CallFuncableType } from '../types/CallFuncableType';
import { BscTypeKind } from '../types/BscTypeKind';
import type { NamespaceType } from '../types/NamespaceType';
import type { BaseFunctionType } from '../types/BaseFunctionType';
Expand All @@ -39,6 +40,7 @@ import { TokenKind } from '../lexer/TokenKind';
import type { Program } from '../Program';
import type { Project } from '../lsp/Project';


// File reflection
export function isBrsFile(file: BscFile | undefined): file is BrsFile {
return file?.constructor.name === 'BrsFile';
Expand Down Expand Up @@ -461,7 +463,11 @@ export function isAssociativeArrayType(value: any): value is AssociativeArrayTyp
return value?.kind === BscTypeKind.AssociativeArrayType;
}
export function isInheritableType(target): target is InheritableType {
return isClassType(target) || isInterfaceType(target) || isComponentType(target);
return isClassType(target) || isCallFuncableType(target);
}

export function isCallFuncableType(target): target is CallFuncableType {
return isInterfaceType(target) || isComponentType(target);
}

export function isCallableType(target): target is BaseFunctionType {
Expand Down
6 changes: 3 additions & 3 deletions src/bscPlugin/validation/ScopeValidator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DiagnosticTag, type Range } from 'vscode-languageserver';
import { isAliasStatement, isAssignmentStatement, isAssociativeArrayType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallableType, isCallfuncExpression, isClassStatement, isClassType, isComponentType, isDottedGetExpression, isDynamicType, isEnumMemberType, isEnumType, isFunctionExpression, isFunctionParameterExpression, isLiteralExpression, isNamespaceStatement, isNamespaceType, isNewExpression, isNumberType, isObjectType, isPrimitiveType, isReferenceType, isReturnStatement, isStringTypeLike, isTypedFunctionType, isUnionType, isVariableExpression, isVoidType, isXmlScope } from '../../astUtils/reflection';
import { isAliasStatement, isAssignmentStatement, isAssociativeArrayType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallFuncableType, isCallableType, isCallfuncExpression, isClassStatement, isClassType, isComponentType, isDottedGetExpression, isDynamicType, isEnumMemberType, isEnumType, isFunctionExpression, isFunctionParameterExpression, isLiteralExpression, isNamespaceStatement, isNamespaceType, isNewExpression, isNumberType, isObjectType, isPrimitiveType, isReferenceType, isReturnStatement, isStringTypeLike, isTypedFunctionType, isUnionType, isVariableExpression, isVoidType, isXmlScope } from '../../astUtils/reflection';
import type { DiagnosticInfo } from '../../DiagnosticMessages';
import { DiagnosticMessages } from '../../DiagnosticMessages';
import type { BrsFile } from '../../files/BrsFile';
Expand Down Expand Up @@ -475,7 +475,7 @@ export class ScopeValidator {
}

const callerType = call.callee.obj?.getType({ flags: SymbolTypeFlag.runtime });
if (!isComponentType(callerType)) {
if (!isCallFuncableType(callerType)) {
return;
}
const firstArgToken = call?.args[0]?.tokens.value;
Expand Down Expand Up @@ -541,7 +541,7 @@ export class ScopeValidator {
if (!funcType?.isResolvable() || !isCallableType(funcType)) {
const funcName = util.getAllDottedGetPartsAsString(callee, ParseMode.BrighterScript, isCallfuncExpression(callee) ? '@.' : '.');
if (isUnionType(funcType)) {
if (!util.isUnionOfFunctions(funcType)) {
if (!util.isUnionOfFunctions(funcType) && !isCallfuncExpression(callee)) {
// union of func and non func. not callable
this.addMultiScopeDiagnostic({
...DiagnosticMessages.notCallable(funcName),
Expand Down
8 changes: 2 additions & 6 deletions src/types/BscType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,11 @@ export abstract class BscType {
throw new Error('Method not implemented.');
}

getCallFuncMemberType(name: string, options: GetSymbolTypeOptions) {
getCallFuncTable(): SymbolTable {
throw new Error('Method not implemented.');
}

getCallFuncMemberTable() {
throw new Error('Method not implemented.');
}

getCallFuncType(name: string, options: GetSymbolTypeOptions) {
getCallFuncType(name: string, options: GetSymbolTypeOptions): BscType {
return undefined;
}

Expand Down
107 changes: 107 additions & 0 deletions src/types/CallFuncableType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { SymbolTypeFlag } from '../SymbolTypeFlag';
import type { BscSymbol, GetSymbolTypeOptions, SymbolTableProvider } from '../SymbolTable';
import { SymbolTable } from '../SymbolTable';
import type { BscType } from './BscType';
import { InheritableType } from './InheritableType';
import util from '../util';
import { ReferenceType } from './ReferenceType';
import type { ExtraSymbolData, TypeCompatibilityData } from '../interfaces';
import type { BaseFunctionType } from './BaseFunctionType';
import { isAnyReferenceType, isCallFuncableType, isPrimitiveType, isReferenceType, isTypedFunctionType } from '../astUtils/reflection';
import { addAssociatedTypesTableAsSiblingToMemberTable } from './helpers';

/**
* A Type to represent types that support Callfunc syntax for members, eg. `[email protected]()`
*/
export abstract class CallFuncableType extends InheritableType {
constructor(name: string, parentType?: CallFuncableType | ReferenceType) {
super(name, parentType);
this.callFuncMemberTable = new SymbolTable(`${this.name}: CallFunc`, () => (this.parentType as CallFuncableType)?.callFuncMemberTable);
this.callFuncAssociatedTypesTable = new SymbolTable(`${this.name}: CallFuncAssociatedTypes`, () => (this.parentType as CallFuncableType)?.callFuncAssociatedTypesTable);
}

public readonly callFuncMemberTable: SymbolTable;
public readonly callFuncAssociatedTypesTable: SymbolTable;


isEqual(targetType: BscType, data: TypeCompatibilityData = {}): boolean {
if (this === targetType) {
return true;
}
if (isReferenceType(targetType)) {
return super.isEqual(targetType, data);
}

if (!isCallFuncableType(targetType)) {
return false;
}
return super.isEqual(targetType, data) &&
this.checkCompatibilityBasedOnMembers(targetType, SymbolTypeFlag.runtime, data, this.callFuncMemberTable, targetType.callFuncMemberTable) &&
targetType.checkCompatibilityBasedOnMembers(this, SymbolTypeFlag.runtime, data, targetType.callFuncMemberTable, this.callFuncMemberTable);
}


/**
* Adds a function to the call func member table
* Also adds any associated custom types to its own table, so they can be used through a callfunc
*/
addCallFuncMember(name: string, data: ExtraSymbolData, funcType: BaseFunctionType, flags: SymbolTypeFlag, associatedTypesTableProvider?: SymbolTableProvider) {
const originalTypesToCheck = new Set<BscType>();
if (isTypedFunctionType(funcType)) {
const paramTypes = (funcType.params ?? []).map(p => p.type);
for (const paramType of paramTypes) {
originalTypesToCheck.add(paramType);
}
}
if (funcType.returnType) {
originalTypesToCheck.add(funcType.returnType);
}
const additionalTypesToCheck = new Set<BscType>();

for (const type of originalTypesToCheck) {
if (!type.isBuiltIn) {
util.getCustomTypesInSymbolTree(additionalTypesToCheck, type, (subSymbol: BscSymbol) => {
return !originalTypesToCheck.has(subSymbol.type);
});
}
}

for (const type of [...originalTypesToCheck.values(), ...additionalTypesToCheck.values()]) {
if (!isPrimitiveType(type) && type.isResolvable()) {
// This type is a reference type, but was able to be resolved here
// add it to the table of associated types, so it can be used through a callfunc
const extraData = {};
if (associatedTypesTableProvider) {
associatedTypesTableProvider().getSymbolType(type.toString(), { flags: SymbolTypeFlag.typetime, data: extraData });
}
let targetType = isAnyReferenceType(type) ? type.getTarget?.() : type;

this.callFuncAssociatedTypesTable.addSymbol(type.toString(), { ...extraData, isFromCallFunc: true }, targetType, SymbolTypeFlag.typetime);
}
}

// add this function to be available through callfunc
this.callFuncMemberTable.addSymbol(name, data, funcType, flags);
}

getCallFuncTable() {
return this.callFuncMemberTable;
}

getCallFuncType(name: string, options: GetSymbolTypeOptions) {
const callFuncType = this.callFuncMemberTable.getSymbolType(name, options);

if (isTypedFunctionType(callFuncType)) {
const typesToCheck = [...callFuncType.params.map(p => p.type), callFuncType.returnType];

for (const type of typesToCheck) {
addAssociatedTypesTableAsSiblingToMemberTable(type, this.callFuncAssociatedTypesTable, SymbolTypeFlag.runtime);
}
} else {
// return a reference type for potentially finding this in the future
return new ReferenceType(name, name, options.flags, () => this.callFuncMemberTable);
}

return callFuncType;
}
}
78 changes: 5 additions & 73 deletions src/types/ComponentType.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import type { BscSymbol, GetSymbolTypeOptions, SymbolTableProvider } from '../SymbolTable';
import { SymbolTypeFlag } from '../SymbolTypeFlag';
import { SymbolTable } from '../SymbolTable';
import { isAnyReferenceType, isComponentType, isDynamicType, isInvalidType, isObjectType, isPrimitiveType, isReferenceType, isTypedFunctionType } from '../astUtils/reflection';
import type { ExtraSymbolData, TypeCompatibilityData } from '../interfaces';
import type { BaseFunctionType } from './BaseFunctionType';
import { isComponentType, isDynamicType, isInvalidType, isObjectType, isReferenceType } from '../astUtils/reflection';
import type { TypeCompatibilityData } from '../interfaces';
import type { BscType } from './BscType';
import { BscTypeKind } from './BscTypeKind';
import { BuiltInInterfaceAdder } from './BuiltInInterfaceAdder';
import { InheritableType } from './InheritableType';
import { addAssociatedTypesTableAsSiblingToMemberTable, isUnionTypeCompatible } from './helpers';
import { isUnionTypeCompatible } from './helpers';
import util from '../util';
import { CallFuncableType } from './CallFuncableType';

export class ComponentType extends InheritableType {
export class ComponentType extends CallFuncableType {

constructor(public name: string, superComponent?: ComponentType) {
super(name, superComponent);
this.callFuncMemberTable = new SymbolTable(`${this.name}: CallFunc`, () => this.parentComponent?.callFuncMemberTable);
this.callFuncAssociatedTypesTable = new SymbolTable(`${this.name}: CallFuncAssociatedTypes`, () => this.parentComponent?.callFuncAssociatedTypesTable);
}

public readonly kind = BscTypeKind.ComponentType;
Expand Down Expand Up @@ -121,69 +117,5 @@ export class ComponentType extends InheritableType {
}
this.hasAddedBuiltInFields = true;
}

public readonly callFuncMemberTable: SymbolTable;
public readonly callFuncAssociatedTypesTable: SymbolTable;

/**
* Adds a function to the call func member table
* Also adds any associated custom types to its own table, so they can be used through a callfunc
*/
addCallFuncMember(name: string, data: ExtraSymbolData, funcType: BaseFunctionType, flags: SymbolTypeFlag, associatedTypesTableProvider?: SymbolTableProvider) {
const originalTypesToCheck = new Set<BscType>();
if (isTypedFunctionType(funcType)) {
const paramTypes = (funcType.params ?? []).map(p => p.type);
for (const paramType of paramTypes) {
originalTypesToCheck.add(paramType);
}
}
if (funcType.returnType) {
originalTypesToCheck.add(funcType.returnType);
}
const additionalTypesToCheck = new Set<BscType>();

for (const type of originalTypesToCheck) {
if (!type.isBuiltIn) {
util.getCustomTypesInSymbolTree(additionalTypesToCheck, type, (subSymbol: BscSymbol) => {
return !originalTypesToCheck.has(subSymbol.type);
});
}
}

for (const type of [...originalTypesToCheck.values(), ...additionalTypesToCheck.values()]) {
if (!isPrimitiveType(type) && type.isResolvable()) {
// This type is a reference type, but was able to be resolved here
// add it to the table of associated types, so it can be used through a callfunc
const extraData = {};
if (associatedTypesTableProvider) {
associatedTypesTableProvider().getSymbolType(type.toString(), { flags: SymbolTypeFlag.typetime, data: extraData });
}
let targetType = isAnyReferenceType(type) ? type.getTarget?.() : type;

this.callFuncAssociatedTypesTable.addSymbol(type.toString(), { ...extraData, isFromCallFunc: true }, targetType, SymbolTypeFlag.typetime);
}
}

// add this function to be available through callfunc
this.callFuncMemberTable.addSymbol(name, data, funcType, flags);
}

getCallFuncTable() {
return this.callFuncMemberTable;
}

getCallFuncType(name: string, options: GetSymbolTypeOptions) {
const callFuncType = this.callFuncMemberTable.getSymbolType(name, options);

if (isTypedFunctionType(callFuncType)) {
const typesToCheck = [...callFuncType.params.map(p => p.type), callFuncType.returnType];

for (const type of typesToCheck) {
addAssociatedTypesTableAsSiblingToMemberTable(type, this.callFuncAssociatedTypesTable, SymbolTypeFlag.runtime);
}
}

return callFuncType;
}
}

Loading