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
39 changes: 25 additions & 14 deletions src/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,15 @@ import { ProgramValidatorDiagnosticsTag } from './bscPlugin/validation/ProgramVa
import type { ProvidedSymbolInfo, BrsFile } from './files/BrsFile';
import type { XmlFile } from './files/XmlFile';
import { SymbolTable } from './SymbolTable';
import { ReferenceType, TypesCreated } from './types';
import type { BscType } from './types/BscType';
import { ReferenceType } from './types/ReferenceType';
import { TypesCreated } from './types/helpers';
import type { Statement } from './parser/AstNode';
import { CallExpressionInfo } from './bscPlugin/CallExpressionInfo';
import { SignatureHelpUtil } from './bscPlugin/SignatureHelpUtil';
import { Sequencer } from './common/Sequencer';
import { Deferred } from './deferred';
import { roFunctionType } from './types/roFunctionType';

const bslibNonAliasedRokuModulesPkgPath = s`source/roku_modules/rokucommunity_bslib/bslib.brs`;
const bslibAliasedRokuModulesPkgPath = s`source/roku_modules/bslib/bslib.brs`;
Expand Down Expand Up @@ -216,31 +219,39 @@ export class Program {
}

for (const ifaceData of Object.values(interfaces) as BRSInterfaceData[]) {
const nodeType = new InterfaceType(ifaceData.name);
nodeType.addBuiltInInterfaces();
nodeType.isBuiltIn = true;
this.globalScope.symbolTable.addSymbol(ifaceData.name, { ...builtInSymbolData, description: ifaceData.description }, nodeType, SymbolTypeFlag.typetime);
const ifaceType = new InterfaceType(ifaceData.name);
ifaceType.addBuiltInInterfaces();
ifaceType.isBuiltIn = true;
this.globalScope.symbolTable.addSymbol(ifaceData.name, { ...builtInSymbolData, description: ifaceData.description }, ifaceType, SymbolTypeFlag.typetime);
}

