From cf051085424eb93e5f124d1cb2a9670d3b8f0b76 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sat, 4 Jan 2020 14:36:34 +1000 Subject: [PATCH 01/66] Add tuple helper --- src/helpers/index.ts | 1 + src/helpers/tuple.ts | 5 + src/index.ts | 1 + src/transformation/helpers/tuple.ts | 94 +++++++++++++++++++ src/transformation/visitors/modules/import.ts | 5 + src/transformation/visitors/return.ts | 5 + .../visitors/variable-declaration.ts | 5 + src/transpilation/transformers.ts | 1 + 8 files changed, 117 insertions(+) create mode 100644 src/helpers/index.ts create mode 100644 src/helpers/tuple.ts create mode 100644 src/transformation/helpers/tuple.ts diff --git a/src/helpers/index.ts b/src/helpers/index.ts new file mode 100644 index 000000000..61b6a05bd --- /dev/null +++ b/src/helpers/index.ts @@ -0,0 +1 @@ +export * from "./tuple"; diff --git a/src/helpers/tuple.ts b/src/helpers/tuple.ts new file mode 100644 index 000000000..b30bcb594 --- /dev/null +++ b/src/helpers/tuple.ts @@ -0,0 +1,5 @@ +export type Tuple = T & { + " __tupleBrand": never; +}; + +export declare function tuple(...values: T): Tuple; diff --git a/src/index.ts b/src/index.ts index 8b0159a9c..c3e9af5e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,3 +8,4 @@ export * from "./LuaPrinter"; export * from "./transformation/context"; export { TranspileError } from "./transformation/utils/errors"; export * from "./transpilation"; +export * from "./helpers"; diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts new file mode 100644 index 000000000..1adfb6b95 --- /dev/null +++ b/src/transformation/helpers/tuple.ts @@ -0,0 +1,94 @@ +import * as ts from "typescript"; +import * as path from "path"; +import * as lua from "../../LuaAST"; +import { TransformationContext } from "../context"; +import { transformArrayBindingElement } from "../visitors/variable-declaration"; +import { transformArguments } from "../visitors/call"; + +const helperPath = path.resolve(__dirname, "../../helpers"); + +function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { + const filePath = path.normalize(sourceFile.fileName); + const tuplePath = path.resolve(helperPath, "./tuple.d.ts"); + return filePath === tuplePath; +} + +export function isHelpersImport( + context: TransformationContext, + importNode: ts.ImportClause | ts.ImportSpecifier +): boolean { + if (importNode.name) { + const symbol = context.checker.getSymbolAtLocation(importNode.name); + if (symbol) { + const originalSymbol = context.checker.getAliasedSymbol(symbol); + return originalSymbol.declarations.map(d => d.getSourceFile()).some(isSourceFileFromHelpers); + } + } + + return false; +} + +export type VariableDeclarationWithCall = ts.VariableDeclaration & { + initializer: ts.CallExpression; +}; + +export type ReturnStatementWithCall = ts.ReturnStatement & { + expression: ts.CallExpression; +}; + +export function hasTupleHelperSignature(context: TransformationContext, expression: ts.CallExpression): boolean { + const signature = context.checker.getResolvedSignature(expression); + const sourceFile = signature?.declaration?.getSourceFile(); + return sourceFile ? isSourceFileFromHelpers(sourceFile) : false; +} + +export function isTupleHelperReturnStatement( + context: TransformationContext, + statement: ts.ReturnStatement +): statement is ReturnStatementWithCall { + if (!statement.expression) { + return false; + } + + if (!ts.isCallExpression(statement.expression)) { + return false; + } + + return hasTupleHelperSignature(context, statement.expression); +} + +export function transformTupleHelperReturnStatement( + context: TransformationContext, + statement: ReturnStatementWithCall +): lua.Statement { + const expressions = transformArguments(context, statement.expression.arguments); + return lua.createReturnStatement(expressions, statement); +} + +export function isTupleHelperVariableDeclaration( + context: TransformationContext, + declaration: ts.VariableDeclaration +): declaration is VariableDeclarationWithCall { + if (!declaration.initializer) { + return false; + } + + if (!ts.isCallExpression(declaration.initializer)) { + return false; + } + + return hasTupleHelperSignature(context, declaration.initializer); +} + +export function transformTupleHelperVariableDeclaration( + context: TransformationContext, + declaration: VariableDeclarationWithCall +): lua.Statement[] { + if (!ts.isArrayBindingPattern(declaration.name)) { + throw Error(); + } + + const leftIdentifiers = declaration.name.elements.map(e => transformArrayBindingElement(context, e)); + const rightExpressions = declaration.initializer.arguments.map(e => context.transformExpression(e)); + return [lua.createVariableDeclarationStatement(leftIdentifiers, rightExpressions, declaration)]; +} diff --git a/src/transformation/visitors/modules/import.ts b/src/transformation/visitors/modules/import.ts index 2dd390b6f..826fc1853 100644 --- a/src/transformation/visitors/modules/import.ts +++ b/src/transformation/visitors/modules/import.ts @@ -9,6 +9,7 @@ import { createDefaultExportStringLiteral } from "../../utils/export"; import { createHoistableVariableDeclarationStatement } from "../../utils/lua-ast"; import { createSafeName } from "../../utils/safe-names"; import { peekScope } from "../../utils/scope"; +import { isHelpersImport } from "../../helpers/tuple"; import { transformIdentifier } from "../identifier"; import { transformPropertyName } from "../literal"; @@ -66,6 +67,10 @@ export function createModuleRequire( } function shouldBeImported(context: TransformationContext, importNode: ts.ImportClause | ts.ImportSpecifier): boolean { + if (isHelpersImport(context, importNode)) { + return false; + } + const annotations = getTypeAnnotations(context, context.checker.getTypeAtLocation(importNode)); return ( diff --git a/src/transformation/visitors/return.ts b/src/transformation/visitors/return.ts index 970db6be0..a74b51149 100644 --- a/src/transformation/visitors/return.ts +++ b/src/transformation/visitors/return.ts @@ -6,6 +6,7 @@ import { validateAssignment } from "../utils/assignment-validation"; import { createUnpackCall, wrapInTable } from "../utils/lua-ast"; import { ScopeType, walkScopesUp } from "../utils/scope"; import { isArrayType } from "../utils/typescript"; +import { isTupleHelperReturnStatement, transformTupleHelperReturnStatement } from "../helpers/tuple"; export const transformReturnStatement: FunctionVisitor = (statement, context) => { // Bubble up explicit return flag and check if we're inside a try/catch block @@ -20,6 +21,10 @@ export const transformReturnStatement: FunctionVisitor = (st insideTryCatch = insideTryCatch || scope.type === ScopeType.Try || scope.type === ScopeType.Catch; } + if (isTupleHelperReturnStatement(context, statement)) { + return transformTupleHelperReturnStatement(context, statement); + } + let results: lua.Expression[]; if (statement.expression) { diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index c02d8ab1d..20f0d1add 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,6 +10,7 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; +import { transformTupleHelperVariableDeclaration, isTupleHelperVariableDeclaration } from "../helpers/tuple"; export function transformArrayBindingElement( context: TransformationContext, @@ -125,6 +126,10 @@ export function transformVariableDeclaration( context: TransformationContext, statement: ts.VariableDeclaration ): lua.Statement[] { + if (isTupleHelperVariableDeclaration(context, statement)) { + return transformTupleHelperVariableDeclaration(context, statement); + } + if (statement.initializer && statement.type) { const initializerType = context.checker.getTypeAtLocation(statement.initializer); const varType = context.checker.getTypeFromTypeNode(statement.type); diff --git a/src/transpilation/transformers.ts b/src/transpilation/transformers.ts index 71371e208..33acb7355 100644 --- a/src/transpilation/transformers.ts +++ b/src/transpilation/transformers.ts @@ -68,6 +68,7 @@ function loadTransformersFromOptions(program: ts.Program, allDiagnostics: ts.Dia const basedir = configFileName ? path.dirname(configFileName) : process.cwd(); for (const [index, transformerImport] of options.plugins.entries()) { + console.log(index, transformerImport); if (!("transform" in transformerImport)) continue; const optionName = `compilerOptions.plugins[${index}]`; From df16137fbaef7259c1bb5fcf1f4b93890cbc0bf8 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 9 Jan 2020 15:13:55 +1000 Subject: [PATCH 02/66] Elide helper import declarations --- src/transformation/helpers/tuple.ts | 17 ++++++++++------- src/transformation/visitors/modules/import.ts | 4 ++++ src/transpilation/transformers.ts | 1 - 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index 1adfb6b95..10c5aac28 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -5,23 +5,26 @@ import { TransformationContext } from "../context"; import { transformArrayBindingElement } from "../visitors/variable-declaration"; import { transformArguments } from "../visitors/call"; -const helperPath = path.resolve(__dirname, "../../helpers"); - function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { - const filePath = path.normalize(sourceFile.fileName); - const tuplePath = path.resolve(helperPath, "./tuple.d.ts"); - return filePath === tuplePath; + const helperDirectory = path.resolve(__dirname, "../../helpers"); + const sourceFileDirectory = path.dirname(path.normalize(sourceFile.fileName)); + return helperDirectory === sourceFileDirectory; } export function isHelpersImport( context: TransformationContext, - importNode: ts.ImportClause | ts.ImportSpecifier + importNode: ts.ImportClause | ts.ImportSpecifier | ts.ImportDeclaration ): boolean { + if (ts.isImportDeclaration(importNode)) { + const symbol = context.checker.getSymbolAtLocation(importNode.moduleSpecifier); + return symbol?.declarations.map(d => d.getSourceFile()).some(isSourceFileFromHelpers) ?? false; + } + if (importNode.name) { const symbol = context.checker.getSymbolAtLocation(importNode.name); if (symbol) { const originalSymbol = context.checker.getAliasedSymbol(symbol); - return originalSymbol.declarations.map(d => d.getSourceFile()).some(isSourceFileFromHelpers); + return originalSymbol?.declarations?.map(d => d.getSourceFile()).some(isSourceFileFromHelpers) ?? false; } } diff --git a/src/transformation/visitors/modules/import.ts b/src/transformation/visitors/modules/import.ts index 826fc1853..7a80934c2 100644 --- a/src/transformation/visitors/modules/import.ts +++ b/src/transformation/visitors/modules/import.ts @@ -105,6 +105,10 @@ export const transformImportDeclaration: FunctionVisitor = scope.importStatements = []; } + if (isHelpersImport(context, statement)) { + return; + } + const result: lua.Statement[] = []; const requireCall = createModuleRequire(context, statement.moduleSpecifier); diff --git a/src/transpilation/transformers.ts b/src/transpilation/transformers.ts index 33acb7355..71371e208 100644 --- a/src/transpilation/transformers.ts +++ b/src/transpilation/transformers.ts @@ -68,7 +68,6 @@ function loadTransformersFromOptions(program: ts.Program, allDiagnostics: ts.Dia const basedir = configFileName ? path.dirname(configFileName) : process.cwd(); for (const [index, transformerImport] of options.plugins.entries()) { - console.log(index, transformerImport); if (!("transform" in transformerImport)) continue; const optionName = `compilerOptions.plugins[${index}]`; From a11e30b3c7222f58c5d386945def0d23b14ea651 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 9 Jan 2020 17:13:15 +1000 Subject: [PATCH 03/66] tuple validation progress --- src/transformation/helpers/tuple.ts | 40 +++-------------- src/transformation/utils/errors.ts | 2 + src/transformation/utils/helpers.ts | 45 +++++++++++++++++++ src/transformation/visitors/identifier.ts | 7 ++- src/transformation/visitors/literal.ts | 12 ++++- src/transformation/visitors/modules/import.ts | 2 +- 6 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 src/transformation/utils/helpers.ts diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index 10c5aac28..dff9b211f 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -1,36 +1,10 @@ import * as ts from "typescript"; -import * as path from "path"; import * as lua from "../../LuaAST"; +import * as utils from "../utils/helpers"; import { TransformationContext } from "../context"; import { transformArrayBindingElement } from "../visitors/variable-declaration"; import { transformArguments } from "../visitors/call"; -function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { - const helperDirectory = path.resolve(__dirname, "../../helpers"); - const sourceFileDirectory = path.dirname(path.normalize(sourceFile.fileName)); - return helperDirectory === sourceFileDirectory; -} - -export function isHelpersImport( - context: TransformationContext, - importNode: ts.ImportClause | ts.ImportSpecifier | ts.ImportDeclaration -): boolean { - if (ts.isImportDeclaration(importNode)) { - const symbol = context.checker.getSymbolAtLocation(importNode.moduleSpecifier); - return symbol?.declarations.map(d => d.getSourceFile()).some(isSourceFileFromHelpers) ?? false; - } - - if (importNode.name) { - const symbol = context.checker.getSymbolAtLocation(importNode.name); - if (symbol) { - const originalSymbol = context.checker.getAliasedSymbol(symbol); - return originalSymbol?.declarations?.map(d => d.getSourceFile()).some(isSourceFileFromHelpers) ?? false; - } - } - - return false; -} - export type VariableDeclarationWithCall = ts.VariableDeclaration & { initializer: ts.CallExpression; }; @@ -39,10 +13,10 @@ export type ReturnStatementWithCall = ts.ReturnStatement & { expression: ts.CallExpression; }; -export function hasTupleHelperSignature(context: TransformationContext, expression: ts.CallExpression): boolean { - const signature = context.checker.getResolvedSignature(expression); - const sourceFile = signature?.declaration?.getSourceFile(); - return sourceFile ? isSourceFileFromHelpers(sourceFile) : false; +export function isTupleHelperType(context: TransformationContext, identifier: ts.Node): boolean { + const type = context.checker.getTypeAtLocation(identifier); + const sourceFiles = type.symbol?.declarations?.map(d => d.getSourceFile()); + return sourceFiles ? sourceFiles.some(file => utils.getHelperFileKind(file) === utils.HelperKind.Tuple) : false; } export function isTupleHelperReturnStatement( @@ -57,7 +31,7 @@ export function isTupleHelperReturnStatement( return false; } - return hasTupleHelperSignature(context, statement.expression); + return isTupleHelperType(context, statement.expression.expression); } export function transformTupleHelperReturnStatement( @@ -80,7 +54,7 @@ export function isTupleHelperVariableDeclaration( return false; } - return hasTupleHelperSignature(context, declaration.initializer); + return isTupleHelperType(context, declaration.initializer); } export function transformTupleHelperVariableDeclaration( diff --git a/src/transformation/utils/errors.ts b/src/transformation/utils/errors.ts index 09634fcc8..025618392 100644 --- a/src/transformation/utils/errors.ts +++ b/src/transformation/utils/errors.ts @@ -151,3 +151,5 @@ export const InvalidForRangeCall = (node: ts.Node, message: string) => export const UnsupportedVarDeclaration = (node: ts.Node) => new TranspileError("`var` declarations are not supported. Use `let` or `const` instead.", node); + +export const InvalidTupleFunctionUse = (node: ts.Node) => new TranspileError("Invalid tuple() function use.", node); diff --git a/src/transformation/utils/helpers.ts b/src/transformation/utils/helpers.ts new file mode 100644 index 000000000..646a8e70e --- /dev/null +++ b/src/transformation/utils/helpers.ts @@ -0,0 +1,45 @@ +import * as ts from "typescript"; +import * as path from "path"; +import { TransformationContext } from "../context"; + +export enum HelperKind { + Tuple = "tuple", +} + +function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { + const helperDirectory = path.resolve(__dirname, "../../helpers"); + const sourceFileDirectory = path.dirname(path.normalize(sourceFile.fileName)); + return helperDirectory === sourceFileDirectory; +} + +export function getHelperFileKind(sourceFile: ts.SourceFile): HelperKind | undefined { + if (isSourceFileFromHelpers(sourceFile)) { + const baseFileName = path.basename(sourceFile.fileName).replace(/(\.d)?\.ts$/g, ""); + switch (baseFileName) { + case "tuple": + return HelperKind.Tuple; + default: + throw new Error(`Unknown Helper Kind ${baseFileName}`); + } + } +} + +export function isHelpersImport( + context: TransformationContext, + importNode: ts.ImportClause | ts.ImportSpecifier | ts.ImportDeclaration +): boolean { + if (ts.isImportDeclaration(importNode)) { + const symbol = context.checker.getSymbolAtLocation(importNode.moduleSpecifier); + return symbol?.declarations.map(d => d.getSourceFile()).some(isSourceFileFromHelpers) ?? false; + } + + if (importNode.name) { + const symbol = context.checker.getSymbolAtLocation(importNode.name); + if (symbol) { + const originalSymbol = context.checker.getAliasedSymbol(symbol); + return originalSymbol?.declarations?.map(d => d.getSourceFile()).some(isSourceFileFromHelpers) ?? false; + } + } + + return false; +} diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index 565c0625b..dc8dd9cd8 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -2,8 +2,9 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import { transformBuiltinIdentifierExpression } from "../builtins"; import { FunctionVisitor, TransformationContext } from "../context"; +import { isTupleHelperType } from "../helpers/tuple"; import { isForRangeType } from "../utils/annotations"; -import { InvalidForRangeCall } from "../utils/errors"; +import { InvalidForRangeCall, InvalidTupleFunctionUse } from "../utils/errors"; import { createExportedIdentifier, getIdentifierExportScope } from "../utils/export"; import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names"; import { getIdentifierSymbolId } from "../utils/symbols"; @@ -20,6 +21,10 @@ export function transformIdentifier(context: TransformationContext, identifier: } } + if (isTupleHelperType(context, identifier)) { + throw InvalidTupleFunctionUse(identifier); + } + const text = hasUnsafeIdentifierName(context, identifier) ? createSafeName(identifier.text) : identifier.text; const symbolId = getIdentifierSymbolId(context, identifier); diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 281f70ec6..81f2fa71c 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import { FunctionVisitor, TransformationContext, Visitors } from "../context"; -import { InvalidAmbientIdentifierName, UnsupportedKind } from "../utils/errors"; +import { InvalidAmbientIdentifierName, UnsupportedKind, InvalidTupleFunctionUse } from "../utils/errors"; import { createExportedIdentifier, getSymbolExportScope } from "../utils/export"; import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { @@ -14,6 +14,7 @@ import { import { getSymbolIdOfSymbol, trackSymbolReference } from "../utils/symbols"; import { isArrayType } from "../utils/typescript"; import { transformFunctionLikeDeclaration } from "./function"; +import { isTupleHelperType } from "../helpers/tuple"; // TODO: Move to object-literal.ts? export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression { @@ -60,6 +61,10 @@ export function createShorthandIdentifier( return identifier; } +function resolveSymbolDeclaration(symbol: ts.Symbol): ts.Declaration | undefined { + return symbol?.declarations.find(d => d); +} + const transformObjectLiteralExpression: FunctionVisitor = (expression, context) => { let properties: lua.TableFieldExpression[] = []; const tableExpressions: lua.Expression[] = []; @@ -73,6 +78,11 @@ const transformObjectLiteralExpression: FunctionVisitor Date: Fri, 31 Jan 2020 16:44:12 +1000 Subject: [PATCH 04/66] Use Visitors --- src/transformation/helpers/tuple.ts | 57 ++++++++++++++++--- src/transformation/index.ts | 3 +- src/transformation/utils/symbols.ts | 4 ++ src/transformation/visitors/identifier.ts | 7 +-- src/transformation/visitors/literal.ts | 12 +--- src/transformation/visitors/return.ts | 5 -- .../visitors/variable-declaration.ts | 5 -- 7 files changed, 57 insertions(+), 36 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index dff9b211f..d72541220 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -1,25 +1,27 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import * as utils from "../utils/helpers"; -import { TransformationContext } from "../context"; +import { TransformationContext, Visitors } from "../context"; import { transformArrayBindingElement } from "../visitors/variable-declaration"; import { transformArguments } from "../visitors/call"; +import { InvalidTupleFunctionUse } from '../utils/errors'; +import { resolveSymbolDeclaration } from '../utils/symbols'; -export type VariableDeclarationWithCall = ts.VariableDeclaration & { +type VariableDeclarationWithCall = ts.VariableDeclaration & { initializer: ts.CallExpression; }; -export type ReturnStatementWithCall = ts.ReturnStatement & { +type ReturnStatementWithCall = ts.ReturnStatement & { expression: ts.CallExpression; }; -export function isTupleHelperType(context: TransformationContext, identifier: ts.Node): boolean { +function isTupleHelperType(context: TransformationContext, identifier: ts.Node): boolean { const type = context.checker.getTypeAtLocation(identifier); const sourceFiles = type.symbol?.declarations?.map(d => d.getSourceFile()); return sourceFiles ? sourceFiles.some(file => utils.getHelperFileKind(file) === utils.HelperKind.Tuple) : false; } -export function isTupleHelperReturnStatement( +function isTupleHelperReturnStatement( context: TransformationContext, statement: ts.ReturnStatement ): statement is ReturnStatementWithCall { @@ -34,7 +36,7 @@ export function isTupleHelperReturnStatement( return isTupleHelperType(context, statement.expression.expression); } -export function transformTupleHelperReturnStatement( +function transformTupleHelperReturnStatement( context: TransformationContext, statement: ReturnStatementWithCall ): lua.Statement { @@ -42,7 +44,7 @@ export function transformTupleHelperReturnStatement( return lua.createReturnStatement(expressions, statement); } -export function isTupleHelperVariableDeclaration( +function isTupleHelperVariableDeclaration( context: TransformationContext, declaration: ts.VariableDeclaration ): declaration is VariableDeclarationWithCall { @@ -57,7 +59,7 @@ export function isTupleHelperVariableDeclaration( return isTupleHelperType(context, declaration.initializer); } -export function transformTupleHelperVariableDeclaration( +function transformTupleHelperVariableDeclaration( context: TransformationContext, declaration: VariableDeclarationWithCall ): lua.Statement[] { @@ -69,3 +71,42 @@ export function transformTupleHelperVariableDeclaration( const rightExpressions = declaration.initializer.arguments.map(e => context.transformExpression(e)); return [lua.createVariableDeclarationStatement(leftIdentifiers, rightExpressions, declaration)]; } + +export const tupleVisitors: Visitors = { + [ts.SyntaxKind.ImportSpecifier]: (node, context) => { + if (isTupleHelperType(context, node)) { + return; + } + return context.superTransformNode(node); + }, + [ts.SyntaxKind.ReturnStatement]: (node, context) => { + if (isTupleHelperReturnStatement(context, node)) { + return transformTupleHelperReturnStatement(context, node); + } + return context.superTransformStatements(node); + }, + [ts.SyntaxKind.Identifier]: (node, context) => { + if (isTupleHelperType(context, node)) { + throw InvalidTupleFunctionUse(node); + } + return context.superTransformExpression(node); + }, + [ts.SyntaxKind.VariableDeclaration]: (node, context) => { + if (isTupleHelperVariableDeclaration(context, node)) { + return transformTupleHelperVariableDeclaration(context, node); + } + return context.superTransformNode(node); + }, + [ts.SyntaxKind.ObjectLiteralExpression]: (node, context) => { + node.properties.filter(ts.isShorthandPropertyAssignment).forEach(element => { + const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); + if (valueSymbol) { + const declaration = resolveSymbolDeclaration(valueSymbol); + if (declaration && isTupleHelperType(context, declaration)) { + throw InvalidTupleFunctionUse(element); + } + } + }); + return context.superTransformExpression(node); + } +}; diff --git a/src/transformation/index.ts b/src/transformation/index.ts index a720a8ffd..9b9064a31 100644 --- a/src/transformation/index.ts +++ b/src/transformation/index.ts @@ -6,6 +6,7 @@ import { ObjectVisitor, TransformationContext, VisitorMap, Visitors } from "./co import { TranspileError } from "./utils/errors"; import { getUsedLuaLibFeatures } from "./utils/lualib"; import { standardVisitors } from "./visitors"; +import { tupleVisitors } from './helpers/tuple'; const transpileErrorDiagnostic = (error: TranspileError): ts.Diagnostic => ({ file: error.node.getSourceFile(), @@ -19,7 +20,7 @@ const transpileErrorDiagnostic = (error: TranspileError): ts.Diagnostic => ({ export function createVisitorMap(customVisitors: Visitors[]): VisitorMap { const visitorMap: VisitorMap = new Map(); - for (const visitors of [standardVisitors, ...customVisitors]) { + for (const visitors of [standardVisitors, tupleVisitors, ...customVisitors]) { const priority = visitors === standardVisitors ? -Infinity : 0; for (const [syntaxKindKey, visitor] of Object.entries(visitors)) { if (!visitor) continue; diff --git a/src/transformation/utils/symbols.ts b/src/transformation/utils/symbols.ts index a08197c72..f8acfac12 100644 --- a/src/transformation/utils/symbols.ts +++ b/src/transformation/utils/symbols.ts @@ -68,3 +68,7 @@ export function getIdentifierSymbolId( return trackSymbolReference(context, symbol, identifier); } } + +export function resolveSymbolDeclaration(symbol: ts.Symbol): ts.Declaration | undefined { + return symbol?.declarations.find(d => d); +} diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index dc8dd9cd8..565c0625b 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -2,9 +2,8 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import { transformBuiltinIdentifierExpression } from "../builtins"; import { FunctionVisitor, TransformationContext } from "../context"; -import { isTupleHelperType } from "../helpers/tuple"; import { isForRangeType } from "../utils/annotations"; -import { InvalidForRangeCall, InvalidTupleFunctionUse } from "../utils/errors"; +import { InvalidForRangeCall } from "../utils/errors"; import { createExportedIdentifier, getIdentifierExportScope } from "../utils/export"; import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names"; import { getIdentifierSymbolId } from "../utils/symbols"; @@ -21,10 +20,6 @@ export function transformIdentifier(context: TransformationContext, identifier: } } - if (isTupleHelperType(context, identifier)) { - throw InvalidTupleFunctionUse(identifier); - } - const text = hasUnsafeIdentifierName(context, identifier) ? createSafeName(identifier.text) : identifier.text; const symbolId = getIdentifierSymbolId(context, identifier); diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 81f2fa71c..281f70ec6 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import { FunctionVisitor, TransformationContext, Visitors } from "../context"; -import { InvalidAmbientIdentifierName, UnsupportedKind, InvalidTupleFunctionUse } from "../utils/errors"; +import { InvalidAmbientIdentifierName, UnsupportedKind } from "../utils/errors"; import { createExportedIdentifier, getSymbolExportScope } from "../utils/export"; import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { @@ -14,7 +14,6 @@ import { import { getSymbolIdOfSymbol, trackSymbolReference } from "../utils/symbols"; import { isArrayType } from "../utils/typescript"; import { transformFunctionLikeDeclaration } from "./function"; -import { isTupleHelperType } from "../helpers/tuple"; // TODO: Move to object-literal.ts? export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression { @@ -61,10 +60,6 @@ export function createShorthandIdentifier( return identifier; } -function resolveSymbolDeclaration(symbol: ts.Symbol): ts.Declaration | undefined { - return symbol?.declarations.find(d => d); -} - const transformObjectLiteralExpression: FunctionVisitor = (expression, context) => { let properties: lua.TableFieldExpression[] = []; const tableExpressions: lua.Expression[] = []; @@ -78,11 +73,6 @@ const transformObjectLiteralExpression: FunctionVisitor = (statement, context) => { // Bubble up explicit return flag and check if we're inside a try/catch block @@ -21,10 +20,6 @@ export const transformReturnStatement: FunctionVisitor = (st insideTryCatch = insideTryCatch || scope.type === ScopeType.Try || scope.type === ScopeType.Catch; } - if (isTupleHelperReturnStatement(context, statement)) { - return transformTupleHelperReturnStatement(context, statement); - } - let results: lua.Expression[]; if (statement.expression) { diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index d71ecc766..95b7a5c24 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,7 +10,6 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; -import { transformTupleHelperVariableDeclaration, isTupleHelperVariableDeclaration } from "../helpers/tuple"; export function transformArrayBindingElement( context: TransformationContext, @@ -212,10 +211,6 @@ export function transformVariableDeclaration( context: TransformationContext, statement: ts.VariableDeclaration ): lua.Statement[] { - if (isTupleHelperVariableDeclaration(context, statement)) { - return transformTupleHelperVariableDeclaration(context, statement); - } - if (statement.initializer && statement.type) { const initializerType = context.checker.getTypeAtLocation(statement.initializer); const varType = context.checker.getTypeFromTypeNode(statement.type); From 2618cd6dc8fc7aad9d470794c55831635b75b2a6 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Fri, 31 Jan 2020 17:03:35 +1000 Subject: [PATCH 05/66] Fix VariableDeclaration --- src/transformation/helpers/tuple.ts | 4 ++-- src/transformation/visitors/index.ts | 3 ++- src/transformation/visitors/loops/for-of.ts | 3 +-- src/transformation/visitors/loops/for.ts | 4 ++-- src/transformation/visitors/variable-declaration.ts | 10 +++------- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index d72541220..51b5db7dd 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -51,12 +51,12 @@ function isTupleHelperVariableDeclaration( if (!declaration.initializer) { return false; } - + if (!ts.isCallExpression(declaration.initializer)) { return false; } - return isTupleHelperType(context, declaration.initializer); + return isTupleHelperType(context, declaration.initializer.expression); } function transformTupleHelperVariableDeclaration( diff --git a/src/transformation/visitors/index.ts b/src/transformation/visitors/index.ts index 17b25ffd7..5566614d4 100644 --- a/src/transformation/visitors/index.ts +++ b/src/transformation/visitors/index.ts @@ -39,7 +39,7 @@ import { transformTaggedTemplateExpression, transformTemplateExpression } from " import { transformTypeOfExpression } from "./typeof"; import { typescriptVisitors } from "./typescript"; import { transformPostfixUnaryExpression, transformPrefixUnaryExpression } from "./unary-expression"; -import { transformVariableStatement } from "./variable-declaration"; +import { transformVariableStatement, transformVariableDeclaration } from "./variable-declaration"; const transformEmptyStatement: FunctionVisitor = () => undefined; const transformParenthesizedExpression: FunctionVisitor = (node, context) => @@ -93,6 +93,7 @@ export const standardVisitors: Visitors = { [ts.SyntaxKind.ThrowStatement]: transformThrowStatement, [ts.SyntaxKind.TryStatement]: transformTryStatement, [ts.SyntaxKind.TypeOfExpression]: transformTypeOfExpression, + [ts.SyntaxKind.VariableDeclaration]: transformVariableDeclaration, [ts.SyntaxKind.VariableStatement]: transformVariableStatement, [ts.SyntaxKind.WhileStatement]: transformWhileStatement, [ts.SyntaxKind.YieldExpression]: transformYieldExpression, diff --git a/src/transformation/visitors/loops/for-of.ts b/src/transformation/visitors/loops/for-of.ts index a4df8f1d7..09890cb2a 100644 --- a/src/transformation/visitors/loops/for-of.ts +++ b/src/transformation/visitors/loops/for-of.ts @@ -17,7 +17,6 @@ import { transformIdentifier } from "../identifier"; import { transformBindingPattern, transformArrayBindingElement, - transformVariableDeclaration, } from "../variable-declaration"; import { getVariableDeclarationBinding, transformLoopBody } from "./utils"; @@ -40,7 +39,7 @@ function transformForOfInitializer( throw UnsupportedObjectDestructuringInForOf(initializer); } - const variableStatements = transformVariableDeclaration(context, initializer.declarations[0]); + const variableStatements = context.transformNode(initializer.declarations[0]) as lua.Statement[]; if (variableStatements[0]) { // we can safely assume that for vars are not exported and therefore declarationstatenents return [ diff --git a/src/transformation/visitors/loops/for.ts b/src/transformation/visitors/loops/for.ts index 970c2f045..41f0fe0eb 100644 --- a/src/transformation/visitors/loops/for.ts +++ b/src/transformation/visitors/loops/for.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import * as lua from "../../../LuaAST"; import { FunctionVisitor } from "../../context"; -import { checkVariableDeclarationList, transformVariableDeclaration } from "../variable-declaration"; +import { checkVariableDeclarationList } from "../variable-declaration"; import { transformLoopBody } from "./utils"; export const transformForStatement: FunctionVisitor = (statement, context) => { @@ -11,7 +11,7 @@ export const transformForStatement: FunctionVisitor = (statemen if (ts.isVariableDeclarationList(statement.initializer)) { checkVariableDeclarationList(statement.initializer); // local initializer = value - result.push(...statement.initializer.declarations.flatMap(d => transformVariableDeclaration(context, d))); + result.push(...statement.initializer.declarations.flatMap(d => context.transformNode(d) as lua.Statement[])); } else { result.push(...context.transformStatements(ts.createExpressionStatement(statement.initializer))); } diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 95b7a5c24..2ef02ad60 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -206,11 +206,7 @@ export function transformBindingVariableDeclaration( return statements; } -// TODO: FunctionVisitor -export function transformVariableDeclaration( - context: TransformationContext, - statement: ts.VariableDeclaration -): lua.Statement[] { +export const transformVariableDeclaration: FunctionVisitor = (statement, context) => { if (statement.initializer && statement.type) { const initializerType = context.checker.getTypeAtLocation(statement.initializer); const varType = context.checker.getTypeFromTypeNode(statement.type); @@ -227,7 +223,7 @@ export function transformVariableDeclaration( } else { return assertNever(statement.name); } -} +}; export function checkVariableDeclarationList(node: ts.VariableDeclarationList): void { if ((node.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) === 0) { @@ -237,5 +233,5 @@ export function checkVariableDeclarationList(node: ts.VariableDeclarationList): export const transformVariableStatement: FunctionVisitor = (node, context) => { checkVariableDeclarationList(node.declarationList); - return node.declarationList.declarations.flatMap(declaration => transformVariableDeclaration(context, declaration)); + return node.declarationList.declarations.flatMap(declaration => context.transformNode(declaration) as lua.Statement[]); }; From 0de51fbd18b7c72261f1dd202d1a652f549eb138 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 12:43:01 +1000 Subject: [PATCH 06/66] Implement tuple helper --- src/transformation/helpers/tuple.ts | 65 ++++++++++++------- src/transformation/index.ts | 2 +- src/transformation/utils/symbols.ts | 4 +- src/transformation/visitors/loops/for-of.ts | 5 +- src/transformation/visitors/loops/for.ts | 4 +- .../visitors/variable-declaration.ts | 4 +- 6 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index 51b5db7dd..7ac99ea38 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -4,8 +4,8 @@ import * as utils from "../utils/helpers"; import { TransformationContext, Visitors } from "../context"; import { transformArrayBindingElement } from "../visitors/variable-declaration"; import { transformArguments } from "../visitors/call"; -import { InvalidTupleFunctionUse } from '../utils/errors'; -import { resolveSymbolDeclaration } from '../utils/symbols'; +import { InvalidTupleFunctionUse } from "../utils/errors"; +import { isSymbolAlias } from "../utils/symbols"; type VariableDeclarationWithCall = ts.VariableDeclaration & { initializer: ts.CallExpression; @@ -15,10 +15,23 @@ type ReturnStatementWithCall = ts.ReturnStatement & { expression: ts.CallExpression; }; -function isTupleHelperType(context: TransformationContext, identifier: ts.Node): boolean { - const type = context.checker.getTypeAtLocation(identifier); - const sourceFiles = type.symbol?.declarations?.map(d => d.getSourceFile()); - return sourceFiles ? sourceFiles.some(file => utils.getHelperFileKind(file) === utils.HelperKind.Tuple) : false; +function isTupleHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { + const type = context.checker.getTypeAtLocation(expression.expression); + return type.symbol?.declarations?.some(isTupleHelperDeclaration) ? true : false; +} + +function isTupleReturningCallExpression(context: TransformationContext, expression: ts.CallExpression): boolean { + const signature = context.checker.getResolvedSignature(expression); + return signature?.getReturnType().aliasSymbol?.declarations?.some(isTupleHelperDeclaration) ? true : false; +} + +function isTupleHelperDeclaration(declaration: ts.Declaration): boolean { + return utils.getHelperFileKind(declaration.getSourceFile()) === utils.HelperKind.Tuple; +} + +function isTupleHelperNode(context: TransformationContext, node: ts.Node): boolean { + const type = context.checker.getTypeAtLocation(node); + return type.symbol?.declarations?.some(isTupleHelperDeclaration) ? true : false; } function isTupleHelperReturnStatement( @@ -33,15 +46,7 @@ function isTupleHelperReturnStatement( return false; } - return isTupleHelperType(context, statement.expression.expression); -} - -function transformTupleHelperReturnStatement( - context: TransformationContext, - statement: ReturnStatementWithCall -): lua.Statement { - const expressions = transformArguments(context, statement.expression.arguments); - return lua.createReturnStatement(expressions, statement); + return isTupleHelperCallSignature(context, statement.expression); } function isTupleHelperVariableDeclaration( @@ -51,12 +56,20 @@ function isTupleHelperVariableDeclaration( if (!declaration.initializer) { return false; } - + if (!ts.isCallExpression(declaration.initializer)) { return false; } - return isTupleHelperType(context, declaration.initializer.expression); + return isTupleReturningCallExpression(context, declaration.initializer); +} + +function transformTupleHelperReturnStatement( + context: TransformationContext, + statement: ReturnStatementWithCall +): lua.Statement { + const expressions = transformArguments(context, statement.expression.arguments); + return lua.createReturnStatement(expressions, statement); } function transformTupleHelperVariableDeclaration( @@ -64,17 +77,19 @@ function transformTupleHelperVariableDeclaration( declaration: VariableDeclarationWithCall ): lua.Statement[] { if (!ts.isArrayBindingPattern(declaration.name)) { - throw Error(); + throw InvalidTupleFunctionUse(declaration.name); } const leftIdentifiers = declaration.name.elements.map(e => transformArrayBindingElement(context, e)); - const rightExpressions = declaration.initializer.arguments.map(e => context.transformExpression(e)); + const rightExpressions = isTupleHelperCallSignature(context, declaration.initializer) + ? declaration.initializer.arguments.map(e => context.transformExpression(e)) + : context.transformExpression(declaration.initializer); return [lua.createVariableDeclarationStatement(leftIdentifiers, rightExpressions, declaration)]; } export const tupleVisitors: Visitors = { [ts.SyntaxKind.ImportSpecifier]: (node, context) => { - if (isTupleHelperType(context, node)) { + if (isTupleHelperNode(context, node)) { return; } return context.superTransformNode(node); @@ -86,7 +101,7 @@ export const tupleVisitors: Visitors = { return context.superTransformStatements(node); }, [ts.SyntaxKind.Identifier]: (node, context) => { - if (isTupleHelperType(context, node)) { + if (isTupleHelperNode(context, node)) { throw InvalidTupleFunctionUse(node); } return context.superTransformExpression(node); @@ -100,13 +115,13 @@ export const tupleVisitors: Visitors = { [ts.SyntaxKind.ObjectLiteralExpression]: (node, context) => { node.properties.filter(ts.isShorthandPropertyAssignment).forEach(element => { const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); - if (valueSymbol) { - const declaration = resolveSymbolDeclaration(valueSymbol); - if (declaration && isTupleHelperType(context, declaration)) { + if (valueSymbol && isSymbolAlias(valueSymbol)) { + const declaration = context.checker.getAliasedSymbol(valueSymbol).valueDeclaration; + if (declaration && isTupleHelperDeclaration(declaration)) { throw InvalidTupleFunctionUse(element); } } }); return context.superTransformExpression(node); - } + }, }; diff --git a/src/transformation/index.ts b/src/transformation/index.ts index 9b9064a31..1aacd9363 100644 --- a/src/transformation/index.ts +++ b/src/transformation/index.ts @@ -6,7 +6,7 @@ import { ObjectVisitor, TransformationContext, VisitorMap, Visitors } from "./co import { TranspileError } from "./utils/errors"; import { getUsedLuaLibFeatures } from "./utils/lualib"; import { standardVisitors } from "./visitors"; -import { tupleVisitors } from './helpers/tuple'; +import { tupleVisitors } from "./helpers/tuple"; const transpileErrorDiagnostic = (error: TranspileError): ts.Diagnostic => ({ file: error.node.getSourceFile(), diff --git a/src/transformation/utils/symbols.ts b/src/transformation/utils/symbols.ts index e7643dd15..123a8c5f3 100644 --- a/src/transformation/utils/symbols.ts +++ b/src/transformation/utils/symbols.ts @@ -59,6 +59,6 @@ export function getIdentifierSymbolId( } } -export function resolveSymbolDeclaration(symbol: ts.Symbol): ts.Declaration | undefined { - return symbol?.declarations.find(d => d); +export function isSymbolAlias(symbol: ts.Symbol): boolean { + return (symbol.flags & ts.SymbolFlags.Alias) !== 0; } diff --git a/src/transformation/visitors/loops/for-of.ts b/src/transformation/visitors/loops/for-of.ts index 09890cb2a..6d06c4063 100644 --- a/src/transformation/visitors/loops/for-of.ts +++ b/src/transformation/visitors/loops/for-of.ts @@ -14,10 +14,7 @@ import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib"; import { isArrayType, isNumberType } from "../../utils/typescript"; import { transformArguments } from "../call"; import { transformIdentifier } from "../identifier"; -import { - transformBindingPattern, - transformArrayBindingElement, -} from "../variable-declaration"; +import { transformBindingPattern, transformArrayBindingElement } from "../variable-declaration"; import { getVariableDeclarationBinding, transformLoopBody } from "./utils"; function transformForOfInitializer( diff --git a/src/transformation/visitors/loops/for.ts b/src/transformation/visitors/loops/for.ts index 41f0fe0eb..1ef586ac7 100644 --- a/src/transformation/visitors/loops/for.ts +++ b/src/transformation/visitors/loops/for.ts @@ -11,7 +11,9 @@ export const transformForStatement: FunctionVisitor = (statemen if (ts.isVariableDeclarationList(statement.initializer)) { checkVariableDeclarationList(statement.initializer); // local initializer = value - result.push(...statement.initializer.declarations.flatMap(d => context.transformNode(d) as lua.Statement[])); + result.push( + ...statement.initializer.declarations.flatMap(d => context.transformNode(d) as lua.Statement[]) + ); } else { result.push(...context.transformStatements(ts.createExpressionStatement(statement.initializer))); } diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 2ef02ad60..aaca8ceb9 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -233,5 +233,7 @@ export function checkVariableDeclarationList(node: ts.VariableDeclarationList): export const transformVariableStatement: FunctionVisitor = (node, context) => { checkVariableDeclarationList(node.declarationList); - return node.declarationList.declarations.flatMap(declaration => context.transformNode(declaration) as lua.Statement[]); + return node.declarationList.declarations.flatMap( + declaration => context.transformNode(declaration) as lua.Statement[] + ); }; From fd878e58d53571a923b4ec862fbd08441e6a5bb2 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 13:15:41 +1000 Subject: [PATCH 07/66] Use nil if no arguments were provided to tuple --- src/transpilation/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index 530fbdd4c..78e898077 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -55,6 +55,16 @@ export function createVirtualProgram(input: Record, options: Com return ts.createSourceFile(filename, input[filename], ts.ScriptTarget.Latest, false); } + if (filename.includes("typescript-to-lua")) { + const filePath = path.resolve(__dirname, "../helpers/tuple.ts"); + if (libCache[filename]) return libCache[filename]; + const content = fs.readFileSync(filePath, "utf8"); + + libCache[filename] = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, false); + + return libCache[filename]; + } + if (filename.startsWith("lib.")) { if (libCache[filename]) return libCache[filename]; const typeScriptDir = path.dirname(require.resolve("typescript")); From 82c45cf12330d734cec897eea1c3c9c8057fd346 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 13:40:43 +1000 Subject: [PATCH 08/66] Add destructuring assignment support --- src/transformation/helpers/tuple.ts | 58 +++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index 7ac99ea38..b4dbd0e9b 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -2,6 +2,7 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import * as utils from "../utils/helpers"; import { TransformationContext, Visitors } from "../context"; +import { transformAssignmentLeftHandSideExpression } from '../visitors/binary-expression/assignments'; import { transformArrayBindingElement } from "../visitors/variable-declaration"; import { transformArguments } from "../visitors/call"; import { InvalidTupleFunctionUse } from "../utils/errors"; @@ -15,6 +16,13 @@ type ReturnStatementWithCall = ts.ReturnStatement & { expression: ts.CallExpression; }; +type ExpressionStatementWithDestructuringAssignment = ts.ExpressionStatement & { + expression: { + left: ts.Expression; + right: ts.CallExpression; + }; +}; + function isTupleHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); return type.symbol?.declarations?.some(isTupleHelperDeclaration) ? true : false; @@ -64,6 +72,25 @@ function isTupleHelperVariableDeclaration( return isTupleReturningCallExpression(context, declaration.initializer); } +function isTupleHelperDestructuringAssignmentStatement( + context: TransformationContext, + statement: ts.ExpressionStatement +): statement is ExpressionStatementWithDestructuringAssignment { + if (!ts.isBinaryExpression(statement.expression)) { + return false; + } + + if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { + return false; + } + + if (!ts.isCallExpression(statement.expression.right)) { + return false; + } + + return isTupleReturningCallExpression(context, statement.expression.right); +} + function transformTupleHelperReturnStatement( context: TransformationContext, statement: ReturnStatementWithCall @@ -72,6 +99,14 @@ function transformTupleHelperReturnStatement( return lua.createReturnStatement(expressions, statement); } +function transformTupleCallArguments(context: TransformationContext, expression: ts.CallExpression): lua.Expression[] | lua.Expression { + return isTupleHelperCallSignature(context, expression) + ? expression.arguments.length > 0 + ? expression.arguments.map(e => context.transformExpression(e)) + : lua.createNilLiteral(expression) + : context.transformExpression(expression); +} + function transformTupleHelperVariableDeclaration( context: TransformationContext, declaration: VariableDeclarationWithCall @@ -81,12 +116,23 @@ function transformTupleHelperVariableDeclaration( } const leftIdentifiers = declaration.name.elements.map(e => transformArrayBindingElement(context, e)); - const rightExpressions = isTupleHelperCallSignature(context, declaration.initializer) - ? declaration.initializer.arguments.map(e => context.transformExpression(e)) - : context.transformExpression(declaration.initializer); + const rightExpressions = transformTupleCallArguments(context, declaration.initializer); return [lua.createVariableDeclarationStatement(leftIdentifiers, rightExpressions, declaration)]; } +function transformTupleHelperDestructuringAssignmentStatement( + context: TransformationContext, + statement: ExpressionStatementWithDestructuringAssignment +): lua.Statement { + if (!ts.isArrayLiteralExpression(statement.expression.left)) { + throw InvalidTupleFunctionUse(statement.expression.left); + } + + const leftIdentifiers = statement.expression.left.elements.map(a => transformAssignmentLeftHandSideExpression(context, a)); + const rightExpressions = transformTupleCallArguments(context, statement.expression.right); + return lua.createAssignmentStatement(leftIdentifiers, rightExpressions, statement); +} + export const tupleVisitors: Visitors = { [ts.SyntaxKind.ImportSpecifier]: (node, context) => { if (isTupleHelperNode(context, node)) { @@ -124,4 +170,10 @@ export const tupleVisitors: Visitors = { }); return context.superTransformExpression(node); }, + [ts.SyntaxKind.ExpressionStatement]: (node, context) => { + if (isTupleHelperDestructuringAssignmentStatement(context, node)) { + return transformTupleHelperDestructuringAssignmentStatement(context, node); + } + return context.superTransformStatements(node); + }, }; From 9b252429d22722bd2b15a854ac506f2c79386a34 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 14:51:12 +1000 Subject: [PATCH 09/66] Support side effects and add more validation --- src/transformation/helpers/tuple.ts | 71 +++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index b4dbd0e9b..1aab12770 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -2,11 +2,12 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import * as utils from "../utils/helpers"; import { TransformationContext, Visitors } from "../context"; -import { transformAssignmentLeftHandSideExpression } from '../visitors/binary-expression/assignments'; +import { transformAssignmentLeftHandSideExpression } from "../visitors/binary-expression/assignments"; import { transformArrayBindingElement } from "../visitors/variable-declaration"; import { transformArguments } from "../visitors/call"; import { InvalidTupleFunctionUse } from "../utils/errors"; import { isSymbolAlias } from "../utils/symbols"; +import { getDependenciesOfSymbol, createExportedIdentifier } from "../utils/export"; type VariableDeclarationWithCall = ts.VariableDeclaration & { initializer: ts.CallExpression; @@ -99,7 +100,10 @@ function transformTupleHelperReturnStatement( return lua.createReturnStatement(expressions, statement); } -function transformTupleCallArguments(context: TransformationContext, expression: ts.CallExpression): lua.Expression[] | lua.Expression { +function transformTupleCallArguments( + context: TransformationContext, + expression: ts.CallExpression +): lua.Expression[] | lua.Expression { return isTupleHelperCallSignature(context, expression) ? expression.arguments.length > 0 ? expression.arguments.map(e => context.transformExpression(e)) @@ -107,6 +111,18 @@ function transformTupleCallArguments(context: TransformationContext, expression: : context.transformExpression(expression); } +function isSimpleArrayBindingElement(element: ts.ArrayBindingElement): boolean { + if (ts.isOmittedExpression(element)) { + return true; + } + + if (ts.isBindingElement(element) && !element.initializer) { + return true; + } + + return false; +} + function transformTupleHelperVariableDeclaration( context: TransformationContext, declaration: VariableDeclarationWithCall @@ -115,22 +131,67 @@ function transformTupleHelperVariableDeclaration( throw InvalidTupleFunctionUse(declaration.name); } + if (declaration.name.elements.length < 1) { + throw InvalidTupleFunctionUse(declaration.name); + } + + if (!declaration.name.elements.every(isSimpleArrayBindingElement)) { + throw InvalidTupleFunctionUse(declaration.name); + } + const leftIdentifiers = declaration.name.elements.map(e => transformArrayBindingElement(context, e)); const rightExpressions = transformTupleCallArguments(context, declaration.initializer); return [lua.createVariableDeclarationStatement(leftIdentifiers, rightExpressions, declaration)]; } +function isSimpleLeftHandSideDestructuringExpression(expression: ts.Expression): boolean { + if (ts.isBinaryExpression(expression)) { + return false; + } + + return true; +} + function transformTupleHelperDestructuringAssignmentStatement( context: TransformationContext, statement: ExpressionStatementWithDestructuringAssignment -): lua.Statement { +): lua.Statement[] { if (!ts.isArrayLiteralExpression(statement.expression.left)) { throw InvalidTupleFunctionUse(statement.expression.left); } - const leftIdentifiers = statement.expression.left.elements.map(a => transformAssignmentLeftHandSideExpression(context, a)); + if (statement.expression.left.elements.length < 1) { + throw InvalidTupleFunctionUse(statement.expression.left); + } + + if (!statement.expression.left.elements.every(isSimpleLeftHandSideDestructuringExpression)) { + throw InvalidTupleFunctionUse(statement.expression.left); + } + + const transformLeft = (expression: ts.Expression): lua.AssignmentLeftHandSideExpression => { + if (ts.isOmittedExpression(expression)) { + return lua.createAnonymousIdentifier(expression); + } else { + return transformAssignmentLeftHandSideExpression(context, expression); + } + }; + + const leftIdentifiers = statement.expression.left.elements.map(transformLeft); + const rightExpressions = transformTupleCallArguments(context, statement.expression.right); - return lua.createAssignmentStatement(leftIdentifiers, rightExpressions, statement); + + const trailingStatements = statement.expression.left.elements + .map(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)); + }); + }) + .flat(); + + return [lua.createAssignmentStatement(leftIdentifiers, rightExpressions, statement), ...trailingStatements]; } export const tupleVisitors: Visitors = { From 68b7c66220a9c6324d9110353bdcb6bcfc57059d Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 14:51:19 +1000 Subject: [PATCH 10/66] Add unit tests --- test/unit/helpers/tuple.spec.ts | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 test/unit/helpers/tuple.spec.ts diff --git a/test/unit/helpers/tuple.spec.ts b/test/unit/helpers/tuple.spec.ts new file mode 100644 index 000000000..44d1d500a --- /dev/null +++ b/test/unit/helpers/tuple.spec.ts @@ -0,0 +1,57 @@ +import * as util from "../../util"; +import { InvalidTupleFunctionUse } from "../../../src/transformation/utils/errors"; + +test.each<[string, any]>([ + ["let a; [a] = tuple();", undefined], + ["const [a] = tuple();", undefined], + ["const [a] = tuple(1);", 1], + ["const ar = [1]; const [a] = tuple(...ar);", 1], +])("valid tuple function assignment (%s)", (statement, result) => { + util.testModule` + import { tuple } from "typescript-to-lua"; + ${statement} + // @ts-ignore + return a; + `.expectToEqual(result); +}); + +test.each([ + "tuple", + "tuple()", + "({ tuple });", + "[] = tuple()", + "const [] = tuple();", + "const a = tuple();", + "const {} = tuple();", + "let a; ([a] = tuple(1)) => {}", +])("invalid tuple function assignment (%s)", statement => { + util.testModule` + import { tuple } from "typescript-to-lua"; + ${statement} + `.expectToHaveDiagnosticOfError(InvalidTupleFunctionUse(util.nodeStub)); +}); + +test.each<[string, any]>([ + ["return tuple();", undefined], + ["return tuple(1);", 1], +])("valid tuple function return statement (%s)", (statement, result) => { + util.testModule` + import { tuple } from "typescript-to-lua"; + const [a] = (function() { + ${statement} + })(); + // @ts-ignore + return a; + `.expectToEqual(result); +}); + +test("tuple destructuring assignment side effects", () => { + util.testModule` + import { tuple } from "typescript-to-lua"; + let a, b; + export { a }; + [a] = tuple(1); + // @ts-ignore + return a; + `.expectToEqual(1); +}); From db3d4877d8dfc643ef94a1d4c2cd7149f603bda0 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 14:55:04 +1000 Subject: [PATCH 11/66] Add test for tuple within multiple VariableDeclarations --- test/unit/helpers/tuple.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/helpers/tuple.spec.ts b/test/unit/helpers/tuple.spec.ts index 44d1d500a..ec88eb8e2 100644 --- a/test/unit/helpers/tuple.spec.ts +++ b/test/unit/helpers/tuple.spec.ts @@ -6,6 +6,7 @@ test.each<[string, any]>([ ["const [a] = tuple();", undefined], ["const [a] = tuple(1);", 1], ["const ar = [1]; const [a] = tuple(...ar);", 1], + ["const _ = null, [a] = tuple(1);", 1], ])("valid tuple function assignment (%s)", (statement, result) => { util.testModule` import { tuple } from "typescript-to-lua"; From 76795212cf9382bb4189b17a58fedd2aab77376b Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 14:58:56 +1000 Subject: [PATCH 12/66] Can be used inside for loops --- test/unit/helpers/tuple.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/helpers/tuple.spec.ts b/test/unit/helpers/tuple.spec.ts index ec88eb8e2..7ceec0f22 100644 --- a/test/unit/helpers/tuple.spec.ts +++ b/test/unit/helpers/tuple.spec.ts @@ -7,6 +7,8 @@ test.each<[string, any]>([ ["const [a] = tuple(1);", 1], ["const ar = [1]; const [a] = tuple(...ar);", 1], ["const _ = null, [a] = tuple(1);", 1], + ["let a; for (const [a] = tuple(1, 2); false; 1) {}", undefined], + ["let a; for ([a] = tuple(1, 2); false; 1) {}", 1], ])("valid tuple function assignment (%s)", (statement, result) => { util.testModule` import { tuple } from "typescript-to-lua"; From f16c80378d71cd60de746ab911aa5e089b660de8 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 15:22:29 +1000 Subject: [PATCH 13/66] Fix syntax error in test case --- test/unit/helpers/tuple.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/helpers/tuple.spec.ts b/test/unit/helpers/tuple.spec.ts index 7ceec0f22..bebe0a635 100644 --- a/test/unit/helpers/tuple.spec.ts +++ b/test/unit/helpers/tuple.spec.ts @@ -26,7 +26,7 @@ test.each([ "const [] = tuple();", "const a = tuple();", "const {} = tuple();", - "let a; ([a] = tuple(1)) => {}", + "([a] = tuple(1)) => {}", ])("invalid tuple function assignment (%s)", statement => { util.testModule` import { tuple } from "typescript-to-lua"; From 8be32211fc5d1e690cdf28c174a80a6dbc4c166a Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 20:12:51 +1000 Subject: [PATCH 14/66] Remove unnecessary boilerplate from tuple helper --- src/transformation/helpers/tuple.ts | 126 +++++++++++----------------- 1 file changed, 48 insertions(+), 78 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index 1aab12770..470824c44 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -9,21 +9,6 @@ import { InvalidTupleFunctionUse } from "../utils/errors"; import { isSymbolAlias } from "../utils/symbols"; import { getDependenciesOfSymbol, createExportedIdentifier } from "../utils/export"; -type VariableDeclarationWithCall = ts.VariableDeclaration & { - initializer: ts.CallExpression; -}; - -type ReturnStatementWithCall = ts.ReturnStatement & { - expression: ts.CallExpression; -}; - -type ExpressionStatementWithDestructuringAssignment = ts.ExpressionStatement & { - expression: { - left: ts.Expression; - right: ts.CallExpression; - }; -}; - function isTupleHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); return type.symbol?.declarations?.some(isTupleHelperDeclaration) ? true : false; @@ -43,59 +28,22 @@ function isTupleHelperNode(context: TransformationContext, node: ts.Node): boole return type.symbol?.declarations?.some(isTupleHelperDeclaration) ? true : false; } -function isTupleHelperReturnStatement( +function transformTupleHelperReturnStatement( context: TransformationContext, statement: ts.ReturnStatement -): statement is ReturnStatementWithCall { +): lua.Statement | undefined { if (!statement.expression) { - return false; + return undefined; } if (!ts.isCallExpression(statement.expression)) { - return false; + return undefined; } - return isTupleHelperCallSignature(context, statement.expression); -} - -function isTupleHelperVariableDeclaration( - context: TransformationContext, - declaration: ts.VariableDeclaration -): declaration is VariableDeclarationWithCall { - if (!declaration.initializer) { - return false; + if (!isTupleHelperCallSignature(context, statement.expression)) { + return undefined; } - if (!ts.isCallExpression(declaration.initializer)) { - return false; - } - - return isTupleReturningCallExpression(context, declaration.initializer); -} - -function isTupleHelperDestructuringAssignmentStatement( - context: TransformationContext, - statement: ts.ExpressionStatement -): statement is ExpressionStatementWithDestructuringAssignment { - if (!ts.isBinaryExpression(statement.expression)) { - return false; - } - - if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { - return false; - } - - if (!ts.isCallExpression(statement.expression.right)) { - return false; - } - - return isTupleReturningCallExpression(context, statement.expression.right); -} - -function transformTupleHelperReturnStatement( - context: TransformationContext, - statement: ReturnStatementWithCall -): lua.Statement { const expressions = transformArguments(context, statement.expression.arguments); return lua.createReturnStatement(expressions, statement); } @@ -125,8 +73,20 @@ function isSimpleArrayBindingElement(element: ts.ArrayBindingElement): boolean { function transformTupleHelperVariableDeclaration( context: TransformationContext, - declaration: VariableDeclarationWithCall -): lua.Statement[] { + declaration: ts.VariableDeclaration +): lua.Statement[] | undefined { + if (!declaration.initializer) { + return undefined; + } + + if (!ts.isCallExpression(declaration.initializer)) { + return undefined; + } + + if (!isTupleReturningCallExpression(context, declaration.initializer)) { + return undefined; + } + if (!ts.isArrayBindingPattern(declaration.name)) { throw InvalidTupleFunctionUse(declaration.name); } @@ -154,8 +114,24 @@ function isSimpleLeftHandSideDestructuringExpression(expression: ts.Expression): function transformTupleHelperDestructuringAssignmentStatement( context: TransformationContext, - statement: ExpressionStatementWithDestructuringAssignment -): lua.Statement[] { + statement: ts.ExpressionStatement +): lua.Statement[] | undefined { + if (!ts.isBinaryExpression(statement.expression)) { + return undefined; + } + + if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { + return undefined; + } + + if (!ts.isCallExpression(statement.expression.right)) { + return undefined; + } + + if (!isTupleReturningCallExpression(context, statement.expression.right)) { + return undefined; + } + if (!ts.isArrayLiteralExpression(statement.expression.left)) { throw InvalidTupleFunctionUse(statement.expression.left); } @@ -202,10 +178,16 @@ export const tupleVisitors: Visitors = { return context.superTransformNode(node); }, [ts.SyntaxKind.ReturnStatement]: (node, context) => { - if (isTupleHelperReturnStatement(context, node)) { - return transformTupleHelperReturnStatement(context, node); - } - return context.superTransformStatements(node); + const result = transformTupleHelperReturnStatement(context, node); + return result ? result : context.superTransformStatements(node); + }, + [ts.SyntaxKind.VariableDeclaration]: (node, context) => { + const result = transformTupleHelperVariableDeclaration(context, node); + return result ? result : context.superTransformNode(node); + }, + [ts.SyntaxKind.ExpressionStatement]: (node, context) => { + const result = transformTupleHelperDestructuringAssignmentStatement(context, node); + return result ? result : context.superTransformStatements(node); }, [ts.SyntaxKind.Identifier]: (node, context) => { if (isTupleHelperNode(context, node)) { @@ -213,12 +195,6 @@ export const tupleVisitors: Visitors = { } return context.superTransformExpression(node); }, - [ts.SyntaxKind.VariableDeclaration]: (node, context) => { - if (isTupleHelperVariableDeclaration(context, node)) { - return transformTupleHelperVariableDeclaration(context, node); - } - return context.superTransformNode(node); - }, [ts.SyntaxKind.ObjectLiteralExpression]: (node, context) => { node.properties.filter(ts.isShorthandPropertyAssignment).forEach(element => { const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); @@ -231,10 +207,4 @@ export const tupleVisitors: Visitors = { }); return context.superTransformExpression(node); }, - [ts.SyntaxKind.ExpressionStatement]: (node, context) => { - if (isTupleHelperDestructuringAssignmentStatement(context, node)) { - return transformTupleHelperDestructuringAssignmentStatement(context, node); - } - return context.superTransformStatements(node); - }, }; From fa426487cbf01cc799bdb5c15240acc5c30c2868 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 20:15:31 +1000 Subject: [PATCH 15/66] Use some instead of every --- src/transformation/helpers/tuple.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index 470824c44..e439b8619 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -59,18 +59,6 @@ function transformTupleCallArguments( : context.transformExpression(expression); } -function isSimpleArrayBindingElement(element: ts.ArrayBindingElement): boolean { - if (ts.isOmittedExpression(element)) { - return true; - } - - if (ts.isBindingElement(element) && !element.initializer) { - return true; - } - - return false; -} - function transformTupleHelperVariableDeclaration( context: TransformationContext, declaration: ts.VariableDeclaration @@ -95,7 +83,7 @@ function transformTupleHelperVariableDeclaration( throw InvalidTupleFunctionUse(declaration.name); } - if (!declaration.name.elements.every(isSimpleArrayBindingElement)) { + if (declaration.name.elements.some(ts.isBinaryExpression)) { throw InvalidTupleFunctionUse(declaration.name); } From 1d87279cbb4276d00b64fe888dde4f6b3cdc4cb9 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 20:18:11 +1000 Subject: [PATCH 16/66] Use flatMap in tuple instead of map --- src/transformation/helpers/tuple.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index e439b8619..33f8aaf5c 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -144,16 +144,14 @@ function transformTupleHelperDestructuringAssignmentStatement( const rightExpressions = transformTupleCallArguments(context, statement.expression.right); - const trailingStatements = statement.expression.left.elements - .map(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)); - }); - }) - .flat(); + 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]; } From 2da03b29251de4bc733a7021189fe75e44643926 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 20:19:38 +1000 Subject: [PATCH 17/66] Use Boolean instead of ternary in tuple --- src/transformation/helpers/tuple.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index 33f8aaf5c..2ee157418 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -11,21 +11,21 @@ import { getDependenciesOfSymbol, createExportedIdentifier } from "../utils/expo function isTupleHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); - return type.symbol?.declarations?.some(isTupleHelperDeclaration) ? true : false; + return Boolean(type.symbol?.declarations?.some(isTupleHelperDeclaration)); } function isTupleReturningCallExpression(context: TransformationContext, expression: ts.CallExpression): boolean { const signature = context.checker.getResolvedSignature(expression); - return signature?.getReturnType().aliasSymbol?.declarations?.some(isTupleHelperDeclaration) ? true : false; -} - -function isTupleHelperDeclaration(declaration: ts.Declaration): boolean { - return utils.getHelperFileKind(declaration.getSourceFile()) === utils.HelperKind.Tuple; + return Boolean(signature?.getReturnType().aliasSymbol?.declarations?.some(isTupleHelperDeclaration)); } function isTupleHelperNode(context: TransformationContext, node: ts.Node): boolean { const type = context.checker.getTypeAtLocation(node); - return type.symbol?.declarations?.some(isTupleHelperDeclaration) ? true : false; + return Boolean(type.symbol?.declarations?.some(isTupleHelperDeclaration)); +} + +function isTupleHelperDeclaration(declaration: ts.Declaration): boolean { + return utils.getHelperFileKind(declaration.getSourceFile()) === utils.HelperKind.Tuple; } function transformTupleHelperReturnStatement( From 104708b4adbe31a477c5ce2d14f86a5a87bb8e41 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 20:29:48 +1000 Subject: [PATCH 18/66] Import helpers from /helpers --- {src/helpers => helpers}/index.ts | 0 {src/helpers => helpers}/tuple.ts | 0 package.json | 3 ++- src/index.ts | 1 - src/transformation/utils/helpers.ts | 2 +- src/transpilation/index.ts | 2 +- test/tsconfig.json | 3 ++- test/unit/helpers/tuple.spec.ts | 6 +++--- tsconfig.json | 2 +- 9 files changed, 10 insertions(+), 9 deletions(-) rename {src/helpers => helpers}/index.ts (100%) rename {src/helpers => helpers}/tuple.ts (100%) diff --git a/src/helpers/index.ts b/helpers/index.ts similarity index 100% rename from src/helpers/index.ts rename to helpers/index.ts diff --git a/src/helpers/tuple.ts b/helpers/tuple.ts similarity index 100% rename from src/helpers/tuple.ts rename to helpers/tuple.ts diff --git a/package.json b/package.json index f0bdfceda..9a3d564b8 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "files": [ "dist/**/*.js", "dist/**/*.lua", - "dist/**/*.ts" + "dist/**/*.ts", + "helpers/**/*.ts" ], "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/index.ts b/src/index.ts index c3e9af5e8..8b0159a9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,4 +8,3 @@ export * from "./LuaPrinter"; export * from "./transformation/context"; export { TranspileError } from "./transformation/utils/errors"; export * from "./transpilation"; -export * from "./helpers"; diff --git a/src/transformation/utils/helpers.ts b/src/transformation/utils/helpers.ts index 646a8e70e..0151c3905 100644 --- a/src/transformation/utils/helpers.ts +++ b/src/transformation/utils/helpers.ts @@ -7,7 +7,7 @@ export enum HelperKind { } function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { - const helperDirectory = path.resolve(__dirname, "../../helpers"); + const helperDirectory = path.resolve(__dirname, "../../../helpers"); const sourceFileDirectory = path.dirname(path.normalize(sourceFile.fileName)); return helperDirectory === sourceFileDirectory; } diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index 78e898077..d5aa3c02f 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -56,7 +56,7 @@ export function createVirtualProgram(input: Record, options: Com } if (filename.includes("typescript-to-lua")) { - const filePath = path.resolve(__dirname, "../helpers/tuple.ts"); + const filePath = path.resolve(__dirname, "../../helpers/tuple.ts"); if (libCache[filename]) return libCache[filename]; const content = fs.readFileSync(filePath, "utf8"); diff --git a/test/tsconfig.json b/test/tsconfig.json index 5388f9434..55c585f7c 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -21,6 +21,7 @@ "cli/watch", "transpile/directories", "transpile/outFile", - "../src/lualib" + "../src/lualib", + "../helpers" ] } diff --git a/test/unit/helpers/tuple.spec.ts b/test/unit/helpers/tuple.spec.ts index bebe0a635..3523b301c 100644 --- a/test/unit/helpers/tuple.spec.ts +++ b/test/unit/helpers/tuple.spec.ts @@ -11,7 +11,7 @@ test.each<[string, any]>([ ["let a; for ([a] = tuple(1, 2); false; 1) {}", 1], ])("valid tuple function assignment (%s)", (statement, result) => { util.testModule` - import { tuple } from "typescript-to-lua"; + import { tuple } from "typescript-to-lua/helpers"; ${statement} // @ts-ignore return a; @@ -29,7 +29,7 @@ test.each([ "([a] = tuple(1)) => {}", ])("invalid tuple function assignment (%s)", statement => { util.testModule` - import { tuple } from "typescript-to-lua"; + import { tuple } from "typescript-to-lua/helpers"; ${statement} `.expectToHaveDiagnosticOfError(InvalidTupleFunctionUse(util.nodeStub)); }); @@ -39,7 +39,7 @@ test.each<[string, any]>([ ["return tuple(1);", 1], ])("valid tuple function return statement (%s)", (statement, result) => { util.testModule` - import { tuple } from "typescript-to-lua"; + import { tuple } from "typescript-to-lua/helpers"; const [a] = (function() { ${statement} })(); diff --git a/tsconfig.json b/tsconfig.json index d16784b37..82dd4f6a7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,5 +13,5 @@ "stripInternal": true }, "include": ["src"], - "exclude": ["src/lualib"] + "exclude": ["src/lualib", "helpers"] } From af1cefba4a6e7d98e022dabcdc39078973c01c2f Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 21:07:51 +1000 Subject: [PATCH 19/66] Do not use ts-ignore in tuple test cases --- src/transformation/helpers/tuple.ts | 21 ++++++++++++++------- test/unit/helpers/tuple.spec.ts | 27 ++++++++++++++------------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index 2ee157418..30e0a2727 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -3,11 +3,12 @@ import * as lua from "../../LuaAST"; import * as utils from "../utils/helpers"; import { TransformationContext, Visitors } from "../context"; import { transformAssignmentLeftHandSideExpression } from "../visitors/binary-expression/assignments"; -import { transformArrayBindingElement } from "../visitors/variable-declaration"; +import { transformIdentifier } from "../visitors/identifier"; import { transformArguments } from "../visitors/call"; -import { InvalidTupleFunctionUse } from "../utils/errors"; +import { InvalidTupleFunctionUse, UnsupportedKind } from "../utils/errors"; import { isSymbolAlias } from "../utils/symbols"; import { getDependenciesOfSymbol, createExportedIdentifier } from "../utils/export"; +import { createLocalOrExportedOrGlobalDeclaration } from "../utils/lua-ast"; function isTupleHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); @@ -83,13 +84,19 @@ function transformTupleHelperVariableDeclaration( throw InvalidTupleFunctionUse(declaration.name); } - if (declaration.name.elements.some(ts.isBinaryExpression)) { - throw InvalidTupleFunctionUse(declaration.name); - } + const leftIdentifiers = declaration.name.elements.map(element => { + if (ts.isBindingElement(element) && ts.isIdentifier(element.name)) { + return transformIdentifier(context, element.name); + } - const leftIdentifiers = declaration.name.elements.map(e => transformArrayBindingElement(context, e)); + if (ts.isOmittedExpression(element)) { + return lua.createAnonymousIdentifier(element); + } + + throw UnsupportedKind("Array Destructure Assignment Element", element.kind, element); + }); const rightExpressions = transformTupleCallArguments(context, declaration.initializer); - return [lua.createVariableDeclarationStatement(leftIdentifiers, rightExpressions, declaration)]; + return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration); } function isSimpleLeftHandSideDestructuringExpression(expression: ts.Expression): boolean { diff --git a/test/unit/helpers/tuple.spec.ts b/test/unit/helpers/tuple.spec.ts index 3523b301c..747d4cf1d 100644 --- a/test/unit/helpers/tuple.spec.ts +++ b/test/unit/helpers/tuple.spec.ts @@ -13,9 +13,10 @@ test.each<[string, any]>([ util.testModule` import { tuple } from "typescript-to-lua/helpers"; ${statement} - // @ts-ignore - return a; - `.expectToEqual(result); + export { a }; + ` + .setReturnExport("a") + .expectToEqual(result); }); test.each([ @@ -39,13 +40,13 @@ test.each<[string, any]>([ ["return tuple(1);", 1], ])("valid tuple function return statement (%s)", (statement, result) => { util.testModule` - import { tuple } from "typescript-to-lua/helpers"; - const [a] = (function() { - ${statement} - })(); - // @ts-ignore - return a; - `.expectToEqual(result); + import { tuple } from "typescript-to-lua/helpers"; + export const [a] = (function() { + ${statement} + })(); + ` + .setReturnExport("a") + .expectToEqual(result); }); test("tuple destructuring assignment side effects", () => { @@ -54,7 +55,7 @@ test("tuple destructuring assignment side effects", () => { let a, b; export { a }; [a] = tuple(1); - // @ts-ignore - return a; - `.expectToEqual(1); + ` + .setReturnExport("a") + .expectToEqual(1); }); From 00c32d8aff2a6594c4b3cf9bc25969fe108af2d0 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 21:08:57 +1000 Subject: [PATCH 20/66] Use more specific test case names for tuple --- test/unit/helpers/tuple.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/helpers/tuple.spec.ts b/test/unit/helpers/tuple.spec.ts index 747d4cf1d..e4612e3db 100644 --- a/test/unit/helpers/tuple.spec.ts +++ b/test/unit/helpers/tuple.spec.ts @@ -9,7 +9,7 @@ test.each<[string, any]>([ ["const _ = null, [a] = tuple(1);", 1], ["let a; for (const [a] = tuple(1, 2); false; 1) {}", undefined], ["let a; for ([a] = tuple(1, 2); false; 1) {}", 1], -])("valid tuple function assignment (%s)", (statement, result) => { +])("valid tuple call and assign (%s)", (statement, result) => { util.testModule` import { tuple } from "typescript-to-lua/helpers"; ${statement} @@ -28,7 +28,7 @@ test.each([ "const a = tuple();", "const {} = tuple();", "([a] = tuple(1)) => {}", -])("invalid tuple function assignment (%s)", statement => { +])("invalid tuple call (%s)", statement => { util.testModule` import { tuple } from "typescript-to-lua/helpers"; ${statement} @@ -38,7 +38,7 @@ test.each([ test.each<[string, any]>([ ["return tuple();", undefined], ["return tuple(1);", 1], -])("valid tuple function return statement (%s)", (statement, result) => { +])("valid tuple call return statement (%s)", (statement, result) => { util.testModule` import { tuple } from "typescript-to-lua/helpers"; export const [a] = (function() { @@ -49,7 +49,7 @@ test.each<[string, any]>([ .expectToEqual(result); }); -test("tuple destructuring assignment side effects", () => { +test("tuple call with destructuring assignment side effects", () => { util.testModule` import { tuple } from "typescript-to-lua"; let a, b; From f8780927131b884bbc83645a8256446cade6d22e Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 21:14:41 +1000 Subject: [PATCH 21/66] Remove unsafe cast in for-of --- src/transformation/visitors/loops/for-of.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/transformation/visitors/loops/for-of.ts b/src/transformation/visitors/loops/for-of.ts index 6d06c4063..8dfcd7131 100644 --- a/src/transformation/visitors/loops/for-of.ts +++ b/src/transformation/visitors/loops/for-of.ts @@ -9,12 +9,16 @@ import { UnsupportedNonDestructuringLuaIterator, UnsupportedObjectDestructuringInForOf, } from "../../utils/errors"; -import { createUnpackCall } from "../../utils/lua-ast"; +import { createUnpackCall, unwrapVisitorResult } from "../../utils/lua-ast"; import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib"; import { isArrayType, isNumberType } from "../../utils/typescript"; import { transformArguments } from "../call"; import { transformIdentifier } from "../identifier"; -import { transformBindingPattern, transformArrayBindingElement } from "../variable-declaration"; +import { + transformBindingPattern, + transformArrayBindingElement, + transformVariableDeclaration, +} from "../variable-declaration"; import { getVariableDeclarationBinding, transformLoopBody } from "./utils"; function transformForOfInitializer( @@ -36,7 +40,9 @@ function transformForOfInitializer( throw UnsupportedObjectDestructuringInForOf(initializer); } - const variableStatements = context.transformNode(initializer.declarations[0]) as lua.Statement[]; + const variableStatements = unwrapVisitorResult( + transformVariableDeclaration(initializer.declarations[0], context) + ); if (variableStatements[0]) { // we can safely assume that for vars are not exported and therefore declarationstatenents return [ From 93ca0a9a7fa0d75ac36da31cffd0612166af65ae Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 21:27:53 +1000 Subject: [PATCH 22/66] Remove tuple visitors --- src/transformation/helpers/tuple.ts | 78 ++++++------------- src/transformation/index.ts | 3 +- .../visitors/expression-statement.ts | 6 ++ src/transformation/visitors/identifier.ts | 7 +- src/transformation/visitors/literal.ts | 3 + src/transformation/visitors/modules/import.ts | 12 ++- src/transformation/visitors/return.ts | 6 ++ .../visitors/variable-declaration.ts | 6 ++ 8 files changed, 60 insertions(+), 61 deletions(-) diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/tuple.ts index 30e0a2727..f77f84548 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/tuple.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import * as utils from "../utils/helpers"; -import { TransformationContext, Visitors } from "../context"; +import { TransformationContext } from "../context"; import { transformAssignmentLeftHandSideExpression } from "../visitors/binary-expression/assignments"; import { transformIdentifier } from "../visitors/identifier"; import { transformArguments } from "../visitors/call"; @@ -10,26 +10,26 @@ import { isSymbolAlias } from "../utils/symbols"; import { getDependenciesOfSymbol, createExportedIdentifier } from "../utils/export"; import { createLocalOrExportedOrGlobalDeclaration } from "../utils/lua-ast"; -function isTupleHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { +export function isTupleHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); return Boolean(type.symbol?.declarations?.some(isTupleHelperDeclaration)); } -function isTupleReturningCallExpression(context: TransformationContext, expression: ts.CallExpression): boolean { +export function isTupleReturningCallExpression(context: TransformationContext, expression: ts.CallExpression): boolean { const signature = context.checker.getResolvedSignature(expression); return Boolean(signature?.getReturnType().aliasSymbol?.declarations?.some(isTupleHelperDeclaration)); } -function isTupleHelperNode(context: TransformationContext, node: ts.Node): boolean { +export function isTupleHelperNode(context: TransformationContext, node: ts.Node): boolean { const type = context.checker.getTypeAtLocation(node); return Boolean(type.symbol?.declarations?.some(isTupleHelperDeclaration)); } -function isTupleHelperDeclaration(declaration: ts.Declaration): boolean { +export function isTupleHelperDeclaration(declaration: ts.Declaration): boolean { return utils.getHelperFileKind(declaration.getSourceFile()) === utils.HelperKind.Tuple; } -function transformTupleHelperReturnStatement( +export function transformTupleHelperReturnStatement( context: TransformationContext, statement: ts.ReturnStatement ): lua.Statement | undefined { @@ -49,7 +49,7 @@ function transformTupleHelperReturnStatement( return lua.createReturnStatement(expressions, statement); } -function transformTupleCallArguments( +export function transformTupleCallArguments( context: TransformationContext, expression: ts.CallExpression ): lua.Expression[] | lua.Expression { @@ -60,7 +60,7 @@ function transformTupleCallArguments( : context.transformExpression(expression); } -function transformTupleHelperVariableDeclaration( +export function transformTupleHelperVariableDeclaration( context: TransformationContext, declaration: ts.VariableDeclaration ): lua.Statement[] | undefined { @@ -99,15 +99,7 @@ function transformTupleHelperVariableDeclaration( return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration); } -function isSimpleLeftHandSideDestructuringExpression(expression: ts.Expression): boolean { - if (ts.isBinaryExpression(expression)) { - return false; - } - - return true; -} - -function transformTupleHelperDestructuringAssignmentStatement( +export function transformTupleHelperDestructuringAssignmentStatement( context: TransformationContext, statement: ts.ExpressionStatement ): lua.Statement[] | undefined { @@ -135,7 +127,7 @@ function transformTupleHelperDestructuringAssignmentStatement( throw InvalidTupleFunctionUse(statement.expression.left); } - if (!statement.expression.left.elements.every(isSimpleLeftHandSideDestructuringExpression)) { + if (statement.expression.left.elements.some(ts.isBinaryExpression)) { throw InvalidTupleFunctionUse(statement.expression.left); } @@ -163,41 +155,17 @@ function transformTupleHelperDestructuringAssignmentStatement( return [lua.createAssignmentStatement(leftIdentifiers, rightExpressions, statement), ...trailingStatements]; } -export const tupleVisitors: Visitors = { - [ts.SyntaxKind.ImportSpecifier]: (node, context) => { - if (isTupleHelperNode(context, node)) { - return; - } - return context.superTransformNode(node); - }, - [ts.SyntaxKind.ReturnStatement]: (node, context) => { - const result = transformTupleHelperReturnStatement(context, node); - return result ? result : context.superTransformStatements(node); - }, - [ts.SyntaxKind.VariableDeclaration]: (node, context) => { - const result = transformTupleHelperVariableDeclaration(context, node); - return result ? result : context.superTransformNode(node); - }, - [ts.SyntaxKind.ExpressionStatement]: (node, context) => { - const result = transformTupleHelperDestructuringAssignmentStatement(context, node); - return result ? result : context.superTransformStatements(node); - }, - [ts.SyntaxKind.Identifier]: (node, context) => { - if (isTupleHelperNode(context, node)) { - throw InvalidTupleFunctionUse(node); - } - return context.superTransformExpression(node); - }, - [ts.SyntaxKind.ObjectLiteralExpression]: (node, context) => { - node.properties.filter(ts.isShorthandPropertyAssignment).forEach(element => { - const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); - if (valueSymbol && isSymbolAlias(valueSymbol)) { - const declaration = context.checker.getAliasedSymbol(valueSymbol).valueDeclaration; - if (declaration && isTupleHelperDeclaration(declaration)) { - throw InvalidTupleFunctionUse(element); - } +export function validateTupleHelperFunctionNotAssignedWithin( + context: TransformationContext, + node: ts.ObjectLiteralExpression +): void { + node.properties.filter(ts.isShorthandPropertyAssignment).forEach(element => { + const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); + if (valueSymbol && isSymbolAlias(valueSymbol)) { + const declaration = context.checker.getAliasedSymbol(valueSymbol).valueDeclaration; + if (declaration && isTupleHelperDeclaration(declaration)) { + throw InvalidTupleFunctionUse(element); } - }); - return context.superTransformExpression(node); - }, -}; + } + }); +} diff --git a/src/transformation/index.ts b/src/transformation/index.ts index 1aacd9363..a720a8ffd 100644 --- a/src/transformation/index.ts +++ b/src/transformation/index.ts @@ -6,7 +6,6 @@ import { ObjectVisitor, TransformationContext, VisitorMap, Visitors } from "./co import { TranspileError } from "./utils/errors"; import { getUsedLuaLibFeatures } from "./utils/lualib"; import { standardVisitors } from "./visitors"; -import { tupleVisitors } from "./helpers/tuple"; const transpileErrorDiagnostic = (error: TranspileError): ts.Diagnostic => ({ file: error.node.getSourceFile(), @@ -20,7 +19,7 @@ const transpileErrorDiagnostic = (error: TranspileError): ts.Diagnostic => ({ export function createVisitorMap(customVisitors: Visitors[]): VisitorMap { const visitorMap: VisitorMap = new Map(); - for (const visitors of [standardVisitors, tupleVisitors, ...customVisitors]) { + for (const visitors of [standardVisitors, ...customVisitors]) { const priority = visitors === standardVisitors ? -Infinity : 0; for (const [syntaxKindKey, visitor] of Object.entries(visitors)) { if (!visitor) continue; diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index 0b338be6f..d2ed2851e 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -5,8 +5,14 @@ import { transformBinaryExpressionStatement } from "./binary-expression"; import { transformDeleteExpressionStatement } from "./delete"; import { transformLuaTableExpressionStatement } from "./lua-table"; import { transformUnaryExpressionStatement } from "./unary-expression"; +import { transformTupleHelperDestructuringAssignmentStatement } from "../helpers/tuple"; export const transformExpressionStatement: FunctionVisitor = (node, context) => { + const tupleResult = transformTupleHelperDestructuringAssignmentStatement(context, node); + if (tupleResult) { + return tupleResult; + } + const luaTableResult = transformLuaTableExpressionStatement(context, node); if (luaTableResult) { return luaTableResult; diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index 565c0625b..5856e98f8 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -3,13 +3,18 @@ import * as lua from "../../LuaAST"; import { transformBuiltinIdentifierExpression } from "../builtins"; import { FunctionVisitor, TransformationContext } from "../context"; import { isForRangeType } from "../utils/annotations"; -import { InvalidForRangeCall } from "../utils/errors"; +import { InvalidForRangeCall, InvalidTupleFunctionUse } from "../utils/errors"; import { createExportedIdentifier, getIdentifierExportScope } from "../utils/export"; import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names"; import { getIdentifierSymbolId } from "../utils/symbols"; import { findFirstNodeAbove } from "../utils/typescript"; +import { isTupleHelperNode } from "../helpers/tuple"; export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier { + if (isTupleHelperNode(context, identifier)) { + throw InvalidTupleFunctionUse(identifier); + } + if (isForRangeType(context, identifier)) { const callExpression = findFirstNodeAbove(identifier, ts.isCallExpression); if (!callExpression || !callExpression.parent || !ts.isForOfStatement(callExpression.parent)) { diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 281f70ec6..ccb8b66d2 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -14,6 +14,7 @@ import { import { getSymbolIdOfSymbol, trackSymbolReference } from "../utils/symbols"; import { isArrayType } from "../utils/typescript"; import { transformFunctionLikeDeclaration } from "./function"; +import { validateTupleHelperFunctionNotAssignedWithin } from "../helpers/tuple"; // TODO: Move to object-literal.ts? export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression { @@ -61,6 +62,8 @@ export function createShorthandIdentifier( } const transformObjectLiteralExpression: FunctionVisitor = (expression, context) => { + validateTupleHelperFunctionNotAssignedWithin(context, expression); + let properties: lua.TableFieldExpression[] = []; const tableExpressions: lua.Expression[] = []; diff --git a/src/transformation/visitors/modules/import.ts b/src/transformation/visitors/modules/import.ts index f142cfe81..0b0399fdc 100644 --- a/src/transformation/visitors/modules/import.ts +++ b/src/transformation/visitors/modules/import.ts @@ -1,7 +1,7 @@ import * as path from "path"; import * as ts from "typescript"; import * as lua from "../../../LuaAST"; -import { formatPathToLuaPath } from "../../../utils"; +import { formatPathToLuaPath, isNonNull } from "../../../utils"; import { FunctionVisitor, TransformationContext } from "../../context"; import { AnnotationKind, getSymbolAnnotations, getTypeAnnotations } from "../../utils/annotations"; import { UnresolvableRequirePath } from "../../utils/errors"; @@ -12,6 +12,7 @@ import { peekScope } from "../../utils/scope"; import { isHelpersImport } from "../../utils/helpers"; import { transformIdentifier } from "../identifier"; import { transformPropertyName } from "../literal"; +import { isTupleHelperNode } from "../../helpers/tuple"; const getAbsoluteImportPath = (relativePath: string, directoryPath: string, options: ts.CompilerOptions): string => relativePath[0] !== "." && options.baseUrl @@ -84,7 +85,11 @@ function transformImportSpecifier( context: TransformationContext, importSpecifier: ts.ImportSpecifier, moduleTableName: lua.Identifier -): lua.VariableDeclarationStatement { +): lua.VariableDeclarationStatement | undefined { + if (isTupleHelperNode(context, importSpecifier)) { + return; + } + const leftIdentifier = transformIdentifier(context, importSpecifier.name); const propertyName = transformPropertyName( context, @@ -172,7 +177,8 @@ export const transformImportDeclaration: FunctionVisitor = if (statement.importClause.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) { const assignmentStatements = statement.importClause.namedBindings.elements .filter(importSpecifier => shouldBeImported(context, importSpecifier)) - .map(importSpecifier => transformImportSpecifier(context, importSpecifier, importUniqueName)); + .map(importSpecifier => transformImportSpecifier(context, importSpecifier, importUniqueName)) + .filter(isNonNull); if (assignmentStatements.length > 0) { usingRequireStatement = true; diff --git a/src/transformation/visitors/return.ts b/src/transformation/visitors/return.ts index 970db6be0..35a9c0218 100644 --- a/src/transformation/visitors/return.ts +++ b/src/transformation/visitors/return.ts @@ -6,6 +6,7 @@ import { validateAssignment } from "../utils/assignment-validation"; import { createUnpackCall, wrapInTable } from "../utils/lua-ast"; import { ScopeType, walkScopesUp } from "../utils/scope"; import { isArrayType } from "../utils/typescript"; +import { transformTupleHelperReturnStatement } from "../helpers/tuple"; export const transformReturnStatement: FunctionVisitor = (statement, context) => { // Bubble up explicit return flag and check if we're inside a try/catch block @@ -20,6 +21,11 @@ export const transformReturnStatement: FunctionVisitor = (st insideTryCatch = insideTryCatch || scope.type === ScopeType.Try || scope.type === ScopeType.Catch; } + const result = transformTupleHelperReturnStatement(context, statement); + if (result) { + return result; + } + let results: lua.Expression[]; if (statement.expression) { diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index aaca8ceb9..8187f72ed 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,6 +10,7 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; +import { transformTupleHelperVariableDeclaration } from "../helpers/tuple"; export function transformArrayBindingElement( context: TransformationContext, @@ -207,6 +208,11 @@ export function transformBindingVariableDeclaration( } export const transformVariableDeclaration: FunctionVisitor = (statement, context) => { + const result = transformTupleHelperVariableDeclaration(context, statement); + if (result) { + return result; + } + if (statement.initializer && statement.type) { const initializerType = context.checker.getTypeAtLocation(statement.initializer); const varType = context.checker.getTypeFromTypeNode(statement.type); From 313676ee18438d6ff1bf24cbb01f2a5e0d910a5e Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 21:46:47 +1000 Subject: [PATCH 23/66] Rename to multi and MultiReturn --- helpers/index.ts | 2 +- helpers/multi.ts | 5 ++ helpers/tuple.ts | 5 -- .../helpers/{tuple.ts => multi.ts} | 58 +++++++++--------- src/transformation/utils/errors.ts | 3 +- src/transformation/utils/helpers.ts | 6 +- .../visitors/expression-statement.ts | 4 +- src/transformation/visitors/identifier.ts | 8 +-- src/transformation/visitors/literal.ts | 4 +- src/transformation/visitors/modules/import.ts | 4 +- src/transformation/visitors/return.ts | 4 +- .../visitors/variable-declaration.ts | 4 +- src/transpilation/index.ts | 2 +- test/unit/helpers/multi.spec.ts | 61 +++++++++++++++++++ test/unit/helpers/tuple.spec.ts | 61 ------------------- 15 files changed, 116 insertions(+), 115 deletions(-) create mode 100644 helpers/multi.ts delete mode 100644 helpers/tuple.ts rename src/transformation/helpers/{tuple.ts => multi.ts} (72%) create mode 100644 test/unit/helpers/multi.spec.ts delete mode 100644 test/unit/helpers/tuple.spec.ts diff --git a/helpers/index.ts b/helpers/index.ts index 61b6a05bd..512cf1895 100644 --- a/helpers/index.ts +++ b/helpers/index.ts @@ -1 +1 @@ -export * from "./tuple"; +export * from "./multi"; diff --git a/helpers/multi.ts b/helpers/multi.ts new file mode 100644 index 000000000..2a2f89192 --- /dev/null +++ b/helpers/multi.ts @@ -0,0 +1,5 @@ +export type MultiReturn = T & { + " __multiBrand": never; +}; + +export declare function multi(...values: T): MultiReturn; diff --git a/helpers/tuple.ts b/helpers/tuple.ts deleted file mode 100644 index b30bcb594..000000000 --- a/helpers/tuple.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type Tuple = T & { - " __tupleBrand": never; -}; - -export declare function tuple(...values: T): Tuple; diff --git a/src/transformation/helpers/tuple.ts b/src/transformation/helpers/multi.ts similarity index 72% rename from src/transformation/helpers/tuple.ts rename to src/transformation/helpers/multi.ts index f77f84548..d2dea9bc4 100644 --- a/src/transformation/helpers/tuple.ts +++ b/src/transformation/helpers/multi.ts @@ -5,31 +5,31 @@ import { TransformationContext } from "../context"; import { transformAssignmentLeftHandSideExpression } from "../visitors/binary-expression/assignments"; import { transformIdentifier } from "../visitors/identifier"; import { transformArguments } from "../visitors/call"; -import { InvalidTupleFunctionUse, UnsupportedKind } from "../utils/errors"; +import { InvalidMultiHelperFunctionUse, UnsupportedKind } from "../utils/errors"; import { isSymbolAlias } from "../utils/symbols"; import { getDependenciesOfSymbol, createExportedIdentifier } from "../utils/export"; import { createLocalOrExportedOrGlobalDeclaration } from "../utils/lua-ast"; -export function isTupleHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { +function isMultiHelperDeclaration(declaration: ts.Declaration): boolean { + return utils.getHelperFileKind(declaration.getSourceFile()) === utils.HelperKind.Multi; +} + +function isMultiHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); - return Boolean(type.symbol?.declarations?.some(isTupleHelperDeclaration)); + return Boolean(type.symbol?.declarations?.some(isMultiHelperDeclaration)); } -export function isTupleReturningCallExpression(context: TransformationContext, expression: ts.CallExpression): boolean { +function isMultiReturningCallExpression(context: TransformationContext, expression: ts.CallExpression): boolean { const signature = context.checker.getResolvedSignature(expression); - return Boolean(signature?.getReturnType().aliasSymbol?.declarations?.some(isTupleHelperDeclaration)); + return Boolean(signature?.getReturnType().aliasSymbol?.declarations?.some(isMultiHelperDeclaration)); } -export function isTupleHelperNode(context: TransformationContext, node: ts.Node): boolean { +export function isMultiHelperNode(context: TransformationContext, node: ts.Node): boolean { const type = context.checker.getTypeAtLocation(node); - return Boolean(type.symbol?.declarations?.some(isTupleHelperDeclaration)); -} - -export function isTupleHelperDeclaration(declaration: ts.Declaration): boolean { - return utils.getHelperFileKind(declaration.getSourceFile()) === utils.HelperKind.Tuple; + return Boolean(type.symbol?.declarations?.some(isMultiHelperDeclaration)); } -export function transformTupleHelperReturnStatement( +export function transformMultiHelperReturnStatement( context: TransformationContext, statement: ts.ReturnStatement ): lua.Statement | undefined { @@ -41,7 +41,7 @@ export function transformTupleHelperReturnStatement( return undefined; } - if (!isTupleHelperCallSignature(context, statement.expression)) { + if (!isMultiHelperCallSignature(context, statement.expression)) { return undefined; } @@ -49,18 +49,18 @@ export function transformTupleHelperReturnStatement( return lua.createReturnStatement(expressions, statement); } -export function transformTupleCallArguments( +function transformMultiHelperCallArguments( context: TransformationContext, expression: ts.CallExpression ): lua.Expression[] | lua.Expression { - return isTupleHelperCallSignature(context, expression) + return isMultiHelperCallSignature(context, expression) ? expression.arguments.length > 0 ? expression.arguments.map(e => context.transformExpression(e)) : lua.createNilLiteral(expression) : context.transformExpression(expression); } -export function transformTupleHelperVariableDeclaration( +export function transformMultiHelperVariableDeclaration( context: TransformationContext, declaration: ts.VariableDeclaration ): lua.Statement[] | undefined { @@ -72,16 +72,16 @@ export function transformTupleHelperVariableDeclaration( return undefined; } - if (!isTupleReturningCallExpression(context, declaration.initializer)) { + if (!isMultiReturningCallExpression(context, declaration.initializer)) { return undefined; } if (!ts.isArrayBindingPattern(declaration.name)) { - throw InvalidTupleFunctionUse(declaration.name); + throw InvalidMultiHelperFunctionUse(declaration.name); } if (declaration.name.elements.length < 1) { - throw InvalidTupleFunctionUse(declaration.name); + throw InvalidMultiHelperFunctionUse(declaration.name); } const leftIdentifiers = declaration.name.elements.map(element => { @@ -95,11 +95,11 @@ export function transformTupleHelperVariableDeclaration( throw UnsupportedKind("Array Destructure Assignment Element", element.kind, element); }); - const rightExpressions = transformTupleCallArguments(context, declaration.initializer); + const rightExpressions = transformMultiHelperCallArguments(context, declaration.initializer); return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration); } -export function transformTupleHelperDestructuringAssignmentStatement( +export function transformMultiHelperDestructuringAssignmentStatement( context: TransformationContext, statement: ts.ExpressionStatement ): lua.Statement[] | undefined { @@ -115,20 +115,20 @@ export function transformTupleHelperDestructuringAssignmentStatement( return undefined; } - if (!isTupleReturningCallExpression(context, statement.expression.right)) { + if (!isMultiReturningCallExpression(context, statement.expression.right)) { return undefined; } if (!ts.isArrayLiteralExpression(statement.expression.left)) { - throw InvalidTupleFunctionUse(statement.expression.left); + throw InvalidMultiHelperFunctionUse(statement.expression.left); } if (statement.expression.left.elements.length < 1) { - throw InvalidTupleFunctionUse(statement.expression.left); + throw InvalidMultiHelperFunctionUse(statement.expression.left); } if (statement.expression.left.elements.some(ts.isBinaryExpression)) { - throw InvalidTupleFunctionUse(statement.expression.left); + throw InvalidMultiHelperFunctionUse(statement.expression.left); } const transformLeft = (expression: ts.Expression): lua.AssignmentLeftHandSideExpression => { @@ -141,7 +141,7 @@ export function transformTupleHelperDestructuringAssignmentStatement( const leftIdentifiers = statement.expression.left.elements.map(transformLeft); - const rightExpressions = transformTupleCallArguments(context, statement.expression.right); + const rightExpressions = transformMultiHelperCallArguments(context, statement.expression.right); const trailingStatements = statement.expression.left.elements.flatMap(expression => { const symbol = context.checker.getSymbolAtLocation(expression); @@ -155,7 +155,7 @@ export function transformTupleHelperDestructuringAssignmentStatement( return [lua.createAssignmentStatement(leftIdentifiers, rightExpressions, statement), ...trailingStatements]; } -export function validateTupleHelperFunctionNotAssignedWithin( +export function validateMultiHelperFunctionNotAssignedWithin( context: TransformationContext, node: ts.ObjectLiteralExpression ): void { @@ -163,8 +163,8 @@ export function validateTupleHelperFunctionNotAssignedWithin( const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); if (valueSymbol && isSymbolAlias(valueSymbol)) { const declaration = context.checker.getAliasedSymbol(valueSymbol).valueDeclaration; - if (declaration && isTupleHelperDeclaration(declaration)) { - throw InvalidTupleFunctionUse(element); + if (declaration && isMultiHelperDeclaration(declaration)) { + throw InvalidMultiHelperFunctionUse(element); } } }); diff --git a/src/transformation/utils/errors.ts b/src/transformation/utils/errors.ts index e25a3678c..4e4c192ff 100644 --- a/src/transformation/utils/errors.ts +++ b/src/transformation/utils/errors.ts @@ -145,4 +145,5 @@ export const InvalidForRangeCall = (node: ts.Node, message: string) => export const UnsupportedVarDeclaration = (node: ts.Node) => new TranspileError("`var` declarations are not supported. Use `let` or `const` instead.", node); -export const InvalidTupleFunctionUse = (node: ts.Node) => new TranspileError("Invalid tuple() function use.", node); +export const InvalidMultiHelperFunctionUse = (node: ts.Node) => + new TranspileError("Invalid multi() function use.", node); diff --git a/src/transformation/utils/helpers.ts b/src/transformation/utils/helpers.ts index 0151c3905..c3f20c100 100644 --- a/src/transformation/utils/helpers.ts +++ b/src/transformation/utils/helpers.ts @@ -3,7 +3,7 @@ import * as path from "path"; import { TransformationContext } from "../context"; export enum HelperKind { - Tuple = "tuple", + Multi = "multi", } function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { @@ -16,8 +16,8 @@ export function getHelperFileKind(sourceFile: ts.SourceFile): HelperKind | undef if (isSourceFileFromHelpers(sourceFile)) { const baseFileName = path.basename(sourceFile.fileName).replace(/(\.d)?\.ts$/g, ""); switch (baseFileName) { - case "tuple": - return HelperKind.Tuple; + case "multi": + return HelperKind.Multi; default: throw new Error(`Unknown Helper Kind ${baseFileName}`); } diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index d2ed2851e..21533aac6 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -5,10 +5,10 @@ import { transformBinaryExpressionStatement } from "./binary-expression"; import { transformDeleteExpressionStatement } from "./delete"; import { transformLuaTableExpressionStatement } from "./lua-table"; import { transformUnaryExpressionStatement } from "./unary-expression"; -import { transformTupleHelperDestructuringAssignmentStatement } from "../helpers/tuple"; +import { transformMultiHelperDestructuringAssignmentStatement } from "../helpers/multi"; export const transformExpressionStatement: FunctionVisitor = (node, context) => { - const tupleResult = transformTupleHelperDestructuringAssignmentStatement(context, node); + const tupleResult = transformMultiHelperDestructuringAssignmentStatement(context, node); if (tupleResult) { return tupleResult; } diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index 5856e98f8..ab5ccedcf 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -3,16 +3,16 @@ import * as lua from "../../LuaAST"; import { transformBuiltinIdentifierExpression } from "../builtins"; import { FunctionVisitor, TransformationContext } from "../context"; import { isForRangeType } from "../utils/annotations"; -import { InvalidForRangeCall, InvalidTupleFunctionUse } from "../utils/errors"; +import { InvalidForRangeCall, InvalidMultiHelperFunctionUse } from "../utils/errors"; import { createExportedIdentifier, getIdentifierExportScope } from "../utils/export"; import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names"; import { getIdentifierSymbolId } from "../utils/symbols"; import { findFirstNodeAbove } from "../utils/typescript"; -import { isTupleHelperNode } from "../helpers/tuple"; +import { isMultiHelperNode } from "../helpers/multi"; export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier { - if (isTupleHelperNode(context, identifier)) { - throw InvalidTupleFunctionUse(identifier); + if (isMultiHelperNode(context, identifier)) { + throw InvalidMultiHelperFunctionUse(identifier); } if (isForRangeType(context, identifier)) { diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index ccb8b66d2..058a41a17 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -14,7 +14,7 @@ import { import { getSymbolIdOfSymbol, trackSymbolReference } from "../utils/symbols"; import { isArrayType } from "../utils/typescript"; import { transformFunctionLikeDeclaration } from "./function"; -import { validateTupleHelperFunctionNotAssignedWithin } from "../helpers/tuple"; +import { validateMultiHelperFunctionNotAssignedWithin } from "../helpers/multi"; // TODO: Move to object-literal.ts? export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression { @@ -62,7 +62,7 @@ export function createShorthandIdentifier( } const transformObjectLiteralExpression: FunctionVisitor = (expression, context) => { - validateTupleHelperFunctionNotAssignedWithin(context, expression); + validateMultiHelperFunctionNotAssignedWithin(context, expression); let properties: lua.TableFieldExpression[] = []; const tableExpressions: lua.Expression[] = []; diff --git a/src/transformation/visitors/modules/import.ts b/src/transformation/visitors/modules/import.ts index 0b0399fdc..d0049f46c 100644 --- a/src/transformation/visitors/modules/import.ts +++ b/src/transformation/visitors/modules/import.ts @@ -12,7 +12,7 @@ import { peekScope } from "../../utils/scope"; import { isHelpersImport } from "../../utils/helpers"; import { transformIdentifier } from "../identifier"; import { transformPropertyName } from "../literal"; -import { isTupleHelperNode } from "../../helpers/tuple"; +import { isMultiHelperNode } from "../../helpers/multi"; const getAbsoluteImportPath = (relativePath: string, directoryPath: string, options: ts.CompilerOptions): string => relativePath[0] !== "." && options.baseUrl @@ -86,7 +86,7 @@ function transformImportSpecifier( importSpecifier: ts.ImportSpecifier, moduleTableName: lua.Identifier ): lua.VariableDeclarationStatement | undefined { - if (isTupleHelperNode(context, importSpecifier)) { + if (isMultiHelperNode(context, importSpecifier)) { return; } diff --git a/src/transformation/visitors/return.ts b/src/transformation/visitors/return.ts index 35a9c0218..17df3370f 100644 --- a/src/transformation/visitors/return.ts +++ b/src/transformation/visitors/return.ts @@ -6,7 +6,7 @@ import { validateAssignment } from "../utils/assignment-validation"; import { createUnpackCall, wrapInTable } from "../utils/lua-ast"; import { ScopeType, walkScopesUp } from "../utils/scope"; import { isArrayType } from "../utils/typescript"; -import { transformTupleHelperReturnStatement } from "../helpers/tuple"; +import { transformMultiHelperReturnStatement } from "../helpers/multi"; export const transformReturnStatement: FunctionVisitor = (statement, context) => { // Bubble up explicit return flag and check if we're inside a try/catch block @@ -21,7 +21,7 @@ export const transformReturnStatement: FunctionVisitor = (st insideTryCatch = insideTryCatch || scope.type === ScopeType.Try || scope.type === ScopeType.Catch; } - const result = transformTupleHelperReturnStatement(context, statement); + const result = transformMultiHelperReturnStatement(context, statement); if (result) { return result; } diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 8187f72ed..9510843c8 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,7 +10,7 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; -import { transformTupleHelperVariableDeclaration } from "../helpers/tuple"; +import { transformMultiHelperVariableDeclaration } from "../helpers/multi"; export function transformArrayBindingElement( context: TransformationContext, @@ -208,7 +208,7 @@ export function transformBindingVariableDeclaration( } export const transformVariableDeclaration: FunctionVisitor = (statement, context) => { - const result = transformTupleHelperVariableDeclaration(context, statement); + const result = transformMultiHelperVariableDeclaration(context, statement); if (result) { return result; } diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index d5aa3c02f..cc0cc9e96 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -56,7 +56,7 @@ export function createVirtualProgram(input: Record, options: Com } if (filename.includes("typescript-to-lua")) { - const filePath = path.resolve(__dirname, "../../helpers/tuple.ts"); + const filePath = path.resolve(__dirname, "../../helpers/multi.ts"); if (libCache[filename]) return libCache[filename]; const content = fs.readFileSync(filePath, "utf8"); diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts new file mode 100644 index 000000000..22dfc3f0d --- /dev/null +++ b/test/unit/helpers/multi.spec.ts @@ -0,0 +1,61 @@ +import * as util from "../../util"; +import { InvalidMultiHelperFunctionUse } from "../../../src/transformation/utils/errors"; + +test.each<[string, any]>([ + ["let a; [a] = multi();", undefined], + ["const [a] = multi();", undefined], + ["const [a] = multi(1);", 1], + ["const ar = [1]; const [a] = multi(...ar);", 1], + ["const _ = null, [a] = multi(1);", 1], + ["let a; for (const [a] = multi(1, 2); false; 1) {}", undefined], + ["let a; for ([a] = multi(1, 2); false; 1) {}", 1], +])("valid multi call and assign (%s)", (statement, result) => { + util.testModule` + import { multi } from "typescript-to-lua/helpers"; + ${statement} + export { a }; + ` + .setReturnExport("a") + .expectToEqual(result); +}); + +test.each([ + "multi", + "multi()", + "({ multi });", + "[] = multi()", + "const [] = multi();", + "const a = multi();", + "const {} = multi();", + "([a] = multi(1)) => {}", +])("invalid multi call (%s)", statement => { + util.testModule` + import { multi } from "typescript-to-lua/helpers"; + ${statement} + `.expectToHaveDiagnosticOfError(InvalidMultiHelperFunctionUse(util.nodeStub)); +}); + +test.each<[string, any]>([ + ["return multi();", undefined], + ["return multi(1);", 1], +])("valid multi call return statement (%s)", (statement, result) => { + util.testModule` + import { multi } from "typescript-to-lua/helpers"; + export const [a] = (function() { + ${statement} + })(); + ` + .setReturnExport("a") + .expectToEqual(result); +}); + +test("multi helper call with destructuring assignment side effects", () => { + util.testModule` + import { multi } from "typescript-to-lua"; + let a, b; + export { a }; + [a] = multi(1); + ` + .setReturnExport("a") + .expectToEqual(1); +}); diff --git a/test/unit/helpers/tuple.spec.ts b/test/unit/helpers/tuple.spec.ts deleted file mode 100644 index e4612e3db..000000000 --- a/test/unit/helpers/tuple.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import * as util from "../../util"; -import { InvalidTupleFunctionUse } from "../../../src/transformation/utils/errors"; - -test.each<[string, any]>([ - ["let a; [a] = tuple();", undefined], - ["const [a] = tuple();", undefined], - ["const [a] = tuple(1);", 1], - ["const ar = [1]; const [a] = tuple(...ar);", 1], - ["const _ = null, [a] = tuple(1);", 1], - ["let a; for (const [a] = tuple(1, 2); false; 1) {}", undefined], - ["let a; for ([a] = tuple(1, 2); false; 1) {}", 1], -])("valid tuple call and assign (%s)", (statement, result) => { - util.testModule` - import { tuple } from "typescript-to-lua/helpers"; - ${statement} - export { a }; - ` - .setReturnExport("a") - .expectToEqual(result); -}); - -test.each([ - "tuple", - "tuple()", - "({ tuple });", - "[] = tuple()", - "const [] = tuple();", - "const a = tuple();", - "const {} = tuple();", - "([a] = tuple(1)) => {}", -])("invalid tuple call (%s)", statement => { - util.testModule` - import { tuple } from "typescript-to-lua/helpers"; - ${statement} - `.expectToHaveDiagnosticOfError(InvalidTupleFunctionUse(util.nodeStub)); -}); - -test.each<[string, any]>([ - ["return tuple();", undefined], - ["return tuple(1);", 1], -])("valid tuple call return statement (%s)", (statement, result) => { - util.testModule` - import { tuple } from "typescript-to-lua/helpers"; - export const [a] = (function() { - ${statement} - })(); - ` - .setReturnExport("a") - .expectToEqual(result); -}); - -test("tuple call with destructuring assignment side effects", () => { - util.testModule` - import { tuple } from "typescript-to-lua"; - let a, b; - export { a }; - [a] = tuple(1); - ` - .setReturnExport("a") - .expectToEqual(1); -}); From e63260f0704e90e9717b2200c17485479ec37533 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 21:59:14 +1000 Subject: [PATCH 24/66] Revert FunctionVisitor conversion --- src/transformation/visitors/index.ts | 3 +-- src/transformation/visitors/loops/for-of.ts | 6 ++---- src/transformation/visitors/loops/for.ts | 6 ++---- src/transformation/visitors/variable-declaration.ts | 12 +++++++----- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/transformation/visitors/index.ts b/src/transformation/visitors/index.ts index 5566614d4..17b25ffd7 100644 --- a/src/transformation/visitors/index.ts +++ b/src/transformation/visitors/index.ts @@ -39,7 +39,7 @@ import { transformTaggedTemplateExpression, transformTemplateExpression } from " import { transformTypeOfExpression } from "./typeof"; import { typescriptVisitors } from "./typescript"; import { transformPostfixUnaryExpression, transformPrefixUnaryExpression } from "./unary-expression"; -import { transformVariableStatement, transformVariableDeclaration } from "./variable-declaration"; +import { transformVariableStatement } from "./variable-declaration"; const transformEmptyStatement: FunctionVisitor = () => undefined; const transformParenthesizedExpression: FunctionVisitor = (node, context) => @@ -93,7 +93,6 @@ export const standardVisitors: Visitors = { [ts.SyntaxKind.ThrowStatement]: transformThrowStatement, [ts.SyntaxKind.TryStatement]: transformTryStatement, [ts.SyntaxKind.TypeOfExpression]: transformTypeOfExpression, - [ts.SyntaxKind.VariableDeclaration]: transformVariableDeclaration, [ts.SyntaxKind.VariableStatement]: transformVariableStatement, [ts.SyntaxKind.WhileStatement]: transformWhileStatement, [ts.SyntaxKind.YieldExpression]: transformYieldExpression, diff --git a/src/transformation/visitors/loops/for-of.ts b/src/transformation/visitors/loops/for-of.ts index 8dfcd7131..a4df8f1d7 100644 --- a/src/transformation/visitors/loops/for-of.ts +++ b/src/transformation/visitors/loops/for-of.ts @@ -9,7 +9,7 @@ import { UnsupportedNonDestructuringLuaIterator, UnsupportedObjectDestructuringInForOf, } from "../../utils/errors"; -import { createUnpackCall, unwrapVisitorResult } from "../../utils/lua-ast"; +import { createUnpackCall } from "../../utils/lua-ast"; import { LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib"; import { isArrayType, isNumberType } from "../../utils/typescript"; import { transformArguments } from "../call"; @@ -40,9 +40,7 @@ function transformForOfInitializer( throw UnsupportedObjectDestructuringInForOf(initializer); } - const variableStatements = unwrapVisitorResult( - transformVariableDeclaration(initializer.declarations[0], context) - ); + const variableStatements = transformVariableDeclaration(context, initializer.declarations[0]); if (variableStatements[0]) { // we can safely assume that for vars are not exported and therefore declarationstatenents return [ diff --git a/src/transformation/visitors/loops/for.ts b/src/transformation/visitors/loops/for.ts index 1ef586ac7..970c2f045 100644 --- a/src/transformation/visitors/loops/for.ts +++ b/src/transformation/visitors/loops/for.ts @@ -1,7 +1,7 @@ import * as ts from "typescript"; import * as lua from "../../../LuaAST"; import { FunctionVisitor } from "../../context"; -import { checkVariableDeclarationList } from "../variable-declaration"; +import { checkVariableDeclarationList, transformVariableDeclaration } from "../variable-declaration"; import { transformLoopBody } from "./utils"; export const transformForStatement: FunctionVisitor = (statement, context) => { @@ -11,9 +11,7 @@ export const transformForStatement: FunctionVisitor = (statemen if (ts.isVariableDeclarationList(statement.initializer)) { checkVariableDeclarationList(statement.initializer); // local initializer = value - result.push( - ...statement.initializer.declarations.flatMap(d => context.transformNode(d) as lua.Statement[]) - ); + result.push(...statement.initializer.declarations.flatMap(d => transformVariableDeclaration(context, d))); } else { result.push(...context.transformStatements(ts.createExpressionStatement(statement.initializer))); } diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 9510843c8..bf4708ad1 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -207,7 +207,11 @@ export function transformBindingVariableDeclaration( return statements; } -export const transformVariableDeclaration: FunctionVisitor = (statement, context) => { +// TODO: FunctionVisitor +export function transformVariableDeclaration( + context: TransformationContext, + statement: ts.VariableDeclaration +): lua.Statement[] { const result = transformMultiHelperVariableDeclaration(context, statement); if (result) { return result; @@ -229,7 +233,7 @@ export const transformVariableDeclaration: FunctionVisitor = (node, context) => { checkVariableDeclarationList(node.declarationList); - return node.declarationList.declarations.flatMap( - declaration => context.transformNode(declaration) as lua.Statement[] - ); + return node.declarationList.declarations.flatMap(declaration => transformVariableDeclaration(context, declaration)); }; From ab9b8ebcba986b8fca8de065fdf9b5a57f238e58 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Thu, 6 Feb 2020 22:20:41 +1000 Subject: [PATCH 25/66] Rename tupleResult to multiResult --- src/transformation/visitors/expression-statement.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index 21533aac6..660fe4df1 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -8,9 +8,9 @@ import { transformUnaryExpressionStatement } from "./unary-expression"; import { transformMultiHelperDestructuringAssignmentStatement } from "../helpers/multi"; export const transformExpressionStatement: FunctionVisitor = (node, context) => { - const tupleResult = transformMultiHelperDestructuringAssignmentStatement(context, node); - if (tupleResult) { - return tupleResult; + const multiResult = transformMultiHelperDestructuringAssignmentStatement(context, node); + if (multiResult) { + return multiResult; } const luaTableResult = transformLuaTableExpressionStatement(context, node); From 68dcee9c7fd65ddca24dcc5eba1e9a746c711c24 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 25 Feb 2020 13:56:29 +1000 Subject: [PATCH 26/66] Update tsconfig.json Co-Authored-By: ark120202 --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 82dd4f6a7..d16784b37 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,5 +13,5 @@ "stripInternal": true }, "include": ["src"], - "exclude": ["src/lualib", "helpers"] + "exclude": ["src/lualib"] } From 343a026fc332bb873645afcb9900d2d7be9912d1 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 25 Feb 2020 13:57:00 +1000 Subject: [PATCH 27/66] Update test/unit/helpers/multi.spec.ts Co-Authored-By: ark120202 --- test/unit/helpers/multi.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index 22dfc3f0d..abc0f6bb6 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -52,7 +52,7 @@ test.each<[string, any]>([ test("multi helper call with destructuring assignment side effects", () => { util.testModule` import { multi } from "typescript-to-lua"; - let a, b; + let a; export { a }; [a] = multi(1); ` From 9fe0e7e8ce2e98d8196058368f1dc8bbd206b105 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 25 Feb 2020 14:16:52 +1000 Subject: [PATCH 28/66] Refactor for new createVirtualProgram code --- src/transpilation/index.ts | 21 +++++++++------------ test/unit/helpers/multi.spec.ts | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index cc0cc9e96..ab53e02b1 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -55,24 +55,21 @@ export function createVirtualProgram(input: Record, options: Com return ts.createSourceFile(filename, input[filename], ts.ScriptTarget.Latest, false); } - if (filename.includes("typescript-to-lua")) { - const filePath = path.resolve(__dirname, "../../helpers/multi.ts"); - if (libCache[filename]) return libCache[filename]; - const content = fs.readFileSync(filePath, "utf8"); + let filePath: string | undefined; - libCache[filename] = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, false); + if (filename.startsWith("lib.")) { + const typeScriptDir = path.dirname(require.resolve("typescript")); + filePath = path.join(typeScriptDir, filename); + } - return libCache[filename]; + if (filename.includes("typescript-to-lua/helpers")) { + filePath = path.resolve(__dirname, "../../helpers/multi.ts"); } - if (filename.startsWith("lib.")) { + if (filePath !== undefined) { if (libCache[filename]) return libCache[filename]; - const typeScriptDir = path.dirname(require.resolve("typescript")); - const filePath = path.join(typeScriptDir, filename); const content = fs.readFileSync(filePath, "utf8"); - - libCache[filename] = ts.createSourceFile(filename, content, ts.ScriptTarget.Latest, false); - + libCache[filename] = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, false); return libCache[filename]; } }, diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index abc0f6bb6..3bea8ef21 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -51,7 +51,7 @@ test.each<[string, any]>([ test("multi helper call with destructuring assignment side effects", () => { util.testModule` - import { multi } from "typescript-to-lua"; + import { multi } from "typescript-to-lua/helpers"; let a; export { a }; [a] = multi(1); From 55782f522012e26fb515a108bb278839e79a53bf Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 25 Feb 2020 14:21:37 +1000 Subject: [PATCH 29/66] Move helper transformer to transformation --- .../visitors/expression-statement.ts | 2 +- .../{ => visitors}/helpers/multi.ts | 20 +++++++++---------- src/transformation/visitors/identifier.ts | 2 +- src/transformation/visitors/literal.ts | 2 +- src/transformation/visitors/modules/import.ts | 2 +- src/transformation/visitors/return.ts | 2 +- .../visitors/variable-declaration.ts | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) rename src/transformation/{ => visitors}/helpers/multi.ts (92%) diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index 660fe4df1..531902b15 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -5,7 +5,7 @@ import { transformBinaryExpressionStatement } from "./binary-expression"; import { transformDeleteExpressionStatement } from "./delete"; import { transformLuaTableExpressionStatement } from "./lua-table"; import { transformUnaryExpressionStatement } from "./unary-expression"; -import { transformMultiHelperDestructuringAssignmentStatement } from "../helpers/multi"; +import { transformMultiHelperDestructuringAssignmentStatement } from "./helpers/multi"; export const transformExpressionStatement: FunctionVisitor = (node, context) => { const multiResult = transformMultiHelperDestructuringAssignmentStatement(context, node); diff --git a/src/transformation/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts similarity index 92% rename from src/transformation/helpers/multi.ts rename to src/transformation/visitors/helpers/multi.ts index d2dea9bc4..a89a227da 100644 --- a/src/transformation/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -1,14 +1,14 @@ import * as ts from "typescript"; -import * as lua from "../../LuaAST"; -import * as utils from "../utils/helpers"; -import { TransformationContext } from "../context"; -import { transformAssignmentLeftHandSideExpression } from "../visitors/binary-expression/assignments"; -import { transformIdentifier } from "../visitors/identifier"; -import { transformArguments } from "../visitors/call"; -import { InvalidMultiHelperFunctionUse, UnsupportedKind } from "../utils/errors"; -import { isSymbolAlias } from "../utils/symbols"; -import { getDependenciesOfSymbol, createExportedIdentifier } from "../utils/export"; -import { createLocalOrExportedOrGlobalDeclaration } from "../utils/lua-ast"; +import * as lua from "../../../LuaAST"; +import * as utils from "../../utils/helpers"; +import { TransformationContext } from "../../context"; +import { transformAssignmentLeftHandSideExpression } from "../binary-expression/assignments"; +import { transformIdentifier } from "../identifier"; +import { transformArguments } from "../call"; +import { InvalidMultiHelperFunctionUse, UnsupportedKind } from "../../utils/errors"; +import { isSymbolAlias } from "../../utils/symbols"; +import { getDependenciesOfSymbol, createExportedIdentifier } from "../../utils/export"; +import { createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast"; function isMultiHelperDeclaration(declaration: ts.Declaration): boolean { return utils.getHelperFileKind(declaration.getSourceFile()) === utils.HelperKind.Multi; diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index ab5ccedcf..522361779 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -8,7 +8,7 @@ import { createExportedIdentifier, getIdentifierExportScope } from "../utils/exp import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names"; import { getIdentifierSymbolId } from "../utils/symbols"; import { findFirstNodeAbove } from "../utils/typescript"; -import { isMultiHelperNode } from "../helpers/multi"; +import { isMultiHelperNode } from "./helpers/multi"; export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier { if (isMultiHelperNode(context, identifier)) { diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 058a41a17..3f04d12c7 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -14,7 +14,7 @@ import { import { getSymbolIdOfSymbol, trackSymbolReference } from "../utils/symbols"; import { isArrayType } from "../utils/typescript"; import { transformFunctionLikeDeclaration } from "./function"; -import { validateMultiHelperFunctionNotAssignedWithin } from "../helpers/multi"; +import { validateMultiHelperFunctionNotAssignedWithin } from "./helpers/multi"; // TODO: Move to object-literal.ts? export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression { diff --git a/src/transformation/visitors/modules/import.ts b/src/transformation/visitors/modules/import.ts index d0049f46c..2b9e9ce1a 100644 --- a/src/transformation/visitors/modules/import.ts +++ b/src/transformation/visitors/modules/import.ts @@ -12,7 +12,7 @@ import { peekScope } from "../../utils/scope"; import { isHelpersImport } from "../../utils/helpers"; import { transformIdentifier } from "../identifier"; import { transformPropertyName } from "../literal"; -import { isMultiHelperNode } from "../../helpers/multi"; +import { isMultiHelperNode } from "../helpers/multi"; const getAbsoluteImportPath = (relativePath: string, directoryPath: string, options: ts.CompilerOptions): string => relativePath[0] !== "." && options.baseUrl diff --git a/src/transformation/visitors/return.ts b/src/transformation/visitors/return.ts index 17df3370f..4a2edf63b 100644 --- a/src/transformation/visitors/return.ts +++ b/src/transformation/visitors/return.ts @@ -6,7 +6,7 @@ import { validateAssignment } from "../utils/assignment-validation"; import { createUnpackCall, wrapInTable } from "../utils/lua-ast"; import { ScopeType, walkScopesUp } from "../utils/scope"; import { isArrayType } from "../utils/typescript"; -import { transformMultiHelperReturnStatement } from "../helpers/multi"; +import { transformMultiHelperReturnStatement } from "./helpers/multi"; export const transformReturnStatement: FunctionVisitor = (statement, context) => { // Bubble up explicit return flag and check if we're inside a try/catch block diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index bf4708ad1..ab24e0e6a 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,7 +10,7 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; -import { transformMultiHelperVariableDeclaration } from "../helpers/multi"; +import { transformMultiHelperVariableDeclaration } from "./helpers/multi"; export function transformArrayBindingElement( context: TransformationContext, From aa579fdfe539be6a1e226f9ed8c9f79b93a97eb1 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 25 Feb 2020 14:26:01 +1000 Subject: [PATCH 30/66] Use nullish operator instead of Boolean constructor --- src/transformation/visitors/helpers/multi.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index a89a227da..2e5f9cdf5 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -16,17 +16,17 @@ function isMultiHelperDeclaration(declaration: ts.Declaration): boolean { function isMultiHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); - return Boolean(type.symbol?.declarations?.some(isMultiHelperDeclaration)); + return type.symbol?.declarations?.some(isMultiHelperDeclaration) ?? false; } function isMultiReturningCallExpression(context: TransformationContext, expression: ts.CallExpression): boolean { const signature = context.checker.getResolvedSignature(expression); - return Boolean(signature?.getReturnType().aliasSymbol?.declarations?.some(isMultiHelperDeclaration)); + return signature?.getReturnType().aliasSymbol?.declarations?.some(isMultiHelperDeclaration) ?? false; } export function isMultiHelperNode(context: TransformationContext, node: ts.Node): boolean { const type = context.checker.getTypeAtLocation(node); - return Boolean(type.symbol?.declarations?.some(isMultiHelperDeclaration)); + return type.symbol?.declarations?.some(isMultiHelperDeclaration) ?? false; } export function transformMultiHelperReturnStatement( From 4913a8f0bdd2a7369d31b88f7fa9831fe6c23878 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 25 Feb 2020 15:00:24 +1000 Subject: [PATCH 31/66] Attempt a CommonJS implementation of multi --- helpers/{index.ts => index.d.ts} | 0 helpers/multi.d.ts | 5 +++++ helpers/multi.js | 8 ++++++++ helpers/multi.ts | 5 ----- src/transpilation/index.ts | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) rename helpers/{index.ts => index.d.ts} (100%) create mode 100644 helpers/multi.d.ts create mode 100644 helpers/multi.js delete mode 100644 helpers/multi.ts diff --git a/helpers/index.ts b/helpers/index.d.ts similarity index 100% rename from helpers/index.ts rename to helpers/index.d.ts diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts new file mode 100644 index 000000000..d98e124af --- /dev/null +++ b/helpers/multi.d.ts @@ -0,0 +1,5 @@ +export type MultiReturn = T & { + readonly " __multiBrand": unique symbol; +}; + +declare function multi(...values: T): MultiReturn; diff --git a/helpers/multi.js b/helpers/multi.js new file mode 100644 index 000000000..b2a3ada9c --- /dev/null +++ b/helpers/multi.js @@ -0,0 +1,8 @@ +module.exports.symbol = Symbol("Multi Brand Symbol"); + +module.exports.multi = (...values) => { + return { + " __multiBrand": module.exports.symbol, + ...values, + }; +}; diff --git a/helpers/multi.ts b/helpers/multi.ts deleted file mode 100644 index 2a2f89192..000000000 --- a/helpers/multi.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type MultiReturn = T & { - " __multiBrand": never; -}; - -export declare function multi(...values: T): MultiReturn; diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index ab53e02b1..bb19af38d 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -63,7 +63,7 @@ export function createVirtualProgram(input: Record, options: Com } if (filename.includes("typescript-to-lua/helpers")) { - filePath = path.resolve(__dirname, "../../helpers/multi.ts"); + filePath = path.resolve(__dirname, "../../helpers/multi.d.ts"); } if (filePath !== undefined) { From 09077c2282b0ac3e254ddb9a1e64e810c93da3eb Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 25 Feb 2020 15:05:25 +1000 Subject: [PATCH 32/66] Add MultiReturn alias MR --- helpers/multi.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index d98e124af..4c14d7db9 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -2,4 +2,6 @@ export type MultiReturn = T & { readonly " __multiBrand": unique symbol; }; +export { MultiReturn as MR }; + declare function multi(...values: T): MultiReturn; From d0e4432136c6b302ab2c9260eb8d4493e665b36a Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 25 Feb 2020 15:08:15 +1000 Subject: [PATCH 33/66] Add export in front of multi function --- helpers/multi.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index 4c14d7db9..f259c11e1 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -4,4 +4,4 @@ export type MultiReturn = T & { export { MultiReturn as MR }; -declare function multi(...values: T): MultiReturn; +export declare function multi(...values: T): MultiReturn; From 2eb5bbc27cbc32f8d7b18885800fd83ed6c80ecb Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 26 Feb 2020 10:00:30 +1000 Subject: [PATCH 34/66] Simplify multi js implementation --- helpers/multi.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/helpers/multi.js b/helpers/multi.js index b2a3ada9c..ae81ca061 100644 --- a/helpers/multi.js +++ b/helpers/multi.js @@ -1,8 +1 @@ -module.exports.symbol = Symbol("Multi Brand Symbol"); - -module.exports.multi = (...values) => { - return { - " __multiBrand": module.exports.symbol, - ...values, - }; -}; +module.exports.multi = (...values) => values; From 6bfd0287136e9a88a6ab19565b41c87e1a30cf0d Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 26 Feb 2020 10:55:01 +1000 Subject: [PATCH 35/66] Remove MR alias --- helpers/multi.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index f259c11e1..40d2d1859 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -2,6 +2,4 @@ export type MultiReturn = T & { readonly " __multiBrand": unique symbol; }; -export { MultiReturn as MR }; - export declare function multi(...values: T): MultiReturn; From a959695ee2347d53205d8b560a2939d3798558b5 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 2 Mar 2020 14:08:33 +1000 Subject: [PATCH 36/66] Make multi helper global --- helpers/multi.d.ts | 12 ++++++++---- src/transformation/utils/symbols.ts | 4 ---- src/transformation/visitors/helpers/multi.ts | 5 ++--- test/unit/helpers/multi.spec.ts | 11 ++++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index 40d2d1859..25789ad85 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -1,5 +1,9 @@ -export type MultiReturn = T & { - readonly " __multiBrand": unique symbol; -}; +declare global { + export type MultiReturn = T & { + readonly " __multiBrand": unique symbol; + }; -export declare function multi(...values: T): MultiReturn; + export function multi(...values: T): MultiReturn; +} + +export {}; diff --git a/src/transformation/utils/symbols.ts b/src/transformation/utils/symbols.ts index 123a8c5f3..c99b41a25 100644 --- a/src/transformation/utils/symbols.ts +++ b/src/transformation/utils/symbols.ts @@ -58,7 +58,3 @@ export function getIdentifierSymbolId( return trackSymbolReference(context, symbol, identifier); } } - -export function isSymbolAlias(symbol: ts.Symbol): boolean { - return (symbol.flags & ts.SymbolFlags.Alias) !== 0; -} diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index 2e5f9cdf5..5428111dc 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -6,7 +6,6 @@ import { transformAssignmentLeftHandSideExpression } from "../binary-expression/ import { transformIdentifier } from "../identifier"; import { transformArguments } from "../call"; import { InvalidMultiHelperFunctionUse, UnsupportedKind } from "../../utils/errors"; -import { isSymbolAlias } from "../../utils/symbols"; import { getDependenciesOfSymbol, createExportedIdentifier } from "../../utils/export"; import { createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast"; @@ -161,8 +160,8 @@ export function validateMultiHelperFunctionNotAssignedWithin( ): void { node.properties.filter(ts.isShorthandPropertyAssignment).forEach(element => { const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); - if (valueSymbol && isSymbolAlias(valueSymbol)) { - const declaration = context.checker.getAliasedSymbol(valueSymbol).valueDeclaration; + if (valueSymbol) { + const declaration = valueSymbol.valueDeclaration; if (declaration && isMultiHelperDeclaration(declaration)) { throw InvalidMultiHelperFunctionUse(element); } diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index 3bea8ef21..5a4c4c6d4 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -11,10 +11,10 @@ test.each<[string, any]>([ ["let a; for ([a] = multi(1, 2); false; 1) {}", 1], ])("valid multi call and assign (%s)", (statement, result) => { util.testModule` - import { multi } from "typescript-to-lua/helpers"; ${statement} export { a }; ` + .setOptions({ types: ["typescript-to-lua/helpers"] }) .setReturnExport("a") .expectToEqual(result); }); @@ -30,9 +30,10 @@ test.each([ "([a] = multi(1)) => {}", ])("invalid multi call (%s)", statement => { util.testModule` - import { multi } from "typescript-to-lua/helpers"; ${statement} - `.expectToHaveDiagnosticOfError(InvalidMultiHelperFunctionUse(util.nodeStub)); + ` + .setOptions({ types: ["typescript-to-lua/helpers"] }) + .expectToHaveDiagnosticOfError(InvalidMultiHelperFunctionUse(util.nodeStub)); }); test.each<[string, any]>([ @@ -40,22 +41,22 @@ test.each<[string, any]>([ ["return multi(1);", 1], ])("valid multi call return statement (%s)", (statement, result) => { util.testModule` - import { multi } from "typescript-to-lua/helpers"; export const [a] = (function() { ${statement} })(); ` + .setOptions({ types: ["typescript-to-lua/helpers"] }) .setReturnExport("a") .expectToEqual(result); }); test("multi helper call with destructuring assignment side effects", () => { util.testModule` - import { multi } from "typescript-to-lua/helpers"; let a; export { a }; [a] = multi(1); ` + .setOptions({ types: ["typescript-to-lua/helpers"] }) .setReturnExport("a") .expectToEqual(1); }); From 3d18107166ded8b14a7ec21a50a10b4869d08623 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 2 Mar 2020 14:17:01 +1000 Subject: [PATCH 37/66] Revert multi helper import changes --- src/transformation/utils/helpers.ts | 21 ------------------- src/transformation/visitors/modules/import.ts | 21 +++---------------- 2 files changed, 3 insertions(+), 39 deletions(-) diff --git a/src/transformation/utils/helpers.ts b/src/transformation/utils/helpers.ts index c3f20c100..844e1ad4f 100644 --- a/src/transformation/utils/helpers.ts +++ b/src/transformation/utils/helpers.ts @@ -1,6 +1,5 @@ import * as ts from "typescript"; import * as path from "path"; -import { TransformationContext } from "../context"; export enum HelperKind { Multi = "multi", @@ -23,23 +22,3 @@ export function getHelperFileKind(sourceFile: ts.SourceFile): HelperKind | undef } } } - -export function isHelpersImport( - context: TransformationContext, - importNode: ts.ImportClause | ts.ImportSpecifier | ts.ImportDeclaration -): boolean { - if (ts.isImportDeclaration(importNode)) { - const symbol = context.checker.getSymbolAtLocation(importNode.moduleSpecifier); - return symbol?.declarations.map(d => d.getSourceFile()).some(isSourceFileFromHelpers) ?? false; - } - - if (importNode.name) { - const symbol = context.checker.getSymbolAtLocation(importNode.name); - if (symbol) { - const originalSymbol = context.checker.getAliasedSymbol(symbol); - return originalSymbol?.declarations?.map(d => d.getSourceFile()).some(isSourceFileFromHelpers) ?? false; - } - } - - return false; -} diff --git a/src/transformation/visitors/modules/import.ts b/src/transformation/visitors/modules/import.ts index 2b9e9ce1a..088fac7d2 100644 --- a/src/transformation/visitors/modules/import.ts +++ b/src/transformation/visitors/modules/import.ts @@ -1,7 +1,7 @@ import * as path from "path"; import * as ts from "typescript"; import * as lua from "../../../LuaAST"; -import { formatPathToLuaPath, isNonNull } from "../../../utils"; +import { formatPathToLuaPath } from "../../../utils"; import { FunctionVisitor, TransformationContext } from "../../context"; import { AnnotationKind, getSymbolAnnotations, getTypeAnnotations } from "../../utils/annotations"; import { UnresolvableRequirePath } from "../../utils/errors"; @@ -9,10 +9,8 @@ import { createDefaultExportStringLiteral } from "../../utils/export"; import { createHoistableVariableDeclarationStatement } from "../../utils/lua-ast"; import { createSafeName } from "../../utils/safe-names"; import { peekScope } from "../../utils/scope"; -import { isHelpersImport } from "../../utils/helpers"; import { transformIdentifier } from "../identifier"; import { transformPropertyName } from "../literal"; -import { isMultiHelperNode } from "../helpers/multi"; const getAbsoluteImportPath = (relativePath: string, directoryPath: string, options: ts.CompilerOptions): string => relativePath[0] !== "." && options.baseUrl @@ -68,10 +66,6 @@ export function createModuleRequire( } function shouldBeImported(context: TransformationContext, importNode: ts.ImportClause | ts.ImportSpecifier): boolean { - if (isHelpersImport(context, importNode)) { - return false; - } - const annotations = getTypeAnnotations(context.checker.getTypeAtLocation(importNode)); return ( @@ -85,11 +79,7 @@ function transformImportSpecifier( context: TransformationContext, importSpecifier: ts.ImportSpecifier, moduleTableName: lua.Identifier -): lua.VariableDeclarationStatement | undefined { - if (isMultiHelperNode(context, importSpecifier)) { - return; - } - +): lua.VariableDeclarationStatement { const leftIdentifier = transformIdentifier(context, importSpecifier.name); const propertyName = transformPropertyName( context, @@ -110,10 +100,6 @@ export const transformImportDeclaration: FunctionVisitor = scope.importStatements = []; } - if (isHelpersImport(context, statement)) { - return; - } - const result: lua.Statement[] = []; const requireCall = createModuleRequire(context, statement.moduleSpecifier); @@ -177,8 +163,7 @@ export const transformImportDeclaration: FunctionVisitor = if (statement.importClause.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) { const assignmentStatements = statement.importClause.namedBindings.elements .filter(importSpecifier => shouldBeImported(context, importSpecifier)) - .map(importSpecifier => transformImportSpecifier(context, importSpecifier, importUniqueName)) - .filter(isNonNull); + .map(importSpecifier => transformImportSpecifier(context, importSpecifier, importUniqueName)); if (assignmentStatements.length > 0) { usingRequireStatement = true; From d82f958e510efac6f6bea141ba5a78ccaa184189 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 29 Mar 2020 11:57:07 +1000 Subject: [PATCH 38/66] Apply suggestions from code review Co-Authored-By: ark120202 --- helpers/multi.d.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index 25789ad85..4fb3affe4 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -1,9 +1,3 @@ -declare global { - export type MultiReturn = T & { - readonly " __multiBrand": unique symbol; - }; +function multi(...values: T): MultiReturn; - export function multi(...values: T): MultiReturn; -} - -export {}; +type MultiReturn = T & { readonly " __multiBrand": unique symbol; }; From 6c9d1a63a0a1b950f7769f338be1302df02e3db1 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 29 Mar 2020 12:01:04 +1000 Subject: [PATCH 39/66] Inline and join multi conditions --- helpers/multi.d.ts | 2 +- src/transformation/visitors/helpers/multi.ts | 73 +++++--------------- 2 files changed, 18 insertions(+), 57 deletions(-) diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index 4fb3affe4..d3784c10f 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -1,3 +1,3 @@ function multi(...values: T): MultiReturn; -type MultiReturn = T & { readonly " __multiBrand": unique symbol; }; +type MultiReturn = T & { readonly " __multiBrand": unique symbol }; diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index f0e13d781..d326d9df2 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -36,17 +36,9 @@ export function transformMultiHelperReturnStatement( context: TransformationContext, statement: ts.ReturnStatement ): lua.Statement | undefined { - if (!statement.expression) { - return; - } - - if (!ts.isCallExpression(statement.expression)) { - return; - } - - if (!isMultiHelperCallSignature(context, statement.expression)) { - return; - } + if (!statement.expression) return; + if (!ts.isCallExpression(statement.expression)) return; + if (!isMultiHelperCallSignature(context, statement.expression)) return; const expressions = transformArguments(context, statement.expression.arguments); return lua.createReturnStatement(expressions, statement); @@ -67,24 +59,11 @@ export function transformMultiHelperVariableDeclaration( context: TransformationContext, declaration: ts.VariableDeclaration ): lua.Statement[] | undefined { - if (!declaration.initializer) { - return; - } - - if (!ts.isCallExpression(declaration.initializer)) { - return; - } + if (!declaration.initializer) return; + if (!ts.isCallExpression(declaration.initializer)) return; + if (!isMultiReturningCallExpression(context, declaration.initializer)) return; - if (!isMultiReturningCallExpression(context, declaration.initializer)) { - return; - } - - if (!ts.isArrayBindingPattern(declaration.name)) { - context.diagnostics.push(invalidMultiHelperFunctionUse(declaration.name)); - return []; - } - - if (declaration.name.elements.length < 1) { + if (!ts.isArrayBindingPattern(declaration.name) || declaration.name.elements.length < 1) { context.diagnostics.push(invalidMultiHelperFunctionUse(declaration.name)); return []; } @@ -100,7 +79,6 @@ export function transformMultiHelperVariableDeclaration( } context.diagnostics.push(unsupportedMultiFunctionAssignment(element)); - return undefined; }) .filter(isNonNull); @@ -112,33 +90,16 @@ export function transformMultiHelperDestructuringAssignmentStatement( context: TransformationContext, statement: ts.ExpressionStatement ): lua.Statement[] | undefined { - if (!ts.isBinaryExpression(statement.expression)) { - return; - } - - if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { - return; - } - - if (!ts.isCallExpression(statement.expression.right)) { - return; - } - - if (!isMultiReturningCallExpression(context, statement.expression.right)) { - return; - } - - if (!ts.isArrayLiteralExpression(statement.expression.left)) { - context.diagnostics.push(invalidMultiHelperFunctionUse(statement.expression.left)); - return []; - } - - if (statement.expression.left.elements.length < 1) { - context.diagnostics.push(invalidMultiHelperFunctionUse(statement.expression.left)); - return []; - } - - if (statement.expression.left.elements.some(ts.isBinaryExpression)) { + if (!ts.isBinaryExpression(statement.expression)) return; + if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return; + if (!ts.isCallExpression(statement.expression.right)) return; + if (!isMultiReturningCallExpression(context, statement.expression.right)) return; + + if ( + !ts.isArrayLiteralExpression(statement.expression.left) || + statement.expression.left.elements.length < 1 || + statement.expression.left.elements.some(ts.isBinaryExpression) + ) { context.diagnostics.push(invalidMultiHelperFunctionUse(statement.expression.left)); return []; } From c0c937717e0f7500fbbb33b1cef795530f34dc32 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 29 Mar 2020 12:12:22 +1000 Subject: [PATCH 40/66] Rename to $multi --- helpers/multi.d.ts | 2 +- src/transformation/utils/diagnostics.ts | 2 +- .../helpers/__snapshots__/multi.spec.ts.snap | 42 +++++++++--------- test/unit/helpers/multi.spec.ts | 44 +++++++++---------- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index d3784c10f..1fda34cb2 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -1,3 +1,3 @@ -function multi(...values: T): MultiReturn; +function $multi(...values: T): MultiReturn; type MultiReturn = T & { readonly " __multiBrand": unique symbol }; diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index f6d03c0a2..b7e8fd17f 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -127,7 +127,7 @@ export const unsupportedVarDeclaration = createDiagnosticFactory( export const unknownHelperKind = createDiagnosticFactory((kind: string) => `Unknown helper kind "${kind}".`); -export const invalidMultiHelperFunctionUse = createDiagnosticFactory("Invalid multi() function use."); +export const invalidMultiHelperFunctionUse = createDiagnosticFactory("Invalid $multi() function use."); export const unsupportedMultiFunctionAssignment = createDiagnosticFactory( "Omitted expressions and BindingElements are expected here." diff --git a/test/unit/helpers/__snapshots__/multi.spec.ts.snap b/test/unit/helpers/__snapshots__/multi.spec.ts.snap index 77cb08762..4ef947bee 100644 --- a/test/unit/helpers/__snapshots__/multi.spec.ts.snap +++ b/test/unit/helpers/__snapshots__/multi.spec.ts.snap @@ -1,40 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`invalid multi call (([a] = multi(1)) => {}): code 1`] = ` +exports[`invalid $multi call ($multi()): code 1`] = `"_____24multi(_G)"`; + +exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`; + +exports[`invalid $multi call ($multi): code 1`] = `"local ____ = _____24multi"`; + +exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`; + +exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` "local function ____(____, ____bindingPattern0) if ____bindingPattern0 == nil then - ____bindingPattern0 = multi(_G, 1) + ____bindingPattern0 = _____24multi(_G, 1) end local a = ____bindingPattern0[1] end" `; -exports[`invalid multi call (([a] = multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: Invalid multi() function use."`; - -exports[`invalid multi call (({ multi });): code 1`] = `"local ____ = {multi = multi}"`; - -exports[`invalid multi call (({ multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: Invalid multi() function use."`; - -exports[`invalid multi call ([] = multi()): code 1`] = `""`; - -exports[`invalid multi call ([] = multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid multi() function use."`; +exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: Invalid $multi() function use."`; -exports[`invalid multi call (const [] = multi();): code 1`] = `""`; +exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = {[\\"$multi\\"] = _____24multi}"`; -exports[`invalid multi call (const [] = multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid multi() function use."`; +exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: Invalid $multi() function use."`; -exports[`invalid multi call (const {} = multi();): code 1`] = `""`; +exports[`invalid $multi call ([] = $multi()): code 1`] = `""`; -exports[`invalid multi call (const {} = multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid multi() function use."`; +exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`; -exports[`invalid multi call (const a = multi();): code 1`] = `""`; +exports[`invalid $multi call (const [] = $multi();): code 1`] = `""`; -exports[`invalid multi call (const a = multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid multi() function use."`; +exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid $multi() function use."`; -exports[`invalid multi call (multi()): code 1`] = `"multi(_G)"`; +exports[`invalid $multi call (const {} = $multi();): code 1`] = `""`; -exports[`invalid multi call (multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid multi() function use."`; +exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid $multi() function use."`; -exports[`invalid multi call (multi): code 1`] = `"local ____ = multi"`; +exports[`invalid $multi call (const a = $multi();): code 1`] = `""`; -exports[`invalid multi call (multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid multi() function use."`; +exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid $multi() function use."`; diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index 2d09ea6f4..df5caae92 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -8,14 +8,14 @@ const multiProjectOptions: tstl.CompilerOptions = { }; test.each<[string, any]>([ - ["let a; [a] = multi();", undefined], - ["const [a] = multi();", undefined], - ["const [a] = multi(1);", 1], - ["const ar = [1]; const [a] = multi(...ar);", 1], - ["const _ = null, [a] = multi(1);", 1], - ["let a; for (const [a] = multi(1, 2); false; 1) {}", undefined], - ["let a; for ([a] = multi(1, 2); false; 1) {}", 1], -])("valid multi call and assign (%s)", (statement, result) => { + ["let a; [a] = $multi();", undefined], + ["const [a] = $multi();", undefined], + ["const [a] = $multi(1);", 1], + ["const ar = [1]; const [a] = $multi(...ar);", 1], + ["const _ = null, [a] = $multi(1);", 1], + ["let a; for (const [a] = $multi(1, 2); false; 1) {}", undefined], + ["let a; for ([a] = $multi(1, 2); false; 1) {}", 1], +])("valid $multi call and assign (%s)", (statement, result) => { util.testModule` ${statement} export { a }; @@ -26,15 +26,15 @@ test.each<[string, any]>([ }); test.each([ - "multi", - "multi()", - "({ multi });", - "[] = multi()", - "const [] = multi();", - "const a = multi();", - "const {} = multi();", - "([a] = multi(1)) => {}", -])("invalid multi call (%s)", statement => { + "$multi", + "$multi()", + "({ $multi });", + "[] = $multi()", + "const [] = $multi();", + "const a = $multi();", + "const {} = $multi();", + "([a] = $multi(1)) => {}", +])("invalid $multi call (%s)", statement => { util.testModule` ${statement} ` @@ -43,9 +43,9 @@ test.each([ }); test.each<[string, any]>([ - ["return multi();", undefined], - ["return multi(1);", 1], -])("valid multi call return statement (%s)", (statement, result) => { + ["return $multi();", undefined], + ["return $multi(1);", 1], +])("valid $multi call return statement (%s)", (statement, result) => { util.testModule` export const [a] = (function() { ${statement} @@ -56,11 +56,11 @@ test.each<[string, any]>([ .expectToEqual(result); }); -test("multi helper call with destructuring assignment side effects", () => { +test("$multi helper call with destructuring assignment side effects", () => { util.testModule` let a; export { a }; - [a] = multi(1); + [a] = $multi(1); ` .setOptions(multiProjectOptions) .setReturnExport("a") From 4e8b8de892b2110d5a6cddd23d70371eef6c7b5b Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 29 Mar 2020 12:28:00 +1000 Subject: [PATCH 41/66] Use declare for top level $multi --- helpers/index.d.ts | 2 +- helpers/multi.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers/index.d.ts b/helpers/index.d.ts index 512cf1895..4a68a2952 100644 --- a/helpers/index.d.ts +++ b/helpers/index.d.ts @@ -1 +1 @@ -export * from "./multi"; +import './multi'; diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index 1fda34cb2..fb4f5e889 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -1,3 +1,3 @@ -function $multi(...values: T): MultiReturn; +declare function $multi(...values: T): MultiReturn; type MultiReturn = T & { readonly " __multiBrand": unique symbol }; From d2227fe57eb5d2b6ba283653fb8c21c2cdab7405 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 29 Mar 2020 12:32:01 +1000 Subject: [PATCH 42/66] Add empty tuple to MultiReturn --- helpers/multi.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index fb4f5e889..156550c89 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -1,3 +1,3 @@ declare function $multi(...values: T): MultiReturn; -type MultiReturn = T & { readonly " __multiBrand": unique symbol }; +type MultiReturn = T & [] & { readonly " __multiBrand": unique symbol }; From fa7626dc2231535d23e5901d834c0c497704de2f Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 29 Mar 2020 12:35:01 +1000 Subject: [PATCH 43/66] Add declare to MultiReturn to top level declaration --- helpers/index.d.ts | 2 +- helpers/multi.d.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/helpers/index.d.ts b/helpers/index.d.ts index 4a68a2952..a92b9f956 100644 --- a/helpers/index.d.ts +++ b/helpers/index.d.ts @@ -1 +1 @@ -import './multi'; +import "./multi"; diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index 156550c89..1a84fbbe5 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -1,3 +1,2 @@ declare function $multi(...values: T): MultiReturn; - -type MultiReturn = T & [] & { readonly " __multiBrand": unique symbol }; +declare type MultiReturn = T & [] & { readonly " __multiBrand": unique symbol }; From 1a44b55431a87a88e25f3ad4334a5a8fc705871f Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 29 Mar 2020 15:51:31 +1000 Subject: [PATCH 44/66] Reduce number of diagnostics for multi error --- src/transformation/visitors/call.ts | 6 +++--- src/transformation/visitors/helpers/multi.ts | 14 ++++++++------ src/transformation/visitors/identifier.ts | 1 + src/transformation/visitors/literal.ts | 1 + test/unit/helpers/__snapshots__/multi.spec.ts.snap | 8 ++++---- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/transformation/visitors/call.ts b/src/transformation/visitors/call.ts index bd8099afa..ae3fe93fd 100644 --- a/src/transformation/visitors/call.ts +++ b/src/transformation/visitors/call.ts @@ -12,6 +12,7 @@ import { isArrayType, isExpressionWithEvaluationEffect, isInDestructingAssignmen import { transformElementAccessArgument } from "./access"; import { transformIdentifier } from "./identifier"; import { transformLuaTableCallExpression } from "./lua-table"; +import { isMultiReturnCall } from "./helpers/multi"; export type PropertyCallExpression = ts.CallExpression & { expression: ts.PropertyAccessExpression }; @@ -251,9 +252,8 @@ export const transformCallExpression: FunctionVisitor = (node // TODO: Currently it's also used as an array member export const transformSpreadElement: FunctionVisitor = (node, context) => { const innerExpression = context.transformExpression(node.expression); - if (isTupleReturnCall(context, node.expression)) { - return innerExpression; - } + if (isTupleReturnCall(context, node.expression)) return innerExpression; + if (isMultiReturnCall(context, node.expression)) return innerExpression; if (ts.isIdentifier(node.expression) && isVarargType(context, node.expression)) { return lua.createDotsLiteral(node); diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index d326d9df2..3cc821322 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -22,8 +22,12 @@ function isMultiHelperCallSignature(context: TransformationContext, expression: return type.symbol?.declarations?.some(isMultiHelperDeclaration(context)) ?? false; } -function isMultiReturningCallExpression(context: TransformationContext, expression: ts.CallExpression): boolean { - const signature = context.checker.getResolvedSignature(expression); +export function isMultiReturnCall(context: TransformationContext, node: ts.Node): node is ts.CallExpression { + if (!ts.isCallExpression(node)) { + return false; + } + + const signature = context.checker.getResolvedSignature(node); return signature?.getReturnType().aliasSymbol?.declarations?.some(isMultiHelperDeclaration(context)) ?? false; } @@ -60,8 +64,7 @@ export function transformMultiHelperVariableDeclaration( declaration: ts.VariableDeclaration ): lua.Statement[] | undefined { if (!declaration.initializer) return; - if (!ts.isCallExpression(declaration.initializer)) return; - if (!isMultiReturningCallExpression(context, declaration.initializer)) return; + if (!isMultiReturnCall(context, declaration.initializer)) return; if (!ts.isArrayBindingPattern(declaration.name) || declaration.name.elements.length < 1) { context.diagnostics.push(invalidMultiHelperFunctionUse(declaration.name)); @@ -92,8 +95,7 @@ export function transformMultiHelperDestructuringAssignmentStatement( ): lua.Statement[] | undefined { if (!ts.isBinaryExpression(statement.expression)) return; if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return; - if (!ts.isCallExpression(statement.expression.right)) return; - if (!isMultiReturningCallExpression(context, statement.expression.right)) return; + if (!isMultiReturnCall(context, statement.expression.right)) return; if ( !ts.isArrayLiteralExpression(statement.expression.left) || diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index f1dea66a4..bdff42b3f 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -13,6 +13,7 @@ import { isMultiHelperNode } from "./helpers/multi"; export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier { if (isMultiHelperNode(context, identifier)) { context.diagnostics.push(invalidMultiHelperFunctionUse(identifier)); + return lua.createAnonymousIdentifier(identifier); } if (isForRangeType(context, identifier)) { diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index e567b946d..23979a448 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -68,6 +68,7 @@ const transformObjectLiteralExpression: FunctionVisitor { context.diagnostics.push(invalidMultiHelperFunctionUse(element)); }); + return lua.createNilLiteral(expression); } let properties: lua.TableFieldExpression[] = []; diff --git a/test/unit/helpers/__snapshots__/multi.spec.ts.snap b/test/unit/helpers/__snapshots__/multi.spec.ts.snap index 4ef947bee..723d607b0 100644 --- a/test/unit/helpers/__snapshots__/multi.spec.ts.snap +++ b/test/unit/helpers/__snapshots__/multi.spec.ts.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`invalid $multi call ($multi()): code 1`] = `"_____24multi(_G)"`; +exports[`invalid $multi call ($multi()): code 1`] = `"____(_G)"`; exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`; -exports[`invalid $multi call ($multi): code 1`] = `"local ____ = _____24multi"`; +exports[`invalid $multi call ($multi): code 1`] = `"local ____ = ____"`; exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`; exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` "local function ____(____, ____bindingPattern0) if ____bindingPattern0 == nil then - ____bindingPattern0 = _____24multi(_G, 1) + ____bindingPattern0 = ____(_G, 1) end local a = ____bindingPattern0[1] end" @@ -19,7 +19,7 @@ end" exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: Invalid $multi() function use."`; -exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = {[\\"$multi\\"] = _____24multi}"`; +exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = nil"`; exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: Invalid $multi() function use."`; From 4c1ea0d34011616ebbfd64aa316122896659e2f9 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Sun, 29 Mar 2020 17:46:28 +1000 Subject: [PATCH 45/66] Improve multi diagnostics --- src/transformation/utils/diagnostics.ts | 22 ++++- src/transformation/visitors/helpers/multi.ts | 80 +++++++++++++------ src/transformation/visitors/identifier.ts | 4 +- src/transformation/visitors/literal.ts | 4 +- .../helpers/__snapshots__/multi.spec.ts.snap | 20 +++-- test/unit/helpers/multi.spec.ts | 30 ++++--- 6 files changed, 111 insertions(+), 49 deletions(-) diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index b7e8fd17f..3df758103 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -127,7 +127,27 @@ export const unsupportedVarDeclaration = createDiagnosticFactory( export const unknownHelperKind = createDiagnosticFactory((kind: string) => `Unknown helper kind "${kind}".`); -export const invalidMultiHelperFunctionUse = createDiagnosticFactory("Invalid $multi() function use."); +export const unsupportedMultiHelperFunctionPosition = createDiagnosticFactory( + "The $multi() function cannot be used here." +); + +export const invalidMultiReturnToNonArrayBindingPattern = createDiagnosticFactory( + "Expected an array destructuring pattern." +); + +export const invalidMultiReturnToNonArrayLiteral = createDiagnosticFactory("Expected an array literal."); + +export const invalidMultiReturnToEmptyPatternOrArrayLiteral = createDiagnosticFactory( + "There must be one or more elements specified here." +); + +export const invalidMultiReturnArrayBindingPatternElementInitializer = createDiagnosticFactory( + "This array binding pattern cannot have initializers." +); + +export const invalidMultiReturnArrayLiteralElementInitializer = createDiagnosticFactory( + "This array literal pattern cannot have initializers." +); export const unsupportedMultiFunctionAssignment = createDiagnosticFactory( "Omitted expressions and BindingElements are expected here." diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index 3cc821322..88933fb0b 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -9,8 +9,13 @@ import { transformArguments } from "../call"; import { getDependenciesOfSymbol, createExportedIdentifier } from "../../utils/export"; import { createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast"; import { - invalidMultiHelperFunctionUse, + invalidMultiReturnArrayBindingPatternElementInitializer, + invalidMultiReturnArrayLiteralElementInitializer, + invalidMultiReturnToEmptyPatternOrArrayLiteral, + invalidMultiReturnToNonArrayBindingPattern, + invalidMultiReturnToNonArrayLiteral, unsupportedMultiFunctionAssignment, + unsupportedMultiHelperFunctionPosition, } from "../../../transformation/utils/diagnostics"; const isMultiHelperDeclaration = (context: TransformationContext) => (declaration: ts.Declaration): boolean => { @@ -52,11 +57,15 @@ function transformMultiHelperCallArguments( context: TransformationContext, expression: ts.CallExpression ): lua.Expression[] | lua.Expression { - return isMultiHelperCallSignature(context, expression) - ? expression.arguments.length > 0 - ? expression.arguments.map(e => context.transformExpression(e)) - : lua.createNilLiteral(expression) - : context.transformExpression(expression); + if (!isMultiHelperCallSignature(context, expression)) { + return context.transformExpression(expression); + } + + if (expression.arguments.length < 1) { + return lua.createNilLiteral(expression); + } + + return expression.arguments.map(e => context.transformExpression(e)); } export function transformMultiHelperVariableDeclaration( @@ -66,24 +75,41 @@ export function transformMultiHelperVariableDeclaration( if (!declaration.initializer) return; if (!isMultiReturnCall(context, declaration.initializer)) return; - if (!ts.isArrayBindingPattern(declaration.name) || declaration.name.elements.length < 1) { - context.diagnostics.push(invalidMultiHelperFunctionUse(declaration.name)); + if (!ts.isArrayBindingPattern(declaration.name)) { + context.diagnostics.push(invalidMultiReturnToNonArrayBindingPattern(declaration.name)); return []; } - const leftIdentifiers = declaration.name.elements - .map(element => { - if (ts.isBindingElement(element) && ts.isIdentifier(element.name)) { - return transformIdentifier(context, element.name); - } + if (declaration.name.elements.length < 1) { + context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(declaration.name)); + return []; + } + + const leftIdentifiers: lua.Identifier[] = []; - if (ts.isOmittedExpression(element)) { - return lua.createAnonymousIdentifier(element); + for (const element of declaration.name.elements) { + let expression: lua.Identifier | undefined; + + if (ts.isBindingElement(element) && ts.isIdentifier(element.name)) { + if (element.initializer) { + context.diagnostics.push(invalidMultiReturnArrayBindingPatternElementInitializer(element)); + continue; + } else { + expression = transformIdentifier(context, element.name); } + } + + if (ts.isOmittedExpression(element)) { + expression = lua.createAnonymousIdentifier(element); + } + if (!expression) { context.diagnostics.push(unsupportedMultiFunctionAssignment(element)); - }) - .filter(isNonNull); + continue; + } + + leftIdentifiers.push(expression); + } const rightExpressions = transformMultiHelperCallArguments(context, declaration.initializer); return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration); @@ -97,12 +123,18 @@ export function transformMultiHelperDestructuringAssignmentStatement( if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return; if (!isMultiReturnCall(context, statement.expression.right)) return; - if ( - !ts.isArrayLiteralExpression(statement.expression.left) || - statement.expression.left.elements.length < 1 || - statement.expression.left.elements.some(ts.isBinaryExpression) - ) { - context.diagnostics.push(invalidMultiHelperFunctionUse(statement.expression.left)); + if (!ts.isArrayLiteralExpression(statement.expression.left)) { + context.diagnostics.push(invalidMultiReturnToNonArrayLiteral(statement.expression.left)); + return []; + } + + if (statement.expression.left.elements.some(ts.isBinaryExpression)) { + context.diagnostics.push(invalidMultiReturnArrayLiteralElementInitializer(statement.expression.left)); + return []; + } + + if (statement.expression.left.elements.length < 1) { + context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(statement.expression.left)); return []; } @@ -141,7 +173,7 @@ export function findMultiHelperAssignmentViolations( if (valueSymbol) { const declaration = valueSymbol.valueDeclaration; if (declaration && isMultiHelperDeclaration(context)(declaration)) { - context.diagnostics.push(invalidMultiHelperFunctionUse(element)); + context.diagnostics.push(unsupportedMultiHelperFunctionPosition(element)); return element; } } diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index bdff42b3f..0fad01fea 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -3,7 +3,7 @@ import * as lua from "../../LuaAST"; import { transformBuiltinIdentifierExpression } from "../builtins"; import { FunctionVisitor, TransformationContext } from "../context"; import { isForRangeType } from "../utils/annotations"; -import { invalidForRangeCall, invalidMultiHelperFunctionUse } from "../utils/diagnostics"; +import { invalidForRangeCall, unsupportedMultiHelperFunctionPosition } from "../utils/diagnostics"; import { createExportedIdentifier, getSymbolExportScope } from "../utils/export"; import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names"; import { getIdentifierSymbolId } from "../utils/symbols"; @@ -12,7 +12,7 @@ import { isMultiHelperNode } from "./helpers/multi"; export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier { if (isMultiHelperNode(context, identifier)) { - context.diagnostics.push(invalidMultiHelperFunctionUse(identifier)); + context.diagnostics.push(unsupportedMultiHelperFunctionPosition(identifier)); return lua.createAnonymousIdentifier(identifier); } diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 23979a448..d31b5686f 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -2,7 +2,7 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import { assertNever } from "../../utils"; import { FunctionVisitor, TransformationContext, Visitors } from "../context"; -import { unsupportedAccessorInObjectLiteral, invalidMultiHelperFunctionUse } from "../utils/diagnostics"; +import { unsupportedAccessorInObjectLiteral, unsupportedMultiHelperFunctionPosition } from "../utils/diagnostics"; import { createExportedIdentifier, getSymbolExportScope } from "../utils/export"; import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { createSafeName, hasUnsafeIdentifierName, hasUnsafeSymbolName } from "../utils/safe-names"; @@ -66,7 +66,7 @@ const transformObjectLiteralExpression: FunctionVisitor 0) { violations.forEach(element => { - context.diagnostics.push(invalidMultiHelperFunctionUse(element)); + context.diagnostics.push(unsupportedMultiHelperFunctionPosition(element)); }); return lua.createNilLiteral(expression); } diff --git a/test/unit/helpers/__snapshots__/multi.spec.ts.snap b/test/unit/helpers/__snapshots__/multi.spec.ts.snap index 723d607b0..f26526f11 100644 --- a/test/unit/helpers/__snapshots__/multi.spec.ts.snap +++ b/test/unit/helpers/__snapshots__/multi.spec.ts.snap @@ -2,11 +2,11 @@ exports[`invalid $multi call ($multi()): code 1`] = `"____(_G)"`; -exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`; +exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`; exports[`invalid $multi call ($multi): code 1`] = `"local ____ = ____"`; -exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`; +exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`; exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` "local function ____(____, ____bindingPattern0) @@ -17,24 +17,28 @@ exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` end" `; -exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: Invalid $multi() function use."`; +exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi() function cannot be used here."`; exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = nil"`; -exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: Invalid $multi() function use."`; +exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi() function cannot be used here."`; exports[`invalid $multi call ([] = $multi()): code 1`] = `""`; -exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: Invalid $multi() function use."`; +exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: There must be one or more elements specified here."`; exports[`invalid $multi call (const [] = $multi();): code 1`] = `""`; -exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid $multi() function use."`; +exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: There must be one or more elements specified here."`; + +exports[`invalid $multi call (const [a = 0] = $multi()): code 1`] = `""`; + +exports[`invalid $multi call (const [a = 0] = $multi()): diagnostics 1`] = `"main.ts(2,16): error TSTL: This array binding pattern cannot have initializers."`; exports[`invalid $multi call (const {} = $multi();): code 1`] = `""`; -exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid $multi() function use."`; +exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`; exports[`invalid $multi call (const a = $multi();): code 1`] = `""`; -exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Invalid $multi() function use."`; +exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`; diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index df5caae92..a46a83a52 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -1,7 +1,12 @@ import * as path from "path"; import * as util from "../../util"; import * as tstl from "../../../src"; -import { invalidMultiHelperFunctionUse } from "../../../src/transformation/utils/diagnostics"; +import { + unsupportedMultiHelperFunctionPosition, + invalidMultiReturnToNonArrayBindingPattern, + invalidMultiReturnArrayBindingPatternElementInitializer, + invalidMultiReturnToEmptyPatternOrArrayLiteral, +} from "../../../src/transformation/utils/diagnostics"; const multiProjectOptions: tstl.CompilerOptions = { types: [path.resolve(__dirname, "../../../helpers")], @@ -25,21 +30,22 @@ test.each<[string, any]>([ .expectToEqual(result); }); -test.each([ - "$multi", - "$multi()", - "({ $multi });", - "[] = $multi()", - "const [] = $multi();", - "const a = $multi();", - "const {} = $multi();", - "([a] = $multi(1)) => {}", -])("invalid $multi call (%s)", statement => { +test.each<[string, number[]]>([ + ["$multi", [unsupportedMultiHelperFunctionPosition.code]], + ["$multi()", [unsupportedMultiHelperFunctionPosition.code]], + ["({ $multi });", [unsupportedMultiHelperFunctionPosition.code]], + ["[] = $multi()", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]], + ["const [] = $multi();", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]], + ["const a = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], + ["const {} = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], + ["([a] = $multi(1)) => {}", [unsupportedMultiHelperFunctionPosition.code]], + ["const [a = 0] = $multi()", [invalidMultiReturnArrayBindingPatternElementInitializer.code]], +])("invalid $multi call (%s)", (statement, diagnostics) => { util.testModule` ${statement} ` .setOptions(multiProjectOptions) - .expectDiagnosticsToMatchSnapshot([invalidMultiHelperFunctionUse.code]); + .expectDiagnosticsToMatchSnapshot(diagnostics); }); test.each<[string, any]>([ From e1d8538e2e0494567394a181cdd11b1ba103b448 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 30 Mar 2020 22:14:06 +1000 Subject: [PATCH 46/66] Apply suggestions from code review Co-Authored-By: ark120202 --- src/transformation/visitors/helpers/multi.ts | 35 ++++++-------------- src/transformation/visitors/literal.ts | 4 +-- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index 88933fb0b..c7c8d9d15 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -61,7 +61,7 @@ function transformMultiHelperCallArguments( return context.transformExpression(expression); } - if (expression.arguments.length < 1) { + if (expression.arguments.length === 0) { return lua.createNilLiteral(expression); } @@ -88,27 +88,17 @@ export function transformMultiHelperVariableDeclaration( const leftIdentifiers: lua.Identifier[] = []; for (const element of declaration.name.elements) { - let expression: lua.Identifier | undefined; - - if (ts.isBindingElement(element) && ts.isIdentifier(element.name)) { + if (ts.isBindingElement(element)) { if (element.initializer) { context.diagnostics.push(invalidMultiReturnArrayBindingPatternElementInitializer(element)); - continue; + } else if (ts.isIdentifier(element.name)) { + leftIdentifiers.push(transformIdentifier(context, element.name)); } else { - expression = transformIdentifier(context, element.name); + context.diagnostics.push(unsupportedMultiFunctionAssignment(element)); } + } else if (ts.isOmittedExpression(element)) { + leftIdentifiers.push(lua.createAnonymousIdentifier(element)); } - - if (ts.isOmittedExpression(element)) { - expression = lua.createAnonymousIdentifier(element); - } - - if (!expression) { - context.diagnostics.push(unsupportedMultiFunctionAssignment(element)); - continue; - } - - leftIdentifiers.push(expression); } const rightExpressions = transformMultiHelperCallArguments(context, declaration.initializer); @@ -138,13 +128,10 @@ export function transformMultiHelperDestructuringAssignmentStatement( return []; } - const transformLeft = (expression: ts.Expression): lua.AssignmentLeftHandSideExpression => { - if (ts.isOmittedExpression(expression)) { - return lua.createAnonymousIdentifier(expression); - } else { - return transformAssignmentLeftHandSideExpression(context, expression); - } - }; + const transformLeft = (expression: ts.Expression): lua.AssignmentLeftHandSideExpression => + ts.isOmittedExpression(expression) + ? lua.createAnonymousIdentifier(expression) + : transformAssignmentLeftHandSideExpression(context, expression); const leftIdentifiers = statement.expression.left.elements.map(transformLeft); diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index d31b5686f..082a1c44c 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -65,9 +65,7 @@ const transformNumericLiteralExpression: FunctionVisitor = ex const transformObjectLiteralExpression: FunctionVisitor = (expression, context) => { const violations = findMultiHelperAssignmentViolations(context, expression); if (violations.length > 0) { - violations.forEach(element => { - context.diagnostics.push(unsupportedMultiHelperFunctionPosition(element)); - }); + context.diagnostics.push(...violations.map(e => unsupportedMultiHelperFunctionPosition(e))); return lua.createNilLiteral(expression); } From fc63fc9af3701fefd29073658a77a61e13f44127 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 30 Mar 2020 22:15:21 +1000 Subject: [PATCH 47/66] Rename utils to helpers --- src/transformation/visitors/helpers/multi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index c7c8d9d15..5a82ad766 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -1,6 +1,6 @@ import * as ts from "typescript"; import * as lua from "../../../LuaAST"; -import * as utils from "../../utils/helpers"; +import * as helpers from "../../utils/helpers"; import { isNonNull } from "../../../utils"; import { TransformationContext } from "../../context"; import { transformAssignmentLeftHandSideExpression } from "../binary-expression/assignments"; @@ -19,7 +19,7 @@ import { } from "../../../transformation/utils/diagnostics"; const isMultiHelperDeclaration = (context: TransformationContext) => (declaration: ts.Declaration): boolean => { - return utils.getHelperFileKind(context, declaration.getSourceFile()) === utils.HelperKind.Multi; + return helpers.getHelperFileKind(context, declaration.getSourceFile()) === helpers.HelperKind.Multi; }; function isMultiHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { From 823ee93657d69b15fd0cd740f3bf667b11eb8eeb Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 30 Mar 2020 22:21:01 +1000 Subject: [PATCH 48/66] Include helpers inside lualib's tsconfig --- src/lualib/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lualib/tsconfig.json b/src/lualib/tsconfig.json index a9ca1cb4d..59abf4aba 100644 --- a/src/lualib/tsconfig.json +++ b/src/lualib/tsconfig.json @@ -3,7 +3,7 @@ "outDir": "../../dist/lualib", "target": "esnext", "lib": ["esnext"], - "types": [], + "types": ["../../helpers"], "skipLibCheck": true, "noUnusedLocals": true, From 19b735cea54adfcb8b6bef78660312ffa497dd06 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 3 Aug 2020 16:03:00 +1000 Subject: [PATCH 49/66] Fix esnext target error --- helpers/multi.d.ts | 2 +- src/transformation/visitors/helpers/multi.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts index 1a84fbbe5..d7270be32 100644 --- a/helpers/multi.d.ts +++ b/helpers/multi.d.ts @@ -1,2 +1,2 @@ declare function $multi(...values: T): MultiReturn; -declare type MultiReturn = T & [] & { readonly " __multiBrand": unique symbol }; +declare type MultiReturn = T & { readonly " __multiBrand": unique symbol }; diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index 5a82ad766..811d77edc 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -18,9 +18,8 @@ import { unsupportedMultiHelperFunctionPosition, } from "../../../transformation/utils/diagnostics"; -const isMultiHelperDeclaration = (context: TransformationContext) => (declaration: ts.Declaration): boolean => { - return helpers.getHelperFileKind(context, declaration.getSourceFile()) === helpers.HelperKind.Multi; -}; +const isMultiHelperDeclaration = (context: TransformationContext) => (declaration: ts.Declaration): boolean => + helpers.getHelperFileKind(context, declaration.getSourceFile()) === helpers.HelperKind.Multi; function isMultiHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); From b7d1ec0c6b29ccbcf5b904974f76ce20108fe1aa Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 3 Aug 2020 16:13:47 +1000 Subject: [PATCH 50/66] Drop support for VariableDeclarations, support only ReturnStatements --- src/transformation/utils/diagnostics.ts | 24 +--- .../visitors/expression-statement.ts | 6 - src/transformation/visitors/helpers/multi.ts | 110 +----------------- .../visitors/variable-declaration.ts | 6 - .../helpers/__snapshots__/multi.spec.ts.snap | 45 +++---- test/unit/helpers/multi.spec.ts | 54 ++------- 6 files changed, 24 insertions(+), 221 deletions(-) diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index 00e249b00..f674c6881 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -130,27 +130,5 @@ export const unsupportedVarDeclaration = createDiagnosticFactory( export const unknownHelperKind = createDiagnosticFactory((kind: string) => `Unknown helper kind "${kind}".`); export const unsupportedMultiHelperFunctionPosition = createDiagnosticFactory( - "The $multi() function cannot be used here." -); - -export const invalidMultiReturnToNonArrayBindingPattern = createDiagnosticFactory( - "Expected an array destructuring pattern." -); - -export const invalidMultiReturnToNonArrayLiteral = createDiagnosticFactory("Expected an array literal."); - -export const invalidMultiReturnToEmptyPatternOrArrayLiteral = createDiagnosticFactory( - "There must be one or more elements specified here." -); - -export const invalidMultiReturnArrayBindingPatternElementInitializer = createDiagnosticFactory( - "This array binding pattern cannot have initializers." -); - -export const invalidMultiReturnArrayLiteralElementInitializer = createDiagnosticFactory( - "This array literal pattern cannot have initializers." -); - -export const unsupportedMultiFunctionAssignment = createDiagnosticFactory( - "Omitted expressions and BindingElements are expected here." + "The $multi() function can only be used in a return statement." ); diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index 531902b15..0b338be6f 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -5,14 +5,8 @@ import { transformBinaryExpressionStatement } from "./binary-expression"; import { transformDeleteExpressionStatement } from "./delete"; import { transformLuaTableExpressionStatement } from "./lua-table"; import { transformUnaryExpressionStatement } from "./unary-expression"; -import { transformMultiHelperDestructuringAssignmentStatement } from "./helpers/multi"; export const transformExpressionStatement: FunctionVisitor = (node, context) => { - const multiResult = transformMultiHelperDestructuringAssignmentStatement(context, node); - if (multiResult) { - return multiResult; - } - const luaTableResult = transformLuaTableExpressionStatement(context, node); if (luaTableResult) { return luaTableResult; diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index 811d77edc..9c4e4b542 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -3,20 +3,8 @@ import * as lua from "../../../LuaAST"; import * as helpers from "../../utils/helpers"; import { isNonNull } from "../../../utils"; 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 { - invalidMultiReturnArrayBindingPatternElementInitializer, - invalidMultiReturnArrayLiteralElementInitializer, - invalidMultiReturnToEmptyPatternOrArrayLiteral, - invalidMultiReturnToNonArrayBindingPattern, - invalidMultiReturnToNonArrayLiteral, - unsupportedMultiFunctionAssignment, - unsupportedMultiHelperFunctionPosition, -} from "../../../transformation/utils/diagnostics"; +import { unsupportedMultiHelperFunctionPosition } from "../../../transformation/utils/diagnostics"; const isMultiHelperDeclaration = (context: TransformationContext) => (declaration: ts.Declaration): boolean => helpers.getHelperFileKind(context, declaration.getSourceFile()) === helpers.HelperKind.Multi; @@ -52,102 +40,6 @@ export function transformMultiHelperReturnStatement( return lua.createReturnStatement(expressions, statement); } -function transformMultiHelperCallArguments( - context: TransformationContext, - expression: ts.CallExpression -): lua.Expression[] | lua.Expression { - if (!isMultiHelperCallSignature(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 transformMultiHelperVariableDeclaration( - context: TransformationContext, - declaration: ts.VariableDeclaration -): lua.Statement[] | undefined { - if (!declaration.initializer) return; - if (!isMultiReturnCall(context, declaration.initializer)) return; - - if (!ts.isArrayBindingPattern(declaration.name)) { - context.diagnostics.push(invalidMultiReturnToNonArrayBindingPattern(declaration.name)); - return []; - } - - if (declaration.name.elements.length < 1) { - context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(declaration.name)); - return []; - } - - const leftIdentifiers: lua.Identifier[] = []; - - for (const element of declaration.name.elements) { - if (ts.isBindingElement(element)) { - if (element.initializer) { - context.diagnostics.push(invalidMultiReturnArrayBindingPatternElementInitializer(element)); - } else 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 = transformMultiHelperCallArguments(context, declaration.initializer); - return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration); -} - -export function transformMultiHelperDestructuringAssignmentStatement( - context: TransformationContext, - statement: ts.ExpressionStatement -): lua.Statement[] | undefined { - if (!ts.isBinaryExpression(statement.expression)) return; - if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return; - if (!isMultiReturnCall(context, statement.expression.right)) return; - - if (!ts.isArrayLiteralExpression(statement.expression.left)) { - context.diagnostics.push(invalidMultiReturnToNonArrayLiteral(statement.expression.left)); - return []; - } - - if (statement.expression.left.elements.some(ts.isBinaryExpression)) { - context.diagnostics.push(invalidMultiReturnArrayLiteralElementInitializer(statement.expression.left)); - return []; - } - - if (statement.expression.left.elements.length < 1) { - context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(statement.expression.left)); - 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 = transformMultiHelperCallArguments(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 findMultiHelperAssignmentViolations( context: TransformationContext, node: ts.ObjectLiteralExpression diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 0bcc9415a..12ffeb763 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,7 +10,6 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; -import { transformMultiHelperVariableDeclaration } from "./helpers/multi"; export function transformArrayBindingElement( context: TransformationContext, @@ -218,11 +217,6 @@ export function transformVariableDeclaration( context: TransformationContext, statement: ts.VariableDeclaration ): lua.Statement[] { - const result = transformMultiHelperVariableDeclaration(context, statement); - if (result) { - return result; - } - if (statement.initializer && statement.type) { const initializerType = context.checker.getTypeAtLocation(statement.initializer); const varType = context.checker.getTypeFromTypeNode(statement.type); diff --git a/test/unit/helpers/__snapshots__/multi.spec.ts.snap b/test/unit/helpers/__snapshots__/multi.spec.ts.snap index f26526f11..2d212e54d 100644 --- a/test/unit/helpers/__snapshots__/multi.spec.ts.snap +++ b/test/unit/helpers/__snapshots__/multi.spec.ts.snap @@ -2,43 +2,28 @@ exports[`invalid $multi call ($multi()): code 1`] = `"____(_G)"`; -exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`; +exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function can only be used in a return statement."`; exports[`invalid $multi call ($multi): code 1`] = `"local ____ = ____"`; -exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`; - -exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` -"local function ____(____, ____bindingPattern0) - if ____bindingPattern0 == nil then - ____bindingPattern0 = ____(_G, 1) - end - local a = ____bindingPattern0[1] -end" -`; - -exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi() function cannot be used here."`; +exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function can only be used in a return statement."`; exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = nil"`; -exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi() function cannot be used here."`; - -exports[`invalid $multi call ([] = $multi()): code 1`] = `""`; - -exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: There must be one or more elements specified here."`; - -exports[`invalid $multi call (const [] = $multi();): code 1`] = `""`; +exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi() function can only be used in a return statement."`; -exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: There must be one or more elements specified here."`; - -exports[`invalid $multi call (const [a = 0] = $multi()): code 1`] = `""`; - -exports[`invalid $multi call (const [a = 0] = $multi()): diagnostics 1`] = `"main.ts(2,16): error TSTL: This array binding pattern cannot have initializers."`; - -exports[`invalid $multi call (const {} = $multi();): code 1`] = `""`; +exports[`invalid $multi call ([] = $multi()): code 1`] = ` +" = table.unpack( + ____(_G) +)" +`; -exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`; +exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,14): error TSTL: The $multi() function can only be used in a return statement."`; -exports[`invalid $multi call (const a = $multi();): code 1`] = `""`; +exports[`invalid $multi call (const [] = $multi();): code 1`] = ` +"____ = table.unpack( + ____(_G) +)" +`; -exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`; +exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,20): error TSTL: The $multi() function can only be used in a return statement."`; diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index a46a83a52..a3e601ff5 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -1,32 +1,21 @@ import * as path from "path"; import * as util from "../../util"; import * as tstl from "../../../src"; -import { - unsupportedMultiHelperFunctionPosition, - invalidMultiReturnToNonArrayBindingPattern, - invalidMultiReturnArrayBindingPatternElementInitializer, - invalidMultiReturnToEmptyPatternOrArrayLiteral, -} from "../../../src/transformation/utils/diagnostics"; +import { unsupportedMultiHelperFunctionPosition } from "../../../src/transformation/utils/diagnostics"; const multiProjectOptions: tstl.CompilerOptions = { types: [path.resolve(__dirname, "../../../helpers")], }; test.each<[string, any]>([ - ["let a; [a] = $multi();", undefined], - ["const [a] = $multi();", undefined], - ["const [a] = $multi(1);", 1], - ["const ar = [1]; const [a] = $multi(...ar);", 1], - ["const _ = null, [a] = $multi(1);", 1], - ["let a; for (const [a] = $multi(1, 2); false; 1) {}", undefined], - ["let a; for ([a] = $multi(1, 2); false; 1) {}", 1], + ["return $multi();", undefined], + ["return $multi(1);", 1], + ["const ar = [1]; return $multi(...ar);", 1], ])("valid $multi call and assign (%s)", (statement, result) => { - util.testModule` + util.testFunction` ${statement} - export { a }; ` .setOptions(multiProjectOptions) - .setReturnExport("a") .expectToEqual(result); }); @@ -34,12 +23,8 @@ test.each<[string, number[]]>([ ["$multi", [unsupportedMultiHelperFunctionPosition.code]], ["$multi()", [unsupportedMultiHelperFunctionPosition.code]], ["({ $multi });", [unsupportedMultiHelperFunctionPosition.code]], - ["[] = $multi()", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]], - ["const [] = $multi();", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]], - ["const a = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], - ["const {} = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], - ["([a] = $multi(1)) => {}", [unsupportedMultiHelperFunctionPosition.code]], - ["const [a = 0] = $multi()", [invalidMultiReturnArrayBindingPatternElementInitializer.code]], + ["[] = $multi()", [unsupportedMultiHelperFunctionPosition.code]], + ["const [] = $multi();", [unsupportedMultiHelperFunctionPosition.code]], ])("invalid $multi call (%s)", (statement, diagnostics) => { util.testModule` ${statement} @@ -47,28 +32,3 @@ test.each<[string, number[]]>([ .setOptions(multiProjectOptions) .expectDiagnosticsToMatchSnapshot(diagnostics); }); - -test.each<[string, any]>([ - ["return $multi();", undefined], - ["return $multi(1);", 1], -])("valid $multi call return statement (%s)", (statement, result) => { - util.testModule` - export const [a] = (function() { - ${statement} - })(); - ` - .setOptions(multiProjectOptions) - .setReturnExport("a") - .expectToEqual(result); -}); - -test("$multi helper call with destructuring assignment side effects", () => { - util.testModule` - let a; - export { a }; - [a] = $multi(1); - ` - .setOptions(multiProjectOptions) - .setReturnExport("a") - .expectToEqual(1); -}); From f559fff00ea2fcc4e588d8560410c2189ae94404 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 3 Aug 2020 16:26:14 +1000 Subject: [PATCH 51/66] Simplify multi tests a bit --- .../helpers/__snapshots__/multi.spec.ts.snap | 58 ++++++++++++++----- test/unit/helpers/multi.spec.ts | 23 ++++---- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/test/unit/helpers/__snapshots__/multi.spec.ts.snap b/test/unit/helpers/__snapshots__/multi.spec.ts.snap index 2d212e54d..538dfd8ca 100644 --- a/test/unit/helpers/__snapshots__/multi.spec.ts.snap +++ b/test/unit/helpers/__snapshots__/multi.spec.ts.snap @@ -1,29 +1,55 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`invalid $multi call ($multi()): code 1`] = `"____(_G)"`; +exports[`unsupported $multi call ($multi()): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + ____(nil) +end +return ____exports" +`; -exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function can only be used in a return statement."`; +exports[`unsupported $multi call ($multi()): diagnostics 1`] = `"main.ts(2,13): error TSTL: The $multi() function can only be used in a return statement."`; -exports[`invalid $multi call ($multi): code 1`] = `"local ____ = ____"`; +exports[`unsupported $multi call ($multi): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + local ____ = ____ +end +return ____exports" +`; -exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function can only be used in a return statement."`; +exports[`unsupported $multi call ($multi): diagnostics 1`] = `"main.ts(2,13): error TSTL: The $multi() function can only be used in a return statement."`; -exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = nil"`; +exports[`unsupported $multi call (({ $multi });): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + local ____ = nil +end +return ____exports" +`; -exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi() function can only be used in a return statement."`; +exports[`unsupported $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi() function can only be used in a return statement."`; -exports[`invalid $multi call ([] = $multi()): code 1`] = ` -" = table.unpack( - ____(_G) -)" +exports[`unsupported $multi call ([] = $multi()): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + = table.unpack( + ____(nil) + ) +end +return ____exports" `; -exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,14): error TSTL: The $multi() function can only be used in a return statement."`; +exports[`unsupported $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,18): error TSTL: The $multi() function can only be used in a return statement."`; -exports[`invalid $multi call (const [] = $multi();): code 1`] = ` -"____ = table.unpack( - ____(_G) -)" +exports[`unsupported $multi call (const [] = $multi();): code 1`] = ` +"local ____exports = {} +function ____exports.__main(self) + local ____ = table.unpack( + ____(nil) + ) +end +return ____exports" `; -exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,20): error TSTL: The $multi() function can only be used in a return statement."`; +exports[`unsupported $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,24): error TSTL: The $multi() function can only be used in a return statement."`; diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index a3e601ff5..e0a62452e 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -19,16 +19,13 @@ test.each<[string, any]>([ .expectToEqual(result); }); -test.each<[string, number[]]>([ - ["$multi", [unsupportedMultiHelperFunctionPosition.code]], - ["$multi()", [unsupportedMultiHelperFunctionPosition.code]], - ["({ $multi });", [unsupportedMultiHelperFunctionPosition.code]], - ["[] = $multi()", [unsupportedMultiHelperFunctionPosition.code]], - ["const [] = $multi();", [unsupportedMultiHelperFunctionPosition.code]], -])("invalid $multi call (%s)", (statement, diagnostics) => { - util.testModule` - ${statement} - ` - .setOptions(multiProjectOptions) - .expectDiagnosticsToMatchSnapshot(diagnostics); -}); +test.each(["$multi", "$multi()", "({ $multi });", "[] = $multi()", "const [] = $multi();"])( + "unsupported $multi call (%s)", + statement => { + util.testFunction` + ${statement} + ` + .setOptions(multiProjectOptions) + .expectDiagnosticsToMatchSnapshot([unsupportedMultiHelperFunctionPosition.code]); + } +); From 079387ab4bb85408683e1542a914952abf25020e Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 4 Aug 2020 20:22:20 +1000 Subject: [PATCH 52/66] Remove multi.js --- helpers/multi.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 helpers/multi.js diff --git a/helpers/multi.js b/helpers/multi.js deleted file mode 100644 index ae81ca061..000000000 --- a/helpers/multi.js +++ /dev/null @@ -1 +0,0 @@ -module.exports.multi = (...values) => values; From e3c1e9b55045dfb1f94a8b4a8753083223d04410 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 5 Aug 2020 16:34:18 +1000 Subject: [PATCH 53/66] Revert "Drop support for VariableDeclarations, support only ReturnStatements" This reverts commit b7d1ec0c6b29ccbcf5b904974f76ce20108fe1aa. --- src/transformation/utils/diagnostics.ts | 24 +++- .../visitors/expression-statement.ts | 6 + src/transformation/visitors/helpers/multi.ts | 110 +++++++++++++++++- .../visitors/variable-declaration.ts | 6 + .../helpers/__snapshots__/multi.spec.ts.snap | 73 +++++++----- test/unit/helpers/multi.spec.ts | 71 ++++++++--- 6 files changed, 244 insertions(+), 46 deletions(-) diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index f674c6881..00e249b00 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -130,5 +130,27 @@ export const unsupportedVarDeclaration = createDiagnosticFactory( export const unknownHelperKind = createDiagnosticFactory((kind: string) => `Unknown helper kind "${kind}".`); export const unsupportedMultiHelperFunctionPosition = createDiagnosticFactory( - "The $multi() function can only be used in a return statement." + "The $multi() function cannot be used here." +); + +export const invalidMultiReturnToNonArrayBindingPattern = createDiagnosticFactory( + "Expected an array destructuring pattern." +); + +export const invalidMultiReturnToNonArrayLiteral = createDiagnosticFactory("Expected an array literal."); + +export const invalidMultiReturnToEmptyPatternOrArrayLiteral = createDiagnosticFactory( + "There must be one or more elements specified here." +); + +export const invalidMultiReturnArrayBindingPatternElementInitializer = createDiagnosticFactory( + "This array binding pattern cannot have initializers." +); + +export const invalidMultiReturnArrayLiteralElementInitializer = createDiagnosticFactory( + "This array literal pattern cannot have initializers." +); + +export const unsupportedMultiFunctionAssignment = createDiagnosticFactory( + "Omitted expressions and BindingElements are expected here." ); diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index 0b338be6f..531902b15 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -5,8 +5,14 @@ import { transformBinaryExpressionStatement } from "./binary-expression"; import { transformDeleteExpressionStatement } from "./delete"; import { transformLuaTableExpressionStatement } from "./lua-table"; import { transformUnaryExpressionStatement } from "./unary-expression"; +import { transformMultiHelperDestructuringAssignmentStatement } from "./helpers/multi"; export const transformExpressionStatement: FunctionVisitor = (node, context) => { + const multiResult = transformMultiHelperDestructuringAssignmentStatement(context, node); + if (multiResult) { + return multiResult; + } + const luaTableResult = transformLuaTableExpressionStatement(context, node); if (luaTableResult) { return luaTableResult; diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index 9c4e4b542..811d77edc 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -3,8 +3,20 @@ import * as lua from "../../../LuaAST"; import * as helpers from "../../utils/helpers"; import { isNonNull } from "../../../utils"; import { TransformationContext } from "../../context"; +import { transformAssignmentLeftHandSideExpression } from "../binary-expression/assignments"; +import { transformIdentifier } from "../identifier"; import { transformArguments } from "../call"; -import { unsupportedMultiHelperFunctionPosition } from "../../../transformation/utils/diagnostics"; +import { getDependenciesOfSymbol, createExportedIdentifier } from "../../utils/export"; +import { createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast"; +import { + invalidMultiReturnArrayBindingPatternElementInitializer, + invalidMultiReturnArrayLiteralElementInitializer, + invalidMultiReturnToEmptyPatternOrArrayLiteral, + invalidMultiReturnToNonArrayBindingPattern, + invalidMultiReturnToNonArrayLiteral, + unsupportedMultiFunctionAssignment, + unsupportedMultiHelperFunctionPosition, +} from "../../../transformation/utils/diagnostics"; const isMultiHelperDeclaration = (context: TransformationContext) => (declaration: ts.Declaration): boolean => helpers.getHelperFileKind(context, declaration.getSourceFile()) === helpers.HelperKind.Multi; @@ -40,6 +52,102 @@ export function transformMultiHelperReturnStatement( return lua.createReturnStatement(expressions, statement); } +function transformMultiHelperCallArguments( + context: TransformationContext, + expression: ts.CallExpression +): lua.Expression[] | lua.Expression { + if (!isMultiHelperCallSignature(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 transformMultiHelperVariableDeclaration( + context: TransformationContext, + declaration: ts.VariableDeclaration +): lua.Statement[] | undefined { + if (!declaration.initializer) return; + if (!isMultiReturnCall(context, declaration.initializer)) return; + + if (!ts.isArrayBindingPattern(declaration.name)) { + context.diagnostics.push(invalidMultiReturnToNonArrayBindingPattern(declaration.name)); + return []; + } + + if (declaration.name.elements.length < 1) { + context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(declaration.name)); + return []; + } + + const leftIdentifiers: lua.Identifier[] = []; + + for (const element of declaration.name.elements) { + if (ts.isBindingElement(element)) { + if (element.initializer) { + context.diagnostics.push(invalidMultiReturnArrayBindingPatternElementInitializer(element)); + } else 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 = transformMultiHelperCallArguments(context, declaration.initializer); + return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration); +} + +export function transformMultiHelperDestructuringAssignmentStatement( + context: TransformationContext, + statement: ts.ExpressionStatement +): lua.Statement[] | undefined { + if (!ts.isBinaryExpression(statement.expression)) return; + if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return; + if (!isMultiReturnCall(context, statement.expression.right)) return; + + if (!ts.isArrayLiteralExpression(statement.expression.left)) { + context.diagnostics.push(invalidMultiReturnToNonArrayLiteral(statement.expression.left)); + return []; + } + + if (statement.expression.left.elements.some(ts.isBinaryExpression)) { + context.diagnostics.push(invalidMultiReturnArrayLiteralElementInitializer(statement.expression.left)); + return []; + } + + if (statement.expression.left.elements.length < 1) { + context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(statement.expression.left)); + 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 = transformMultiHelperCallArguments(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 findMultiHelperAssignmentViolations( context: TransformationContext, node: ts.ObjectLiteralExpression diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 12ffeb763..0bcc9415a 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,6 +10,7 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; +import { transformMultiHelperVariableDeclaration } from "./helpers/multi"; export function transformArrayBindingElement( context: TransformationContext, @@ -217,6 +218,11 @@ export function transformVariableDeclaration( context: TransformationContext, statement: ts.VariableDeclaration ): lua.Statement[] { + const result = transformMultiHelperVariableDeclaration(context, statement); + if (result) { + return result; + } + if (statement.initializer && statement.type) { const initializerType = context.checker.getTypeAtLocation(statement.initializer); const varType = context.checker.getTypeFromTypeNode(statement.type); diff --git a/test/unit/helpers/__snapshots__/multi.spec.ts.snap b/test/unit/helpers/__snapshots__/multi.spec.ts.snap index 538dfd8ca..7f867af97 100644 --- a/test/unit/helpers/__snapshots__/multi.spec.ts.snap +++ b/test/unit/helpers/__snapshots__/multi.spec.ts.snap @@ -1,5 +1,48 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`invalid $multi call ($multi()): code 1`] = `"____(_G)"`; + +exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`; + +exports[`invalid $multi call ($multi): code 1`] = `"local ____ = ____"`; + +exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`; + +exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` +"local function ____(____, ____bindingPattern0) + if ____bindingPattern0 == nil then + ____bindingPattern0 = ____(_G, 1) + end + local a = ____bindingPattern0[1] +end" +`; + +exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi() function cannot be used here."`; + +exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = nil"`; + +exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi() function cannot be used here."`; + +exports[`invalid $multi call ([] = $multi()): code 1`] = `""`; + +exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: There must be one or more elements specified here."`; + +exports[`invalid $multi call (const [] = $multi();): code 1`] = `""`; + +exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: There must be one or more elements specified here."`; + +exports[`invalid $multi call (const [a = 0] = $multi()): code 1`] = `""`; + +exports[`invalid $multi call (const [a = 0] = $multi()): diagnostics 1`] = `"main.ts(2,16): error TSTL: This array binding pattern cannot have initializers."`; + +exports[`invalid $multi call (const {} = $multi();): code 1`] = `""`; + +exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`; + +exports[`invalid $multi call (const a = $multi();): code 1`] = `""`; + +exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`; + exports[`unsupported $multi call ($multi()): code 1`] = ` "local ____exports = {} function ____exports.__main(self) @@ -8,8 +51,6 @@ end return ____exports" `; -exports[`unsupported $multi call ($multi()): diagnostics 1`] = `"main.ts(2,13): error TSTL: The $multi() function can only be used in a return statement."`; - exports[`unsupported $multi call ($multi): code 1`] = ` "local ____exports = {} function ____exports.__main(self) @@ -18,8 +59,6 @@ end return ____exports" `; -exports[`unsupported $multi call ($multi): diagnostics 1`] = `"main.ts(2,13): error TSTL: The $multi() function can only be used in a return statement."`; - exports[`unsupported $multi call (({ $multi });): code 1`] = ` "local ____exports = {} function ____exports.__main(self) @@ -27,29 +66,3 @@ function ____exports.__main(self) end return ____exports" `; - -exports[`unsupported $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi() function can only be used in a return statement."`; - -exports[`unsupported $multi call ([] = $multi()): code 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - = table.unpack( - ____(nil) - ) -end -return ____exports" -`; - -exports[`unsupported $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,18): error TSTL: The $multi() function can only be used in a return statement."`; - -exports[`unsupported $multi call (const [] = $multi();): code 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local ____ = table.unpack( - ____(nil) - ) -end -return ____exports" -`; - -exports[`unsupported $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,24): error TSTL: The $multi() function can only be used in a return statement."`; diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index e0a62452e..a46a83a52 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -1,31 +1,74 @@ import * as path from "path"; import * as util from "../../util"; import * as tstl from "../../../src"; -import { unsupportedMultiHelperFunctionPosition } from "../../../src/transformation/utils/diagnostics"; +import { + unsupportedMultiHelperFunctionPosition, + invalidMultiReturnToNonArrayBindingPattern, + invalidMultiReturnArrayBindingPatternElementInitializer, + invalidMultiReturnToEmptyPatternOrArrayLiteral, +} from "../../../src/transformation/utils/diagnostics"; const multiProjectOptions: tstl.CompilerOptions = { types: [path.resolve(__dirname, "../../../helpers")], }; test.each<[string, any]>([ - ["return $multi();", undefined], - ["return $multi(1);", 1], - ["const ar = [1]; return $multi(...ar);", 1], + ["let a; [a] = $multi();", undefined], + ["const [a] = $multi();", undefined], + ["const [a] = $multi(1);", 1], + ["const ar = [1]; const [a] = $multi(...ar);", 1], + ["const _ = null, [a] = $multi(1);", 1], + ["let a; for (const [a] = $multi(1, 2); false; 1) {}", undefined], + ["let a; for ([a] = $multi(1, 2); false; 1) {}", 1], ])("valid $multi call and assign (%s)", (statement, result) => { - util.testFunction` + util.testModule` ${statement} + export { a }; ` .setOptions(multiProjectOptions) + .setReturnExport("a") .expectToEqual(result); }); -test.each(["$multi", "$multi()", "({ $multi });", "[] = $multi()", "const [] = $multi();"])( - "unsupported $multi call (%s)", - statement => { - util.testFunction` +test.each<[string, number[]]>([ + ["$multi", [unsupportedMultiHelperFunctionPosition.code]], + ["$multi()", [unsupportedMultiHelperFunctionPosition.code]], + ["({ $multi });", [unsupportedMultiHelperFunctionPosition.code]], + ["[] = $multi()", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]], + ["const [] = $multi();", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]], + ["const a = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], + ["const {} = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], + ["([a] = $multi(1)) => {}", [unsupportedMultiHelperFunctionPosition.code]], + ["const [a = 0] = $multi()", [invalidMultiReturnArrayBindingPatternElementInitializer.code]], +])("invalid $multi call (%s)", (statement, diagnostics) => { + util.testModule` + ${statement} + ` + .setOptions(multiProjectOptions) + .expectDiagnosticsToMatchSnapshot(diagnostics); +}); + +test.each<[string, any]>([ + ["return $multi();", undefined], + ["return $multi(1);", 1], +])("valid $multi call return statement (%s)", (statement, result) => { + util.testModule` + export const [a] = (function() { ${statement} - ` - .setOptions(multiProjectOptions) - .expectDiagnosticsToMatchSnapshot([unsupportedMultiHelperFunctionPosition.code]); - } -); + })(); + ` + .setOptions(multiProjectOptions) + .setReturnExport("a") + .expectToEqual(result); +}); + +test("$multi helper call with destructuring assignment side effects", () => { + util.testModule` + let a; + export { a }; + [a] = $multi(1); + ` + .setOptions(multiProjectOptions) + .setReturnExport("a") + .expectToEqual(1); +}); From a108957d209d549e57ae6dfb516740aa1be2c92d Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 17 Aug 2020 10:18:55 +1000 Subject: [PATCH 54/66] State that multi can only be used in return statements --- src/transformation/utils/diagnostics.ts | 4 +- src/transformation/visitors/helpers/multi.ts | 4 +- src/transformation/visitors/identifier.ts | 4 +- src/transformation/visitors/literal.ts | 4 +- .../helpers/__snapshots__/multi.spec.ts.snap | 40 ++------------- test/unit/helpers/multi.spec.ts | 51 +++++++++---------- 6 files changed, 35 insertions(+), 72 deletions(-) diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index 00e249b00..5f6264775 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -129,8 +129,8 @@ export const unsupportedVarDeclaration = createDiagnosticFactory( export const unknownHelperKind = createDiagnosticFactory((kind: string) => `Unknown helper kind "${kind}".`); -export const unsupportedMultiHelperFunctionPosition = createDiagnosticFactory( - "The $multi() function cannot be used here." +export const invalidMultiFunctionUse = createDiagnosticFactory( + "The $multi function must be called in return statements." ); export const invalidMultiReturnToNonArrayBindingPattern = createDiagnosticFactory( diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index 811d77edc..1b0bd22b8 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -15,7 +15,7 @@ import { invalidMultiReturnToNonArrayBindingPattern, invalidMultiReturnToNonArrayLiteral, unsupportedMultiFunctionAssignment, - unsupportedMultiHelperFunctionPosition, + invalidMultiFunctionUse, } from "../../../transformation/utils/diagnostics"; const isMultiHelperDeclaration = (context: TransformationContext) => (declaration: ts.Declaration): boolean => @@ -159,7 +159,7 @@ export function findMultiHelperAssignmentViolations( if (valueSymbol) { const declaration = valueSymbol.valueDeclaration; if (declaration && isMultiHelperDeclaration(context)(declaration)) { - context.diagnostics.push(unsupportedMultiHelperFunctionPosition(element)); + context.diagnostics.push(invalidMultiFunctionUse(element)); return element; } } diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index 0fad01fea..b1c39ff7a 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -3,7 +3,7 @@ import * as lua from "../../LuaAST"; import { transformBuiltinIdentifierExpression } from "../builtins"; import { FunctionVisitor, TransformationContext } from "../context"; import { isForRangeType } from "../utils/annotations"; -import { invalidForRangeCall, unsupportedMultiHelperFunctionPosition } 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"; @@ -12,7 +12,7 @@ import { isMultiHelperNode } from "./helpers/multi"; export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier { if (isMultiHelperNode(context, identifier)) { - context.diagnostics.push(unsupportedMultiHelperFunctionPosition(identifier)); + context.diagnostics.push(invalidMultiFunctionUse(identifier)); return lua.createAnonymousIdentifier(identifier); } diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 082a1c44c..19ffcffd5 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -2,7 +2,7 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import { assertNever } from "../../utils"; import { FunctionVisitor, TransformationContext, Visitors } from "../context"; -import { unsupportedAccessorInObjectLiteral, unsupportedMultiHelperFunctionPosition } 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"; @@ -65,7 +65,7 @@ const transformNumericLiteralExpression: FunctionVisitor = ex const transformObjectLiteralExpression: FunctionVisitor = (expression, context) => { const violations = findMultiHelperAssignmentViolations(context, expression); if (violations.length > 0) { - context.diagnostics.push(...violations.map(e => unsupportedMultiHelperFunctionPosition(e))); + context.diagnostics.push(...violations.map(e => invalidMultiFunctionUse(e))); return lua.createNilLiteral(expression); } diff --git a/test/unit/helpers/__snapshots__/multi.spec.ts.snap b/test/unit/helpers/__snapshots__/multi.spec.ts.snap index 7f867af97..5497af1a6 100644 --- a/test/unit/helpers/__snapshots__/multi.spec.ts.snap +++ b/test/unit/helpers/__snapshots__/multi.spec.ts.snap @@ -2,11 +2,11 @@ exports[`invalid $multi call ($multi()): code 1`] = `"____(_G)"`; -exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`; +exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi function must be called in return statements."`; exports[`invalid $multi call ($multi): code 1`] = `"local ____ = ____"`; -exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi() function cannot be used here."`; +exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi function must be called in return statements."`; exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` "local function ____(____, ____bindingPattern0) @@ -17,19 +17,11 @@ exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` end" `; -exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi() function cannot be used here."`; +exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi function must be called in return statements."`; exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = nil"`; -exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi() function cannot be used here."`; - -exports[`invalid $multi call ([] = $multi()): code 1`] = `""`; - -exports[`invalid $multi call ([] = $multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: There must be one or more elements specified here."`; - -exports[`invalid $multi call (const [] = $multi();): code 1`] = `""`; - -exports[`invalid $multi call (const [] = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: There must be one or more elements specified here."`; +exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi function must be called in return statements."`; exports[`invalid $multi call (const [a = 0] = $multi()): code 1`] = `""`; @@ -42,27 +34,3 @@ exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts exports[`invalid $multi call (const a = $multi();): code 1`] = `""`; exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`; - -exports[`unsupported $multi call ($multi()): code 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - ____(nil) -end -return ____exports" -`; - -exports[`unsupported $multi call ($multi): code 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local ____ = ____ -end -return ____exports" -`; - -exports[`unsupported $multi call (({ $multi });): code 1`] = ` -"local ____exports = {} -function ____exports.__main(self) - local ____ = nil -end -return ____exports" -`; diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index a46a83a52..8fbbab118 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -2,10 +2,9 @@ import * as path from "path"; import * as util from "../../util"; import * as tstl from "../../../src"; import { - unsupportedMultiHelperFunctionPosition, + invalidMultiFunctionUse, invalidMultiReturnToNonArrayBindingPattern, invalidMultiReturnArrayBindingPatternElementInitializer, - invalidMultiReturnToEmptyPatternOrArrayLiteral, } from "../../../src/transformation/utils/diagnostics"; const multiProjectOptions: tstl.CompilerOptions = { @@ -13,14 +12,26 @@ const multiProjectOptions: tstl.CompilerOptions = { }; test.each<[string, any]>([ - ["let a; [a] = $multi();", undefined], - ["const [a] = $multi();", undefined], - ["const [a] = $multi(1);", 1], - ["const ar = [1]; const [a] = $multi(...ar);", 1], - ["const _ = null, [a] = $multi(1);", 1], + ["$multi()", undefined], + ["$multi(true)", true], + ["$multi(1, 2)", 1], +])("$multi call on return statement (%s)", (expression, result) => { + util.testFunction` + return ${expression}; + ` + .setOptions(multiProjectOptions) + .expectToEqual(result); +}); + +test.each<[string, any]>([ + ["let a; [a] = $multi()", undefined], + ["const [a] = $multi()", undefined], + ["const [a] = $multi(1)", 1], + ["const ar = [1]; const [a] = $multi(...ar)", 1], + ["const _ = null, [a] = $multi(1)", 1], ["let a; for (const [a] = $multi(1, 2); false; 1) {}", undefined], ["let a; for ([a] = $multi(1, 2); false; 1) {}", 1], -])("valid $multi call and assign (%s)", (statement, result) => { +])("$multi returning call (%s)", (statement, result) => { util.testModule` ${statement} export { a }; @@ -31,14 +42,12 @@ test.each<[string, any]>([ }); test.each<[string, number[]]>([ - ["$multi", [unsupportedMultiHelperFunctionPosition.code]], - ["$multi()", [unsupportedMultiHelperFunctionPosition.code]], - ["({ $multi });", [unsupportedMultiHelperFunctionPosition.code]], - ["[] = $multi()", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]], - ["const [] = $multi();", [invalidMultiReturnToEmptyPatternOrArrayLiteral.code]], + ["$multi", [invalidMultiFunctionUse.code]], + ["$multi()", [invalidMultiFunctionUse.code]], + ["({ $multi });", [invalidMultiFunctionUse.code]], ["const a = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], ["const {} = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], - ["([a] = $multi(1)) => {}", [unsupportedMultiHelperFunctionPosition.code]], + ["([a] = $multi(1)) => {}", [invalidMultiFunctionUse.code]], ["const [a = 0] = $multi()", [invalidMultiReturnArrayBindingPatternElementInitializer.code]], ])("invalid $multi call (%s)", (statement, diagnostics) => { util.testModule` @@ -48,20 +57,6 @@ test.each<[string, number[]]>([ .expectDiagnosticsToMatchSnapshot(diagnostics); }); -test.each<[string, any]>([ - ["return $multi();", undefined], - ["return $multi(1);", 1], -])("valid $multi call return statement (%s)", (statement, result) => { - util.testModule` - export const [a] = (function() { - ${statement} - })(); - ` - .setOptions(multiProjectOptions) - .setReturnExport("a") - .expectToEqual(result); -}); - test("$multi helper call with destructuring assignment side effects", () => { util.testModule` let a; From 6646695a558d13263905479a53fb0e67117c7d9f Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 17 Aug 2020 10:21:44 +1000 Subject: [PATCH 55/66] Throw an error if a helper is unknown --- src/transformation/utils/diagnostics.ts | 2 -- src/transformation/utils/helpers.ts | 12 ++++++------ src/transformation/visitors/helpers/multi.ts | 12 ++++++------ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index 5f6264775..e240e3969 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -127,8 +127,6 @@ export const unsupportedVarDeclaration = createDiagnosticFactory( "`var` declarations are not supported. Use `let` or `const` instead." ); -export const unknownHelperKind = createDiagnosticFactory((kind: string) => `Unknown helper kind "${kind}".`); - export const invalidMultiFunctionUse = createDiagnosticFactory( "The $multi function must be called in return statements." ); diff --git a/src/transformation/utils/helpers.ts b/src/transformation/utils/helpers.ts index d4b5cc3fd..3d2994bdc 100644 --- a/src/transformation/utils/helpers.ts +++ b/src/transformation/utils/helpers.ts @@ -1,10 +1,9 @@ import * as ts from "typescript"; import * as path from "path"; -import { TransformationContext } from "../context"; -import { unknownHelperKind } from "./diagnostics"; export enum HelperKind { - Multi = "multi", + MultiFunction = "MultiFunction", + MultiReturnFunction = "MultiReturnFunction", } function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { @@ -13,14 +12,15 @@ function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { return helperDirectory === sourceFileDirectory; } -export function getHelperFileKind(context: TransformationContext, sourceFile: ts.SourceFile): HelperKind | undefined { +export function getHelperFileKind(declaration: ts.Declaration): HelperKind | undefined { + const sourceFile = declaration.getSourceFile(); if (isSourceFileFromHelpers(sourceFile)) { const baseFileName = path.basename(sourceFile.fileName).replace(/(\.d)?\.ts$/g, ""); switch (baseFileName) { case "multi": - return HelperKind.Multi; + return HelperKind.MultiReturnFunction; default: - context.diagnostics.push(unknownHelperKind(sourceFile, baseFileName)); + throw new Error("Unknown helper"); } } } diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index 1b0bd22b8..33d130417 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -18,12 +18,12 @@ import { invalidMultiFunctionUse, } from "../../../transformation/utils/diagnostics"; -const isMultiHelperDeclaration = (context: TransformationContext) => (declaration: ts.Declaration): boolean => - helpers.getHelperFileKind(context, declaration.getSourceFile()) === helpers.HelperKind.Multi; +const isMultiHelperDeclaration = (declaration: ts.Declaration): boolean => + helpers.getHelperFileKind(declaration) === helpers.HelperKind.MultiFunction; function isMultiHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); - return type.symbol?.declarations?.some(isMultiHelperDeclaration(context)) ?? false; + return type.symbol?.declarations?.some(isMultiHelperDeclaration) ?? false; } export function isMultiReturnCall(context: TransformationContext, node: ts.Node): node is ts.CallExpression { @@ -32,12 +32,12 @@ export function isMultiReturnCall(context: TransformationContext, node: ts.Node) } const signature = context.checker.getResolvedSignature(node); - return signature?.getReturnType().aliasSymbol?.declarations?.some(isMultiHelperDeclaration(context)) ?? false; + return signature?.getReturnType().aliasSymbol?.declarations?.some(isMultiHelperDeclaration) ?? false; } export function isMultiHelperNode(context: TransformationContext, node: ts.Node): boolean { const type = context.checker.getTypeAtLocation(node); - return type.symbol?.declarations?.some(isMultiHelperDeclaration(context)) ?? false; + return type.symbol?.declarations?.some(isMultiHelperDeclaration) ?? false; } export function transformMultiHelperReturnStatement( @@ -158,7 +158,7 @@ export function findMultiHelperAssignmentViolations( const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); if (valueSymbol) { const declaration = valueSymbol.valueDeclaration; - if (declaration && isMultiHelperDeclaration(context)(declaration)) { + if (declaration && isMultiHelperDeclaration(declaration)) { context.diagnostics.push(invalidMultiFunctionUse(element)); return element; } From cbe8f4600a8ad5e8cba067bd6e9b416300514cdc Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 17 Aug 2020 10:41:53 +1000 Subject: [PATCH 56/66] Improve multi nomenclature --- src/transformation/utils/helpers.ts | 18 +++++---- src/transformation/visitors/call.ts | 4 +- src/transformation/visitors/helpers/multi.ts | 40 ++++++++++---------- src/transformation/visitors/identifier.ts | 4 +- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/transformation/utils/helpers.ts b/src/transformation/utils/helpers.ts index 3d2994bdc..4737fbcb1 100644 --- a/src/transformation/utils/helpers.ts +++ b/src/transformation/utils/helpers.ts @@ -3,7 +3,7 @@ import * as path from "path"; export enum HelperKind { MultiFunction = "MultiFunction", - MultiReturnFunction = "MultiReturnFunction", + MultiType = "MultiType", } function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { @@ -12,15 +12,17 @@ function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { return helperDirectory === sourceFileDirectory; } -export function getHelperFileKind(declaration: ts.Declaration): HelperKind | undefined { +export function getHelperKind(declaration: ts.Declaration): HelperKind | undefined { const sourceFile = declaration.getSourceFile(); if (isSourceFileFromHelpers(sourceFile)) { - const baseFileName = path.basename(sourceFile.fileName).replace(/(\.d)?\.ts$/g, ""); - switch (baseFileName) { - case "multi": - return HelperKind.MultiReturnFunction; - default: - throw new Error("Unknown helper"); + if (ts.isFunctionDeclaration(declaration) && declaration?.name?.text === "$multi") { + return HelperKind.MultiFunction; } + + if (ts.isTypeAliasDeclaration(declaration) && declaration.name.text === "MultiReturn") { + return HelperKind.MultiType; + } + + throw new Error("Unknown helper kind"); } } diff --git a/src/transformation/visitors/call.ts b/src/transformation/visitors/call.ts index e43492f73..f3e62c125 100644 --- a/src/transformation/visitors/call.ts +++ b/src/transformation/visitors/call.ts @@ -11,7 +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 { isMultiReturnCall } from "./helpers/multi"; +import { returnsMultiType } from "./helpers/multi"; export type PropertyCallExpression = ts.CallExpression & { expression: ts.PropertyAccessExpression }; @@ -252,7 +252,7 @@ export const transformCallExpression: FunctionVisitor = (node export const transformSpreadElement: FunctionVisitor = (node, context) => { const innerExpression = context.transformExpression(node.expression); if (isTupleReturnCall(context, node.expression)) return innerExpression; - if (isMultiReturnCall(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); diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index 33d130417..af26059f5 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -18,26 +18,25 @@ import { invalidMultiFunctionUse, } from "../../../transformation/utils/diagnostics"; -const isMultiHelperDeclaration = (declaration: ts.Declaration): boolean => - helpers.getHelperFileKind(declaration) === helpers.HelperKind.MultiFunction; +const isMultiFunctionDeclaration = (declaration: ts.Declaration): boolean => + helpers.getHelperKind(declaration) === helpers.HelperKind.MultiFunction; -function isMultiHelperCallSignature(context: TransformationContext, expression: ts.CallExpression): boolean { +const isMultiTypeDeclaration = (declaration: ts.Declaration): boolean => + helpers.getHelperKind(declaration) === helpers.HelperKind.MultiType; + +function isMultiFunction(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); - return type.symbol?.declarations?.some(isMultiHelperDeclaration) ?? false; + return type.symbol?.declarations?.some(isMultiFunctionDeclaration) ?? false; } -export function isMultiReturnCall(context: TransformationContext, node: ts.Node): node is ts.CallExpression { - if (!ts.isCallExpression(node)) { - return false; - } - +export function returnsMultiType(context: TransformationContext, node: ts.CallExpression): boolean { const signature = context.checker.getResolvedSignature(node); - return signature?.getReturnType().aliasSymbol?.declarations?.some(isMultiHelperDeclaration) ?? false; + return signature?.getReturnType().aliasSymbol?.declarations?.some(isMultiTypeDeclaration) ?? false; } -export function isMultiHelperNode(context: TransformationContext, node: ts.Node): boolean { +export function isMultiFunctionNode(context: TransformationContext, node: ts.Node): boolean { const type = context.checker.getTypeAtLocation(node); - return type.symbol?.declarations?.some(isMultiHelperDeclaration) ?? false; + return type.symbol?.declarations?.some(isMultiFunctionDeclaration) ?? false; } export function transformMultiHelperReturnStatement( @@ -46,17 +45,17 @@ export function transformMultiHelperReturnStatement( ): lua.Statement | undefined { if (!statement.expression) return; if (!ts.isCallExpression(statement.expression)) return; - if (!isMultiHelperCallSignature(context, statement.expression)) return; + if (!isMultiFunction(context, statement.expression)) return; const expressions = transformArguments(context, statement.expression.arguments); return lua.createReturnStatement(expressions, statement); } -function transformMultiHelperCallArguments( +function transformMultiFunctionArguments( context: TransformationContext, expression: ts.CallExpression ): lua.Expression[] | lua.Expression { - if (!isMultiHelperCallSignature(context, expression)) { + if (!isMultiFunction(context, expression)) { return context.transformExpression(expression); } @@ -72,7 +71,7 @@ export function transformMultiHelperVariableDeclaration( declaration: ts.VariableDeclaration ): lua.Statement[] | undefined { if (!declaration.initializer) return; - if (!isMultiReturnCall(context, declaration.initializer)) return; + if (!ts.isCallExpression(declaration.initializer) || !returnsMultiType(context, declaration.initializer)) return; if (!ts.isArrayBindingPattern(declaration.name)) { context.diagnostics.push(invalidMultiReturnToNonArrayBindingPattern(declaration.name)); @@ -100,7 +99,7 @@ export function transformMultiHelperVariableDeclaration( } } - const rightExpressions = transformMultiHelperCallArguments(context, declaration.initializer); + const rightExpressions = transformMultiFunctionArguments(context, declaration.initializer); return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration); } @@ -110,7 +109,8 @@ export function transformMultiHelperDestructuringAssignmentStatement( ): lua.Statement[] | undefined { if (!ts.isBinaryExpression(statement.expression)) return; if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return; - if (!isMultiReturnCall(context, statement.expression.right)) return; + if (!ts.isCallExpression(statement.expression.right)) return; + if (!returnsMultiType(context, statement.expression.right)) return; if (!ts.isArrayLiteralExpression(statement.expression.left)) { context.diagnostics.push(invalidMultiReturnToNonArrayLiteral(statement.expression.left)); @@ -134,7 +134,7 @@ export function transformMultiHelperDestructuringAssignmentStatement( const leftIdentifiers = statement.expression.left.elements.map(transformLeft); - const rightExpressions = transformMultiHelperCallArguments(context, statement.expression.right); + const rightExpressions = transformMultiFunctionArguments(context, statement.expression.right); const trailingStatements = statement.expression.left.elements.flatMap(expression => { const symbol = context.checker.getSymbolAtLocation(expression); @@ -158,7 +158,7 @@ export function findMultiHelperAssignmentViolations( const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); if (valueSymbol) { const declaration = valueSymbol.valueDeclaration; - if (declaration && isMultiHelperDeclaration(declaration)) { + if (declaration && isMultiFunctionDeclaration(declaration)) { context.diagnostics.push(invalidMultiFunctionUse(element)); return element; } diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index b1c39ff7a..08f192110 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -8,10 +8,10 @@ import { createExportedIdentifier, getSymbolExportScope } from "../utils/export" import { createSafeName, hasUnsafeIdentifierName } from "../utils/safe-names"; import { getIdentifierSymbolId } from "../utils/symbols"; import { findFirstNodeAbove } from "../utils/typescript"; -import { isMultiHelperNode } from "./helpers/multi"; +import { isMultiFunctionNode } from "./helpers/multi"; export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier { - if (isMultiHelperNode(context, identifier)) { + if (isMultiFunctionNode(context, identifier)) { context.diagnostics.push(invalidMultiFunctionUse(identifier)); return lua.createAnonymousIdentifier(identifier); } From 29bef1089b55c74931a8a24fad0d611b838d9cb3 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 17 Aug 2020 11:06:09 +1000 Subject: [PATCH 57/66] Disallow multi function use in non-return statements --- src/transformation/utils/diagnostics.ts | 10 +- src/transformation/visitors/helpers/multi.ts | 42 +++++--- .../helpers/__snapshots__/multi.spec.ts.snap | 101 +++++++++++++++++- test/unit/helpers/multi.spec.ts | 56 +++++++--- 4 files changed, 173 insertions(+), 36 deletions(-) diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index e240e3969..a7e032d96 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -131,21 +131,21 @@ export const invalidMultiFunctionUse = createDiagnosticFactory( "The $multi function must be called in return statements." ); -export const invalidMultiReturnToNonArrayBindingPattern = createDiagnosticFactory( +export const invalidMultiTypeToNonArrayBindingPattern = createDiagnosticFactory( "Expected an array destructuring pattern." ); -export const invalidMultiReturnToNonArrayLiteral = createDiagnosticFactory("Expected an array literal."); +export const invalidMultiTypeToNonArrayLiteral = createDiagnosticFactory("Expected an array literal."); -export const invalidMultiReturnToEmptyPatternOrArrayLiteral = createDiagnosticFactory( +export const invalidMultiTypeToEmptyPatternOrArrayLiteral = createDiagnosticFactory( "There must be one or more elements specified here." ); -export const invalidMultiReturnArrayBindingPatternElementInitializer = createDiagnosticFactory( +export const invalidMultiTypeArrayBindingPatternElementInitializer = createDiagnosticFactory( "This array binding pattern cannot have initializers." ); -export const invalidMultiReturnArrayLiteralElementInitializer = createDiagnosticFactory( +export const invalidMultiTypeArrayLiteralElementInitializer = createDiagnosticFactory( "This array literal pattern cannot have initializers." ); diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index af26059f5..bb875f417 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -9,11 +9,11 @@ import { transformArguments } from "../call"; import { getDependenciesOfSymbol, createExportedIdentifier } from "../../utils/export"; import { createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast"; import { - invalidMultiReturnArrayBindingPatternElementInitializer, - invalidMultiReturnArrayLiteralElementInitializer, - invalidMultiReturnToEmptyPatternOrArrayLiteral, - invalidMultiReturnToNonArrayBindingPattern, - invalidMultiReturnToNonArrayLiteral, + invalidMultiTypeArrayBindingPatternElementInitializer, + invalidMultiTypeArrayLiteralElementInitializer, + invalidMultiTypeToEmptyPatternOrArrayLiteral, + invalidMultiTypeToNonArrayBindingPattern, + invalidMultiTypeToNonArrayLiteral, unsupportedMultiFunctionAssignment, invalidMultiFunctionUse, } from "../../../transformation/utils/diagnostics"; @@ -71,15 +71,26 @@ export function transformMultiHelperVariableDeclaration( declaration: ts.VariableDeclaration ): lua.Statement[] | undefined { if (!declaration.initializer) return; - if (!ts.isCallExpression(declaration.initializer) || !returnsMultiType(context, declaration.initializer)) return; + if (!ts.isCallExpression(declaration.initializer)) return; + if (!returnsMultiType(context, declaration.initializer)) return; if (!ts.isArrayBindingPattern(declaration.name)) { - context.diagnostics.push(invalidMultiReturnToNonArrayBindingPattern(declaration.name)); + context.diagnostics.push(invalidMultiTypeToNonArrayBindingPattern(declaration.name)); return []; } if (declaration.name.elements.length < 1) { - context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(declaration.name)); + 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 []; } @@ -87,9 +98,7 @@ export function transformMultiHelperVariableDeclaration( for (const element of declaration.name.elements) { if (ts.isBindingElement(element)) { - if (element.initializer) { - context.diagnostics.push(invalidMultiReturnArrayBindingPatternElementInitializer(element)); - } else if (ts.isIdentifier(element.name)) { + if (ts.isIdentifier(element.name)) { leftIdentifiers.push(transformIdentifier(context, element.name)); } else { context.diagnostics.push(unsupportedMultiFunctionAssignment(element)); @@ -113,17 +122,22 @@ export function transformMultiHelperDestructuringAssignmentStatement( if (!returnsMultiType(context, statement.expression.right)) return; if (!ts.isArrayLiteralExpression(statement.expression.left)) { - context.diagnostics.push(invalidMultiReturnToNonArrayLiteral(statement.expression.left)); + context.diagnostics.push(invalidMultiTypeToNonArrayLiteral(statement.expression.left)); return []; } if (statement.expression.left.elements.some(ts.isBinaryExpression)) { - context.diagnostics.push(invalidMultiReturnArrayLiteralElementInitializer(statement.expression.left)); + context.diagnostics.push(invalidMultiTypeArrayLiteralElementInitializer(statement.expression.left)); return []; } if (statement.expression.left.elements.length < 1) { - context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(statement.expression.left)); + context.diagnostics.push(invalidMultiTypeToEmptyPatternOrArrayLiteral(statement.expression.left)); + return []; + } + + if (isMultiFunction(context, statement.expression.right)) { + context.diagnostics.push(invalidMultiFunctionUse(statement.expression.right)); return []; } diff --git a/test/unit/helpers/__snapshots__/multi.spec.ts.snap b/test/unit/helpers/__snapshots__/multi.spec.ts.snap index 5497af1a6..bc08361f2 100644 --- a/test/unit/helpers/__snapshots__/multi.spec.ts.snap +++ b/test/unit/helpers/__snapshots__/multi.spec.ts.snap @@ -25,7 +25,7 @@ exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): exports[`invalid $multi call (const [a = 0] = $multi()): code 1`] = `""`; -exports[`invalid $multi call (const [a = 0] = $multi()): diagnostics 1`] = `"main.ts(2,16): error TSTL: This array binding pattern cannot have initializers."`; +exports[`invalid $multi call (const [a = 0] = $multi()): diagnostics 1`] = `"main.ts(2,15): error TSTL: This array binding pattern cannot have initializers."`; exports[`invalid $multi call (const {} = $multi();): code 1`] = `""`; @@ -34,3 +34,102 @@ exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts exports[`invalid $multi call (const a = $multi();): code 1`] = `""`; exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`; + +exports[`invalid direct $multi function use (const [a] = $multi()): code 1`] = ` +"local ____exports = {} +local function multi(self, ...) + local args = {...} + return table.unpack(args) +end +____exports.a = a +return ____exports" +`; + +exports[`invalid direct $multi function use (const [a] = $multi()): diagnostics 1`] = `"main.ts(7,21): error TSTL: The $multi function must be called in return statements."`; + +exports[`invalid direct $multi function use (const [a] = $multi(1)): code 1`] = ` +"local ____exports = {} +local function multi(self, ...) + local args = {...} + return table.unpack(args) +end +____exports.a = a +return ____exports" +`; + +exports[`invalid direct $multi function use (const [a] = $multi(1)): diagnostics 1`] = `"main.ts(7,21): error TSTL: The $multi function must be called in return statements."`; + +exports[`invalid direct $multi function use (const _ = null, [a] = $multi(1)): code 1`] = ` +"local ____exports = {} +local function multi(self, ...) + local args = {...} + return table.unpack(args) +end +local _ = nil +____exports.a = a +return ____exports" +`; + +exports[`invalid direct $multi function use (const _ = null, [a] = $multi(1)): diagnostics 1`] = `"main.ts(7,31): error TSTL: The $multi function must be called in return statements."`; + +exports[`invalid direct $multi function use (const ar = [1]; const [a] = $multi(...ar)): code 1`] = ` +"local ____exports = {} +local function multi(self, ...) + local args = {...} + return table.unpack(args) +end +local ar = {1} +____exports.a = a +return ____exports" +`; + +exports[`invalid direct $multi function use (const ar = [1]; const [a] = $multi(...ar)): diagnostics 1`] = `"main.ts(7,37): error TSTL: The $multi function must be called in return statements."`; + +exports[`invalid direct $multi function use (let a; [a] = $multi()): code 1`] = ` +"local ____exports = {} +local function multi(self, ...) + local args = {...} + return table.unpack(args) +end +local a +____exports.a = a +return ____exports" +`; + +exports[`invalid direct $multi function use (let a; [a] = $multi()): diagnostics 1`] = `"main.ts(7,22): error TSTL: The $multi function must be called in return statements."`; + +exports[`invalid direct $multi function use (let a; for ([a] = $multi(1, 2); false; 1) {}): code 1`] = ` +"local ____exports = {} +local function multi(self, ...) + local args = {...} + return table.unpack(args) +end +local a +do + while false do + local ____ = 1 + end +end +____exports.a = a +return ____exports" +`; + +exports[`invalid direct $multi function use (let a; for ([a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,27): error TSTL: The $multi function must be called in return statements."`; + +exports[`invalid direct $multi function use (let a; for (const [a] = $multi(1, 2); false; 1) {}): code 1`] = ` +"local ____exports = {} +local function multi(self, ...) + local args = {...} + return table.unpack(args) +end +local a +do + while false do + local ____ = 1 + end +end +____exports.a = a +return ____exports" +`; + +exports[`invalid direct $multi function use (let a; for (const [a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,33): error TSTL: The $multi function must be called in return statements."`; diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index 8fbbab118..1e9d3c0f5 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -3,8 +3,8 @@ import * as util from "../../util"; import * as tstl from "../../../src"; import { invalidMultiFunctionUse, - invalidMultiReturnToNonArrayBindingPattern, - invalidMultiReturnArrayBindingPatternElementInitializer, + invalidMultiTypeToNonArrayBindingPattern, + invalidMultiTypeArrayBindingPatternElementInitializer, } from "../../../src/transformation/utils/diagnostics"; const multiProjectOptions: tstl.CompilerOptions = { @@ -23,32 +23,55 @@ test.each<[string, any]>([ .expectToEqual(result); }); -test.each<[string, any]>([ - ["let a; [a] = $multi()", undefined], - ["const [a] = $multi()", undefined], - ["const [a] = $multi(1)", 1], - ["const ar = [1]; const [a] = $multi(...ar)", 1], - ["const _ = null, [a] = $multi(1)", 1], - ["let a; for (const [a] = $multi(1, 2); false; 1) {}", undefined], - ["let a; for ([a] = $multi(1, 2); false; 1) {}", 1], -])("$multi returning call (%s)", (statement, result) => { +const multiFunction = ` +function multi(...args) { + return $multi(...args); +} +`; + +const createCasesThatCall = (name: string): Array<[string, any]> => [ + [`let a; [a] = ${name}()`, undefined], + [`const [a] = ${name}()`, undefined], + [`const [a] = ${name}(1)`, 1], + [`const ar = [1]; const [a] = ${name}(...ar)`, 1], + [`const _ = null, [a] = ${name}(1)`, 1], + [`let a; for (const [a] = ${name}(1, 2); false; 1) {}`, undefined], + [`let a; for ([a] = ${name}(1, 2); false; 1) {}`, 1], +]; + +test.each<[string, any]>(createCasesThatCall("$multi"))("invalid direct $multi function use (%s)", statement => { util.testModule` + ${multiFunction} ${statement} export { a }; ` .setOptions(multiProjectOptions) .setReturnExport("a") - .expectToEqual(result); + .expectDiagnosticsToMatchSnapshot([invalidMultiFunctionUse.code]); }); +test.each<[string, any]>(createCasesThatCall("multi"))( + "valid indirect $multi function use (%s)", + (statement, result) => { + util.testModule` + ${multiFunction} + ${statement} + export { a }; + ` + .setOptions(multiProjectOptions) + .setReturnExport("a") + .expectToEqual(result); + } +); + test.each<[string, number[]]>([ ["$multi", [invalidMultiFunctionUse.code]], ["$multi()", [invalidMultiFunctionUse.code]], ["({ $multi });", [invalidMultiFunctionUse.code]], - ["const a = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], - ["const {} = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]], + ["const a = $multi();", [invalidMultiTypeToNonArrayBindingPattern.code]], + ["const {} = $multi();", [invalidMultiTypeToNonArrayBindingPattern.code]], ["([a] = $multi(1)) => {}", [invalidMultiFunctionUse.code]], - ["const [a = 0] = $multi()", [invalidMultiReturnArrayBindingPatternElementInitializer.code]], + ["const [a = 0] = $multi()", [invalidMultiTypeArrayBindingPatternElementInitializer.code]], ])("invalid $multi call (%s)", (statement, diagnostics) => { util.testModule` ${statement} @@ -59,9 +82,10 @@ test.each<[string, number[]]>([ test("$multi helper call with destructuring assignment side effects", () => { util.testModule` + ${multiFunction} let a; export { a }; - [a] = $multi(1); + [a] = multi(1); ` .setOptions(multiProjectOptions) .setReturnExport("a") From 3c0f61e7a560d6a515609e50f434258111a17359 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 17 Aug 2020 11:07:50 +1000 Subject: [PATCH 58/66] Merge helpers together into index.d.ts --- helpers/index.d.ts | 3 ++- helpers/multi.d.ts | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 helpers/multi.d.ts diff --git a/helpers/index.d.ts b/helpers/index.d.ts index a92b9f956..d7270be32 100644 --- a/helpers/index.d.ts +++ b/helpers/index.d.ts @@ -1 +1,2 @@ -import "./multi"; +declare function $multi(...values: T): MultiReturn; +declare type MultiReturn = T & { readonly " __multiBrand": unique symbol }; diff --git a/helpers/multi.d.ts b/helpers/multi.d.ts deleted file mode 100644 index d7270be32..000000000 --- a/helpers/multi.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare function $multi(...values: T): MultiReturn; -declare type MultiReturn = T & { readonly " __multiBrand": unique symbol }; From 3e2e4cc4c3f33b3f0f040ad3c8149ab5609d3f1e Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 17 Aug 2020 11:12:13 +1000 Subject: [PATCH 59/66] Prefer for loop to filter/map/foreach --- src/transformation/visitors/helpers/multi.ts | 27 ++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/helpers/multi.ts index bb875f417..417b49789 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/helpers/multi.ts @@ -1,7 +1,6 @@ import * as ts from "typescript"; import * as lua from "../../../LuaAST"; import * as helpers from "../../utils/helpers"; -import { isNonNull } from "../../../utils"; import { TransformationContext } from "../../context"; import { transformAssignmentLeftHandSideExpression } from "../binary-expression/assignments"; import { transformIdentifier } from "../identifier"; @@ -166,17 +165,19 @@ export function findMultiHelperAssignmentViolations( context: TransformationContext, node: ts.ObjectLiteralExpression ): ts.Node[] { - return node.properties - .filter(ts.isShorthandPropertyAssignment) - .map(element => { - const valueSymbol = context.checker.getShorthandAssignmentValueSymbol(element); - if (valueSymbol) { - const declaration = valueSymbol.valueDeclaration; - if (declaration && isMultiFunctionDeclaration(declaration)) { - context.diagnostics.push(invalidMultiFunctionUse(element)); - return element; - } + 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); } - }) - .filter(isNonNull); + } + } + + return result; } From a68811de62643d1c18bf39a11c6ccbe2fcd36ed3 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Mon, 17 Aug 2020 11:28:07 +1000 Subject: [PATCH 60/66] Add test to return spreaded multi type from multi type function --- test/unit/helpers/multi.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index 1e9d3c0f5..382d78ccd 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -80,6 +80,18 @@ test.each<[string, number[]]>([ .expectDiagnosticsToMatchSnapshot(diagnostics); }); +test("function to spread multi type result from multi type function", () => { + util.testFunction` + ${multiFunction} + function m() { + return $multi(...multi(true)); + } + return m(); + ` + .setOptions(multiProjectOptions) + .expectToEqual(true); +}); + test("$multi helper call with destructuring assignment side effects", () => { util.testModule` ${multiFunction} From e0d3cf43aa145b8e3bfa20ad485375c0c04de981 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Dec 2020 13:39:40 +1000 Subject: [PATCH 61/66] Rename to language extensions / extensions --- {helpers => language-extensions}/index.d.ts | 0 package.json | 2 +- src/lualib/tsconfig.json | 2 +- .../utils/{helpers.ts => language-extensions.ts} | 16 ++++++++-------- src/transformation/visitors/call.ts | 2 +- .../visitors/expression-statement.ts | 2 +- src/transformation/visitors/identifier.ts | 2 +- .../{helpers => language-extensions}/multi.ts | 8 ++++---- src/transformation/visitors/literal.ts | 2 +- src/transformation/visitors/return.ts | 2 +- .../visitors/variable-declaration.ts | 2 +- src/transpilation/index.ts | 2 +- test/tsconfig.json | 2 +- test/unit/helpers/multi.spec.ts | 2 +- 14 files changed, 23 insertions(+), 23 deletions(-) rename {helpers => language-extensions}/index.d.ts (100%) rename src/transformation/utils/{helpers.ts => language-extensions.ts} (52%) rename src/transformation/visitors/{helpers => language-extensions}/multi.ts (96%) diff --git a/helpers/index.d.ts b/language-extensions/index.d.ts similarity index 100% rename from helpers/index.d.ts rename to language-extensions/index.d.ts diff --git a/package.json b/package.json index 31a00605e..766720d7a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dist/**/*.js", "dist/**/*.lua", "dist/**/*.ts", - "helpers/**/*.ts" + "language-extensions/**/*.ts" ], "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/lualib/tsconfig.json b/src/lualib/tsconfig.json index 23370ec00..f16968dc1 100644 --- a/src/lualib/tsconfig.json +++ b/src/lualib/tsconfig.json @@ -3,7 +3,7 @@ "outDir": "../../dist/lualib", "target": "esnext", "lib": ["esnext"], - "types": ["../../helpers"], + "types": ["../../language-extensions"], "skipLibCheck": true, "noUnusedLocals": true, diff --git a/src/transformation/utils/helpers.ts b/src/transformation/utils/language-extensions.ts similarity index 52% rename from src/transformation/utils/helpers.ts rename to src/transformation/utils/language-extensions.ts index 4737fbcb1..d56850b39 100644 --- a/src/transformation/utils/helpers.ts +++ b/src/transformation/utils/language-extensions.ts @@ -1,28 +1,28 @@ import * as ts from "typescript"; import * as path from "path"; -export enum HelperKind { +export enum ExtensionKind { MultiFunction = "MultiFunction", MultiType = "MultiType", } -function isSourceFileFromHelpers(sourceFile: ts.SourceFile): boolean { - const helperDirectory = path.resolve(__dirname, "../../../helpers"); +function isSourceFileFromLanguageExtensions(sourceFile: ts.SourceFile): boolean { + const helperDirectory = path.resolve(__dirname, "../../../language-extensions"); const sourceFileDirectory = path.dirname(path.normalize(sourceFile.fileName)); return helperDirectory === sourceFileDirectory; } -export function getHelperKind(declaration: ts.Declaration): HelperKind | undefined { +export function getExtensionKind(declaration: ts.Declaration): ExtensionKind | undefined { const sourceFile = declaration.getSourceFile(); - if (isSourceFileFromHelpers(sourceFile)) { + if (isSourceFileFromLanguageExtensions(sourceFile)) { if (ts.isFunctionDeclaration(declaration) && declaration?.name?.text === "$multi") { - return HelperKind.MultiFunction; + return ExtensionKind.MultiFunction; } if (ts.isTypeAliasDeclaration(declaration) && declaration.name.text === "MultiReturn") { - return HelperKind.MultiType; + return ExtensionKind.MultiType; } - throw new Error("Unknown helper kind"); + throw new Error("Unknown extension kind"); } } diff --git a/src/transformation/visitors/call.ts b/src/transformation/visitors/call.ts index f3e62c125..90959a580 100644 --- a/src/transformation/visitors/call.ts +++ b/src/transformation/visitors/call.ts @@ -11,7 +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 "./helpers/multi"; +import { returnsMultiType } from "./language-extensions/multi"; export type PropertyCallExpression = ts.CallExpression & { expression: ts.PropertyAccessExpression }; diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index 531902b15..5a4cc1bfb 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -5,7 +5,7 @@ import { transformBinaryExpressionStatement } from "./binary-expression"; import { transformDeleteExpressionStatement } from "./delete"; import { transformLuaTableExpressionStatement } from "./lua-table"; import { transformUnaryExpressionStatement } from "./unary-expression"; -import { transformMultiHelperDestructuringAssignmentStatement } from "./helpers/multi"; +import { transformMultiHelperDestructuringAssignmentStatement } from "./language-extensions/multi"; export const transformExpressionStatement: FunctionVisitor = (node, context) => { const multiResult = transformMultiHelperDestructuringAssignmentStatement(context, node); diff --git a/src/transformation/visitors/identifier.ts b/src/transformation/visitors/identifier.ts index 08f192110..6e95411a5 100644 --- a/src/transformation/visitors/identifier.ts +++ b/src/transformation/visitors/identifier.ts @@ -8,7 +8,7 @@ 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 "./helpers/multi"; +import { isMultiFunctionNode } from "./language-extensions/multi"; export function transformIdentifier(context: TransformationContext, identifier: ts.Identifier): lua.Identifier { if (isMultiFunctionNode(context, identifier)) { diff --git a/src/transformation/visitors/helpers/multi.ts b/src/transformation/visitors/language-extensions/multi.ts similarity index 96% rename from src/transformation/visitors/helpers/multi.ts rename to src/transformation/visitors/language-extensions/multi.ts index 417b49789..00078b9c9 100644 --- a/src/transformation/visitors/helpers/multi.ts +++ b/src/transformation/visitors/language-extensions/multi.ts @@ -1,6 +1,6 @@ import * as ts from "typescript"; import * as lua from "../../../LuaAST"; -import * as helpers from "../../utils/helpers"; +import * as extensions from "../../utils/language-extensions"; import { TransformationContext } from "../../context"; import { transformAssignmentLeftHandSideExpression } from "../binary-expression/assignments"; import { transformIdentifier } from "../identifier"; @@ -15,13 +15,13 @@ import { invalidMultiTypeToNonArrayLiteral, unsupportedMultiFunctionAssignment, invalidMultiFunctionUse, -} from "../../../transformation/utils/diagnostics"; +} from "../../utils/diagnostics"; const isMultiFunctionDeclaration = (declaration: ts.Declaration): boolean => - helpers.getHelperKind(declaration) === helpers.HelperKind.MultiFunction; + extensions.getExtensionKind(declaration) === extensions.ExtensionKind.MultiFunction; const isMultiTypeDeclaration = (declaration: ts.Declaration): boolean => - helpers.getHelperKind(declaration) === helpers.HelperKind.MultiType; + extensions.getExtensionKind(declaration) === extensions.ExtensionKind.MultiType; function isMultiFunction(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 19ffcffd5..3485bed6a 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -10,7 +10,7 @@ import { getSymbolIdOfSymbol, trackSymbolReference } from "../utils/symbols"; import { isArrayType } from "../utils/typescript"; import { transformFunctionLikeDeclaration } from "./function"; import { flattenSpreadExpressions } from "./call"; -import { findMultiHelperAssignmentViolations } from "./helpers/multi"; +import { findMultiHelperAssignmentViolations } from "./language-extensions/multi"; // TODO: Move to object-literal.ts? export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression { diff --git a/src/transformation/visitors/return.ts b/src/transformation/visitors/return.ts index 4a2edf63b..0d613df56 100644 --- a/src/transformation/visitors/return.ts +++ b/src/transformation/visitors/return.ts @@ -6,7 +6,7 @@ import { validateAssignment } from "../utils/assignment-validation"; import { createUnpackCall, wrapInTable } from "../utils/lua-ast"; import { ScopeType, walkScopesUp } from "../utils/scope"; import { isArrayType } from "../utils/typescript"; -import { transformMultiHelperReturnStatement } from "./helpers/multi"; +import { transformMultiHelperReturnStatement } from "./language-extensions/multi"; export const transformReturnStatement: FunctionVisitor = (statement, context) => { // Bubble up explicit return flag and check if we're inside a try/catch block diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 0bcc9415a..97aebecc2 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,7 +10,7 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; -import { transformMultiHelperVariableDeclaration } from "./helpers/multi"; +import { transformMultiHelperVariableDeclaration } from "./language-extensions/multi"; export function transformArrayBindingElement( context: TransformationContext, diff --git a/src/transpilation/index.ts b/src/transpilation/index.ts index a67017ddf..608b42818 100644 --- a/src/transpilation/index.ts +++ b/src/transpilation/index.ts @@ -66,7 +66,7 @@ export function createVirtualProgram(input: Record, options: Com filePath = path.join(typeScriptDir, fileName); } - if (fileName.includes("helpers")) { + if (fileName.includes("language-extensions")) { const dtsName = fileName.replace(/(\.d)?(\.ts)$/, ".d.ts"); filePath = path.resolve(dtsName); } diff --git a/test/tsconfig.json b/test/tsconfig.json index 2a8e31dcb..2c06244c7 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -14,6 +14,6 @@ "transpile/directories", "transpile/outFile", "../src/lualib", - "../helpers" + "../language-extensions" ] } diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/helpers/multi.spec.ts index 382d78ccd..1f7da28ee 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/helpers/multi.spec.ts @@ -8,7 +8,7 @@ import { } from "../../../src/transformation/utils/diagnostics"; const multiProjectOptions: tstl.CompilerOptions = { - types: [path.resolve(__dirname, "../../../helpers")], + types: [path.resolve(__dirname, "../../../language-extensions")], }; test.each<[string, any]>([ From e1df59ed70665a1957acdff28ac88b8269457be1 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Dec 2020 13:50:14 +1000 Subject: [PATCH 62/66] Remove remaining helper references --- src/transformation/utils/language-extensions.ts | 4 ++-- src/transformation/visitors/expression-statement.ts | 4 ++-- src/transformation/visitors/language-extensions/multi.ts | 8 ++++---- src/transformation/visitors/literal.ts | 4 ++-- src/transformation/visitors/return.ts | 4 ++-- src/transformation/visitors/variable-declaration.ts | 4 ++-- .../__snapshots__/multi.spec.ts.snap | 0 test/unit/{helpers => language-extensions}/multi.spec.ts | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) rename test/unit/{helpers => language-extensions}/__snapshots__/multi.spec.ts.snap (100%) rename test/unit/{helpers => language-extensions}/multi.spec.ts (97%) diff --git a/src/transformation/utils/language-extensions.ts b/src/transformation/utils/language-extensions.ts index d56850b39..8f3b07672 100644 --- a/src/transformation/utils/language-extensions.ts +++ b/src/transformation/utils/language-extensions.ts @@ -7,9 +7,9 @@ export enum ExtensionKind { } function isSourceFileFromLanguageExtensions(sourceFile: ts.SourceFile): boolean { - const helperDirectory = path.resolve(__dirname, "../../../language-extensions"); + const extensionDirectory = path.resolve(__dirname, "../../../language-extensions"); const sourceFileDirectory = path.dirname(path.normalize(sourceFile.fileName)); - return helperDirectory === sourceFileDirectory; + return extensionDirectory === sourceFileDirectory; } export function getExtensionKind(declaration: ts.Declaration): ExtensionKind | undefined { diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index 5a4cc1bfb..24bb799b7 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -5,10 +5,10 @@ import { transformBinaryExpressionStatement } from "./binary-expression"; import { transformDeleteExpressionStatement } from "./delete"; import { transformLuaTableExpressionStatement } from "./lua-table"; import { transformUnaryExpressionStatement } from "./unary-expression"; -import { transformMultiHelperDestructuringAssignmentStatement } from "./language-extensions/multi"; +import { transformMultiDestructuringAssignmentStatement } from "./language-extensions/multi"; export const transformExpressionStatement: FunctionVisitor = (node, context) => { - const multiResult = transformMultiHelperDestructuringAssignmentStatement(context, node); + const multiResult = transformMultiDestructuringAssignmentStatement(context, node); if (multiResult) { return multiResult; } diff --git a/src/transformation/visitors/language-extensions/multi.ts b/src/transformation/visitors/language-extensions/multi.ts index 00078b9c9..22af77b60 100644 --- a/src/transformation/visitors/language-extensions/multi.ts +++ b/src/transformation/visitors/language-extensions/multi.ts @@ -38,7 +38,7 @@ export function isMultiFunctionNode(context: TransformationContext, node: ts.Nod return type.symbol?.declarations?.some(isMultiFunctionDeclaration) ?? false; } -export function transformMultiHelperReturnStatement( +export function transformMultiReturnStatement( context: TransformationContext, statement: ts.ReturnStatement ): lua.Statement | undefined { @@ -65,7 +65,7 @@ function transformMultiFunctionArguments( return expression.arguments.map(e => context.transformExpression(e)); } -export function transformMultiHelperVariableDeclaration( +export function transformMultiVariableDeclaration( context: TransformationContext, declaration: ts.VariableDeclaration ): lua.Statement[] | undefined { @@ -111,7 +111,7 @@ export function transformMultiHelperVariableDeclaration( return createLocalOrExportedOrGlobalDeclaration(context, leftIdentifiers, rightExpressions, declaration); } -export function transformMultiHelperDestructuringAssignmentStatement( +export function transformMultiDestructuringAssignmentStatement( context: TransformationContext, statement: ts.ExpressionStatement ): lua.Statement[] | undefined { @@ -161,7 +161,7 @@ export function transformMultiHelperDestructuringAssignmentStatement( return [lua.createAssignmentStatement(leftIdentifiers, rightExpressions, statement), ...trailingStatements]; } -export function findMultiHelperAssignmentViolations( +export function findMultiAssignmentViolations( context: TransformationContext, node: ts.ObjectLiteralExpression ): ts.Node[] { diff --git a/src/transformation/visitors/literal.ts b/src/transformation/visitors/literal.ts index 3485bed6a..36c7e48c6 100644 --- a/src/transformation/visitors/literal.ts +++ b/src/transformation/visitors/literal.ts @@ -10,7 +10,7 @@ import { getSymbolIdOfSymbol, trackSymbolReference } from "../utils/symbols"; import { isArrayType } from "../utils/typescript"; import { transformFunctionLikeDeclaration } from "./function"; import { flattenSpreadExpressions } from "./call"; -import { findMultiHelperAssignmentViolations } from "./language-extensions/multi"; +import { findMultiAssignmentViolations } from "./language-extensions/multi"; // TODO: Move to object-literal.ts? export function transformPropertyName(context: TransformationContext, node: ts.PropertyName): lua.Expression { @@ -63,7 +63,7 @@ const transformNumericLiteralExpression: FunctionVisitor = ex }; const transformObjectLiteralExpression: FunctionVisitor = (expression, context) => { - const violations = findMultiHelperAssignmentViolations(context, expression); + const violations = findMultiAssignmentViolations(context, expression); if (violations.length > 0) { context.diagnostics.push(...violations.map(e => invalidMultiFunctionUse(e))); return lua.createNilLiteral(expression); diff --git a/src/transformation/visitors/return.ts b/src/transformation/visitors/return.ts index 0d613df56..74c855931 100644 --- a/src/transformation/visitors/return.ts +++ b/src/transformation/visitors/return.ts @@ -6,7 +6,7 @@ import { validateAssignment } from "../utils/assignment-validation"; import { createUnpackCall, wrapInTable } from "../utils/lua-ast"; import { ScopeType, walkScopesUp } from "../utils/scope"; import { isArrayType } from "../utils/typescript"; -import { transformMultiHelperReturnStatement } from "./language-extensions/multi"; +import { transformMultiReturnStatement } from "./language-extensions/multi"; export const transformReturnStatement: FunctionVisitor = (statement, context) => { // Bubble up explicit return flag and check if we're inside a try/catch block @@ -21,7 +21,7 @@ export const transformReturnStatement: FunctionVisitor = (st insideTryCatch = insideTryCatch || scope.type === ScopeType.Try || scope.type === ScopeType.Catch; } - const result = transformMultiHelperReturnStatement(context, statement); + const result = transformMultiReturnStatement(context, statement); if (result) { return result; } diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 97aebecc2..9e7ff5413 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,7 +10,7 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; -import { transformMultiHelperVariableDeclaration } from "./language-extensions/multi"; +import { transformMultiVariableDeclaration } from "./language-extensions/multi"; export function transformArrayBindingElement( context: TransformationContext, @@ -218,7 +218,7 @@ export function transformVariableDeclaration( context: TransformationContext, statement: ts.VariableDeclaration ): lua.Statement[] { - const result = transformMultiHelperVariableDeclaration(context, statement); + const result = transformMultiVariableDeclaration(context, statement); if (result) { return result; } diff --git a/test/unit/helpers/__snapshots__/multi.spec.ts.snap b/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap similarity index 100% rename from test/unit/helpers/__snapshots__/multi.spec.ts.snap rename to test/unit/language-extensions/__snapshots__/multi.spec.ts.snap diff --git a/test/unit/helpers/multi.spec.ts b/test/unit/language-extensions/multi.spec.ts similarity index 97% rename from test/unit/helpers/multi.spec.ts rename to test/unit/language-extensions/multi.spec.ts index 1f7da28ee..b36d884a4 100644 --- a/test/unit/helpers/multi.spec.ts +++ b/test/unit/language-extensions/multi.spec.ts @@ -92,7 +92,7 @@ test("function to spread multi type result from multi type function", () => { .expectToEqual(true); }); -test("$multi helper call with destructuring assignment side effects", () => { +test("$multi call with destructuring assignment side effects", () => { util.testModule` ${multiFunction} let a; From e0232f55de5e1c64ccf59fb9cd780a1f3cd40cd9 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Tue, 22 Dec 2020 14:38:08 +1000 Subject: [PATCH 63/66] Allow multi call as ConciseBody --- src/transformation/utils/diagnostics.ts | 2 +- src/transformation/visitors/function.ts | 6 +++++ .../visitors/language-extensions/multi.ts | 16 ++++++++++---- src/utils.ts | 1 + .../__snapshots__/multi.spec.ts.snap | 22 +++++++++---------- test/unit/language-extensions/multi.spec.ts | 10 +++++++++ 6 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index a7e032d96..ae9f1a751 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -128,7 +128,7 @@ export const unsupportedVarDeclaration = createDiagnosticFactory( ); export const invalidMultiFunctionUse = createDiagnosticFactory( - "The $multi function must be called in return statements." + "The $multi function must be called in an expression that is returned." ); export const invalidMultiTypeToNonArrayBindingPattern = createDiagnosticFactory( diff --git a/src/transformation/visitors/function.ts b/src/transformation/visitors/function.ts index d3fb88a5a..fd5e66c06 100644 --- a/src/transformation/visitors/function.ts +++ b/src/transformation/visitors/function.ts @@ -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 { transformMultiCallExpressionToReturnStatement } from "./language-extensions/multi"; import { transformExpressionBodyToReturnStatement } from "./return"; import { transformBindingPattern } from "./variable-declaration"; @@ -55,6 +56,11 @@ function isRestParameterReferenced(context: TransformationContext, identifier: l export function transformFunctionBodyContent(context: TransformationContext, body: ts.ConciseBody): lua.Statement[] { if (!ts.isBlock(body)) { + const result = transformMultiCallExpressionToReturnStatement(context, body); + if (result) { + return [result]; + } + const returnStatement = transformExpressionBodyToReturnStatement(context, body); return [returnStatement]; } diff --git a/src/transformation/visitors/language-extensions/multi.ts b/src/transformation/visitors/language-extensions/multi.ts index 22af77b60..766041ea2 100644 --- a/src/transformation/visitors/language-extensions/multi.ts +++ b/src/transformation/visitors/language-extensions/multi.ts @@ -38,16 +38,24 @@ export function isMultiFunctionNode(context: TransformationContext, node: ts.Nod return type.symbol?.declarations?.some(isMultiFunctionDeclaration) ?? false; } +export function transformMultiCallExpressionToReturnStatement( + context: TransformationContext, + expression: ts.Expression +): lua.Statement | undefined { + if (!ts.isCallExpression(expression)) return; + if (!isMultiFunction(context, expression)) return; + + const expressions = transformArguments(context, expression.arguments); + return lua.createReturnStatement(expressions, expression); +} + export function transformMultiReturnStatement( context: TransformationContext, statement: ts.ReturnStatement ): lua.Statement | undefined { if (!statement.expression) return; - if (!ts.isCallExpression(statement.expression)) return; - if (!isMultiFunction(context, statement.expression)) return; - const expressions = transformArguments(context, statement.expression.arguments); - return lua.createReturnStatement(expressions, statement); + return transformMultiCallExpressionToReturnStatement(context, statement.expression); } function transformMultiFunctionArguments( diff --git a/src/utils.ts b/src/utils.ts index 1244068a4..d152b8528 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,6 +5,7 @@ import * as path from "path"; export function castArray(value: T | T[]): T[]; export function castArray(value: T | readonly T[]): readonly T[]; export function castArray(value: T | readonly T[]): readonly T[] { + // @ts-ignore return Array.isArray(value) ? value : [value]; } diff --git a/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap b/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap index bc08361f2..587cdfd90 100644 --- a/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap +++ b/test/unit/language-extensions/__snapshots__/multi.spec.ts.snap @@ -2,11 +2,11 @@ exports[`invalid $multi call ($multi()): code 1`] = `"____(_G)"`; -exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid $multi call ($multi()): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid $multi call ($multi): code 1`] = `"local ____ = ____"`; -exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid $multi call ($multi): diagnostics 1`] = `"main.ts(2,9): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` "local function ____(____, ____bindingPattern0) @@ -17,11 +17,11 @@ exports[`invalid $multi call (([a] = $multi(1)) => {}): code 1`] = ` end" `; -exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid $multi call (([a] = $multi(1)) => {}): diagnostics 1`] = `"main.ts(2,16): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid $multi call (({ $multi });): code 1`] = `"local ____ = nil"`; -exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid $multi call (const [a = 0] = $multi()): code 1`] = `""`; @@ -45,7 +45,7 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (const [a] = $multi()): diagnostics 1`] = `"main.ts(7,21): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid direct $multi function use (const [a] = $multi()): diagnostics 1`] = `"main.ts(7,21): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid direct $multi function use (const [a] = $multi(1)): code 1`] = ` "local ____exports = {} @@ -57,7 +57,7 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (const [a] = $multi(1)): diagnostics 1`] = `"main.ts(7,21): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid direct $multi function use (const [a] = $multi(1)): diagnostics 1`] = `"main.ts(7,21): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid direct $multi function use (const _ = null, [a] = $multi(1)): code 1`] = ` "local ____exports = {} @@ -70,7 +70,7 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (const _ = null, [a] = $multi(1)): diagnostics 1`] = `"main.ts(7,31): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid direct $multi function use (const _ = null, [a] = $multi(1)): diagnostics 1`] = `"main.ts(7,31): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid direct $multi function use (const ar = [1]; const [a] = $multi(...ar)): code 1`] = ` "local ____exports = {} @@ -83,7 +83,7 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (const ar = [1]; const [a] = $multi(...ar)): diagnostics 1`] = `"main.ts(7,37): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid direct $multi function use (const ar = [1]; const [a] = $multi(...ar)): diagnostics 1`] = `"main.ts(7,37): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid direct $multi function use (let a; [a] = $multi()): code 1`] = ` "local ____exports = {} @@ -96,7 +96,7 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (let a; [a] = $multi()): diagnostics 1`] = `"main.ts(7,22): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid direct $multi function use (let a; [a] = $multi()): diagnostics 1`] = `"main.ts(7,22): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid direct $multi function use (let a; for ([a] = $multi(1, 2); false; 1) {}): code 1`] = ` "local ____exports = {} @@ -114,7 +114,7 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (let a; for ([a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,27): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid direct $multi function use (let a; for ([a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,27): error TSTL: The $multi function must be called in an expression that is returned."`; exports[`invalid direct $multi function use (let a; for (const [a] = $multi(1, 2); false; 1) {}): code 1`] = ` "local ____exports = {} @@ -132,4 +132,4 @@ ____exports.a = a return ____exports" `; -exports[`invalid direct $multi function use (let a; for (const [a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,33): error TSTL: The $multi function must be called in return statements."`; +exports[`invalid direct $multi function use (let a; for (const [a] = $multi(1, 2); false; 1) {}): diagnostics 1`] = `"main.ts(7,33): error TSTL: The $multi function must be called in an expression that is returned."`; diff --git a/test/unit/language-extensions/multi.spec.ts b/test/unit/language-extensions/multi.spec.ts index b36d884a4..068146baf 100644 --- a/test/unit/language-extensions/multi.spec.ts +++ b/test/unit/language-extensions/multi.spec.ts @@ -103,3 +103,13 @@ test("$multi call with destructuring assignment side effects", () => { .setReturnExport("a") .expectToEqual(1); }); + +test("allow $multi call in ArrowFunction body", () => { + util.testFunction` + const call = () => $multi(1); + const [result] = call(); + return result; + ` + .setOptions(multiProjectOptions) + .expectToEqual(1); +}); From 409160bb2db62720c614e05aee9ef0a420523b99 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 23 Dec 2020 07:29:46 +1000 Subject: [PATCH 64/66] Remove ts-ignore --- src/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index d152b8528..1244068a4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,7 +5,6 @@ import * as path from "path"; export function castArray(value: T | T[]): T[]; export function castArray(value: T | readonly T[]): readonly T[]; export function castArray(value: T | readonly T[]): readonly T[] { - // @ts-ignore return Array.isArray(value) ? value : [value]; } From 33b875f08a2597427e247412b02bf761fe6679d6 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 23 Dec 2020 08:22:00 +1000 Subject: [PATCH 65/66] Add check first to all multi transforms --- .../visitors/expression-statement.ts | 12 ++++++--- src/transformation/visitors/function.ts | 7 +++--- .../visitors/language-extensions/multi.ts | 25 ++++++++----------- src/transformation/visitors/return.ts | 11 +++++--- .../visitors/variable-declaration.ts | 11 +++++--- 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index 831be662a..4901a3210 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -4,12 +4,16 @@ import { FunctionVisitor } from "../context"; import { transformBinaryExpressionStatement } from "./binary-expression"; import { transformLuaTableExpressionStatement } from "./lua-table"; import { transformUnaryExpressionStatement } from "./unary-expression"; -import { transformMultiDestructuringAssignmentStatement } from "./language-extensions/multi"; +import { returnsMultiType, transformMultiDestructuringAssignmentStatement } from "./language-extensions/multi"; export const transformExpressionStatement: FunctionVisitor = (node, context) => { - const multiResult = transformMultiDestructuringAssignmentStatement(context, node); - if (multiResult) { - return multiResult; + 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); diff --git a/src/transformation/visitors/function.ts b/src/transformation/visitors/function.ts index fd5e66c06..424e75efe 100644 --- a/src/transformation/visitors/function.ts +++ b/src/transformation/visitors/function.ts @@ -15,7 +15,7 @@ import { import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { peekScope, performHoisting, popScope, pushScope, Scope, ScopeType } from "../utils/scope"; import { transformIdentifier } from "./identifier"; -import { transformMultiCallExpressionToReturnStatement } from "./language-extensions/multi"; +import { isMultiFunction, transformMultiCallExpressionToReturnStatement } from "./language-extensions/multi"; import { transformExpressionBodyToReturnStatement } from "./return"; import { transformBindingPattern } from "./variable-declaration"; @@ -56,9 +56,8 @@ function isRestParameterReferenced(context: TransformationContext, identifier: l export function transformFunctionBodyContent(context: TransformationContext, body: ts.ConciseBody): lua.Statement[] { if (!ts.isBlock(body)) { - const result = transformMultiCallExpressionToReturnStatement(context, body); - if (result) { - return [result]; + if (ts.isCallExpression(body) && isMultiFunction(context, body)) { + return [transformMultiCallExpressionToReturnStatement(context, body)]; } const returnStatement = transformExpressionBodyToReturnStatement(context, body); diff --git a/src/transformation/visitors/language-extensions/multi.ts b/src/transformation/visitors/language-extensions/multi.ts index 766041ea2..a6cb2a755 100644 --- a/src/transformation/visitors/language-extensions/multi.ts +++ b/src/transformation/visitors/language-extensions/multi.ts @@ -16,6 +16,7 @@ import { unsupportedMultiFunctionAssignment, invalidMultiFunctionUse, } from "../../utils/diagnostics"; +import { assert } from "../../../utils"; const isMultiFunctionDeclaration = (declaration: ts.Declaration): boolean => extensions.getExtensionKind(declaration) === extensions.ExtensionKind.MultiFunction; @@ -23,7 +24,7 @@ const isMultiFunctionDeclaration = (declaration: ts.Declaration): boolean => const isMultiTypeDeclaration = (declaration: ts.Declaration): boolean => extensions.getExtensionKind(declaration) === extensions.ExtensionKind.MultiType; -function isMultiFunction(context: TransformationContext, expression: ts.CallExpression): boolean { +export function isMultiFunction(context: TransformationContext, expression: ts.CallExpression): boolean { const type = context.checker.getTypeAtLocation(expression.expression); return type.symbol?.declarations?.some(isMultiFunctionDeclaration) ?? false; } @@ -41,9 +42,8 @@ export function isMultiFunctionNode(context: TransformationContext, node: ts.Nod export function transformMultiCallExpressionToReturnStatement( context: TransformationContext, expression: ts.Expression -): lua.Statement | undefined { - if (!ts.isCallExpression(expression)) return; - if (!isMultiFunction(context, expression)) return; +): lua.Statement { + assert(ts.isCallExpression(expression)); const expressions = transformArguments(context, expression.arguments); return lua.createReturnStatement(expressions, expression); @@ -52,8 +52,8 @@ export function transformMultiCallExpressionToReturnStatement( export function transformMultiReturnStatement( context: TransformationContext, statement: ts.ReturnStatement -): lua.Statement | undefined { - if (!statement.expression) return; +): lua.Statement { + assert(statement.expression); return transformMultiCallExpressionToReturnStatement(context, statement.expression); } @@ -76,10 +76,9 @@ function transformMultiFunctionArguments( export function transformMultiVariableDeclaration( context: TransformationContext, declaration: ts.VariableDeclaration -): lua.Statement[] | undefined { - if (!declaration.initializer) return; - if (!ts.isCallExpression(declaration.initializer)) return; - if (!returnsMultiType(context, declaration.initializer)) return; +): lua.Statement[] { + assert(declaration.initializer); + assert(ts.isCallExpression(declaration.initializer)); if (!ts.isArrayBindingPattern(declaration.name)) { context.diagnostics.push(invalidMultiTypeToNonArrayBindingPattern(declaration.name)); @@ -123,10 +122,8 @@ export function transformMultiDestructuringAssignmentStatement( context: TransformationContext, statement: ts.ExpressionStatement ): lua.Statement[] | undefined { - if (!ts.isBinaryExpression(statement.expression)) return; - if (statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return; - if (!ts.isCallExpression(statement.expression.right)) return; - if (!returnsMultiType(context, statement.expression.right)) return; + assert(ts.isBinaryExpression(statement.expression)); + assert(ts.isCallExpression(statement.expression.right)); if (!ts.isArrayLiteralExpression(statement.expression.left)) { context.diagnostics.push(invalidMultiTypeToNonArrayLiteral(statement.expression.left)); diff --git a/src/transformation/visitors/return.ts b/src/transformation/visitors/return.ts index ae40d69e5..c13d571ff 100644 --- a/src/transformation/visitors/return.ts +++ b/src/transformation/visitors/return.ts @@ -6,7 +6,7 @@ import { validateAssignment } from "../utils/assignment-validation"; import { createUnpackCall, wrapInTable } from "../utils/lua-ast"; import { ScopeType, walkScopesUp } from "../utils/scope"; import { isArrayType } from "../utils/typescript"; -import { transformMultiReturnStatement } from "./language-extensions/multi"; +import { isMultiFunction, transformMultiReturnStatement } from "./language-extensions/multi"; function transformExpressionsInReturn( context: TransformationContext, @@ -60,9 +60,12 @@ export const transformReturnStatement: FunctionVisitor = (st insideTryCatch = insideTryCatch || scope.type === ScopeType.Try || scope.type === ScopeType.Catch; } - const result = transformMultiReturnStatement(context, statement); - if (result) { - return result; + if ( + statement.expression && + ts.isCallExpression(statement.expression) && + isMultiFunction(context, statement.expression) + ) { + return transformMultiReturnStatement(context, statement); } let results: lua.Expression[]; diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 9de6c8c4b..6ddd4acdc 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -10,7 +10,7 @@ import { createLocalOrExportedOrGlobalDeclaration, createUnpackCall } from "../u import { LuaLibFeature, transformLuaLibFunction } from "../utils/lualib"; import { transformIdentifier } from "./identifier"; import { transformPropertyName } from "./literal"; -import { transformMultiVariableDeclaration } from "./language-extensions/multi"; +import { returnsMultiType, transformMultiVariableDeclaration } from "./language-extensions/multi"; export function transformArrayBindingElement( context: TransformationContext, @@ -230,9 +230,12 @@ export function transformVariableDeclaration( context: TransformationContext, statement: ts.VariableDeclaration ): lua.Statement[] { - const result = transformMultiVariableDeclaration(context, statement); - if (result) { - return result; + if ( + statement.initializer && + ts.isCallExpression(statement.initializer) && + returnsMultiType(context, statement.initializer) + ) { + return transformMultiVariableDeclaration(context, statement); } if (statement.initializer && statement.type) { From bcd158d01e3eb912ba2b1a46e4552e63dcd54a53 Mon Sep 17 00:00:00 2001 From: hazzard993 Date: Wed, 23 Dec 2020 21:56:09 +1000 Subject: [PATCH 66/66] Add multi example use case --- test/unit/language-extensions/multi.spec.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/unit/language-extensions/multi.spec.ts b/test/unit/language-extensions/multi.spec.ts index 068146baf..ed5353bbc 100644 --- a/test/unit/language-extensions/multi.spec.ts +++ b/test/unit/language-extensions/multi.spec.ts @@ -11,6 +11,19 @@ const multiProjectOptions: tstl.CompilerOptions = { types: [path.resolve(__dirname, "../../../language-extensions")], }; +test("multi example use case", () => { + util.testModule` + function multiReturn(): MultiReturn<[string, number]> { + return $multi("foo", 5); + } + + const [a, b] = multiReturn(); + export { a, b }; + ` + .setOptions(multiProjectOptions) + .expectToEqual({ a: "foo", b: 5 }); +}); + test.each<[string, any]>([ ["$multi()", undefined], ["$multi(true)", true],