Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion src/astUtils/reflection.ts
Original file line number Diff line number Diff line change
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
7 changes: 6 additions & 1 deletion src/parser/Expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as fileUrl from 'file-url';
import type { WalkOptions, WalkVisitor } from '../astUtils/visitors';
import { WalkMode } from '../astUtils/visitors';
import { walk, InternalWalkMode, walkArray } from '../astUtils/visitors';
import { isAALiteralExpression, isAAMemberExpression, isArrayLiteralExpression, isArrayType, isCallableType, isCallExpression, isCallfuncExpression, isDottedGetExpression, isEscapedCharCodeLiteralExpression, isFunctionExpression, isFunctionStatement, isIntegerType, isInterfaceMethodStatement, isInvalidType, isLiteralBoolean, isLiteralExpression, isLiteralNumber, isLiteralString, isLongIntegerType, isMethodStatement, isNamespaceStatement, isNativeType, isNewExpression, isPrimitiveType, isReferenceType, isStringType, isTemplateStringExpression, isTypecastExpression, isUnaryExpression, isVariableExpression, isVoidType } from '../astUtils/reflection';
import { isAALiteralExpression, isAAMemberExpression, isArrayLiteralExpression, isArrayType, isCallableType, isCallExpression, isCallfuncExpression, isDottedGetExpression, isEscapedCharCodeLiteralExpression, isFunctionExpression, isFunctionStatement, isIntegerType, isInterfaceMethodStatement, isInterfaceType, isInvalidType, isLiteralBoolean, isLiteralExpression, isLiteralNumber, isLiteralString, isLongIntegerType, isMethodStatement, isNamespaceStatement, isNativeType, isNewExpression, isPrimitiveType, isReferenceType, isStringType, isTemplateStringExpression, isTypecastExpression, isUnaryExpression, isVariableExpression, isVoidType } from '../astUtils/reflection';
import type { GetTypeOptions, TranspileResult, TypedefProvider } from '../interfaces';
import { TypeChainEntry } from '../interfaces';
import { VoidType } from '../types/VoidType';
Expand Down Expand Up @@ -200,6 +200,11 @@ export class CallExpression extends Expression {
if (isNewExpression(this.parent)) {
return calleeType;
}
if (isInterfaceType(calleeType) && calleeType?.isBuiltIn && calleeType?.name === 'roFunction') {
// things typed as `roFunction` can be called, but we don't know the return type
return DynamicType.instance;
}

const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this, options);
if (specialCaseReturnType) {
return specialCaseReturnType;
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
Loading