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: 14 additions & 0 deletions src/files/BrsFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4583,6 +4583,20 @@ describe('BrsFile', () => {
`);
});
});

describe('typed functions in type expressions', () => {
it('transpiles to function', () => {
testTranspile(`
function test(func as function(name as string, num as integer) as integer) as integer
return func("hello", 123)
end function
`, `
function test(func as Function) as integer
return func("hello", 123)
end function
`);
});
});
});

it('allows up to 63 function params', () => {
Expand Down
102 changes: 102 additions & 0 deletions src/parser/Parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,108 @@ describe('parser', () => {
expectZeroDiagnostics(diagnostics);
});
});

describe('typed functions as types', () => {
it('disallowed in brightscript mode', () => {
let { diagnostics } = parse(`
function test(func as function())
return func()
end function
`, ParseMode.BrightScript);
expectDiagnosticsIncludes(diagnostics, [
DiagnosticMessages.unexpectedToken(')')
]);
});

it('can be passed as param types', () => {
let { diagnostics } = parse(`
function test(func as function())
return func()
end function
`, ParseMode.BrighterScript);
expectZeroDiagnostics(diagnostics);
});

it('can have a return type', () => {
let { diagnostics } = parse(`
function test(func as sub() as integer) as integer
return func()
end function
`, ParseMode.BrighterScript);
expectZeroDiagnostics(diagnostics);
});

it('can use sub or function', () => {
let { diagnostics } = parse(`
function test(func as sub() as integer) as integer
return func()
end function

function test2(func as function() as integer) as integer
return func()
end function
`, ParseMode.BrighterScript);
expectZeroDiagnostics(diagnostics);
});

it('can have primitive parameters', () => {
let { diagnostics } = parse(`
function test(func as function(name as string, num as integer) as integer) as integer
return func("hello", 123)
end function
`, ParseMode.BrighterScript);
expectZeroDiagnostics(diagnostics);
});

it('can have complex parameters', () => {
let { diagnostics } = parse(`
interface IFace
name as string
end interface

function test(func as function(thing as IFace) as integer) as integer
return func({name: "hello"})
end function
`, ParseMode.BrighterScript);
expectZeroDiagnostics(diagnostics);
});

it('can have compound parameters', () => {
let { diagnostics } = parse(`
interface IFace
name as string
end interface

function test(func as function(arg1 as string or integer, arg2 as IFace) as integer) as integer
return func("hello", {name: "hello"})
end function
`, ParseMode.BrighterScript);
expectZeroDiagnostics(diagnostics);
});

it('can be used as return types', () => {
let { diagnostics } = parse(`
function test() as function() as integer
return function() as integer
return 123
end function
end function
`, ParseMode.BrighterScript);
expectZeroDiagnostics(diagnostics);
});

it('can have a union as return type', () => {
let { diagnostics } = parse(`
type foo = function() as integer or string
function test() as foo
return function() as integer
return 123
end function
end function
`, ParseMode.BrighterScript);
expectZeroDiagnostics(diagnostics);
});
});
});

function parse(text: string, mode?: ParseMode) {
Expand Down
29 changes: 28 additions & 1 deletion src/parser/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2813,7 +2813,9 @@ export class Parser {
while (lookForCompounds) {
lookForCompounds = false;

if (this.checkAny(...DeclarableTypes)) {
const isTypedFunction = this.checkAny(TokenKind.Function, TokenKind.Sub) && this.checkNext(TokenKind.LeftParen);

if (this.checkAny(...DeclarableTypes) && !isTypedFunction) {
// Token is a built in type
typeToken = this.advance();
} else if (this.options.mode === ParseMode.BrighterScript) {
Expand All @@ -2824,6 +2826,9 @@ export class Parser {
} else if (this.check(TokenKind.LeftParen)) {
// could be an inline interface
typeToken = this.groupedTypeExpression();
} else if (isTypedFunction) {
//typed function type
typeToken = this.typedFunctionType();
} else {
// see if we can get a namespaced identifer
const qualifiedType = this.getNamespacedVariableNameExpression(ignoreDiagnostics);
Expand Down Expand Up @@ -2941,6 +2946,28 @@ export class Parser {
return createToken(TokenKind.Dynamic, null, util.createBoundingRange(leftParen, typeToken, rightParen));
}

private typedFunctionType() {
const funcOrSub = this.advance();
const leftParen = this.advance();

let params = [] as FunctionParameterExpression[];
if (!this.check(TokenKind.RightParen)) {
do {
params.push(this.functionParameter());
} while (this.match(TokenKind.Comma));
}
const rightParen = this.advance();
let asToken: Token;
let returnType: Token;
if ((this.check(TokenKind.As))) {
// this is a function type with a return type, e.g. `function(string) as void`
asToken = this.advance();
returnType = this.typeToken();
}

return createToken(TokenKind.Function, null, util.createBoundingRange(funcOrSub, leftParen, rightParen, asToken, returnType));
}

private primary(): Expression {
switch (true) {
case this.matchAny(
Expand Down