Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
cf05108
Add tuple helper
hazzard993 Jan 4, 2020
df16137
Elide helper import declarations
hazzard993 Jan 9, 2020
edae003
Merge remote-tracking branch 'upstream/master' into helpers
hazzard993 Jan 9, 2020
a11e30b
tuple validation progress
hazzard993 Jan 9, 2020
cc967f1
Use Visitors
hazzard993 Jan 31, 2020
43a612c
Merge remote-tracking branch 'upstream/master' into helpers
hazzard993 Jan 31, 2020
2618cd6
Fix VariableDeclaration
hazzard993 Jan 31, 2020
0de51fb
Implement tuple helper
hazzard993 Feb 6, 2020
fd878e5
Use nil if no arguments were provided to tuple
hazzard993 Feb 6, 2020
82c45cf
Add destructuring assignment support
hazzard993 Feb 6, 2020
9b25242
Support side effects and add more validation
hazzard993 Feb 6, 2020
68b7c66
Add unit tests
hazzard993 Feb 6, 2020
db3d487
Add test for tuple within multiple VariableDeclarations
hazzard993 Feb 6, 2020
7679521
Can be used inside for loops
hazzard993 Feb 6, 2020
f16c803
Fix syntax error in test case
hazzard993 Feb 6, 2020
8be3221
Remove unnecessary boilerplate from tuple helper
hazzard993 Feb 6, 2020
fa42648
Use some instead of every
hazzard993 Feb 6, 2020
1d87279
Use flatMap in tuple instead of map
hazzard993 Feb 6, 2020
2da03b2
Use Boolean instead of ternary in tuple
hazzard993 Feb 6, 2020
104708b
Import helpers from /helpers
hazzard993 Feb 6, 2020
af1cefb
Do not use ts-ignore in tuple test cases
hazzard993 Feb 6, 2020
00c32d8
Use more specific test case names for tuple
hazzard993 Feb 6, 2020
f878092
Remove unsafe cast in for-of
hazzard993 Feb 6, 2020
93ca0a9
Remove tuple visitors
hazzard993 Feb 6, 2020
313676e
Rename to multi and MultiReturn
hazzard993 Feb 6, 2020
e63260f
Revert FunctionVisitor conversion
hazzard993 Feb 6, 2020
ab9b8eb
Rename tupleResult to multiResult
hazzard993 Feb 6, 2020
68dcee9
Update tsconfig.json
hazzard993 Feb 25, 2020
343a026
Update test/unit/helpers/multi.spec.ts
hazzard993 Feb 25, 2020
9fe0e7e
Refactor for new createVirtualProgram code
hazzard993 Feb 25, 2020
55782f5
Move helper transformer to transformation
hazzard993 Feb 25, 2020
aa579fd
Use nullish operator instead of Boolean constructor
hazzard993 Feb 25, 2020
4913a8f
Attempt a CommonJS implementation of multi
hazzard993 Feb 25, 2020
09077c2
Add MultiReturn alias MR
hazzard993 Feb 25, 2020
d0e4432
Add export in front of multi function
hazzard993 Feb 25, 2020
2eb5bbc
Simplify multi js implementation
hazzard993 Feb 26, 2020
6bfd028
Remove MR alias
hazzard993 Feb 26, 2020
4df1b0a
Merge remote-tracking branch 'upstream/master' into helpers
hazzard993 Mar 2, 2020
a846816
Merge remote-tracking branch 'upstream/master' into helpers
hazzard993 Mar 2, 2020
a959695
Make multi helper global
hazzard993 Mar 2, 2020
3d18107
Revert multi helper import changes
hazzard993 Mar 2, 2020
ac71643
Complex merge of 'upstream/master' into helpers
hazzard993 Mar 29, 2020
d82f958
Apply suggestions from code review
hazzard993 Mar 29, 2020
6c9d1a6
Inline and join multi conditions
hazzard993 Mar 29, 2020
c0c9377
Rename to $multi
hazzard993 Mar 29, 2020
4e8b8de
Use declare for top level $multi
hazzard993 Mar 29, 2020
d2227fe
Add empty tuple to MultiReturn
hazzard993 Mar 29, 2020
fa7626d
Add declare to MultiReturn to top level declaration
hazzard993 Mar 29, 2020
1a44b55
Reduce number of diagnostics for multi error
hazzard993 Mar 29, 2020
4c1ea0d
Improve multi diagnostics
hazzard993 Mar 29, 2020
e1d8538
Apply suggestions from code review
hazzard993 Mar 30, 2020
fc63fc9
Rename utils to helpers
hazzard993 Mar 30, 2020
823ee93
Include helpers inside lualib's tsconfig
hazzard993 Mar 30, 2020
0464ca6
Merge remote-tracking branch 'upstream/master' into helpers
hazzard993 Jun 24, 2020
8b13adb
Merge remote-tracking branch 'upstream/master' into helpers
hazzard993 Aug 3, 2020
19b735c
Fix esnext target error
hazzard993 Aug 3, 2020
b7d1ec0
Drop support for VariableDeclarations, support only ReturnStatements
hazzard993 Aug 3, 2020
f559fff
Simplify multi tests a bit
hazzard993 Aug 3, 2020
079387a
Remove multi.js
hazzard993 Aug 4, 2020
e3c1e9b
Revert "Drop support for VariableDeclarations, support only ReturnSta…
hazzard993 Aug 5, 2020
a108957
State that multi can only be used in return statements
hazzard993 Aug 17, 2020
6646695
Throw an error if a helper is unknown
hazzard993 Aug 17, 2020
cbe8f46
Improve multi nomenclature
hazzard993 Aug 17, 2020
29bef10
Disallow multi function use in non-return statements
hazzard993 Aug 17, 2020
3c0f61e
Merge helpers together into index.d.ts
hazzard993 Aug 17, 2020
3e2e4cc
Prefer for loop to filter/map/foreach
hazzard993 Aug 17, 2020
a68811d
Add test to return spreaded multi type from multi type function
hazzard993 Aug 17, 2020
e0d3cf4
Rename to language extensions / extensions
hazzard993 Dec 22, 2020
e1df59e
Remove remaining helper references
hazzard993 Dec 22, 2020
a535623
Merge remote-tracking branch 'upstream/master' into helpers
hazzard993 Dec 22, 2020
e0232f5
Allow multi call as ConciseBody
hazzard993 Dec 22, 2020
409160b
Remove ts-ignore
hazzard993 Dec 22, 2020
33b875f
Add check first to all multi transforms
hazzard993 Dec 22, 2020
9f2b45a
Merge remote-tracking branch 'upstream/master' into helpers
hazzard993 Dec 22, 2020
bcd158d
Add multi example use case
hazzard993 Dec 23, 2020
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
2 changes: 2 additions & 0 deletions language-extensions/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare function $multi<T extends any[]>(...values: T): MultiReturn<T>;
declare type MultiReturn<T extends any[]> = T & { readonly " __multiBrand": unique symbol };
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"files": [
"dist/**/*.js",
"dist/**/*.lua",
"dist/**/*.ts"
"dist/**/*.ts",
"language-extensions/**/*.ts"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/lualib/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"outDir": "../../dist/lualib",
"target": "esnext",
"lib": ["esnext"],
"types": [],
"types": ["../../language-extensions"],
"skipLibCheck": true,

"noUnusedLocals": true,
Expand Down
26 changes: 26 additions & 0 deletions src/transformation/utils/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,32 @@ export const unsupportedVarDeclaration = createErrorDiagnosticFactory(
"`var` declarations are not supported. Use `let` or `const` instead."
);

export const invalidMultiFunctionUse = createErrorDiagnosticFactory(
"The $multi function must be called in an expression that is returned."
);

export const invalidMultiTypeToNonArrayBindingPattern = createErrorDiagnosticFactory(
"Expected an array destructuring pattern."
);

export const invalidMultiTypeToNonArrayLiteral = createErrorDiagnosticFactory("Expected an array literal.");

export const invalidMultiTypeToEmptyPatternOrArrayLiteral = createErrorDiagnosticFactory(
"There must be one or more elements specified here."
);

export const invalidMultiTypeArrayBindingPatternElementInitializer = createErrorDiagnosticFactory(
"This array binding pattern cannot have initializers."
);

export const invalidMultiTypeArrayLiteralElementInitializer = createErrorDiagnosticFactory(
"This array literal pattern cannot have initializers."
);

export const unsupportedMultiFunctionAssignment = createErrorDiagnosticFactory(
"Omitted expressions and BindingElements are expected here."
);

export const annotationDeprecated = createWarningDiagnosticFactory(
(kind: AnnotationKind) =>
`'@${kind}' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. ` +
Expand Down
28 changes: 28 additions & 0 deletions src/transformation/utils/language-extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as ts from "typescript";
import * as path from "path";

export enum ExtensionKind {
MultiFunction = "MultiFunction",
MultiType = "MultiType",
}

function isSourceFileFromLanguageExtensions(sourceFile: ts.SourceFile): boolean {
const extensionDirectory = path.resolve(__dirname, "../../../language-extensions");
const sourceFileDirectory = path.dirname(path.normalize(sourceFile.fileName));
return extensionDirectory === sourceFileDirectory;
}

export function getExtensionKind(declaration: ts.Declaration): ExtensionKind | undefined {
const sourceFile = declaration.getSourceFile();
if (isSourceFileFromLanguageExtensions(sourceFile)) {
if (ts.isFunctionDeclaration(declaration) && declaration?.name?.text === "$multi") {
return ExtensionKind.MultiFunction;
}

if (ts.isTypeAliasDeclaration(declaration) && declaration.name.text === "MultiReturn") {
return ExtensionKind.MultiType;
}

throw new Error("Unknown extension kind");
}
}
6 changes: 3 additions & 3 deletions src/transformation/visitors/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { isValidLuaIdentifier } from "../utils/safe-names";
import { isArrayType, isExpressionWithEvaluationEffect, isInDestructingAssignment } from "../utils/typescript";
import { transformElementAccessArgument } from "./access";
import { transformLuaTableCallExpression } from "./lua-table";
import { returnsMultiType } from "./language-extensions/multi";

export type PropertyCallExpression = ts.CallExpression & { expression: ts.PropertyAccessExpression };

Expand Down Expand Up @@ -250,9 +251,8 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
// TODO: Currently it's also used as an array member
export const transformSpreadElement: FunctionVisitor<ts.SpreadElement> = (node, context) => {
const innerExpression = context.transformExpression(node.expression);
if (isTupleReturnCall(context, node.expression)) {
return innerExpression;
}
if (isTupleReturnCall(context, node.expression)) return innerExpression;
if (ts.isCallExpression(node.expression) && returnsMultiType(context, node.expression)) return innerExpression;

if (ts.isIdentifier(node.expression) && isVarargType(context, node.expression)) {
return lua.createDotsLiteral(node);
Expand Down
10 changes: 10 additions & 0 deletions src/transformation/visitors/expression-statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@ import { FunctionVisitor } from "../context";
import { transformBinaryExpressionStatement } from "./binary-expression";
import { transformLuaTableExpressionStatement } from "./lua-table";
import { transformUnaryExpressionStatement } from "./unary-expression";
import { returnsMultiType, transformMultiDestructuringAssignmentStatement } from "./language-extensions/multi";

export const transformExpressionStatement: FunctionVisitor<ts.ExpressionStatement> = (node, context) => {
if (
ts.isBinaryExpression(node.expression) &&
node.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
ts.isCallExpression(node.expression.right) &&
returnsMultiType(context, node.expression.right)
) {
return transformMultiDestructuringAssignmentStatement(context, node);
}

const luaTableResult = transformLuaTableExpressionStatement(context, node);
if (luaTableResult) {
return luaTableResult;
Expand Down
5 changes: 5 additions & 0 deletions src/transformation/visitors/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
import { peekScope, performHoisting, popScope, pushScope, Scope, ScopeType } from "../utils/scope";
import { transformIdentifier } from "./identifier";
import { isMultiFunction, transformMultiCallExpressionToReturnStatement } from "./language-extensions/multi";
import { transformExpressionBodyToReturnStatement } from "./return";
import { transformBindingPattern } from "./variable-declaration";

Expand Down Expand Up @@ -55,6 +56,10 @@ function isRestParameterReferenced(context: TransformationContext, identifier: l

export function transformFunctionBodyContent(context: TransformationContext, body: ts.ConciseBody): lua.Statement[] {
if (!ts.isBlock(body)) {
if (ts.isCallExpression(body) && isMultiFunction(context, body)) {
return [transformMultiCallExpressionToReturnStatement(context, body)];
}

const returnStatement = transformExpressionBodyToReturnStatement(context, body);
return [returnStatement];
}
Expand Down
8 changes: 7 additions & 1 deletion src/transformation/visitors/identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import * as lua from "../../LuaAST";
import { transformBuiltinIdentifierExpression } from "../builtins";
import { FunctionVisitor, TransformationContext } from "../context";
import { isForRangeType } from "../utils/annotations";
import { invalidForRangeCall } from "../utils/diagnostics";
import { invalidForRangeCall, invalidMultiFunctionUse } from "../utils/diagnostics";
import { createExportedIdentifier, getSymbolExportScope } from "../utils/export";
import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names";
import { getIdentifierSymbolId } from "../utils/symbols";
import { findFirstNodeAbove } from "../utils/typescript";
import { isMultiFunctionNode } from "./language-extensions/multi";

export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier {
if (isMultiFunctionNode(context, identifier)) {
context.diagnostics.push(invalidMultiFunctionUse(identifier));
return lua.createAnonymousIdentifier(identifier);
}

if (isForRangeType(context, identifier)) {
const callExpression = findFirstNodeAbove(identifier, ts.isCallExpression);
if (!callExpression || !callExpression.parent || !ts.isForOfStatement(callExpression.parent)) {
Expand Down
188 changes: 188 additions & 0 deletions src/transformation/visitors/language-extensions/multi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import * as ts from "typescript";
import * as lua from "../../../LuaAST";
import * as extensions from "../../utils/language-extensions";
import { TransformationContext } from "../../context";
import { transformAssignmentLeftHandSideExpression } from "../binary-expression/assignments";
import { transformIdentifier } from "../identifier";
import { transformArguments } from "../call";
import { getDependenciesOfSymbol, createExportedIdentifier } from "../../utils/export";
import { createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast";
import {
invalidMultiTypeArrayBindingPatternElementInitializer,
invalidMultiTypeArrayLiteralElementInitializer,
invalidMultiTypeToEmptyPatternOrArrayLiteral,
invalidMultiTypeToNonArrayBindingPattern,
invalidMultiTypeToNonArrayLiteral,
unsupportedMultiFunctionAssignment,
invalidMultiFunctionUse,
} from "../../utils/diagnostics";
import { assert } from "../../../utils";

const isMultiFunctionDeclaration = (declaration: ts.Declaration): boolean =>
extensions.getExtensionKind(declaration) === extensions.ExtensionKind.MultiFunction;

const isMultiTypeDeclaration = (declaration: ts.Declaration): boolean =>
extensions.getExtensionKind(declaration) === extensions.ExtensionKind.MultiType;

export function isMultiFunction(context: TransformationContext, expression: ts.CallExpression): boolean {
const type = context.checker.getTypeAtLocation(expression.expression);
return type.symbol?.declarations?.some(isMultiFunctionDeclaration) ?? false;
}

export function returnsMultiType(context: TransformationContext, node: ts.CallExpression): boolean {
const signature = context.checker.getResolvedSignature(node);
return signature?.getReturnType().aliasSymbol?.declarations?.some(isMultiTypeDeclaration) ?? false;
}

export function isMultiFunctionNode(context: TransformationContext, node: ts.Node): boolean {
const type = context.checker.getTypeAtLocation(node);
return type.symbol?.declarations?.some(isMultiFunctionDeclaration) ?? false;
}

export function transformMultiCallExpressionToReturnStatement(
context: TransformationContext,
expression: ts.Expression
): lua.Statement {
assert(ts.isCallExpression(expression));

const expressions = transformArguments(context, expression.arguments);
return lua.createReturnStatement(expressions, expression);
}

export function transformMultiReturnStatement(
context: TransformationContext,
statement: ts.ReturnStatement
): lua.Statement {
assert(statement.expression);

return transformMultiCallExpressionToReturnStatement(context, statement.expression);
}

function transformMultiFunctionArguments(
context: TransformationContext,
expression: ts.CallExpression
): lua.Expression[] | lua.Expression {
if (!isMultiFunction(context, expression)) {
return context.transformExpression(expression);
}

if (expression.arguments.length === 0) {
return lua.createNilLiteral(expression);
}

return expression.arguments.map(e => context.transformExpression(e));
}

export function transformMultiVariableDeclaration(
context: TransformationContext,
declaration: ts.VariableDeclaration
): lua.Statement[] {
assert(declaration.initializer);
assert(ts.isCallExpression(declaration.initializer));

if (!ts.isArrayBindingPattern(declaration.name)) {
context.diagnostics.push(invalidMultiTypeToNonArrayBindingPattern(declaration.name));
return [];
}

if (declaration.name.elements.length < 1) {
context.diagnostics.push(invalidMultiTypeToEmptyPatternOrArrayLiteral(declaration.name));
return [];
}

if (declaration.name.elements.some(e => ts.isBindingElement(e) && e.initializer)) {
context.diagnostics.push(invalidMultiTypeArrayBindingPatternElementInitializer(declaration.name));
return [];
}

if (isMultiFunction(context, declaration.initializer)) {
context.diagnostics.push(invalidMultiFunctionUse(declaration.initializer));
return [];
}

const leftIdentifiers: lua.Identifier[] = [];

for (const element of declaration.name.elements) {
if (ts.isBindingElement(element)) {
if (ts.isIdentifier(element.name)) {
leftIdentifiers.push(transformIdentifier(context, element.name));
} else {
context.diagnostics.push(unsupportedMultiFunctionAssignment(element));
}
} else if (ts.isOmittedExpression(element)) {
leftIdentifiers.push(lua.createAnonymousIdentifier(element));
}
}

const rightExpressions = transformMultiFunctionArguments(context, declaration.initializer);
return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration);
}

export function transformMultiDestructuringAssignmentStatement(
context: TransformationContext,
statement: ts.ExpressionStatement
): lua.Statement[] | undefined {
assert(ts.isBinaryExpression(statement.expression));
assert(ts.isCallExpression(statement.expression.right));

if (!ts.isArrayLiteralExpression(statement.expression.left)) {
context.diagnostics.push(invalidMultiTypeToNonArrayLiteral(statement.expression.left));
return [];
}

if (statement.expression.left.elements.some(ts.isBinaryExpression)) {
context.diagnostics.push(invalidMultiTypeArrayLiteralElementInitializer(statement.expression.left));
return [];
}

if (statement.expression.left.elements.length < 1) {
context.diagnostics.push(invalidMultiTypeToEmptyPatternOrArrayLiteral(statement.expression.left));
return [];
}

if (isMultiFunction(context, statement.expression.right)) {
context.diagnostics.push(invalidMultiFunctionUse(statement.expression.right));
return [];
}

const transformLeft = (expression: ts.Expression): lua.AssignmentLeftHandSideExpression =>
ts.isOmittedExpression(expression)
? lua.createAnonymousIdentifier(expression)
: transformAssignmentLeftHandSideExpression(context, expression);

const leftIdentifiers = statement.expression.left.elements.map(transformLeft);

const rightExpressions = transformMultiFunctionArguments(context, statement.expression.right);

const trailingStatements = statement.expression.left.elements.flatMap(expression => {
const symbol = context.checker.getSymbolAtLocation(expression);
const dependentSymbols = symbol ? getDependenciesOfSymbol(context, symbol) : [];
return dependentSymbols.map(symbol => {
const identifierToAssign = createExportedIdentifier(context, lua.createIdentifier(symbol.name));
return lua.createAssignmentStatement(identifierToAssign, transformLeft(expression));
});
});

return [lua.createAssignmentStatement(leftIdentifiers, rightExpressions, statement), ...trailingStatements];
}

export function findMultiAssignmentViolations(
context: TransformationContext,
node: ts.ObjectLiteralExpression
): ts.Node[] {
const result: ts.Node[] = [];

for (const element of node.properties) {
if (!ts.isShorthandPropertyAssignment(element)) continue;
const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element);
if (valueSymbol) {
const declaration = valueSymbol.valueDeclaration;
if (declaration && isMultiFunctionDeclaration(declaration)) {
context.diagnostics.push(invalidMultiFunctionUse(element));
result.push(element);
}
}
}

return result;
}
9 changes: 8 additions & 1 deletion src/transformation/visitors/literal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import * as ts from "typescript";
import * as lua from "../../LuaAST";
import { assertNever } from "../../utils";
import { FunctionVisitor, TransformationContext, Visitors } from "../context";
import { unsupportedAccessorInObjectLiteral } from "../utils/diagnostics";
import { unsupportedAccessorInObjectLiteral, invalidMultiFunctionUse } from "../utils/diagnostics";
import { createExportedIdentifier, getSymbolExportScope } from "../utils/export";
import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
import { createSafeName, hasUnsafeIdentifierName, hasUnsafeSymbolName } from "../utils/safe-names";
import { getSymbolIdOfSymbol, trackSymbolReference } from "../utils/symbols";
import { isArrayType } from "../utils/typescript";
import { transformFunctionLikeDeclaration } from "./function";
import { flattenSpreadExpressions } from "./call";
import { findMultiAssignmentViolations } from "./language-extensions/multi";

// TODO: Move to object-literal.ts?
export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression {
Expand Down Expand Up @@ -62,6 +63,12 @@ const transformNumericLiteralExpression: FunctionVisitor<ts.NumericLiteral> = ex
};

const transformObjectLiteralExpression: FunctionVisitor<ts.ObjectLiteralExpression> = (expression, context) => {
const violations = findMultiAssignmentViolations(context, expression);
if (violations.length > 0) {
context.diagnostics.push(...violations.map(e => invalidMultiFunctionUse(e)));
return lua.createNilLiteral(expression);
}

let properties: lua.TableFieldExpression[] = [];
const tableExpressions: lua.Expression[] = [];

Expand Down
Loading