Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Alias Statement #1151

Merged
merged 14 commits into from
May 6, 2024
40 changes: 40 additions & 0 deletions docs/variable-shadowing.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,43 @@ namespace alpha
end sub
end namespace
```

## Aliasing Shadowed Names

If there is a need to reference a shadowed name, you can `alias` the name at the beginning of the file, theus providing a different name to use.
markwpearce marked this conversation as resolved.
Show resolved Hide resolved

For example, in the following, the namespace `get` is shadowed by the function `http.get`.
To work around that, an alias is used to create a new name that can be used to reference the `get` namespace.

```BrighterScript
alias get2 = get

namespace http
'Do an HTTP request
sub get()
print get2.aa().data 'using `get2` aliased symbol here. it's now clear which item we intended to use
end sub
end namespace

namespace get
function aa()
return {
data: "abc"
}
end function
end namespace
```

transpiles to

```BrightScript
sub http_get()
print get_aa().data
end sub

sub get_aa()
retrun {
data: "abc"
}
end sub
```
5 changes: 5 additions & 0 deletions src/DiagnosticMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,11 @@ export let DiagnosticMessages = {
message: `'typecast' statement can only be applied to 'm', but was applied to '${foundApplication}'`,
code: 1148,
severity: DiagnosticSeverity.Error
}),
aliasStatementMustBeDeclaredAtTopOfFile: () => ({
markwpearce marked this conversation as resolved.
Show resolved Hide resolved
message: `'alias' statement must be declared at the top of the file`,
code: 1149,
severity: DiagnosticSeverity.Error
})
};
export const defaultMaximumTruncationLength = 160;
Expand Down
1 change: 1 addition & 0 deletions src/SymbolTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export class SymbolTable implements SymbolTypeGetter {
options.data.flags = foundFlags ?? options.flags;
options.data.memberOfAncestor = data?.memberOfAncestor;
options.data.doNotMerge = data?.doNotMerge;
options.data.isAlias = data?.isAlias;
}
return resolvedType;
}
Expand Down
11 changes: 10 additions & 1 deletion src/astUtils/CachedLookups.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AALiteralExpression, CallExpression, CallfuncExpression, DottedGetExpression, FunctionExpression, VariableExpression } from '../parser/Expression';
import type { AssignmentStatement, ClassStatement, ConstStatement, EnumStatement, FunctionStatement, ImportStatement, InterfaceStatement, LibraryStatement, NamespaceStatement, TypecastStatement } from '../parser/Statement';
import type { AliasStatement, AssignmentStatement, ClassStatement, ConstStatement, EnumStatement, FunctionStatement, ImportStatement, InterfaceStatement, LibraryStatement, NamespaceStatement, TypecastStatement } from '../parser/Statement';
import { Cache } from '../Cache';
import { WalkMode, createVisitor } from './visitors';
import type { Expression } from '../parser/AstNode';
Expand Down Expand Up @@ -57,6 +57,10 @@ export class CachedLookups {
return this.getFromCache<Array<TypecastStatement>>('typecastStatements');
}

get aliasStatements(): AliasStatement[] {
return this.getFromCache<Array<AliasStatement>>('aliasStatements');
}

/**
* A collection of full expressions. This excludes intermediary expressions.
*
Expand Down Expand Up @@ -165,6 +169,7 @@ export class CachedLookups {
const libraryStatements: LibraryStatement[] = [];
const importStatements: ImportStatement[] = [];
const typecastStatements: TypecastStatement[] = [];
const aliasStatements: AliasStatement[] = [];
const functionStatements: FunctionStatement[] = [];
const functionExpressions: FunctionExpression[] = [];

Expand Down Expand Up @@ -263,6 +268,9 @@ export class CachedLookups {
LibraryStatement: s => {
libraryStatements.push(s);
},
AliasStatement: s => {
aliasStatements.push(s);
},
FunctionExpression: (expression, parent) => {
if (!isMethodStatement(parent)) {
functionExpressions.push(expression);
Expand Down Expand Up @@ -341,6 +349,7 @@ export class CachedLookups {
this.cache.set('libraryStatements', libraryStatements);
this.cache.set('importStatements', importStatements);
this.cache.set('typecastStatements', typecastStatements);
this.cache.set('aliasStatements', aliasStatements);
this.cache.set('functionStatements', functionStatements);
this.cache.set('functionExpressions', functionExpressions);
this.cache.set('propertyHints', propertyHints);
Expand Down
5 changes: 4 additions & 1 deletion src/astUtils/reflection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Body, AssignmentStatement, Block, ExpressionStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, InterfaceFieldStatement, InterfaceMethodStatement, InterfaceStatement, EnumStatement, EnumMemberStatement, TryCatchStatement, CatchStatement, ThrowStatement, MethodStatement, FieldStatement, ConstStatement, ContinueStatement, TypecastStatement } from '../parser/Statement';
import type { Body, AssignmentStatement, Block, ExpressionStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, InterfaceFieldStatement, InterfaceMethodStatement, InterfaceStatement, EnumStatement, EnumMemberStatement, TryCatchStatement, CatchStatement, ThrowStatement, MethodStatement, FieldStatement, ConstStatement, ContinueStatement, TypecastStatement, AliasStatement } from '../parser/Statement';
import type { LiteralExpression, BinaryExpression, CallExpression, FunctionExpression, DottedGetExpression, XmlAttributeGetExpression, IndexedGetExpression, GroupingExpression, EscapedCharCodeLiteralExpression, ArrayLiteralExpression, AALiteralExpression, UnaryExpression, VariableExpression, SourceLiteralExpression, NewExpression, CallfuncExpression, TemplateStringQuasiExpression, TemplateStringExpression, TaggedTemplateStringExpression, AnnotationExpression, FunctionParameterExpression, AAMemberExpression, TypecastExpression, TypeExpression, TypedArrayExpression } from '../parser/Expression';
import type { BrsFile } from '../files/BrsFile';
import type { XmlFile } from '../files/XmlFile';
Expand Down Expand Up @@ -186,6 +186,9 @@ export function isThrowStatement(element: AstNode | undefined): element is Throw
export function isTypecastStatement(element: AstNode | undefined): element is TypecastStatement {
return element?.kind === AstNodeKind.TypecastStatement;
}
export function isAliasStatement(element: AstNode | undefined): element is AliasStatement {
return element?.kind === AstNodeKind.AliasStatement;
}

// Expressions reflection
/**
Expand Down
3 changes: 2 additions & 1 deletion src/astUtils/visitors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-bitwise */
import type { CancellationToken } from 'vscode-languageserver';
import type { Body, AssignmentStatement, Block, ExpressionStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, EnumStatement, EnumMemberStatement, DimStatement, TryCatchStatement, CatchStatement, ThrowStatement, InterfaceStatement, InterfaceFieldStatement, InterfaceMethodStatement, FieldStatement, MethodStatement, ConstStatement, ContinueStatement, TypecastStatement } from '../parser/Statement';
import type { Body, AssignmentStatement, Block, ExpressionStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, EnumStatement, EnumMemberStatement, DimStatement, TryCatchStatement, CatchStatement, ThrowStatement, InterfaceStatement, InterfaceFieldStatement, InterfaceMethodStatement, FieldStatement, MethodStatement, ConstStatement, ContinueStatement, TypecastStatement, AliasStatement } from '../parser/Statement';
import type { AALiteralExpression, AAMemberExpression, AnnotationExpression, ArrayLiteralExpression, BinaryExpression, CallExpression, CallfuncExpression, DottedGetExpression, EscapedCharCodeLiteralExpression, FunctionExpression, FunctionParameterExpression, GroupingExpression, IndexedGetExpression, LiteralExpression, NewExpression, NullCoalescingExpression, RegexLiteralExpression, SourceLiteralExpression, TaggedTemplateStringExpression, TemplateStringExpression, TemplateStringQuasiExpression, TernaryExpression, TypecastExpression, TypeExpression, UnaryExpression, VariableExpression, XmlAttributeGetExpression } from '../parser/Expression';
import { isExpression, isStatement } from './reflection';
import type { Editor } from './Editor';
Expand Down Expand Up @@ -141,6 +141,7 @@ export function createVisitor(
EnumStatement?: (statement: EnumStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
EnumMemberStatement?: (statement: EnumMemberStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
ConstStatement?: (statement: ConstStatement, parent?: Statement, owner?: any, key?: any) => Statement | void;
AliasStatement?: (expression: AliasStatement, parent?: AstNode, owner?: any, key?: any) => Statement | void;
//expressions
BinaryExpression?: (expression: BinaryExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
CallExpression?: (expression: CallExpression, parent?: AstNode, owner?: any, key?: any) => Expression | void;
Expand Down
116 changes: 104 additions & 12 deletions src/bscPlugin/transpile/BrsFileTranspileProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { createToken } from '../../astUtils/creators';
import type { Editor } from '../../astUtils/Editor';
import { isDottedGetExpression, isLiteralExpression, isVariableExpression, isUnaryExpression } from '../../astUtils/reflection';
import { isDottedGetExpression, isLiteralExpression, isVariableExpression, isUnaryExpression, isAliasStatement, isCallExpression, isCallfuncExpression, isEnumType } from '../../astUtils/reflection';
import { createVisitor, WalkMode } from '../../astUtils/visitors';
import type { BrsFile } from '../../files/BrsFile';
import type { OnPrepareFileEvent } from '../../interfaces';
import type { ExtraSymbolData, OnPrepareFileEvent } from '../../interfaces';
import { TokenKind } from '../../lexer/TokenKind';
import type { Expression } from '../../parser/AstNode';
import { LiteralExpression } from '../../parser/Expression';
import { LiteralExpression, VariableExpression } from '../../parser/Expression';
import { ParseMode } from '../../parser/Parser';
import type { AliasStatement } from '../../parser/Statement';
import type { Scope } from '../../Scope';
import { SymbolTypeFlag } from '../../SymbolTypeFlag';
import util from '../../util';
import { BslibManager } from '../serialize/BslibManager';

Expand Down Expand Up @@ -88,38 +90,93 @@ export class BrsFilePreTranspileProcessor {
}
}

/**
* Given a string optionally separated by dots, find an namespace Member related to it.
*/
private getNamespaceInfo(name: string, scope: Scope) {
//look for the namespace directly
let result = scope?.getNamespace(name);

if (result) {
return {
namespace: result
};
}
//assume we've been given the namespace.member syntax, so pop the member and try again
const parts = name.toLowerCase().split('.');
const memberName = parts.pop();

result = scope?.getNamespace(parts.join('.'));
if (result) {
const memberType = result.symbolTable?.getSymbolType(memberName, { flags: SymbolTypeFlag.runtime });
if (memberType && !isEnumType(memberType)) {
return {
namespace: result,
value: new VariableExpression({
name: createToken(TokenKind.Identifier, parts.join('_') + '_' + memberName)
})
};
}
}
}

private processExpression(expression: Expression, scope: Scope | undefined) {
if (expression.findAncestor(isAliasStatement)) {
// skip any changes in an Alias Statement
return;
}

let containingNamespace = this.event.file.getNamespaceStatementForPosition(expression.range.start)?.getName(ParseMode.BrighterScript);

const parts = util.splitExpression(expression);
const processedNames: string[] = [];
let isAlias = false;
let isCall = isCallExpression(expression) || isCallfuncExpression(expression);
for (let part of parts) {
let entityName: string;
if (isVariableExpression(part) || isDottedGetExpression(part)) {
processedNames.push(part?.tokens.name?.text?.toLocaleLowerCase());

let firstPart = part === parts[0];
let actualNameExpression = firstPart ? this.replaceAlias(part) : part;
let currentPartIsAlias = actualNameExpression !== part;
isAlias = isAlias || currentPartIsAlias;

if (currentPartIsAlias) {
entityName = util.getAllDottedGetPartsAsString(actualNameExpression);
processedNames.push(entityName);
containingNamespace = '';
} else if (isVariableExpression(part) || isDottedGetExpression(part)) {
processedNames.push(part?.tokens.name?.text?.toLowerCase());
entityName = processedNames.join('.');
} else {
return;
}

let value: Expression;

//did we find a const? transpile the value
let constStatement = scope?.getConstFileLink(entityName, containingNamespace)?.item;
let enumInfo = this.getEnumInfo(entityName, containingNamespace, scope);
let namespaceInfo = isAlias && this.getNamespaceInfo(entityName, scope);
if (constStatement) {
//did we find a const? transpile the value
value = constStatement.value;
} else {
} else if (enumInfo?.value) {
//did we find an enum member? transpile that
let enumInfo = this.getEnumInfo(entityName, containingNamespace, scope);
if (enumInfo?.value) {
value = enumInfo.value;
}
value = enumInfo.value;

} else if (namespaceInfo?.value) {
// use the transpiled namespace member
value = namespaceInfo.value;

} else if (currentPartIsAlias && !(enumInfo || namespaceInfo)) {
// this was an aliased expression that is NOT am enum or namespace
value = actualNameExpression;
}

if (value) {
//override the transpile for this item.
this.event.editor.setProperty(part, 'transpile', (state) => {
if (isLiteralExpression(value)) {

if (isLiteralExpression(value) || isCall) {
return value.transpile(state);
} else {
//wrap non-literals with parens to prevent on-device compile errors
Expand All @@ -131,4 +188,39 @@ export class BrsFilePreTranspileProcessor {
}
}
}


private replaceAlias(expression: Expression) {
let alias: AliasStatement;
let potentiallyAliased = expression;
// eslint-disable-next-line @typescript-eslint/dot-notation
const fileAliasStatements = this.event.file['_cachedLookups'].aliasStatements;

if (fileAliasStatements.length === 0) {
return expression;
}
// eslint-disable-next-line @typescript-eslint/dot-notation
let potentialAliasedTextsLower = fileAliasStatements.map(stmt => stmt.tokens.name.text.toLowerCase());

if (isVariableExpression(potentiallyAliased) && potentialAliasedTextsLower.includes(potentiallyAliased.getName().toLowerCase())) {
//check if it is an alias
let data = {} as ExtraSymbolData;

potentiallyAliased.getSymbolTable().getSymbolType(potentiallyAliased.getName(), {
data: data,
// eslint-disable-next-line no-bitwise
flags: SymbolTypeFlag.runtime | SymbolTypeFlag.typetime
});

if (data.isAlias && isAliasStatement(data.definingNode)) {
alias = data.definingNode;

}
}

if (alias && isVariableExpression(potentiallyAliased)) {
return alias.value;
}
return expression;
}
}
Loading