Skip to content

Commit

Permalink
Merge 1d45ee1 into f38faa0
Browse files Browse the repository at this point in the history
  • Loading branch information
markwpearce committed May 4, 2024
2 parents f38faa0 + 1d45ee1 commit 002ecff
Show file tree
Hide file tree
Showing 17 changed files with 741 additions and 43 deletions.
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, thus providing a different name to use.

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()
return {
data: "abc"
}
end sub
```
9 changes: 7 additions & 2 deletions src/DiagnosticMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export let DiagnosticMessages = {
code: 1023,
severity: DiagnosticSeverity.Error
}),
importStatementMustBeDeclaredAtTopOfFile: () => ({
__unused6: () => ({
message: `'import' statement must be declared at the top of the file`,
code: 1024,
severity: DiagnosticSeverity.Error
Expand Down Expand Up @@ -311,7 +311,7 @@ export let DiagnosticMessages = {
code: 1056,
severity: DiagnosticSeverity.Error
}),
libraryStatementMustBeDeclaredAtTopOfFile: () => ({
__unused5: () => ({
message: `'library' statement must be declared at the top of the file`,
code: 1057,
severity: DiagnosticSeverity.Error
Expand Down 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
}),
statementMustBeDeclaredAtTopOfFile: (statementKeyword: string) => ({
message: `'${statementKeyword}' 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

0 comments on commit 002ecff

Please sign in to comment.