for (const componentData of Object.values(components) as BRSComponentData[]) {
const nodeType = new InterfaceType(componentData.name);
nodeType.addBuiltInInterfaces();
nodeType.isBuiltIn = true;
if (componentData.name !== 'roSGNode') {
let roComponentType: BscType;
const lowerComponentName = componentData.name.toLowerCase();

if (lowerComponentName === 'rosgnode') {
// we will add `roSGNode` as shorthand for `roSGNodeNode`, since all roSgNode components are SceneGraph nodes
this.globalScope.symbolTable.addSymbol(componentData.name, { ...builtInSymbolData, description: componentData.description }, nodeType, SymbolTypeFlag.typetime);
continue;
}
if (lowerComponentName === 'rofunction') {
roComponentType = new roFunctionType();
} else {
roComponentType = new InterfaceType(componentData.name);
}
roComponentType.addBuiltInInterfaces();
roComponentType.isBuiltIn = true;
this.globalScope.symbolTable.addSymbol(componentData.name, { ...builtInSymbolData, description: componentData.description }, roComponentType, SymbolTypeFlag.typetime);
}

for (const nodeData of Object.values(nodes) as SGNodeData[]) {
this.recursivelyAddNodeToSymbolTable(nodeData);
}

for (const eventData of Object.values(events) as BRSEventData[]) {
const nodeType = new InterfaceType(eventData.name);
nodeType.addBuiltInInterfaces();
nodeType.isBuiltIn = true;
this.globalScope.symbolTable.addSymbol(eventData.name, { ...builtInSymbolData, description: eventData.description }, nodeType, SymbolTypeFlag.typetime);
const eventType = new InterfaceType(eventData.name);
eventType.addBuiltInInterfaces();
eventType.isBuiltIn = true;
this.globalScope.symbolTable.addSymbol(eventData.name, { ...builtInSymbolData, description: eventData.description }, eventType, SymbolTypeFlag.typetime);
}

}
Expand Down
4 changes: 2 additions & 2 deletions src/astUtils/reflection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ export function isFunctionType(value: any): value is FunctionType {
return value?.kind === BscTypeKind.FunctionType;
}
export function isRoFunctionType(value: any): value is InterfaceType {
return isBuiltInType(value, 'roFunction');
return value?.kind === BscTypeKind.RoFunctionType || isBuiltInType(value, 'roFunction');
}
export function isFunctionTypeLike(value: any): value is FunctionType | InterfaceType {
return isFunctionType(value) || isRoFunctionType(value);
Expand Down Expand Up @@ -465,7 +465,7 @@ export function isInheritableType(target): target is InheritableType {
}

export function isCallableType(target): target is BaseFunctionType {
return isFunctionTypeLike(target) || isTypedFunctionType(target) || (isDynamicType(target) && !isAnyReferenceType(target));
return isFunctionTypeLike(target) || isTypedFunctionType(target) || isObjectType(target) || (isDynamicType(target) && !isAnyReferenceType(target));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

object is callable , because roFunction is an object. sheesh

}

export function isAnyReferenceType(target): target is AnyReferenceType {
Expand Down
80 changes: 80 additions & 0 deletions src/bscPlugin/validation/ScopeValidator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { tempDir, rootDir } from '../../testHelpers.spec';
import { isReturnStatement } from '../../astUtils/reflection';
import { ScopeValidator } from './ScopeValidator';
import type { ReturnStatement } from '../../parser/Statement';
import { Logger } from '@rokucommunity/logger';

describe('ScopeValidator', () => {

Expand Down Expand Up @@ -2892,6 +2893,75 @@ describe('ScopeValidator', () => {
program.validate();
expectZeroDiagnostics(program);
});

it('allows returning a function call', () => {
const spy = sinon.spy(Logger.prototype, 'error');
program.setFile<BrsFile>('source/main.bs', `
function abc(func as function) as dynamic
return func()
end function
`);
program.validate();
expectZeroDiagnostics(program);
expect(
spy.getCalls().map(x => (x.args?.[0] as string)?.toString()).filter(x => x?.includes('Error when calling plugin'))
).to.eql([]);
});

it('allows returning a roFunction call', () => {
const spy = sinon.spy(Logger.prototype, 'error');
program.setFile<BrsFile>('source/main.bs', `
function abc(func as roFunction) as dynamic
return func()
end function
`);
program.validate();
expectZeroDiagnostics(program);
expect(
spy.getCalls().map(x => (x.args?.[0] as string)?.toString()).filter(x => x?.includes('Error when calling plugin'))
).to.eql([]);
});

it('allows returning a call on an object type', () => {
const spy = sinon.spy(Logger.prototype, 'error');
program.setFile<BrsFile>('source/main.bs', `
function abc(func as object) as dynamic
return func()
end function
`);
program.validate();
expectZeroDiagnostics(program);
expect(
spy.getCalls().map(x => (x.args?.[0] as string)?.toString()).filter(x => x?.includes('Error when calling plugin'))
).to.eql([]);
});

it('allows calling func returned from other func', () => {
const spy = sinon.spy(Logger.prototype, 'error');
program.setFile<BrsFile>('source/calc.bs', `
sub otherFuncFirst()
' forces getOperation to be referenceType called from ReferenceType
end sub

function calc(a as dynamic, b as dynamic, op as string) as dynamic
op = getOperation(op)
return op(1, 2)
end function

function getOperation(name as string) as object
return {
"sum": function(a as dynamic, b as dynamic) as dynamic
return a + b
end function
}[name]
end function
`);
program.validate();
expectZeroDiagnostics(program);
expect(
spy.getCalls().map(x => (x.args?.[0] as string)?.toString()).filter(x => x?.includes('Error when calling plugin'))
).to.eql([]);
});
});

describe('returnTypeCoercionMismatch', () => {
Expand Down Expand Up @@ -4618,6 +4688,16 @@ describe('ScopeValidator', () => {
DiagnosticMessages.notCallable('"string"').message
]);
});

it('allows calling an object type', () => {
program.setFile<BrsFile>('source/calc.bs', `
function someFunc(otherFunc as object) as dynamic
return otherFunc()
end function
`);
program.validate();
expectZeroDiagnostics(program);
});
});

