Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/LuaAST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export enum SyntaxKind {
MethodCallExpression,
Identifier,
TableIndexExpression,
ParenthesizedExpression,

// Operators

Expand Down Expand Up @@ -843,3 +844,20 @@ export function isInlineFunctionExpression(expression: FunctionExpression): expr
(expression.flags & NodeFlags.Inline) !== 0
);
}

export type ParenthesizedExpression = Expression & {
expression: Expression;
};

export function isParenthesizedExpression(node: Node): node is ParenthesizedExpression {
return node.kind === SyntaxKind.ParenthesizedExpression;
}

export function createParenthesizedExpression(expression: Expression, tsOriginal?: ts.Node): ParenthesizedExpression {
const parenthesizedExpression = createNode(
SyntaxKind.ParenthesizedExpression,
tsOriginal
) as ParenthesizedExpression;
parenthesizedExpression.expression = expression;
return parenthesizedExpression;
}
6 changes: 6 additions & 0 deletions src/LuaPrinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,8 @@ export class LuaPrinter {
return this.printIdentifier(expression as lua.Identifier);
case lua.SyntaxKind.TableIndexExpression:
return this.printTableIndexExpression(expression as lua.TableIndexExpression);
case lua.SyntaxKind.ParenthesizedExpression:
return this.printParenthesizedExpression(expression as lua.ParenthesizedExpression);
default:
throw new Error(`Tried to print unknown statement kind: ${lua.SyntaxKind[expression.kind]}`);
}
Expand Down Expand Up @@ -822,6 +824,10 @@ export class LuaPrinter {
return this.createSourceNode(expression, chunks);
}

public printParenthesizedExpression(expression: lua.ParenthesizedExpression) {
return this.createSourceNode(expression, ["(", this.printExpression(expression.expression), ")"]);
}

public printOperator(kind: lua.Operator): SourceNode {
return new SourceNode(null, null, this.relativeSourcePath, LuaPrinter.operatorMap[kind]);
}
Expand Down
14 changes: 8 additions & 6 deletions src/transformation/visitors/access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,15 @@ export function transformElementAccessExpressionWithCapture(
context.diagnostics.push(invalidMultiReturnAccess(node));
}

// When selecting the first element, we can shortcut
if (ts.isNumericLiteral(node.argumentExpression) && node.argumentExpression.text === "0") {
return { expression: table };
} else {
const selectIdentifier = lua.createIdentifier("select");
return { expression: lua.createCallExpression(selectIdentifier, [updatedAccessExpression, table]) };
const canOmitSelect = ts.isNumericLiteral(node.argumentExpression) && node.argumentExpression.text === "0";
if (canOmitSelect) {
// wrapping in parenthesis ensures only the first return value is used
// https://www.lua.org/manual/5.1/manual.html#2.5
return { expression: lua.createParenthesizedExpression(table) };
}

const selectIdentifier = lua.createIdentifier("select");
return { expression: lua.createCallExpression(selectIdentifier, [updatedAccessExpression, table]) };
}

if (thisValueCapture) {
Expand Down
35 changes: 35 additions & 0 deletions test/unit/language-extensions/multi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,38 @@ test("LuaMultiReturn applies after casting a function (#1404)", () => {
.withLanguageExtensions()
.expectToEqual([3, 4]);
});

// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1411
describe("LuaMultiReturn returns all values even when indexed with [0] #1411", () => {
const sharedCode = `
declare function select<T>(this:void, index: '#', ...args: T[]): number;

function foo(this: void): LuaMultiReturn<[number, number]> {
return $multi(123, 456);
}

function bar(this: void): number {
return foo()[0];
}
`;
test("Returns the correct value", () => {
util.testFunction`
return bar();
`
.setTsHeader(sharedCode)
.withLanguageExtensions()
.expectToEqual(123);
});

// We require an extra test since the test above would succeed even if bar() returned more than one value.
// This is because our test helper always returns just the first lua value returned from the testFunction.
// Here we need to test explicitly that only one value is returned.
test("Does not return multiple values", () => {
util.testFunction`
return select("#", bar());
`
.setTsHeader(sharedCode)
.withLanguageExtensions()
.expectToEqual(1);
});
});
5 changes: 3 additions & 2 deletions test/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export abstract class TestBuilder {
skipLibCheck: true,
target: ts.ScriptTarget.ES2017,
lib: ["lib.esnext.d.ts"],
moduleResolution: ts.ModuleResolutionKind.NodeJs,
moduleResolution: ts.ModuleResolutionKind.Node10,
resolveJsonModule: true,
experimentalDecorators: true,
sourceMap: true,
Expand All @@ -173,7 +173,8 @@ export abstract class TestBuilder {
}

public withLanguageExtensions(): this {
this.setOptions({ types: [path.resolve(__dirname, "..", "language-extensions")] });
const langExtTypes = path.resolve(__dirname, "..", "language-extensions");
this.options.types = this.options.types ? [...this.options.types, langExtTypes] : [langExtTypes];
// Polyfill lualib for JS
this.setJsHeader(`
function $multi(...args) { return args; }
Expand Down