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
34 changes: 33 additions & 1 deletion tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,11 @@ class CastLogicHelper

if (auto boolType = dyn_cast<mlir_ts::BooleanType>(resType))
{
if (isa<mlir_ts::StringType>(inType))
{
return castStringToBool(in);
}

if (auto tupleType = dyn_cast<mlir_ts::TupleType>(inType))
{
auto llvmBoolType = tch.convertType(boolType);
Expand Down Expand Up @@ -456,8 +461,19 @@ class CastLogicHelper
{
// TODO: use cond switch
auto hasValue = rewriter.create<mlir_ts::HasValueOp>(loc, boolType, in);
auto val = rewriter.create<mlir_ts::ValueOp>(loc, optType.getElementType(), in);
mlir::Value val = rewriter.create<mlir_ts::ValueOp>(loc, optType.getElementType(), in);
auto llvmBoolType = tch.convertType(boolType);
if (isa<mlir_ts::StringType>(optType.getElementType()))
{
// a string is tested by reading its first character, and the value of an undefined
// optional is no pointer at all: test null instead
auto llvmPtrType = tch.convertType(optType.getElementType());
mlir::Value hasValueAsLLVMType = rewriter.create<mlir_ts::DialectCastOp>(loc, llvmBoolType, hasValue);
mlir::Value valAsLLVMType = rewriter.create<mlir_ts::DialectCastOp>(loc, llvmPtrType, val);
mlir::Value nullPtr = rewriter.create<LLVM::ZeroOp>(loc, llvmPtrType);
return castStringToBool(rewriter.create<LLVM::SelectOp>(loc, hasValueAsLLVMType, valAsLLVMType, nullPtr));
}

auto valAsBool = cast(val, val.getType(), tch.convertType(val.getType()), boolType, llvmBoolType);
if (valAsBool)
{
Expand Down Expand Up @@ -1001,6 +1017,22 @@ class CastLogicHelper
ch.getOrCreateGlobalString("__false__", std::string("false")));
}

// `""` is falsy: a string is true when it is not null and its first character is not the terminator.
// A null string reads the terminator of a static "" instead, so there is no branch to make.
mlir::Value castStringToBool(mlir::Value in)
{
auto llvmPtrType = tch.convertType(mlir_ts::StringType::get(rewriter.getContext()));
mlir::Value ptr = in.getType() == llvmPtrType ? in : rewriter.create<mlir_ts::DialectCastOp>(loc, llvmPtrType, in);

mlir::Value nullPtr = rewriter.create<LLVM::ZeroOp>(loc, llvmPtrType);
auto isNull = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, ptr, nullPtr);
auto readPtr = rewriter.create<LLVM::SelectOp>(loc, isNull, ch.getOrCreateGlobalString(std::string()), ptr);

auto i8Type = rewriter.getIntegerType(8);
auto firstChar = rewriter.create<LLVM::LoadOp>(loc, i8Type, readPtr);
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ne, firstChar, clh.createIConstantOf(8, 0));
}

