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
20 changes: 18 additions & 2 deletions tslang/lib/TypeScript/MLIRGenCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,20 @@ namespace mlirgen
return castInterfaceToInterface(location, value, srcInterfaceType, interfaceType, genContext);
}

// `J | null` or `C | null` (a `J | undefined` given to `I | undefined` gets here too) is stored
// as its one member, null a null pointer in it: this is the cast of that member. A plain cast
// kept J's vtable for I, and lowering could not make a class pointer an interface.
if (auto srcUnionType = dyn_cast<mlir_ts::UnionType>(valueType))
{
mlir::Type srcBaseType;
if (!mth.isUnionTypeNeedsTag(location, srcUnionType, srcBaseType)
&& isa_and_nonnull<mlir_ts::ClassType, mlir_ts::InterfaceType>(srcBaseType))
{
auto member = builder.create<mlir_ts::GetValueFromUnionOp>(location, srcBaseType, value);
return cast(location, interfaceType, member, genContext);
}
}

// A number, string, boolean or array has no fields an interface's members could be read
// from: passed for an `{ kind: number }` it compiled, without a word, to a cast that
// lowering could not make, and the callee read garbage. An interface without members
Expand Down Expand Up @@ -1692,11 +1706,13 @@ namespace mlirgen

// `C | null` given to `I | null`: both are stored as their one member (null is a null pointer
// in it), so this is the class-to-interface cast of `C` to `I`. A plain cast read the class
// pointer as an interface.
// pointer as an interface. `J | null` given to `I | null` is likewise the cast of `J` to `I`,
// which gives an `I` that `J` does not extend a vtable of its own (castInterfaceToInterface).
if (auto srcUnionType = dyn_cast<mlir_ts::UnionType>(valueType); srcUnionType && isa<mlir_ts::InterfaceType>(baseType))
{
mlir::Type srcBaseType;
if (mth.isUnionTypeNeedsTag(location, srcUnionType, srcBaseType) || !isa_and_nonnull<mlir_ts::ClassType>(srcBaseType))
if (mth.isUnionTypeNeedsTag(location, srcUnionType, srcBaseType)
|| !isa_and_nonnull<mlir_ts::ClassType, mlir_ts::InterfaceType>(srcBaseType))
{
return std::nullopt;
}
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 @@ -401,6 +401,7 @@ tslang_add_test(NAME test-compile-00-string-view-copy COMMAND test-runner "${PRO
tslang_add_test(NAME test-compile-00-global-copy-counted COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_copy_counted.ts")
tslang_add_test(NAME test-compile-00-global-interface-initializer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_interface_initializer.ts")
tslang_add_test(NAME test-compile-00-interface-cast-unrelated COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_cast_unrelated.ts")
tslang_add_test(NAME test-compile-00-interface-union-cast-unrelated COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_union_cast_unrelated.ts")
tslang_add_test(NAME test-compile-00-c-string-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00c_string_copy.ts")
tslang_add_test(NAME test-compile-00-const-field-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_field_read.ts")
tslang_add_test(NAME test-compile-00-const-element-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_element_read.ts")
Expand Down Expand Up @@ -996,6 +997,7 @@ tslang_add_test(NAME test-jit-00-string-view-copy COMMAND test-runner -jit "${PR
tslang_add_test(NAME test-jit-00-global-copy-counted COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_copy_counted.ts")
tslang_add_test(NAME test-jit-00-global-interface-initializer COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_interface_initializer.ts")
tslang_add_test(NAME test-jit-00-interface-cast-unrelated COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_cast_unrelated.ts")
tslang_add_test(NAME test-jit-00-interface-union-cast-unrelated COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_union_cast_unrelated.ts")
tslang_add_test(NAME test-jit-00-c-string-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00c_string_copy.ts")
tslang_add_test(NAME test-jit-00-const-field-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_field_read.ts")
tslang_add_test(NAME test-jit-00-const-element-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_element_read.ts")
Expand Down Expand Up @@ -1981,6 +1983,7 @@ set(TSLANG_CORPUS
00global_copy_counted.ts
00global_interface_initializer.ts
00interface_cast_unrelated.ts
00interface_union_cast_unrelated.ts
00c_string_copy.ts
00const_field_read.ts
00const_element_read.ts
Expand Down
97 changes: 97 additions & 0 deletions tslang/test/tester/tests/00interface_union_cast_unrelated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// `J | null` given to `I | null`, where J does not extend I, was a plain cast of the union: the value kept
// J's vtable and I read it by its own slot numbers (00interface_cast_unrelated.ts has the cast of J to I
// alone). `C | null` given to an interface (through `I | undefined`) was a class pointer read as an
// interface. Both are the cast of the union's one member now; null stays null.
interface Hit {
thing: number;
ray: number;
dist: number;
}

interface HasDist {
dist: number;
}

interface A {
f(): number;
g(): number;
}

interface B {
g(): number;
}

class AC implements A {
base: number = 100;
f(): number {
return this.base + 1;
}
g(): number {
return this.base + 2;
}
}

class HitC implements Hit {
thing: number = 10;
ray: number = 20;
dist: number = 30;
}

function pick(flag: boolean): Hit | null {
if (flag) {
const h: Hit = { thing: 1, ray: 2, dist: 3 };
return h;
}

return null;
}

function pickC(flag: boolean): HitC | null {
if (flag) {
return new HitC();
}

return null;
}

function distOrMinus(d: HasDist | null) {
return d ? d.dist : -1;
}

function main() {
// `Hit | null` to `HasDist | null`
const h = pick(true);
const d: HasDist | null = h;
assert(d !== null && d.dist == 3, "a field through J | null");
assert(distOrMinus(h) == 3, "a field through J | null as an argument");

const n = pick(false);
const dn: HasDist | null = n;
assert(dn === null, "null stays null");
assert(distOrMinus(n) == -1, "null as an argument");

// methods, `A | null` to `B | null`
const a: A | null = new AC();
const b: B | null = a;
assert(b !== null && b.g() == 102, "a method through J | null");

// to `I | undefined`, from an interface and from a class
const u: HasDist | undefined = h;
assert(u !== undefined && u.dist == 3, "an interface through I | undefined");
const c: HasDist | undefined = pickC(true);
assert(c !== undefined && c.dist == 30, "a class through I | undefined");

// `<I>` of `J | null`
const cast = <HasDist>h;
assert(cast.dist == 3, "<I> of J | null");

let sum: number = 0;
for (let i = 0; i < 100; i++) {
const each: HasDist | null = pick(i % 2 == 0);
sum += distOrMinus(each);
}

assert(sum == 50 * 3 - 50, "casts in a loop");

print("done.");
}
Loading