diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index 79fb3380f..fa5116c24 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -2272,6 +2272,13 @@ namespace mlirgen } else { + // Tested for truth, a null or undefined member is false, as for an `any` + // (castFromAny). `return t` there cast null to boolean - "can't cast from 'null' + // to 'boolean'" - so a union of null and two or more other types, which is what + // `enabled && handle` makes of a boolean and a `Handle | null`, could not be + // tested in an `if` at all. + auto toBoolean = isa(type) || isa(type); + // default auto next = false; for (auto& pair : typeOfs) @@ -2281,7 +2288,19 @@ namespace mlirgen ss << S("if (typeof t == '"); ss << stows(pair.getKey().str()); ss << S("') "); - if (pair.getKey() == "class") + if (toBoolean && (pair.getKey() == "null" || pair.getKey() == "undefined")) + { + ss << S("return false;\n"); + } + else if (toBoolean && pair.getKey() == "class" && classInstances.size() == 1) + { + // tested for truth, an instance is true and null is false - and a null + // instance does carry the class's tag, when a `Class | null` became this + // union (`enabled && handle` with handle null). `instanceof` would read + // its vtable through the null pointer. + ss << S("return t;\n"); + } + else if (pair.getKey() == "class") { ss << S("{ \n"); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 7a9a4d06e..5961bec9a 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -406,6 +406,7 @@ tslang_add_test(NAME test-compile-00-union-bin-ops-2 COMMAND test-runner "${PROJ tslang_add_test(NAME test-compile-00-union-to-any COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_to_any.ts") 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-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") @@ -926,6 +927,7 @@ tslang_add_test(NAME test-jit-00-union-bin-ops-2 COMMAND test-runner -jit "${PRO tslang_add_test(NAME test-jit-00-union-to-any COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_to_any.ts") 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-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") @@ -2033,6 +2035,7 @@ set(TSLANG_CORPUS 00union_bin_ops2.ts 00union_errors.ts 00union_null_to_string.ts + 00union_null_truthiness.ts 00union_ops.ts 00union_to_any.ts 00union_type.ts diff --git a/tslang/test/tester/tests/00union_null_truthiness.ts b/tslang/test/tester/tests/00union_null_truthiness.ts new file mode 100644 index 000000000..411b331c7 --- /dev/null +++ b/tslang/test/tester/tests/00union_null_truthiness.ts @@ -0,0 +1,69 @@ +// Testing a union of null and two or more other types for truth failed to compile: "can't +// instantiate function. '___cast' not all generic types can be identified", after "can't cast +// from 'null' to 'boolean'" reported at an unrelated place. `boolean && (Handle | null)` is such +// a union, so `if (enabled && handle)` failed the same way. + +class Handle { + value: number; + constructor() { + this.value = 7; + } +} + +function use(enabled: boolean, handle: Handle | null): number { + if (enabled && handle) { + return handle.value; + } + + return 0; +} + +function useReversed(enabled: boolean, handle: Handle | null): number { + if (handle && enabled) { + return 1; + } + + return 0; +} + +function truthy(x: boolean | string | null): boolean { + if (x) { + return true; + } + + return false; +} + +function notTruthy(x: boolean | number | null): boolean { + return !x; +} + +function andText(e: boolean, s: string | null): number { + return e && s ? 1 : 0; +} + +function main() { + const h = new Handle(); + assert(use(true, h) == 7, "use(true, h)"); + assert(use(false, h) == 0, "use(false, h)"); + assert(use(true, null) == 0, "use(true, null)"); + assert(useReversed(true, h) == 1, "useReversed(true, h)"); + assert(useReversed(true, null) == 0, "useReversed(true, null)"); + + assert(truthy("a"), "truthy(\"a\")"); + assert(!truthy(""), "truthy(\"\")"); + assert(truthy(true), "truthy(true)"); + assert(!truthy(false), "truthy(false)"); + assert(!truthy(null), "truthy(null)"); + + assert(notTruthy(null), "!null"); + assert(notTruthy(0), "!0"); + assert(!notTruthy(3), "!3"); + assert(!notTruthy(true), "!true"); + + assert(andText(true, "x") == 1, "true && \"x\""); + assert(andText(true, null) == 0, "true && null"); + assert(andText(false, "x") == 0, "false && \"x\""); + + print("done."); +}