diff --git a/src/transformation/visitors/binary-expression/assignments.ts b/src/transformation/visitors/binary-expression/assignments.ts index a138781dc..5635de44a 100644 --- a/src/transformation/visitors/binary-expression/assignments.ts +++ b/src/transformation/visitors/binary-expression/assignments.ts @@ -225,6 +225,8 @@ export function transformAssignmentStatement( if (ts.isArrayLiteralExpression(expression.right)) { right = transformExpressionList(context, expression.right.elements); + } else if (ts.isStringLiteral(expression.right)) { + right = Array.from(expression.right.text).map(c => lua.createStringLiteral(c)); } else { right = context.transformExpression(expression.right); if (!isMultiReturnCall(context, expression.right) && isArrayType(context, rightType)) { diff --git a/src/transformation/visitors/variable-declaration.ts b/src/transformation/visitors/variable-declaration.ts index 42a2b6538..a1f508262 100644 --- a/src/transformation/visitors/variable-declaration.ts +++ b/src/transformation/visitors/variable-declaration.ts @@ -13,6 +13,7 @@ import { transformIdentifier } from "./identifier"; import { isMultiReturnCall } from "./language-extensions/multi"; import { transformPropertyName } from "./literal"; import { moveToPrecedingTemp } from "./expression-list"; +import { isStringType } from "../utils/typescript"; export function transformArrayBindingElement( context: TransformationContext, @@ -40,7 +41,8 @@ export function transformBindingPattern( context: TransformationContext, pattern: ts.BindingPattern, table: lua.Expression, - propertyAccessStack: ts.PropertyName[] = [] + propertyAccessStack: ts.PropertyName[] = [], + isStringBinding = false ): lua.Statement[] { const result: lua.Statement[] = []; @@ -125,10 +127,21 @@ export function transformBindingPattern( ); } } else { - expression = lua.createTableIndexExpression( - tableExpression, - ts.isObjectBindingPattern(pattern) ? propertyName : lua.createNumericLiteral(index + 1) - ); + if (isStringBinding) { + // don't add 1 to index stringaccess already takes care of that + expression = transformLuaLibFunction( + context, + LuaLibFeature.StringAccess, + pattern, + table, + lua.createNumericLiteral(index) + ); + } else { + expression = lua.createTableIndexExpression( + tableExpression, + ts.isObjectBindingPattern(pattern) ? propertyName : lua.createNumericLiteral(index + 1) + ); + } } result.push(...createLocalOrExportedOrGlobalDeclaration(context, variableName, expression)); @@ -153,36 +166,67 @@ export function transformBindingPattern( return result; } -export function transformBindingVariableDeclaration( +function requiresComplexBindingVariableDeclaration( context: TransformationContext, bindingPattern: ts.BindingPattern, initializer?: ts.Expression -): lua.Statement[] { - const statements: lua.Statement[] = []; - - // For object, nested or rest bindings fall back to transformBindingPattern +): boolean { + // For object, strings, nested or rest bindings fall back to transformBindingPattern const isComplexBindingElement = (e: ts.ArrayBindingElement) => ts.isBindingElement(e) && (!ts.isIdentifier(e.name) || e.dotDotDotToken); - if (ts.isObjectBindingPattern(bindingPattern) || bindingPattern.elements.some(isComplexBindingElement)) { - let table: lua.Expression; - if (initializer) { - // Contain the expression in a temporary variable - let expression = context.transformExpression(initializer); - if (isMultiReturnCall(context, initializer)) { - expression = wrapInTable(expression); - } - const { precedingStatements: moveStatements, result: movedExpr } = transformInPrecedingStatementScope( - context, - () => moveToPrecedingTemp(context, expression, initializer) - ); - statements.push(...moveStatements); - table = movedExpr; - } else { - table = lua.createAnonymousIdentifier(); + const hasStringInitializer = initializer && isStringType(context, context.checker.getTypeAtLocation(initializer)); + + return ( + ts.isObjectBindingPattern(bindingPattern) || + bindingPattern.elements.some(isComplexBindingElement) || + Boolean(hasStringInitializer) + ); +} +function transformComplexBindingVariableDeclaration( + context: TransformationContext, + bindingPattern: ts.BindingPattern, + initializer?: ts.Expression +): lua.Statement[] { + const statements: lua.Statement[] = []; + + let table: lua.Expression; + if (initializer) { + // Contain the expression in a temporary variable + let expression = context.transformExpression(initializer); + if (isMultiReturnCall(context, initializer)) { + expression = wrapInTable(expression); } - statements.push(...transformBindingPattern(context, bindingPattern, table)); - return statements; + const { precedingStatements: moveStatements, result: movedExpr } = transformInPrecedingStatementScope( + context, + () => moveToPrecedingTemp(context, expression, initializer) + ); + statements.push(...moveStatements); + table = movedExpr; + } else { + table = lua.createAnonymousIdentifier(); + } + statements.push( + ...transformBindingPattern( + context, + bindingPattern, + table, + [], + initializer && isStringType(context, context.checker.getTypeAtLocation(initializer)) + ) + ); + return statements; +} + +export function transformBindingVariableDeclaration( + context: TransformationContext, + bindingPattern: ts.BindingPattern, + initializer?: ts.Expression +): lua.Statement[] { + const statements: lua.Statement[] = []; + + if (requiresComplexBindingVariableDeclaration(context, bindingPattern, initializer)) { + return transformComplexBindingVariableDeclaration(context, bindingPattern, initializer); } const vars = @@ -208,6 +252,12 @@ export function transformBindingVariableDeclaration( ? initializer.elements.map(e => context.transformExpression(e)) : lua.createNilLiteral(); statements.push(...createLocalOrExportedOrGlobalDeclaration(context, vars, values, initializer)); + } else if (ts.isStringLiteral(initializer)) { + const values = + initializer.text.length > 0 + ? Array.from(initializer.text).map(c => lua.createStringLiteral(c)) + : lua.createNilLiteral(); + statements.push(...createLocalOrExportedOrGlobalDeclaration(context, vars, values, initializer)); } else { // local vars = this.transpileDestructingAssignmentValue(node.initializer); const unpackedInitializer = createUnpackCall( diff --git a/test/unit/destructuring.spec.ts b/test/unit/destructuring.spec.ts index 80d3d482c..ae7529970 100644 --- a/test/unit/destructuring.spec.ts +++ b/test/unit/destructuring.spec.ts @@ -237,3 +237,44 @@ test("no exception from semantically invalid TS", () => { .disableSemanticCheck() .expectToHaveDiagnostics([invalidMultiReturnAccess.code, cannotAssignToNodeOfKind.code]); }); + +describe("string destructuring", () => { + test("string literal declaration", () => { + util.testFunction` + const [a, b, c] = "test"; + return { a, b, c }; + `.expectToMatchJsResult(); + }); + + test("string literal assignment", () => { + util.testFunction` + let a = ""; + let b = ""; + let c = ""; + [a, b, c] = "test"; + return { a, b, c }; + ` + .debug() + .expectToMatchJsResult(); + }); + + test("string assignment", () => { + util.testFunction` + const foo = "test"; + const [a, b, c] = foo; + return { a, b, c }; + `.expectToMatchJsResult(); + }); + + // not wokring right now: send help pls + test("for loop init", () => { + util.testFunction` + const foo = "test"; + for (const [a, b, c] of foo) { + return { a, b, c }; + } + ` + .debug() + .expectToMatchJsResult(); + }); +});