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
36 changes: 35 additions & 1 deletion tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,35 @@ class CastLogicHelper
bool needTag = mth.isUnionTypeNeedsTag(loc, resUnionType, baseType);
if (needTag)
{
auto typeOfValue = rewriter.create<mlir_ts::TypeOfOp>(loc, mlir_ts::StringType::get(rewriter.getContext()), in);
auto stringType = mlir_ts::StringType::get(rewriter.getContext());
mlir::Value typeOfValue = rewriter.create<mlir_ts::TypeOfOp>(loc, stringType, in);

// A class, string, array or object value that holds null is null: where the union
// has a null member of its own, that member is what it becomes. Tagged as its type
// instead, a null `Handle | null` widened to `boolean | Handle | null` was a class
// holding a null pointer, which `=== null` did not see and `instanceof` read through.
auto storageTypes = mlir::cast<mlir_ts::UnionType>(baseType).getTypes();
if (isPointerNullableType(inType)
&& llvm::any_of(storageTypes, [](mlir::Type type) { return isa<mlir_ts::NullType>(type); }))
{
auto llvmStringType = tch.convertType(stringType);
auto nullValue = rewriter.create<mlir_ts::NullOp>(loc, mlir_ts::NullType::get(rewriter.getContext()));
auto nullTag = rewriter.create<mlir_ts::TypeOfOp>(loc, stringType, nullValue);

mlir::Value pointer = in;
if (!isa<LLVM::LLVMPointerType>(pointer.getType()))
{
pointer = rewriter.create<mlir_ts::DialectCastOp>(loc, th.getPtrType(), in);
}

auto isNull = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, pointer,
rewriter.create<LLVM::ZeroOp>(loc, th.getPtrType()));
auto tag = rewriter.create<LLVM::SelectOp>(
loc, isNull, rewriter.create<mlir_ts::DialectCastOp>(loc, llvmStringType, nullTag),
rewriter.create<mlir_ts::DialectCastOp>(loc, llvmStringType, typeOfValue));
typeOfValue = rewriter.create<mlir_ts::DialectCastOp>(loc, stringType, tag);
}

auto unionValue = rewriter.create<mlir_ts::CreateUnionInstanceOp>(loc, resUnionType, in, typeOfValue);
return unionValue;
}
Expand Down Expand Up @@ -680,6 +708,12 @@ class CastLogicHelper
return mlir::Value();
}

// A type whose value is a single pointer that is null when it holds null.
static bool isPointerNullableType(mlir::Type type)
{
return isa<mlir_ts::ClassType, mlir_ts::StringType, mlir_ts::ArrayType, mlir_ts::ObjectType, mlir_ts::OpaqueType>(type);
}

bool isBool(mlir::Type type)
{
return type.isInteger(1);
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 @@ -409,6 +409,7 @@ tslang_add_test(NAME test-compile-00-union-to-any COMMAND test-runner "${PROJECT
tslang_add_test(NAME test-compile-00-union-errors COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_errors.ts")
tslang_add_test(NAME test-compile-00-union-null-to-string COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_null_to_string.ts")
tslang_add_test(NAME test-compile-00-union-null-truthiness COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_null_truthiness.ts")
tslang_add_test(NAME test-compile-00-null-into-tagged-union COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00null_into_tagged_union.ts")
tslang_add_test(NAME test-compile-00-union-null-undefined COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_null_undefined.ts")
tslang_add_test(NAME test-compile-00-union-null-undefined-nonstrict COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_null_undefined_nonstrict.ts")
tslang_add_test(NAME test-compile-00-optional-number-compare COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00optional_number_compare.ts")
Expand Down Expand Up @@ -936,6 +937,7 @@ tslang_add_test(NAME test-jit-00-union-to-any COMMAND test-runner -jit "${PROJEC
tslang_add_test(NAME test-jit-00-union-errors COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_errors.ts")
tslang_add_test(NAME test-jit-00-union-null-to-string COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_null_to_string.ts")
tslang_add_test(NAME test-jit-00-union-null-truthiness COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_null_truthiness.ts")
tslang_add_test(NAME test-jit-00-null-into-tagged-union COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00null_into_tagged_union.ts")
tslang_add_test(NAME test-jit-00-union-null-undefined COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_null_undefined.ts")
tslang_add_test(NAME test-jit-00-union-null-undefined-nonstrict COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_null_undefined_nonstrict.ts")
tslang_add_test(NAME test-jit-00-optional-number-compare COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00optional_number_compare.ts")
Expand Down Expand Up @@ -2052,6 +2054,7 @@ set(TSLANG_CORPUS
00union_errors.ts
00union_null_to_string.ts
00union_null_truthiness.ts
00null_into_tagged_union.ts
00union_ops.ts
00union_to_any.ts
00union_type.ts
Expand Down
62 changes: 62 additions & 0 deletions tslang/test/tester/tests/00null_into_tagged_union.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// A class, string or array value holding null that is widened to a union with a null member of its
// own becomes that null member. It was tagged as its type instead - a class holding a null pointer -
// so `=== null` missed it, `typeof` said "class", and a truth test read its vtable through the null.

class A {
a = 1;
}

class B {
b = 2;
}

function widenClass(h: A | null): boolean | A | B | null {
return h;
}

function widenString(s: string | null): string | number | null {
return s;
}

function widenArray(v: number[] | null): number[] | boolean | null {
return v;
}

function classes() {
const n = widenClass(null);
assert(n === null, "null A === null");
assert(!(n !== null), "null A !== null");
assert(typeof n == "null", "typeof null A");
assert(!n, "null A is falsy");
assert((n ?? true) === true, "null A ?? true");

const v = widenClass(new A());
assert(v !== null, "A !== null");
assert(typeof v == "class", "typeof A");
assert(!!v, "A is truthy");
}

function strings() {
const n = widenString(null);
assert(n === null, "null string === null");
assert(typeof n == "null", "typeof null string");

const s = widenString("x");
assert(s !== null, "string !== null");
assert(typeof s == "string", "typeof string");
}

function arrays() {
const n = widenArray(null);
assert(n === null, "null array === null");

const v = widenArray([1, 2]);
assert(v !== null, "array !== null");
}

function main() {
classes();
strings();
arrays();
print("done.");
}
Loading