X Tutup
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
44 changes: 30 additions & 14 deletions src/transformation/utils/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,12 @@ export function getSymbolExportScope(
return scope;
}

export function isSymbolExported(context: TransformationContext, symbol: ts.Symbol): boolean {
return (
getExportedSymbolDeclaration(symbol) !== undefined ||
// Symbol may have been exported separately (e.g. 'const foo = "bar"; export { foo }')
isSymbolExportedFromScope(context, symbol, context.sourceFile)
);
}

export function isSymbolExportedFromScope(
export function getExportedSymbolsFromScope(
context: TransformationContext,
symbol: ts.Symbol,
scope: ts.SourceFile | ts.ModuleDeclaration
): boolean {
): ts.Symbol[] {
if (ts.isSourceFile(scope) && !isFileModule(scope)) {
return false;
return [];
}

let scopeSymbol = context.checker.getSymbolAtLocation(scope);
Expand All @@ -95,12 +86,37 @@ export function isSymbolExportedFromScope(
}

if (scopeSymbol === undefined || scopeSymbol.exports === undefined) {
return false;
return [];
}

// ts.Iterator is not a ES6-compatible iterator, because TypeScript targets ES5
const it: Iterable<ts.Symbol> = { [Symbol.iterator]: () => scopeSymbol!.exports!.values() };
return [...it].includes(symbol);
return [...it];
}

export function getDependenciesOfSymbol(context: TransformationContext, originalSymbol: ts.Symbol): ts.Symbol[] {
return getExportedSymbolsFromScope(context, context.sourceFile).filter(exportSymbol =>
exportSymbol.declarations
.filter(ts.isExportSpecifier)
.map(context.checker.getExportSpecifierLocalTargetSymbol)
.includes(originalSymbol)
);
}

export function isSymbolExported(context: TransformationContext, symbol: ts.Symbol): boolean {
return (
getExportedSymbolDeclaration(symbol) !== undefined ||
// Symbol may have been exported separately (e.g. 'const foo = "bar"; export { foo }')
isSymbolExportedFromScope(context, symbol, context.sourceFile)
);
}

export function isSymbolExportedFromScope(
context: TransformationContext,
symbol: ts.Symbol,
scope: ts.SourceFile | ts.ModuleDeclaration
): boolean {
return getExportedSymbolsFromScope(context, scope).includes(symbol);
}

export function addExportToIdentifier(
Expand Down
88 changes: 66 additions & 22 deletions src/transformation/visitors/binary-expression/assignments.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import * as ts from "typescript";
import * as lua from "../../../LuaAST";
import { cast, castEach } from "../../../utils";
import { cast } from "../../../utils";
import { TransformationContext } from "../../context";
import { isTupleReturnCall } from "../../utils/annotations";
import { validateAssignment, validatePropertyAssignment } from "../../utils/assignment-validation";
import { createExportedIdentifier, getDependenciesOfSymbol, isSymbolExported } from "../../utils/export";
import { createImmediatelyInvokedFunctionExpression, createUnpackCall, wrapInTable } from "../../utils/lua-ast";
import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
import { isArrayType, isDestructuringAssignment } from "../../utils/typescript";
import { transformElementAccessArgument } from "../access";
import { isArrayLength, transformDestructuringAssignment } from "./destructuring-assignments";

export function transformAssignmentLeftHandSideExpression(
context: TransformationContext,
node: ts.Expression
): lua.AssignmentLeftHandSideExpression {
const symbol = context.checker.getSymbolAtLocation(node);
const left = context.transformExpression(node);

return lua.isIdentifier(left) && symbol && isSymbolExported(context, symbol)
? createExportedIdentifier(context, left)
: cast(left, lua.isAssignmentLeftHandSideExpression);
}

export function transformAssignment(
context: TransformationContext,
// TODO: Change type to ts.LeftHandSideExpression?
lhs: ts.Expression,
right: lua.Expression,
parent?: ts.Expression
): lua.Statement {
): lua.Statement[] {
if (isArrayLength(context, lhs)) {
return lua.createExpressionStatement(
const arrayLengthAssignment = lua.createExpressionStatement(
transformLuaLibFunction(
context,
LuaLibFeature.ArraySetLength,
Expand All @@ -27,13 +40,28 @@ export function transformAssignment(
right
)
);

return [arrayLengthAssignment];
}

return lua.createAssignmentStatement(
cast(context.transformExpression(lhs), lua.isAssignmentLeftHandSideExpression),
right,
lhs.parent
);
const symbol = ts.isShorthandPropertyAssignment(lhs.parent)
? context.checker.getShorthandAssignmentValueSymbol(lhs.parent)
: context.checker.getSymbolAtLocation(lhs);

const dependentSymbols = symbol ? getDependenciesOfSymbol(context, symbol) : [];

const left = transformAssignmentLeftHandSideExpression(context, lhs);

const rootAssignment = lua.createAssignmentStatement(left, right, lhs.parent);

return [
rootAssignment,
...dependentSymbols.map(symbol => {
const [left] = rootAssignment.left;
const identifierToAssign = createExportedIdentifier(context, lua.createIdentifier(symbol.name));
return lua.createAssignmentStatement(identifierToAssign, left);
}),
];
}

export function transformAssignmentExpression(
Expand Down Expand Up @@ -106,13 +134,39 @@ export function transformAssignmentExpression(
const left = context.transformExpression(expression.left);
const right = context.transformExpression(expression.right);
return createImmediatelyInvokedFunctionExpression(
[transformAssignment(context, expression.left, right)],
transformAssignment(context, expression.left, right),
left,
expression
);
}
}

const canBeTransformedToLuaAssignmentStatement = (
context: TransformationContext,
node: ts.DestructuringAssignment
): node is ts.ArrayDestructuringAssignment => {
return (
ts.isArrayLiteralExpression(node.left) &&
node.left.elements.every(element => {
if (isArrayLength(context, element)) {
return false;
}

if (ts.isPropertyAccessExpression(element) || ts.isElementAccessExpression(element)) {
return true;
}

if (ts.isIdentifier(element)) {
const symbol = context.checker.getSymbolAtLocation(element);
if (symbol) {
const aliases = getDependenciesOfSymbol(context, symbol);
return aliases.length === 0;
}
}
})
);
};

export function transformAssignmentStatement(
context: TransformationContext,
expression: ts.AssignmentExpression<ts.EqualsToken>
Expand All @@ -124,25 +178,15 @@ export function transformAssignmentStatement(
validatePropertyAssignment(context, expression);

if (isDestructuringAssignment(expression)) {
if (
ts.isArrayLiteralExpression(expression.left) &&
expression.left.elements.every(
e =>
(ts.isIdentifier(e) || ts.isPropertyAccessExpression(e) || ts.isElementAccessExpression(e)) &&
!isArrayLength(context, e)
)
) {
if (canBeTransformedToLuaAssignmentStatement(context, expression)) {
const rightType = context.checker.getTypeAtLocation(expression.right);
let right = context.transformExpression(expression.right);

if (!isTupleReturnCall(context, expression.right) && isArrayType(context, rightType)) {
right = createUnpackCall(context, right, expression.right);
}

const left = castEach(
expression.left.elements.map(e => context.transformExpression(e)),
lua.isAssignmentLeftHandSideExpression
);
const left = expression.left.elements.map(e => transformAssignmentLeftHandSideExpression(context, e));

return [lua.createAssignmentStatement(left, right, expression)];
}
Expand All @@ -158,6 +202,6 @@ export function transformAssignmentStatement(
...transformDestructuringAssignment(context, expression, rootIdentifier),
];
} else {
return [transformAssignment(context, expression.left, context.transformExpression(expression.right))];
return transformAssignment(context, expression.left, context.transformExpression(expression.right));
}
}
27 changes: 18 additions & 9 deletions src/transformation/visitors/binary-expression/compound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,12 @@ export function transformCompoundAssignmentExpression(
replacementOperator,
expression
);
const assignStatement = transformAssignment(context, lhs, operatorExpression);
return createImmediatelyInvokedFunctionExpression([tmpDeclaration, assignStatement], tmpIdentifier, expression);
const assignStatements = transformAssignment(context, lhs, operatorExpression);
return createImmediatelyInvokedFunctionExpression(
[tmpDeclaration, ...assignStatements],
tmpIdentifier,
expression
);
} else if (ts.isPropertyAccessExpression(lhs) || ts.isElementAccessExpression(lhs)) {
// Simple property/element access expressions need to cache in temp to avoid double-evaluation
// local ____tmp = ${left} ${replacementOperator} ${right};
Expand All @@ -131,14 +135,18 @@ export function transformCompoundAssignmentExpression(
const tmpIdentifier = lua.createIdentifier("____tmp");
const operatorExpression = transformBinaryOperation(context, left, right, replacementOperator, expression);
const tmpDeclaration = lua.createVariableDeclarationStatement(tmpIdentifier, operatorExpression);
const assignStatement = transformAssignment(context, lhs, tmpIdentifier);
return createImmediatelyInvokedFunctionExpression([tmpDeclaration, assignStatement], tmpIdentifier, expression);
const assignStatements = transformAssignment(context, lhs, tmpIdentifier);
return createImmediatelyInvokedFunctionExpression(
[tmpDeclaration, ...assignStatements],
tmpIdentifier,
expression
);
} else {
// Simple expressions
// ${left} = ${right}; return ${right}
const operatorExpression = transformBinaryOperation(context, left, right, replacementOperator, expression);
const assignStatement = transformAssignment(context, lhs, operatorExpression);
return createImmediatelyInvokedFunctionExpression([assignStatement], left, expression);
const assignStatements = transformAssignment(context, lhs, operatorExpression);
return createImmediatelyInvokedFunctionExpression(assignStatements, left, expression);
}
}

Expand All @@ -148,7 +156,7 @@ export function transformCompoundAssignmentStatement(
lhs: ts.Expression,
rhs: ts.Expression,
replacementOperator: ts.BinaryOperator
): lua.Statement {
): lua.Statement[] {
const left = cast(context.transformExpression(lhs), lua.isAssignmentLeftHandSideExpression);
const right = context.transformExpression(rhs);

Expand All @@ -172,11 +180,12 @@ export function transformCompoundAssignmentStatement(
node
);
const assignStatement = lua.createAssignmentStatement(accessExpression, operatorExpression);
return lua.createDoStatement([objAndIndexDeclaration, assignStatement]);
return [objAndIndexDeclaration, assignStatement];
} else {
// Simple statements
// ${left} = ${left} ${replacementOperator} ${right}
const operatorExpression = transformBinaryOperation(context, left, right, replacementOperator, node);
return transformAssignment(context, lhs, operatorExpression);
const assignmentStatements = transformAssignment(context, lhs, operatorExpression);
return assignmentStatements;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { TransformationContext } from "../../context";
import { UnsupportedKind } from "../../utils/errors";
import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
import { isArrayType, isAssignmentPattern } from "../../utils/typescript";
import { transformIdentifier } from "../identifier";
import { transformPropertyName } from "../literal";
import { transformAssignment, transformAssignmentStatement } from "./assignments";
import {
transformAssignment,
transformAssignmentLeftHandSideExpression,
transformAssignmentStatement,
} from "./assignments";

export function isArrayLength(
context: TransformationContext,
Expand Down Expand Up @@ -86,21 +89,21 @@ function transformArrayLiteralAssignmentPattern(
lua.SyntaxKind.EqualityOperator
);

const defaultAssignmentStatement = transformAssignment(
const defaultAssignmentStatements = transformAssignment(
context,
(element as ts.BinaryExpression).left,
context.transformExpression((element as ts.BinaryExpression).right)
);

const elseAssignmentStatement = transformAssignment(
const elseAssignmentStatements = transformAssignment(
context,
(element as ts.BinaryExpression).left,
assignedVariable
);

const ifBlock = lua.createBlock([defaultAssignmentStatement]);
const ifBlock = lua.createBlock(defaultAssignmentStatements);

const elseBlock = lua.createBlock([elseAssignmentStatement]);
const elseBlock = lua.createBlock(elseAssignmentStatements);

const ifStatement = lua.createIfStatement(nilCondition, ifBlock, elseBlock, node);

Expand Down Expand Up @@ -169,14 +172,15 @@ function transformShorthandPropertyAssignment(
root: lua.Expression
): lua.Statement[] {
const result: lua.Statement[] = [];
const assignmentVariableName = transformIdentifier(context, node.name);
const assignmentVariableName = transformAssignmentLeftHandSideExpression(context, node.name);
const extractionIndex = lua.createStringLiteral(node.name.text);
const variableExtractionAssignmentStatement = lua.createAssignmentStatement(
assignmentVariableName,
const variableExtractionAssignmentStatements = transformAssignment(
context,
node.name,
lua.createTableIndexExpression(root, extractionIndex)
);

result.push(variableExtractionAssignmentStatement);
result.push(...variableExtractionAssignmentStatements);

const defaultInitializer = node.objectAssignmentInitializer
? context.transformExpression(node.objectAssignmentInitializer)
Expand All @@ -189,9 +193,9 @@ function transformShorthandPropertyAssignment(
lua.SyntaxKind.EqualityOperator
);

const assignment = lua.createAssignmentStatement(assignmentVariableName, defaultInitializer);
const assignmentStatements = transformAssignment(context, node.name, defaultInitializer);

const ifBlock = lua.createBlock([assignment]);
const ifBlock = lua.createBlock(assignmentStatements);

result.push(lua.createIfStatement(nilCondition, ifBlock, undefined, node));
}
Expand Down Expand Up @@ -223,9 +227,9 @@ function transformPropertyAssignment(
const variableToExtract = transformPropertyName(context, node.name);
const extractingExpression = lua.createTableIndexExpression(root, variableToExtract);

const destructureAssignmentStatement = transformAssignment(context, leftExpression, extractingExpression);
const destructureAssignmentStatements = transformAssignment(context, leftExpression, extractingExpression);

result.push(destructureAssignmentStatement);
result.push(...destructureAssignmentStatements);

if (ts.isBinaryExpression(node.initializer)) {
const assignmentLeftHandSide = context.transformExpression(node.initializer.left);
Expand Down Expand Up @@ -274,5 +278,5 @@ function transformSpreadAssignment(
lua.createTableExpression(usedProperties)
);

return [transformAssignment(context, node.expression, extractingExpression)];
return transformAssignment(context, node.expression, extractingExpression);
}
2 changes: 1 addition & 1 deletion src/transformation/visitors/unary-expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
export function transformUnaryExpressionStatement(
context: TransformationContext,
node: ts.ExpressionStatement
): lua.Statement | undefined {
): lua.Statement[] | undefined {
const expression = ts.isExpressionStatement(node) ? node.expression : node;
if (
ts.isPrefixUnaryExpression(expression) &&
Expand Down
Loading
X Tutup