Skip to content

Commit

Permalink
Allow interfaces and enums as function param types (#580)
Browse files Browse the repository at this point in the history
  • Loading branch information
TwitchBronBron authored May 2, 2022
1 parent 3bf7bd0 commit 65422b2
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 2 deletions.
101 changes: 99 additions & 2 deletions src/Scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DiagnosticMessages } from './DiagnosticMessages';
import type { CallableContainer, BsDiagnostic, FileReference, BscFile, CallableContainerMap, FileLink } from './interfaces';
import type { Program } from './Program';
import { BsClassValidator } from './validators/ClassValidator';
import type { NamespaceStatement, Statement, FunctionStatement, ClassStatement, EnumStatement } from './parser/Statement';
import type { NamespaceStatement, Statement, FunctionStatement, ClassStatement, EnumStatement, InterfaceStatement } from './parser/Statement';
import type { NewExpression } from './parser/Expression';
import { ParseMode } from './parser/Parser';
import { standardizePath as s, util } from './util';
Expand Down Expand Up @@ -63,6 +63,24 @@ export class Scope {
return this.getClassFileLink(className, containingNamespace)?.item;
}

/**
* Get the interface with the specified name.
* @param ifaceName - The interface name, including the namespace of the interface if possible
* @param containingNamespace - The namespace used to resolve relative interface names. (i.e. the namespace around the current statement trying to find a interface)
*/
public getInterface(ifaceName: string, containingNamespace?: string): InterfaceStatement {
return this.getInterfaceFileLink(ifaceName, containingNamespace)?.item;
}

/**
* Get the enum with the specified name.
* @param enumName - The enum name, including the namespace if possible
* @param containingNamespace - The namespace used to resolve relative enum names. (i.e. the namespace around the current statement trying to find an enum)
*/
public getEnum(enumName: string, containingNamespace?: string): EnumStatement {
return this.getEnumFileLink(enumName, containingNamespace)?.item;
}

/**
* Get a class and its containing file by the class name
* @param className - The class name, including the namespace of the class if possible
Expand All @@ -82,6 +100,45 @@ export class Scope {
return cls;
}


/**
* Get an interface and its containing file by the interface name
* @param ifaceName - The interface name, including the namespace of the interface if possible
* @param containingNamespace - The namespace used to resolve relative interface names. (i.e. the namespace around the current statement trying to find a interface)
*/
public getInterfaceFileLink(ifaceName: string, containingNamespace?: string): FileLink<InterfaceStatement> {
const lowerName = ifaceName?.toLowerCase();
const ifaceMap = this.getInterfaceMap();

let iface = ifaceMap.get(
util.getFullyQualifiedClassName(lowerName, containingNamespace?.toLowerCase())
);
//if we couldn't find the iface by its full namespaced name, look for a global class with that name
if (!iface) {
iface = ifaceMap.get(lowerName);
}
return iface;
}

/**
* Get an Enum and its containing file by the Enum name
* @param enumName - The Enum name, including the namespace of the enum if possible
* @param containingNamespace - The namespace used to resolve relative enum names. (i.e. the namespace around the current statement trying to find a enum)
*/
public getEnumFileLink(enumName: string, containingNamespace?: string): FileLink<EnumStatement> {
const lowerName = enumName?.toLowerCase();
const enumMap = this.getEnumMap();

let enumeration = enumMap.get(
util.getFullyQualifiedClassName(lowerName, containingNamespace?.toLowerCase())
);
//if we couldn't find the enum by its full namespaced name, look for a global enum with that name
if (!enumeration) {
enumeration = enumMap.get(lowerName);
}
return enumeration;
}

/**
* Tests if a class exists with the specified name
* @param className - the all-lower-case namespace-included class name
Expand All @@ -91,6 +148,24 @@ export class Scope {
return !!this.getClass(className, namespaceName);
}

/**
* Tests if an interface exists with the specified name
* @param ifaceName - the all-lower-case namespace-included interface name
* @param namespaceName - the current namespace name
*/
public hasInterface(ifaceName: string, namespaceName?: string): boolean {
return !!this.getInterface(ifaceName, namespaceName);
}

/**
* Tests if an enum exists with the specified name
* @param enumName - the all-lower-case namespace-included enum name
* @param namespaceName - the current namespace name
*/
public hasEnum(enumName: string, namespaceName?: string): boolean {
return !!this.getEnum(enumName, namespaceName);
}

/**
* A dictionary of all classes in this scope. This includes namespaced classes always with their full name.
* The key is stored in lower case
Expand All @@ -113,6 +188,28 @@ export class Scope {
});
}

/**
* A dictionary of all Interfaces in this scope. This includes namespaced Interfaces always with their full name.
* The key is stored in lower case
*/
public getInterfaceMap(): Map<string, FileLink<InterfaceStatement>> {
return this.cache.getOrAdd('interfaceMap', () => {
const map = new Map<string, FileLink<InterfaceStatement>>();
this.enumerateBrsFiles((file) => {
if (isBrsFile(file)) {
for (let iface of file.parser.references.interfaceStatements) {
const lowerIfaceName = iface.getName(ParseMode.BrighterScript)?.toLowerCase();
//only track classes with a defined name (i.e. exclude nameless malformed classes)
if (lowerIfaceName) {
map.set(lowerIfaceName, { item: iface, file: file });
}
}
}
});
return map;
});
}

/**
* A dictionary of all enums in this scope. This includes namespaced enums always with their full name.
* The key is stored in lower case
Expand Down Expand Up @@ -603,7 +700,7 @@ export class Scope {
if (isCustomType(param.type) && param.typeToken) {
const paramTypeName = param.type.name;
const currentNamespaceName = func.namespaceName?.getName(ParseMode.BrighterScript);
if (!this.hasClass(paramTypeName, currentNamespaceName)) {
if (!this.hasClass(paramTypeName, currentNamespaceName) && !this.hasInterface(paramTypeName) && !this.hasEnum(paramTypeName)) {
this.diagnostics.push({
...DiagnosticMessages.functionParameterTypeIsInvalid(param.name.text, paramTypeName),
range: param.typeToken.range,
Expand Down
40 changes: 40 additions & 0 deletions src/files/BrsFile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,46 @@ describe('BrsFile', () => {
});

describe('parse', () => {
it('allows class as parameter type', () => {
program.setFile(`source/main.bs`, `
class Person
name as string
end class
sub PrintPerson(p as Person)
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

it('allows interface as parameter type', () => {
program.setFile(`source/main.bs`, `
interface Person
name as string
end interface
sub PrintPerson(p as Person)
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

it('allows enum as parameter type', () => {
program.setFile(`source/main.bs`, `
enum Direction
up
down
end enum
sub PrintDirection(d as Direction)
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

it('supports iife in assignment', () => {
program.setFile('source/main.brs', `
sub main()
Expand Down
13 changes: 13 additions & 0 deletions src/parser/Statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,19 @@ export class InterfaceStatement extends Statement implements TypedefProvider {
return this.tokens.name?.text;
}

/**
* Get the name of this expression based on the parse mode
*/
public getName(parseMode: ParseMode) {
if (this.namespaceName) {
let delimiter = parseMode === ParseMode.BrighterScript ? '.' : '_';
let namespaceName = this.namespaceName.getName(parseMode);
return namespaceName + delimiter + this.name;
} else {
return this.name;
}
}

public transpile(state: BrsTranspileState): TranspileResult {
//interfaces should completely disappear at runtime
return [];
Expand Down

0 comments on commit 65422b2

Please sign in to comment.