Skip to content

Commit 29bef10

Browse files
committed
Disallow multi function use in non-return statements
1 parent cbe8f46 commit 29bef10

File tree

4 files changed

+173
-36
lines changed

4 files changed

+173
-36
lines changed

src/transformation/utils/diagnostics.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,21 +131,21 @@ export const invalidMultiFunctionUse = createDiagnosticFactory(
131131
"The $multi function must be called in return statements."
132132
);
133133

134-
export const invalidMultiReturnToNonArrayBindingPattern = createDiagnosticFactory(
134+
export const invalidMultiTypeToNonArrayBindingPattern = createDiagnosticFactory(
135135
"Expected an array destructuring pattern."
136136
);
137137

138-
export const invalidMultiReturnToNonArrayLiteral = createDiagnosticFactory("Expected an array literal.");
138+
export const invalidMultiTypeToNonArrayLiteral = createDiagnosticFactory("Expected an array literal.");
139139

140-
export const invalidMultiReturnToEmptyPatternOrArrayLiteral = createDiagnosticFactory(
140+
export const invalidMultiTypeToEmptyPatternOrArrayLiteral = createDiagnosticFactory(
141141
"There must be one or more elements specified here."
142142
);
143143

144-
export const invalidMultiReturnArrayBindingPatternElementInitializer = createDiagnosticFactory(
144+
export const invalidMultiTypeArrayBindingPatternElementInitializer = createDiagnosticFactory(
145145
"This array binding pattern cannot have initializers."
146146
);
147147

148-
export const invalidMultiReturnArrayLiteralElementInitializer = createDiagnosticFactory(
148+
export const invalidMultiTypeArrayLiteralElementInitializer = createDiagnosticFactory(
149149
"This array literal pattern cannot have initializers."
150150
);
151151

src/transformation/visitors/helpers/multi.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { transformArguments } from "../call";
99
import { getDependenciesOfSymbol, createExportedIdentifier } from "../../utils/export";
1010
import { createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast";
1111
import {
12-
invalidMultiReturnArrayBindingPatternElementInitializer,
13-
invalidMultiReturnArrayLiteralElementInitializer,
14-
invalidMultiReturnToEmptyPatternOrArrayLiteral,
15-
invalidMultiReturnToNonArrayBindingPattern,
16-
invalidMultiReturnToNonArrayLiteral,
12+
invalidMultiTypeArrayBindingPatternElementInitializer,
13+
invalidMultiTypeArrayLiteralElementInitializer,
14+
invalidMultiTypeToEmptyPatternOrArrayLiteral,
15+
invalidMultiTypeToNonArrayBindingPattern,
16+
invalidMultiTypeToNonArrayLiteral,
1717
unsupportedMultiFunctionAssignment,
1818
invalidMultiFunctionUse,
1919
} from "../../../transformation/utils/diagnostics";
@@ -71,25 +71,34 @@ export function transformMultiHelperVariableDeclaration(
7171
declaration: ts.VariableDeclaration
7272
): lua.Statement[] | undefined {
7373
if (!declaration.initializer) return;
74-
if (!ts.isCallExpression(declaration.initializer) || !returnsMultiType(context, declaration.initializer)) return;
74+
if (!ts.isCallExpression(declaration.initializer)) return;
75+
if (!returnsMultiType(context, declaration.initializer)) return;
7576

7677
if (!ts.isArrayBindingPattern(declaration.name)) {
77-
context.diagnostics.push(invalidMultiReturnToNonArrayBindingPattern(declaration.name));
78+
context.diagnostics.push(invalidMultiTypeToNonArrayBindingPattern(declaration.name));
7879
return [];
7980
}
8081

8182
if (declaration.name.elements.length < 1) {
82-
context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(declaration.name));
83+
context.diagnostics.push(invalidMultiTypeToEmptyPatternOrArrayLiteral(declaration.name));
84+
return [];
85+
}
86+
87+
if (declaration.name.elements.some(e => ts.isBindingElement(e) && e.initializer)) {
88+
context.diagnostics.push(invalidMultiTypeArrayBindingPatternElementInitializer(declaration.name));
89+
return [];
90+
}
91+
92+
if (isMultiFunction(context, declaration.initializer)) {
93+
context.diagnostics.push(invalidMultiFunctionUse(declaration.initializer));
8394
return [];
8495
}
8596

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

8899
for (const element of declaration.name.elements) {
89100
if (ts.isBindingElement(element)) {
90-
if (element.initializer) {
91-
context.diagnostics.push(invalidMultiReturnArrayBindingPatternElementInitializer(element));
92-
} else if (ts.isIdentifier(element.name)) {
101+
if (ts.isIdentifier(element.name)) {
93102
leftIdentifiers.push(transformIdentifier(context, element.name));
94103
} else {
95104
context.diagnostics.push(unsupportedMultiFunctionAssignment(element));
@@ -113,17 +122,22 @@ export function transformMultiHelperDestructuringAssignmentStatement(
113122
if (!returnsMultiType(context, statement.expression.right)) return;
114123

115124
if (!ts.isArrayLiteralExpression(statement.expression.left)) {
116-
context.diagnostics.push(invalidMultiReturnToNonArrayLiteral(statement.expression.left));
125+
context.diagnostics.push(invalidMultiTypeToNonArrayLiteral(statement.expression.left));
117126
return [];
118127
}
119128

120129
if (statement.expression.left.elements.some(ts.isBinaryExpression)) {
121-
context.diagnostics.push(invalidMultiReturnArrayLiteralElementInitializer(statement.expression.left));
130+
context.diagnostics.push(invalidMultiTypeArrayLiteralElementInitializer(statement.expression.left));
122131
return [];
123132
}
124133

125134
if (statement.expression.left.elements.length < 1) {
126-
context.diagnostics.push(invalidMultiReturnToEmptyPatternOrArrayLiteral(statement.expression.left));
135+
context.diagnostics.push(invalidMultiTypeToEmptyPatternOrArrayLiteral(statement.expression.left));
136+
return [];
137+
}
138+
139+
if (isMultiFunction(context, statement.expression.right)) {
140+
context.diagnostics.push(invalidMultiFunctionUse(statement.expression.right));
127141
return [];
128142
}
129143

test/unit/helpers/__snapshots__/multi.spec.ts.snap

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ exports[`invalid $multi call (({ $multi });): diagnostics 1`] = `"main.ts(2,12):
2525

2626
exports[`invalid $multi call (const [a = 0] = $multi()): code 1`] = `""`;
2727

28-
exports[`invalid $multi call (const [a = 0] = $multi()): diagnostics 1`] = `"main.ts(2,16): error TSTL: This array binding pattern cannot have initializers."`;
28+
exports[`invalid $multi call (const [a = 0] = $multi()): diagnostics 1`] = `"main.ts(2,15): error TSTL: This array binding pattern cannot have initializers."`;
2929

3030
exports[`invalid $multi call (const {} = $multi();): code 1`] = `""`;
3131

@@ -34,3 +34,102 @@ exports[`invalid $multi call (const {} = $multi();): diagnostics 1`] = `"main.ts
3434
exports[`invalid $multi call (const a = $multi();): code 1`] = `""`;
3535

3636
exports[`invalid $multi call (const a = $multi();): diagnostics 1`] = `"main.ts(2,15): error TSTL: Expected an array destructuring pattern."`;
37+
38+
exports[`invalid direct $multi function use (const [a] = $multi()): code 1`] = `
39+
"local ____exports = {}
40+
local function multi(self, ...)
41+
local args = {...}
42+
return table.unpack(args)
43+
end
44+
____exports.a = a
45+
return ____exports"
46+
`;
47+
48+
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."`;
49+
50+
exports[`invalid direct $multi function use (const [a] = $multi(1)): code 1`] = `
51+
"local ____exports = {}
52+
local function multi(self, ...)
53+
local args = {...}
54+
return table.unpack(args)
55+
end
56+
____exports.a = a
57+
return ____exports"
58+
`;
59+
60+
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."`;
61+
62+
exports[`invalid direct $multi function use (const _ = null, [a] = $multi(1)): code 1`] = `
63+
"local ____exports = {}
64+
local function multi(self, ...)
65+
local args = {...}
66+
return table.unpack(args)
67+
end
68+
local _ = nil
69+
____exports.a = a
70+
return ____exports"
71+
`;
72+
73+
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."`;
74+
75+
exports[`invalid direct $multi function use (const ar = [1]; const [a] = $multi(...ar)): code 1`] = `
76+
"local ____exports = {}
77+
local function multi(self, ...)
78+
local args = {...}
79+
return table.unpack(args)
80+
end
81+
local ar = {1}
82+
____exports.a = a
83+
return ____exports"
84+
`;
85+
86+
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."`;
87+
88+
exports[`invalid direct $multi function use (let a; [a] = $multi()): code 1`] = `
89+
"local ____exports = {}
90+
local function multi(self, ...)
91+
local args = {...}
92+
return table.unpack(args)
93+
end
94+
local a
95+
____exports.a = a
96+
return ____exports"
97+
`;
98+
99+
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."`;
100+
101+
exports[`invalid direct $multi function use (let a; for ([a] = $multi(1, 2); false; 1) {}): code 1`] = `
102+
"local ____exports = {}
103+
local function multi(self, ...)
104+
local args = {...}
105+
return table.unpack(args)
106+
end
107+
local a
108+
do
109+
while false do
110+
local ____ = 1
111+
end
112+
end
113+
____exports.a = a
114+
return ____exports"
115+
`;
116+
117+
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."`;
118+
119+
exports[`invalid direct $multi function use (let a; for (const [a] = $multi(1, 2); false; 1) {}): code 1`] = `
120+
"local ____exports = {}
121+
local function multi(self, ...)
122+
local args = {...}
123+
return table.unpack(args)
124+
end
125+
local a
126+
do
127+
while false do
128+
local ____ = 1
129+
end
130+
end
131+
____exports.a = a
132+
return ____exports"
133+
`;
134+
135+
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."`;

test/unit/helpers/multi.spec.ts

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import * as util from "../../util";
33
import * as tstl from "../../../src";
44
import {
55
invalidMultiFunctionUse,
6-
invalidMultiReturnToNonArrayBindingPattern,
7-
invalidMultiReturnArrayBindingPatternElementInitializer,
6+
invalidMultiTypeToNonArrayBindingPattern,
7+
invalidMultiTypeArrayBindingPatternElementInitializer,
88
} from "../../../src/transformation/utils/diagnostics";
99

1010
const multiProjectOptions: tstl.CompilerOptions = {
@@ -23,32 +23,55 @@ test.each<[string, any]>([
2323
.expectToEqual(result);
2424
});
2525

26-
test.each<[string, any]>([
27-
["let a; [a] = $multi()", undefined],
28-
["const [a] = $multi()", undefined],
29-
["const [a] = $multi(1)", 1],
30-
["const ar = [1]; const [a] = $multi(...ar)", 1],
31-
["const _ = null, [a] = $multi(1)", 1],
32-
["let a; for (const [a] = $multi(1, 2); false; 1) {}", undefined],
33-
["let a; for ([a] = $multi(1, 2); false; 1) {}", 1],
34-
])("$multi returning call (%s)", (statement, result) => {
26+
const multiFunction = `
27+
function multi(...args) {
28+
return $multi(...args);
29+
}
30+
`;
31+
32+
const createCasesThatCall = (name: string): Array<[string, any]> => [
33+
[`let a; [a] = ${name}()`, undefined],
34+
[`const [a] = ${name}()`, undefined],
35+
[`const [a] = ${name}(1)`, 1],
36+
[`const ar = [1]; const [a] = ${name}(...ar)`, 1],
37+
[`const _ = null, [a] = ${name}(1)`, 1],
38+
[`let a; for (const [a] = ${name}(1, 2); false; 1) {}`, undefined],
39+
[`let a; for ([a] = ${name}(1, 2); false; 1) {}`, 1],
40+
];
41+
42+
test.each<[string, any]>(createCasesThatCall("$multi"))("invalid direct $multi function use (%s)", statement => {
3543
util.testModule`
44+
${multiFunction}
3645
${statement}
3746
export { a };
3847
`
3948
.setOptions(multiProjectOptions)
4049
.setReturnExport("a")
41-
.expectToEqual(result);
50+
.expectDiagnosticsToMatchSnapshot([invalidMultiFunctionUse.code]);
4251
});
4352

53+
test.each<[string, any]>(createCasesThatCall("multi"))(
54+
"valid indirect $multi function use (%s)",
55+
(statement, result) => {
56+
util.testModule`
57+
${multiFunction}
58+
${statement}
59+
export { a };
60+
`
61+
.setOptions(multiProjectOptions)
62+
.setReturnExport("a")
63+
.expectToEqual(result);
64+
}
65+
);
66+
4467
test.each<[string, number[]]>([
4568
["$multi", [invalidMultiFunctionUse.code]],
4669
["$multi()", [invalidMultiFunctionUse.code]],
4770
["({ $multi });", [invalidMultiFunctionUse.code]],
48-
["const a = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]],
49-
["const {} = $multi();", [invalidMultiReturnToNonArrayBindingPattern.code]],
71+
["const a = $multi();", [invalidMultiTypeToNonArrayBindingPattern.code]],
72+
["const {} = $multi();", [invalidMultiTypeToNonArrayBindingPattern.code]],
5073
["([a] = $multi(1)) => {}", [invalidMultiFunctionUse.code]],
51-
["const [a = 0] = $multi()", [invalidMultiReturnArrayBindingPatternElementInitializer.code]],
74+
["const [a = 0] = $multi()", [invalidMultiTypeArrayBindingPatternElementInitializer.code]],
5275
])("invalid $multi call (%s)", (statement, diagnostics) => {
5376
util.testModule`
5477
${statement}
@@ -59,9 +82,10 @@ test.each<[string, number[]]>([
5982

6083
test("$multi helper call with destructuring assignment side effects", () => {
6184
util.testModule`
85+
${multiFunction}
6286
let a;
6387
export { a };
64-
[a] = $multi(1);
88+
[a] = multi(1);
6589
`
6690
.setOptions(multiProjectOptions)
6791
.setReturnExport("a")

0 commit comments

Comments
 (0)