Skip to content
Merged
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A superset of Roku's BrightScript language. Compiles to standard BrightScript.

## Overview

**WARNING: this is the v0.66.0 branch with experimental features**
**WARNING: this is the `v1` branch with experimental features**

The BrighterScript language provides new features and syntax enhancements to Roku's BrightScript language. Because the language is a superset of BrightScript, the parser and associated tools (VSCode integration, cli, etc...) work with standard BrightScript (.brs) files. This means you will get benefits (as described in the following section) from using the BrighterScript compiler, whether your project contains BrighterScript (.bs) files or not. The BrighterScript language transpiles to standard BrightScript, so your code is fully compatible with all roku devices.

Expand Down
9 changes: 9 additions & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ function getY(translation as float[]) as float
end function
```

## [Type Statements](type-statements.md)
```brighterscript
type number = integer or float or double

function sum(x as number, y as number) as number
return x + y
end function
```

## [Union Types](union-types.md)
```brighterscript
sub logData(data as string or number)
Expand Down
71 changes: 71 additions & 0 deletions docs/type-statements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Type Statements

BrighterScript supports declaring custom named types using the `type` keyword. In this way, new types can be created as wrappers, or aliases for other types. This is useful to create custom names for advanced types like unions, so they can be more easily used throughout the code.

## Syntax

To create a new custom named types, use the syntax `type <customName> = <existingType>` at either the top level of a file, or within a namespace.

For example, if you wanted to create a shorthand for the union type `integer or float or double`, you could do the following:

```BrighterScript
type number = integer or float or double

function sum(x as number, y as number) as number
return x + y
end function

function product(x as number, y as number) as number
return x * y
end function
```

<details>
<summary>View the transpiled BrightScript code</summary>

```BrightScript
function sum(x as dynamic, y as dynamic) as dynamic
return x + y
end function

function product(x as dynamic, y as dynamic) as dynamic
return x * y
end function
```

</details>

## Importing

Types created with the `type` keyword are available through importing the file they are declared in.

For example:

**src/components/Widget.bs**

```BrighterScript
namespace Widgets

type widget = Button or Checkbox

interface Button
text as string
action as function
end interface

interface Checkbox
text as string
checked as boolean
end interface
end namespace
```

**src/components/UI.bs**

```BrighterScript
import "pkg:/components/Widget.bs" ' provides the "Widgets.widget" type

function getName(w as Widgets.widget) as string
return w.text
end function
```
16 changes: 14 additions & 2 deletions src/AstValidationSegmenter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DottedGetExpression, TypeExpression, VariableExpression } from './parser/Expression';
import { isAliasStatement, isArrayType, isBinaryExpression, isBlock, isBody, isClassStatement, isConditionalCompileStatement, isDottedGetExpression, isInterfaceStatement, isNamespaceStatement, isTypecastStatement, isTypeExpression, isVariableExpression } from './astUtils/reflection';
import { isAliasStatement, isArrayType, isBinaryExpression, isBlock, isBody, isClassStatement, isConditionalCompileStatement, isDottedGetExpression, isInterfaceStatement, isNamespaceStatement, isTypecastStatement, isTypeExpression, isTypeStatement, isVariableExpression } from './astUtils/reflection';
import { ChildrenSkipper, WalkMode, createVisitor } from './astUtils/visitors';
import type { ExtraSymbolData, GetTypeOptions, TypeChainEntry } from './interfaces';
import type { AstNode, Expression } from './parser/AstNode';
Expand Down Expand Up @@ -115,6 +115,13 @@ export class AstValidationSegmenter {
extraData.definingNode.value.getType({ ...options, flags: SymbolTypeFlag.runtime | SymbolTypeFlag.typetime, typeChain: aliasTypeChain });
typeChain = [...aliasTypeChain, ...typeChain.slice(1)];
}
if (extraData.isFromTypeStatement && isTypeStatement(extraData.definingNode)) {
//set the unwrapped version of this symbol as required.
const wrappedTypeChain = [];
// eslint-disable-next-line no-bitwise
extraData.definingNode.getType({ ...options, flags: SymbolTypeFlag.typetime, typeChain: wrappedTypeChain });
typeChain = [...wrappedTypeChain, ...typeChain.slice(1)];
}
const possibleNamespace = this.currentNamespaceStatement?.getNameParts()?.map(t => t.text)?.join('.').toLowerCase() ?? '';
const fullChainName = util.processTypeChain(typeChain).fullChainName?.toLowerCase();
const possibleNamesLower = [] as string[];
Expand Down Expand Up @@ -180,7 +187,6 @@ export class AstValidationSegmenter {
return;
}


