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
5 changes: 4 additions & 1 deletion tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,10 @@ class MLIRGenImpl

if (genContext->usingVars != nullptr)
{
for (auto vi : *genContext->usingVars)
// reverse declaration order, as JS disposes a scope (and as mlirGenReleaseOwned releases
// one): a later `using` may still need an earlier one while its `[Symbol.dispose]()` runs.
// The unwind cleanup region comes through here too, so it unwinds in the same order.
for (auto vi : llvm::reverse(*genContext->usingVars))
{
auto varInTable = symbolTable.lookup(vi->getName());
if (!varInTable.first)
Expand Down
3 changes: 3 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ tslang_add_test(NAME test-compile-00-break-continue COMMAND test-runner "${PROJE
tslang_add_test(NAME test-compile-00-label-chain-loop COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00label_chain_loop.ts")
tslang_add_test(NAME test-compile-00-labelled-scope-exit COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00labelled_scope_exit.ts")
tslang_add_test(NAME test-compile-00-switch-case-scope-exit COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_case_scope_exit.ts")
tslang_add_test(NAME test-compile-00-using-dispose-order COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_dispose_order.ts")
tslang_add_test(NAME test-compile-00-vars COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00vars.ts")
tslang_add_test(NAME test-compile-00-var-bindings COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00var_bindings.ts")
tslang_add_test(NAME test-compile-00-globals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00globals.ts")
Expand Down Expand Up @@ -876,6 +877,7 @@ tslang_add_test(NAME test-jit-00-break-continue COMMAND test-runner -jit "${PROJ
tslang_add_test(NAME test-jit-00-label-chain-loop COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00label_chain_loop.ts")
tslang_add_test(NAME test-jit-00-labelled-scope-exit COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00labelled_scope_exit.ts")
tslang_add_test(NAME test-jit-00-switch-case-scope-exit COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_case_scope_exit.ts")
tslang_add_test(NAME test-jit-00-using-dispose-order COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_dispose_order.ts")
tslang_add_test(NAME test-jit-00-vars COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00vars.ts")
tslang_add_test(NAME test-jit-00-var-bindings COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00var_bindings.ts")
tslang_add_test(NAME test-jit-00-globals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00globals.ts")
Expand Down Expand Up @@ -1828,6 +1830,7 @@ set(TSLANG_CORPUS
00label_chain_loop.ts
00labelled_scope_exit.ts
00switch_case_scope_exit.ts
00using_dispose_order.ts
00capture_in_new_arguments.ts
00cast_to_f32.ts
00catch_untyped_value.ts
Expand Down
18 changes: 18 additions & 0 deletions tslang/test/tester/tests/00switch_case_scope_exit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ function fallThrough(i: number) {
trail += "after;";
}

// the cases share the switch's scope, so a `using` in each of two cases are two `using`s in one
// scope: disposed in reverse declaration order, the one the path skipped not at all
function usingInTwoCases(i: number) {
switch (i) {
case 0:
using a = new Res("a");
trail += "body0;";
case 1:
using b = new Res("b");
trail += "body1;";
break;
}

trail += "after;";
}

function bracedCases(i: number) {
switch (i) {
case 0: {
Expand Down Expand Up @@ -178,6 +194,8 @@ function main() {
check(() => caseWithBreak(5), "after;", "no case matched");
check(() => fallThrough(0), "body0;body1;a;after;", "falling through");
check(() => fallThrough(7), "default;after;", "default");
check(() => usingInTwoCases(0), "body0;body1;b;a;after;", "a using in each of two cases");
check(() => usingInTwoCases(1), "body1;b;after;", "a using in each of two cases, first skipped");
check(() => bracedCases(0), "body0;a;body1;b;after;", "braced cases falling through");
check(() => breakFromIf(0), "a;after;fn;", "break from an if in a case");
check(() => breakFromIf(1), "body1;after;fn;", "break from an if, case not taken");
Expand Down
141 changes: 141 additions & 0 deletions tslang/test/tester/tests/00using_dispose_order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// The `using`s of one scope are disposed in reverse declaration order, as JS disposes them: the
// last one declared goes first, so a later resource may still use an earlier one while it is
// disposed. It used to be declaration order - `{ using a; using b; }` disposed `a` then `b`.
//
// The unwind cleanup region is generated from the same list, so a throw disposes in the same
// order as falling off the end, a `return`, a `break` or a `continue`.

let trail = "";

class Res {
constructor(public name: string) {}

[Symbol.dispose]() {
trail += this.name + ";";
}
}

function plainBlock() {
{
using a = new Res("a");
using b = new Res("b");
using c = new Res("c");
}

trail += "after;";
}

function functionBody() {
using a = new Res("a");
using b = new Res("b");
trail += "body;";
}

function earlyReturn(f: boolean) {
using a = new Res("a");
{
using b = new Res("b");
using c = new Res("c");
if (f) {
return;
}
}

trail += "after;";
}

function throwsInBlock() {
{
using a = new Res("a");
using b = new Res("b");
using c = new Res("c");
throw 1;
}
}

function throwsInTryBody() {
try {
using a = new Res("a");
using b = new Res("b");
throw 1;
}
catch (e: TypeOf<1>) {
trail += "caught;";
}
}

class Throws {
constructor(public name: string) {}

[Symbol.dispose]() {
trail += this.name + ";";
throw 1;
}
}

// `b` is disposed first and throws; `a` is still disposed, by the cleanup the throw unwinds to
function throwingDispose() {
try {
using a = new Res("a");
using b = new Throws("b");
trail += "body;";
}
catch (e: TypeOf<1>) {
trail += "caught;";
}
}

function loopBody() {
for (let i = 0; i < 2; i++) {
using a = new Res("a" + i);
using b = new Res("b" + i);
if (i == 0) {
continue;
}

break;
}
}

function switchCase(n: number) {
switch (n) {
case 1:
using a = new Res("a");
using b = new Res("b");
break;
}

trail += "after;";
}

function expectThrow(f: () => void) {
try {
f();
}
catch (e: TypeOf<1>) {
return true;
}

return false;
}

function check(f: () => void, expected: string, what: string) {
trail = "";
f();
print(what + ": " + trail);
assert(trail == expected, what);
}

function main() {
check(plainBlock, "c;b;a;after;", "plain block");
check(functionBody, "body;b;a;", "function body");
check(() => earlyReturn(true), "c;b;a;", "return out of a nested block");
check(() => earlyReturn(false), "c;b;after;a;", "falling off a nested block");
check(() => assert(expectThrow(throwsInBlock), "must throw"), "c;b;a;", "throw out of a block");
check(throwsInTryBody, "b;a;caught;", "throw out of a try body");
check(throwingDispose, "body;b;a;caught;", "a dispose that throws");
check(loopBody, "b0;a0;b1;a1;", "continue and break out of a loop body");
check(() => switchCase(1), "b;a;after;", "break out of a switch case");

print("done.");
}
Loading