describe('callFunc', () => {
Expand Down
1 change: 1 addition & 0 deletions src/parser/Expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export class CallExpression extends Expression {
if (isNewExpression(this.parent)) {
return calleeType;
}

const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this, options);
if (specialCaseReturnType) {
return specialCaseReturnType;
Expand Down
1 change: 1 addition & 0 deletions src/types/BscTypeKind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum BscTypeKind {
NamespaceType = 'NamespaceType',
ObjectType = 'ObjectType',
ReferenceType = 'ReferenceType',
RoFunctionType = 'RoFunctionType',
StringType = 'StringType',
UninitializedType = 'UninitializedType',
UnionType = 'UnionType',
Expand Down
4 changes: 4 additions & 0 deletions src/types/ObjectType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export class ObjectType extends BscType {
isEqual(otherType: BscType) {
return isObjectType(otherType) && this.checkCompatibilityBasedOnMembers(otherType, SymbolTypeFlag.runtime);
}

get returnType() {
return DynamicType.instance;
}
}


Expand Down
19 changes: 16 additions & 3 deletions src/types/ReferenceType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,12 @@ export class TypePropertyReferenceType extends BscType {
return outerType;
}

if (propName === 'getTarget') {
return () => {
return this.getTarget();
};
}

if (propName === 'isResolvable') {
return () => {
return !!(isAnyReferenceType(this.outerType) ? (this.outerType as any).getTarget() : this.outerType?.isResolvable());
Expand Down Expand Up @@ -394,7 +400,7 @@ export class TypePropertyReferenceType extends BscType {
return () => false;
}
}
let inner = (isAnyReferenceType(this.outerType) ? (this.outerType as ReferenceType).getTarget() : this.outerType)?.[this.propertyName];
let inner = this.getTarget();

if (!inner) {
inner = DynamicType.instance;
Expand All @@ -420,12 +426,19 @@ export class TypePropertyReferenceType extends BscType {
});
}

getTarget: () => BscType;
getTarget(): BscType {
let actualOuterType = this.outerType;
if (isAnyReferenceType(this.outerType)) {
if ((this.outerType as ReferenceType).isResolvable()) {
actualOuterType = (this.outerType as ReferenceType)?.getTarget();
}
}
return actualOuterType?.[this.propertyName];
}

tableProvider: SymbolTableProvider;
}


/**
* Use this class for when there is a binary operator and either the left hand side and/or the right hand side
* are ReferenceTypes
Expand Down
20 changes: 20 additions & 0 deletions src/types/roFunctionType.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect } from '../chai-config.spec';

import { DynamicType } from './DynamicType';
import { FunctionType } from './FunctionType';
import { IntegerType } from './IntegerType';
import { ObjectType } from './ObjectType';
import { roFunctionType } from './roFunctionType';
import { TypedFunctionType } from './TypedFunctionType';

describe('roFunctionType', () => {
it('is equivalent to other function types', () => {
const roFunc = new roFunctionType();

expect(roFunc.isTypeCompatible(new ObjectType())).to.be.true;
expect(roFunc.isTypeCompatible(new DynamicType())).to.be.true;
expect(roFunc.isTypeCompatible(new FunctionType())).to.be.true;
expect(roFunc.isTypeCompatible(new roFunctionType())).to.be.true;
expect(roFunc.isTypeCompatible(new TypedFunctionType(IntegerType.instance))).to.be.true;
});
});
39 changes: 39 additions & 0 deletions src/types/roFunctionType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { isCallableType, isDynamicType, isFunctionTypeLike, isObjectType } from '../astUtils/reflection';
import type { TypeCompatibilityData } from '../interfaces';
import { BaseFunctionType } from './BaseFunctionType';
import type { BscType } from './BscType';
import { BscTypeKind } from './BscTypeKind';
import { isUnionTypeCompatible } from './helpers';

export class roFunctionType extends BaseFunctionType {
public readonly kind = BscTypeKind.RoFunctionType;

public isTypeCompatible(targetType: BscType, data?: TypeCompatibilityData) {
if (
isDynamicType(targetType) ||
isCallableType(targetType) ||
isFunctionTypeLike(targetType) ||
isObjectType(targetType) ||
isUnionTypeCompatible(this, targetType, data)
) {
return true;
}
return false;
}

public toString() {
return 'roFunction';
}

public toTypeString(): string {
return 'dynamic';
}


isEqual(targetType: BscType) {
if (isFunctionTypeLike(targetType)) {
return true;
}
return false;
}
}
Loading