Skip to content

Commit bd649ef

Browse files
Add dedicated test coverage for root-level code with no function main() (#294)
* Add tests for plain function and type alias cross-module exports/imports * Add dedicated test coverage for root-level code with no function main() When a file has no explicit main(), generateGlobalEntryCode (MLIRGenModule.cpp) gathers every statement not inside a function/class body and compiles it AS the synthesized main() itself. ~54 existing test files incidentally rely on this (root-level assert/print calls with no main wrapper), but they're all short/simple - none deliberately exercised control flow at true root scope. 44toplevelcode.ts has top-level declarations but still wraps its actual test logic in an explicit main(). Also notable: a `let`/`const` declared directly at root level is module-scope storage (like a static field), not a stack local - a real difference from the normal case worth covering, especially for closures capturing it. Added: - 00toplevel_control_flow.ts: for/while/switch/try-catch-finally running directly at root scope, a closure capturing a root-level `let`, and a class defined and used at root (instantiated, method called). - 00toplevel_no_main_with_helpers.ts: root-level statements coexisting with ordinary function/class declarations, calling into them. Registered in both compile and jit tiers (4 new add_test entries), next to the existing 44toplevelcode.ts registration. Investigation note: hit what looked like a real bug along the way - JIT crashed with an uncaught C++ exception on the try/catch block, AOT produced no output at all. Root cause was in the test, not the compiler: `throw 42;` throws an integer literal, but `catch (v: number)` expects `number` (f64), and catch-by-type requires an exact type match - the exception propagated uncaught. Fixed to `throw 42.0;`, matching the established idiom in 00try_catch.ts's main2(), and confirmed passing in both tiers before adding. 827/827 tests green (823 + these 4 new entries). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 30efabc commit bd649ef

3 files changed

Lines changed: 111 additions & 0 deletions

File tree

tslang/test/tester/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,13 @@ add_test(NAME test-compile-41-anonymoustypes COMMAND test-runner "${PROJECT_SOUR
425425
add_test(NAME test-compile-42-lambdaproperties COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/42lambdaproperties.ts")
426426
add_test(NAME test-compile-43-nestednamespace COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/43nestednamespace.ts")
427427
add_test(NAME test-compile-44-toplevelcode COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/44toplevelcode.ts")
428+
# 44toplevelcode.ts has top-level declarations but wraps its actual test logic in an
429+
# explicit main() - these two exercise executable statements running directly at root
430+
# scope with NO main() anywhere (generateGlobalEntryCode compiles the root statements
431+
# AS the synthesized `main` itself): control flow (for/while/switch/try-catch-finally)
432+
# plus a closure capturing a root-level `let` (module-scope storage, not a stack local).
433+
add_test(NAME test-compile-00-toplevel-control-flow COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00toplevel_control_flow.ts")
434+
add_test(NAME test-compile-00-toplevel-no-main-with-helpers COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00toplevel_no_main_with_helpers.ts")
428435
add_test(NAME test-compile-45-enumtostring COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/45enumtostring.ts")
429436
add_test(NAME test-compile-48-instanceof COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/48instanceof.ts")
430437
add_test(NAME test-compile-51-exceptions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/51exceptions.ts")
@@ -793,6 +800,8 @@ add_test(NAME test-jit-41-anonymoustypes COMMAND test-runner -jit "${PROJECT_SOU
793800
add_test(NAME test-jit-42-lambdaproperties COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/42lambdaproperties.ts")
794801
add_test(NAME test-jit-43-nestednamespace COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/43nestednamespace.ts")
795802
add_test(NAME test-jit-44-toplevelcode COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/44toplevelcode.ts")
803+
add_test(NAME test-jit-00-toplevel-control-flow COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00toplevel_control_flow.ts")
804+
add_test(NAME test-jit-00-toplevel-no-main-with-helpers COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00toplevel_no_main_with_helpers.ts")
796805
add_test(NAME test-jit-45-enumtostring COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/45enumtostring.ts")
797806
add_test(NAME test-jit-48-instanceof COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/48instanceof.ts")
798807
add_test(NAME test-jit-51-exceptions COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/51exceptions.ts")
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Root-level executable code with NO `function main()` anywhere in the file.
2+
// When there's no explicit `main`, generateGlobalEntryCode (MLIRGenModule.cpp)
3+
// gathers every statement that isn't inside a function/class body and compiles
4+
// it AS the synthesized `main` function itself - so this exercises the same
5+
// control-flow/closure/exception codegen every other test exercises inside an
6+
// explicit main(), just routed through the auto-synthesis path instead.
7+
// Also: a `let`/`const` declared directly at this level is module-scope
8+
// storage (like a static field), not a stack local - so the closure test
9+
// below captures a GLOBAL, not a stack frame slot.
10+
11+
let sum = 0;
12+
for (let i = 0; i < 5; i++) {
13+
if (i == 2) continue;
14+
if (i == 4) break;
15+
sum = sum + i;
16+
}
17+
assert(sum == 4, "for-if-continue-break");
18+
19+
let count = 0;
20+
while (count < 3) {
21+
count = count + 1;
22+
}
23+
assert(count == 3, "while");
24+
25+
let label = "";
26+
switch (count) {
27+
case 1:
28+
label = "one";
29+
break;
30+
case 3:
31+
label = "three";
32+
break;
33+
default:
34+
label = "other";
35+
break;
36+
}
37+
assert(label == "three", "switch");
38+
39+
let caught = false;
40+
try {
41+
throw 42.0;
42+
} catch (v: number) {
43+
caught = v == 42;
44+
} finally {
45+
sum = sum + 100;
46+
}
47+
assert(caught, "try-catch");
48+
assert(sum == 104, "finally-ran");
49+
50+
let counter = 0;
51+
const increment = () => {
52+
counter = counter + 1;
53+
};
54+
increment();
55+
increment();
56+
assert(counter == 2, "closure-capture-root-let");
57+
58+
class Counter {
59+
value: number = 0;
60+
increment(): void {
61+
this.value = this.value + 1;
62+
}
63+
}
64+
65+
const c = new Counter();
66+
c.increment();
67+
c.increment();
68+
c.increment();
69+
assert(c.value == 3, "class-at-root");
70+
71+
print("done.");
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Root-level executable code coexisting with ordinary function/class
2+
// declarations, still with no explicit `function main()` anywhere - only the
3+
// root-level STATEMENTS (not the helper declarations) become the synthesized
4+
// `main` body (generateGlobalEntryCode in MLIRGenModule.cpp); the helpers stay
5+
// ordinary top-level declarations, callable from that synthesized body exactly
6+
// like they'd be callable from a hand-written main().
7+
8+
function double(x: number): number {
9+
return x * 2;
10+
}
11+
12+
class Box {
13+
value: number;
14+
constructor(v: number) {
15+
this.value = v;
16+
}
17+
get(): number {
18+
return this.value;
19+
}
20+
}
21+
22+
const results: number[] = [];
23+
for (let i = 1; i <= 3; i++) {
24+
results.push(double(i));
25+
}
26+
assert(results[0] == 2 && results[1] == 4 && results[2] == 6, "helpers-from-root");
27+
28+
const box = new Box(21);
29+
assert(double(box.get()) == 42, "class-and-function-mixed");
30+
31+
print("done.");

0 commit comments

Comments
 (0)