Skip to content

Commit 5e995d7

Browse files
Extend test coverage for object-literal/interface casts and method dispatch (#259)
* Extend test coverage for object-literal/interface casts and method dispatch Adds four new test cases exercising variations not covered by the recent interface-vtable-cast arc (#256-258), which only ever tested zero-argument, single-method interfaces: - 00object_annotated_method_params.ts: type-literal-annotated object methods with PARAMETERS (that arc's fix only had zero-arg method coverage), including a same-module chained sibling-method call. - 00interface_object_array.ts: several distinct object literals (each its own per-type constant vtable) collected into a single interface-typed array and dispatched through the same call site in a loop - would catch a shared/aliased vtable or wrong-`this` bug that a single-object test can't. - export/import_object_literal_structural_typed_params.ts: extends export/import_object_literal_structural_typed.ts's cross-module coverage along the "zero-arg -> parameterized method" axis. Along the way, found and documented (not fixed) two new, real gaps in docs/interface-vtable-simplification-design.md: - a cross-module cast to a MULTI-method interface breaks for every vtable slot past 0 (wrong value at slot 1, crash at slot 2+) - every prior test in this arc only used single-method interfaces, so this was never exercised before. The committed cross-module test deliberately stays single-method to avoid committing a failing case. - (same-module only, unrelated) a value-returning method can't `return this.siblingMethod(...)` within the same type-literal-annotated object literal - calling the sibling as a bare statement works fine. Avoided in the committed tests. Full ctest suite: 730/730 passed (722 + 8 new). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Update CMake configuration for Visual Studio 18 and adjust ROOT_PATH in typescript.cmake --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 847a070 commit 5e995d7

10 files changed

Lines changed: 190 additions & 4 deletions

docs/how/cmake_vulkan/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ target_link_directories(
5050
${GCLIBPATH}
5151
)
5252

53-
set (LIBS "gcmt-lib")
53+
set (LIBS "gc")
5454

5555
target_link_libraries(
5656
${PROJECT_NAME}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
pushd
22
mkdir "__build/debug"
33
cd "__build/debug"
4-
cmake ../.. -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=Debug -Wno-dev
4+
cmake ../.. -G "Visual Studio 18 2026" -A x64 -DCMAKE_BUILD_TYPE=Debug -Wno-dev
55
cmake --build . --config Debug -j 1
66
popd
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
pushd
22
mkdir "__build/release"
33
cd "__build/release"
4-
cmake ../.. -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=Release -Wno-dev
4+
cmake ../.. -G "Visual Studio 18 2026" -A x64 -DCMAKE_BUILD_TYPE=Release -Wno-dev
55
cmake --build . --config Release -j 1
66
popd

docs/how/cmake_vulkan/typescript.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
set (ROOT_PATH "I:\\tslang\\57")
1+
set (ROOT_PATH "I:\\tslang")
22
set (_3RD_PARTY_PATH "${ROOT_PATH}")
33
set (BUILD_PATH "${ROOT_PATH}")
44
set (TSLANGPATH "${BUILD_PATH}")

tslang/docs/interface-vtable-simplification-design.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,3 +473,60 @@ are size-changing coercions (e.g. si32 -> f64 number) still shifts offsets
473473
relative to already-compiled method bodies expecting the original layout;
474474
that can only bite literals whose inferred field types differ in size from
475475
the interface's, and is out of scope here. 722/722 suite (720 + 2 new).
476+
477+
### Newly found: multi-method cross-module vtable slot bug (2026-07-19)
478+
479+
Found while extending test coverage beyond this arc's fixes - every prior
480+
test/fix in #256-#258 only ever exercised a **single-method** interface
481+
cast cross-module (`Counter {count; inc()}`). Trying a genuinely
482+
multi-method interface (`Accumulator {total; add(n); addTwice(n);
483+
scaled(factor): number}`, canonical vtable order after
484+
`assignCanonicalVirtualIndexes` = methods-first-in-declaration-order then
485+
fields = `add`@0, `addTwice`@1, `scaled`@2, `total`@3) surfaced a clean,
486+
reproducible pattern when casting a cross-module structurally-typed VALUE
487+
to it and calling each method **in isolation** (bisected one at a time via
488+
a temporary `test-runner.cpp` stdout-surfacing patch, same technique as
489+
earlier bugs in this file - reverted before commit):
490+
491+
| method (canonical slot) | isolated result |
492+
|---|---|
493+
| `add(n)` (slot 0) | correct - mutates `total` as expected |
494+
| `addTwice(n)` (slot 1) | WRONG VALUE, no crash - `total` ends up incorrect but the process completes and reports the mismatch cleanly |
495+
| `scaled(factor)` (slot 2) | CRASH - silent, no assert/error text reaches output at all (raw access violation with buffered stdout lost, unlike the controlled assert failures elsewhere in this file) |
496+
497+
Slot 0 works, slot 1 is wrong-but-survives, slot 2 crashes outright -
498+
consistent with SOMETHING going wrong specifically in how slots beyond 0
499+
are constructed or addressed for a cross-module structurally-typed cast
500+
(as opposed to the field-order bug from earlier in this file, which was a
501+
uniform reversal affecting all slots equally and is already fixed). Not
502+
yet root-caused - candidates worth checking first: whether
503+
`getInterfaceCloneFields`/the vtable-patch loop in
504+
`mlirGenCreateInterfaceVTableForObject` (MLIRGenInterfaces.cpp) iterates
505+
methods needing patching in the right order relative to the CANONICAL
506+
`virtualIndex` for interfaces with >1 method (an off-by-one or
507+
wrong-iteration-source bug would explain "slot 0 fine, slot 1+ broken");
508+
or whether the heap-cloned vtable's allocated SIZE is computed from a
509+
stale/undersized type (a 2-slot allocation for a >2-slot vtable would also
510+
match this exact crash-only-past-slot-N shape).
511+
512+
**Not fixed.** The regression test actually added for this session
513+
(`export/import_object_literal_structural_typed_params.ts`) deliberately
514+
stays within the single-method shape that's known to work, to avoid
515+
committing a failing test; it extends coverage only along the
516+
"zero-arg -> parameterized method" axis, not the "single-method ->
517+
multi-method" axis. A genuinely multi-method cross-module test is blocked
518+
on this bug and is the natural next thing to add once it's fixed.
519+
520+
Also worth noting for whoever investigates: a completely SEPARATE,
521+
same-module-only finding surfaced while building the initial (broken)
522+
version of this test - a value-returning method cannot `return
523+
this.siblingMethod(...)` (using a sibling call's return value directly in
524+
a `return` statement) within the same type-literal-annotated object
525+
literal; calling the sibling as a bare statement (discarding its return
526+
value) works fine. Confirmed same-module, unrelated to cross-module
527+
casting at all - likely a self-referential type-inference ordering gap
528+
(the caller's return type depends on resolving the callee's return type,
529+
which depends on `this`, which is still being constructed). Not
530+
investigated further; avoided in the committed tests
531+
(`00object_annotated_method_params.ts` uses `setBase`/re-`scale`, not a
532+
chained-return pattern).

tslang/test/tester/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ add_test(NAME test-compile-00-interface-optional-cast-order COMMAND test-runner
288288
add_test(NAME test-compile-00-interface-function-typed-field COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_function_typed_field.ts")
289289
add_test(NAME test-compile-00-interface-captures COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_captures.ts")
290290
add_test(NAME test-compile-00-object-annotated-method COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method.ts")
291+
add_test(NAME test-compile-00-object-annotated-method-params COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_params.ts")
292+
add_test(NAME test-compile-00-interface-object-array COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_object_array.ts")
291293
add_test(NAME test-compile-00-interface-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts")
292294
add_test(NAME test-compile-00-interface-new COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts")
293295
add_test(NAME test-compile-00-interface-indexer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_indexer.ts")
@@ -642,6 +644,8 @@ add_test(NAME test-jit-00-interface-optional-cast-order COMMAND test-runner -jit
642644
add_test(NAME test-jit-00-interface-function-typed-field COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_function_typed_field.ts")
643645
add_test(NAME test-jit-00-interface-captures COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_captures.ts")
644646
add_test(NAME test-jit-00-object-annotated-method COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method.ts")
647+
add_test(NAME test-jit-00-object-annotated-method-params COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_params.ts")
648+
add_test(NAME test-jit-00-interface-object-array COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_object_array.ts")
645649
add_test(NAME test-jit-00-interface-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts")
646650
add_test(NAME test-jit-00-interface-new COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts")
647651
add_test(NAME test-jit-00-interface-indexer COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_indexer.ts")
@@ -850,6 +854,7 @@ add_test(NAME test-compile-shared-export-import-class-interface COMMAND test-run
850854
add_test(NAME test-compile-shared-export-import-object-literal-with-class-types COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_with_class_types.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_with_class_types.ts")
851855
add_test(NAME test-compile-shared-export-import-object-literal-with-interface COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_with_interface.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_with_interface.ts")
852856
add_test(NAME test-compile-shared-export-import-object-literal-structural-typed COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed.ts")
857+
add_test(NAME test-compile-shared-export-import-object-literal-structural-typed-params COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_params.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_params.ts")
853858
add_test(NAME test-compile-shared-export-import-vars COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_vars.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_vars.ts")
854859
add_test(NAME test-compile-shared-export-import-vars-2 COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_vars2.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_vars2.ts")
855860
add_test(NAME test-compile-shared-export-import-enum COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_enum.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_enum.ts")
@@ -866,6 +871,7 @@ add_test(NAME test-jit-shared-export-import-class-interface COMMAND test-runner
866871
add_test(NAME test-jit-shared-export-import-object-literal-with-class-types COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_with_class_types.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_with_class_types.ts")
867872
add_test(NAME test-jit-shared-export-import-object-literal-with-interface COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_with_interface.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_with_interface.ts")
868873
add_test(NAME test-jit-shared-export-import-object-literal-structural-typed COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed.ts")
874+
add_test(NAME test-jit-shared-export-import-object-literal-structural-typed-params COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_params.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_params.ts")
869875
add_test(NAME test-jit-shared-export-import-vars COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_vars.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_vars.ts")
870876
add_test(NAME test-jit-shared-export-import-vars-2 COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_vars2.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_vars2.ts")
871877
add_test(NAME test-jit-shared-export-import-enum COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_enum.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_enum.ts")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Array-of-interface coverage: several DISTINCT object literals (each its own
2+
// location-hashed storage type and its own lifted method, per
3+
// docs/interface-vtable-simplification-design.md section 3) collected into a
4+
// single Shape[]-typed array and dispatched through the SAME interface at a
5+
// single call site in a loop. Exercises that each element's own vtable
6+
// (constant, per-type) is independently correct - a bug here would typically
7+
// show up as every element calling the FIRST element's method (a shared/
8+
// aliased vtable) or a wrong `this` binding once mixed in a homogeneous
9+
// array.
10+
11+
interface Shape {
12+
area(): number;
13+
}
14+
15+
function main() {
16+
const square = {
17+
side: 4.0,
18+
area() { return this.side * this.side; },
19+
};
20+
21+
const rectangle = {
22+
width: 3.0,
23+
height: 5.0,
24+
area() { return this.width * this.height; },
25+
};
26+
27+
const circleLike = {
28+
radius: 2.0,
29+
area() { return this.radius * this.radius * 3.0; },
30+
};
31+
32+
let shapes: Shape[] = [<Shape>square, <Shape>rectangle, <Shape>circleLike];
33+
34+
let total = 0.0;
35+
for (let i = 0; i < shapes.length; i++) {
36+
total = total + shapes[i].area();
37+
}
38+
39+
print(total);
40+
assert(square.area() == 16.0);
41+
assert(rectangle.area() == 15.0);
42+
assert(circleLike.area() == 12.0);
43+
assert(total == 43.0);
44+
45+
print("done.");
46+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Extends 00object_annotated_method.ts's coverage: that test only exercised
2+
// zero-argument methods (inc(): void, twice(): number). Here the type-literal
3+
// method members take parameters and one method calls ANOTHER method on
4+
// `this` (chained dispatch through the same implicit-this-param mechanism
5+
// fixed for MethodSignature tuple members).
6+
7+
function main() {
8+
let acc: { total: number; add(n: number): void; addTwice(n: number): void } = {
9+
total: 0,
10+
add(n: number) { this.total = this.total + n; },
11+
addTwice(n: number) { this.add(n); this.add(n); },
12+
};
13+
14+
acc.add(3);
15+
assert(acc.total == 3);
16+
17+
acc.addTwice(4);
18+
assert(acc.total == 11);
19+
print(acc.total);
20+
21+
let calc: { base: number; scale(factor: number): number; setBase(value: number): void } = {
22+
base: 5,
23+
scale(factor: number) { return this.base * factor; },
24+
setBase(value: number) { this.base = value; },
25+
};
26+
27+
const scaled = calc.scale(3);
28+
assert(scaled == 15);
29+
print(scaled);
30+
31+
calc.setBase(10);
32+
const rescaled = calc.scale(3);
33+
assert(rescaled == 30);
34+
print(rescaled);
35+
36+
print("done.");
37+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace A {
2+
3+
export interface Accumulator {
4+
total: number;
5+
add(n: number): void;
6+
}
7+
8+
// structurally-typed (not interface-typed) export, like
9+
// export_object_literal_structural_typed.ts, but extends that test's
10+
// coverage: the method takes a PARAMETER (that test's inc() took none).
11+
//
12+
// NOTE: deliberately kept to ONE method. A multi-method version of this
13+
// (total; add(n); addTwice(n); scaled(factor): number) was tried and
14+
// found broken cross-module for any method beyond vtable slot 0 - see
15+
// docs/interface-vtable-simplification-design.md's "multi-method
16+
// cross-module vtable slot bug" section. That's a distinct, deeper,
17+
// not-yet-fixed bug; this test intentionally stays within the
18+
// currently-working single-method shape.
19+
export var acc: { total: number; add(n: number): void } = {
20+
total: 0.0,
21+
add(n: number) { this.total = this.total + n; },
22+
};
23+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import './export_object_literal_structural_typed_params'
2+
3+
// Casts the imported structurally-typed VALUE to a single-method interface in
4+
// the importer, exercising a PARAMETERIZED (not zero-arg) method - extends
5+
// export/import_object_literal_structural_typed.ts's coverage (that test's
6+
// inc() took no arguments).
7+
var acc: A.Accumulator = <A.Accumulator>A.acc;
8+
9+
acc.add(3);
10+
print(acc.total);
11+
assert(acc.total == 3);
12+
13+
acc.add(4);
14+
print(acc.total);
15+
assert(acc.total == 7);
16+
17+
print("done.");

0 commit comments

Comments
 (0)