this.segmentsForValidation.push(segment);
this.validatedSegments.set(segment, false);
let foundUnresolvedInSegment = false;
Expand All @@ -199,6 +205,12 @@ export class AstValidationSegmenter {
unresolvedTypeCastTypeExpression = this.unresolvedTypeCastTypeExpressions[this.unresolvedTypeCastTypeExpressions.length - 1];
}

if (isTypeStatement(segment)) {
// this is a straight assignment,
assignedSymbols.add({ token: segment.tokens.name, node: segment });
assignedSymbolsNames.add(segment.tokens.name.text.toLowerCase());
}

segment.walk(createVisitor({
AssignmentStatement: (stmt) => {
if (stmt.tokens.equals.kind === TokenKind.Equal) {
Expand Down
19 changes: 19 additions & 0 deletions src/CrossScopeValidator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1635,5 +1635,24 @@ describe('CrossScopeValidator', () => {
DiagnosticMessages.nameCollision('Interface', 'Const', 'alpha.beta.Test')
]);
});

it('finds duplicates when using type statements', () => {
program.setFile('source/utils.bs', `
namespace alpha
type myInt = integer


interface myInt
value as string
end interface
end namespace
`);

program.validate();
expectDiagnostics(program, [
DiagnosticMessages.nameCollision('Type', 'Interface', 'alpha.myInt'),
DiagnosticMessages.nameCollision('Interface', 'Type', 'alpha.myInt')
]);
});
});
});
86 changes: 86 additions & 0 deletions src/Scope.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3910,6 +3910,92 @@ describe('Scope', () => {

});

describe('type Statements', () => {
it('allows type statement types of primitives in function params', () => {
program.setFile<BrsFile>('source/wrapped.bs', `
type myString = string
sub useTypeStatementType(data as myString)
print data.len()
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

it('allows type statement types of primitives in variable declarations', () => {
program.setFile<BrsFile>('source/wrapped.bs', `
type myInteger = integer
sub useTypeStatementType()
value as myInteger = 123
print value.toStr()
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

it('allows type statement types of unions in function params', () => {
program.setFile<BrsFile>('source/wrapped.bs', `
type StringOrInteger = string or integer
sub useTypeStatementType(data as StringOrInteger)
print data
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

it('allows type statement types wrapping custom types in function params', () => {
program.setFile<BrsFile>('source/wrapped.bs', `
interface WithName1
name as string
end interface

interface WithName2
name as string
end interface

type HasName = WithName1 or WithName2

sub useTypeStatementType(data as HasName)
print data.name
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

it('provides a type statement type when importing a file', () => {
program.setFile<BrsFile>('source/wrapped.bs', `
interface WithName1
name as string
end interface

interface WithName2
name as string
end interface

type HasName = WithName1 or WithName2
`);

program.setFile<BrsFile>('components/useWrapped.xml', `
<component name="useWrapped" extends="Group">
<script uri="useWrapped.bs"/>
</component>
`);

program.setFile<BrsFile>('components/useWrapped.bs', `
import "pkg:/source/wrapped.bs"
sub useTypeStatementType(data as HasName)
print data.name
end sub
`);
program.validate();
expectZeroDiagnostics(program);
});

});

it('classes in namespaces that reference themselves without namespace work', () => {
program.setFile<BrsFile>('source/class.bs', `
namespace Alpha
Expand Down
5 changes: 3 additions & 2 deletions src/SymbolTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export class SymbolTable implements SymbolTypeGetter {
resolvedType = SymbolTable.referenceTypeFactory(name, options.fullName, options.flags, options.tableProvider);
}
const resolvedTypeIsReference = isAnyReferenceType(resolvedType);
const newNonReferenceType = originalIsReferenceType && !isAnyReferenceType(resolvedType);
const newNonReferenceType = originalIsReferenceType && !isAnyReferenceType(resolvedType) && resolvedType;
doSetCache = doSetCache && (options.onlyCacheResolvedTypes ? !resolvedTypeIsReference : true);
if (doSetCache || newNonReferenceType) {
this.setCachedType(name, { type: resolvedType, data: data, flags: foundFlags }, options);
Expand All @@ -415,6 +415,7 @@ export class SymbolTable implements SymbolTypeGetter {
options.data.isFromDocComment = data?.isFromDocComment;
options.data.isBuiltIn = data?.isBuiltIn;
options.data.isFromCallFunc = data?.isFromCallFunc;
options.data.isFromTypeStatement = data?.isFromTypeStatement;
}
return resolvedType;
}
Expand Down Expand Up @@ -562,7 +563,7 @@ export class SymbolTable implements SymbolTypeGetter {
}

setCachedType(name: string, cacheEntry: TypeCacheEntry, options: GetTypeOptions) {
if (!cacheEntry) {
if (!cacheEntry || !cacheEntry.type) {
return;
}
if (SymbolTable.cacheVerifier) {
Expand Down
Loading