mlir::Value castBoolToNumber(mlir::Value in)
{
mlir::Value valueAsLLVMType = rewriter.create<mlir_ts::DialectCastOp>(loc, tch.convertType(in.getType()), in);
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 @@ -356,6 +356,7 @@ tslang_add_test(NAME test-compile-00-await-void COMMAND test-runner "${PROJECT_S
tslang_add_test(NAME test-compile-00-for-await-sequential COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts")
tslang_add_test(NAME test-compile-00-for-await-control COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_control.ts")
tslang_add_test(NAME test-compile-00-for-optional-class-condition COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_optional_class_condition.ts")
tslang_add_test(NAME test-compile-00-string-empty-falsy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_empty_falsy.ts")
tslang_add_test(NAME test-compile-00-const-record-owned-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-compile-00-string-view-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
Expand Down Expand Up @@ -869,6 +870,7 @@ tslang_add_test(NAME test-jit-00-await-void COMMAND test-runner -jit "${PROJECT_
tslang_add_test(NAME test-jit-00-for-await-sequential COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts")
tslang_add_test(NAME test-jit-00-for-await-control COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_control.ts")
tslang_add_test(NAME test-jit-00-for-optional-class-condition COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_optional_class_condition.ts")
tslang_add_test(NAME test-jit-00-string-empty-falsy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_empty_falsy.ts")
tslang_add_test(NAME test-jit-00-const-record-owned-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-jit-00-string-view-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
Expand Down Expand Up @@ -1711,6 +1713,7 @@ set(TSLANG_CORPUS
00for_await_sequential.ts
00for_await_control.ts
00for_optional_class_condition.ts
00string_empty_falsy.ts
00const_record_owned_fields.ts
00array_length_valid.ts
00string_view_copy.ts
Expand Down
85 changes: 85 additions & 0 deletions tslang/test/tester/tests/00string_empty_falsy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// an empty string is falsy (#509): a string tested as a boolean was a non-null pointer test, so "" was true
// in `if`, `while`, `!`, `?:`, `&&` and `||`, plain, optional, nullable and as `any`
let globalEmpty = "";
const globalPick = "" ? 1 : 2;

function truthy(s: string) {
return s ? true : false;
}

function truthyIf(s: string) {
if (s) {
return true;
}

return false;
}

function truthyOptional(s: string | undefined) {
if (s) {
return true;
}

return false;
}

function truthyNullable(s: string | null) {
return !!s;
}

function truthyAny(s: any) {
return !!s;
}

function countWhile(s: string, steps: number) {
let count = 0;
while (s) {
count++;
s = count < steps ? s + "-" : "";
}

return count;
}

function main() {
let e = "";
let a = "a";

assert(!truthy(e), "ternary on an empty string");
assert(truthy(a), "ternary on a string");
assert(!truthyIf(e), "if on an empty string");
assert(truthyIf(a), "if on a string");
assert(!e, "! of an empty string");
assert(!!a, "!! of a string");
if ("") {
assert(false, "if on an empty literal");
}

assert((e || "x") == "x", "empty || x");
assert((a || "x") == "a", "a || x");
assert((e && "x") == "", "empty && x");
assert((a && "x") == "x", "a && x");
assert((e ?? "x") == "", "empty ?? x keeps the empty string");

assert(countWhile("abc", 3) == 3, "while until the string is empty");
assert(countWhile(e, 3) == 0, "while on an empty string");

assert(!truthyOptional(e), "optional holding an empty string");
assert(truthyOptional(a), "optional holding a string");
assert(!truthyOptional(undefined), "undefined optional");

assert(!truthyNullable(e), "nullable holding an empty string");
assert(truthyNullable(a), "nullable holding a string");
assert(!truthyNullable(null), "null");

assert(!truthyAny(e), "any holding an empty string");
assert(truthyAny(a), "any holding a string");

assert(!globalEmpty, "global empty string");
assert(globalPick == 2, "module-level ternary on an empty literal");

assert(!(e + e), "empty concatenation");
assert(!`${e}`, "empty template");

print("done.");
}
5 changes: 1 addition & 4 deletions tslang/test/tester/tests/28boolcasts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@ function testBoolCasts() {
boolDie();
}

// TODO: finish it
/*
if ("") {
boolDie()
boolDie();
}
*/

let v = new BazClass();
if (v) {
Expand Down
8 changes: 2 additions & 6 deletions tslang/test/tester/tests/29lazyreferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ class Testrec {
function testLazyRef() {
print("testLazyRef")
let x = ("x" + "Y") || "foo"
// "" should be false for boolean
//let y = "" || "bXr" + "2"
let y = "bXr" + "2"
let y = "" || "bXr" + "2"
assert(x.length == 2, "two")
assert(y.length == 4, "emp")

Expand All @@ -21,9 +19,7 @@ function testLazyRef() {
x = "x" + "12x" && "7" + "xx"
assert(x.length == 3, "and")

// "" should be false for boolean
//x = "" && "blah"
x = ""
x = "" && "blah"
assert(x == "", "andemp")
x = "foo" && "x" + "Y"
assert(x.length == 2, "twoand")
Expand Down
Loading