diff --git a/tslang/include/TypeScript/LowerToLLVM/UnaryBinLogicalOrHelper.h b/tslang/include/TypeScript/LowerToLLVM/UnaryBinLogicalOrHelper.h index 4d0b119b3..a5dc95d2a 100644 --- a/tslang/include/TypeScript/LowerToLLVM/UnaryBinLogicalOrHelper.h +++ b/tslang/include/TypeScript/LowerToLLVM/UnaryBinLogicalOrHelper.h @@ -229,7 +229,10 @@ mlir::Value LogicOp(Operation *binOp, SyntaxKind op, mlir::Value left, mlir::Typ return cmpOp; } - return LogicOp(binOp, op, left, baseType, right, rightType, builder, typeConverter, compileOptions); + // the union is the right operand, so it is the right type that becomes its base type: + // passing the base type as the left type and the union again as the right one came + // straight back here, until the compiler's stack ran out (`opaque != (opaque | null)`) + return LogicOp(binOp, op, left, leftType, right, baseType, builder, typeConverter, compileOptions); } // excluded string diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index f48ed6eed..40a5aa6b4 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -413,6 +413,7 @@ tslang_add_test(NAME test-compile-00-optional-number-compare COMMAND test-runner tslang_add_test(NAME test-compile-00-optional-loose-null COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00optional_loose_null.ts") tslang_add_test(NAME test-compile-00-union-nullable-array COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_nullable_array.ts") tslang_add_test(NAME test-compile-00-union-nullable-value COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_nullable_value.ts") +tslang_add_test(NAME test-compile-00-opaque-nullable-compare COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00opaque_nullable_compare.ts") tslang_add_test(NAME test-compile-00-union-value-equality COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_value_equality.ts") tslang_add_test(NAME test-compile-00-strict-equals-mismatched-types COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00strict_equals_mismatched_types.ts") tslang_add_test(NAME test-compile-00-any-number-compare COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_number_compare.ts") @@ -937,6 +938,7 @@ tslang_add_test(NAME test-jit-00-optional-number-compare COMMAND test-runner -ji tslang_add_test(NAME test-jit-00-optional-loose-null COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00optional_loose_null.ts") tslang_add_test(NAME test-jit-00-union-nullable-array COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_nullable_array.ts") tslang_add_test(NAME test-jit-00-union-nullable-value COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_nullable_value.ts") +tslang_add_test(NAME test-jit-00-opaque-nullable-compare COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00opaque_nullable_compare.ts") tslang_add_test(NAME test-jit-00-union-value-equality COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_value_equality.ts") tslang_add_test(NAME test-jit-00-strict-equals-mismatched-types COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00strict_equals_mismatched_types.ts") tslang_add_test(NAME test-jit-00-any-number-compare COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_number_compare.ts") @@ -2178,6 +2180,7 @@ set(TSLANG_CORPUS typeGuardOfFormTypeOfBoolean.ts types_vs_interfaces.ts 00any_number_compare.ts + 00opaque_nullable_compare.ts ) # the default model runs these ahead of time only, so the other models do too diff --git a/tslang/test/tester/tests/00opaque_nullable_compare.ts b/tslang/test/tester/tests/00opaque_nullable_compare.ts new file mode 100644 index 000000000..f94441f29 --- /dev/null +++ b/tslang/test/tester/tests/00opaque_nullable_compare.ts @@ -0,0 +1,61 @@ +// Comparing an Opaque with an `Opaque | null` overflowed the compiler's stack (0xC00000FD, no +// message): the comparison's lowering kept calling itself with the same operand types. + +class State { + current: Opaque | null; + + constructor() { + this.current = null; + } + + set(next: Opaque): boolean { + if (next != this.current) { + this.current = next; + return true; + } + + return false; + } +} + +class Item { + id = 1; +} + +function sameOpaque(a: Opaque, b: Opaque | null) { + return a == b; +} + +function sameOpaqueReversed(a: Opaque | null, b: Opaque) { + return a == b; +} + +function sameItem(a: Item, b: Item | null) { + return a === b; +} + +function differentItem(a: Item, b: Item | null) { + return a !== b; +} + +function main() { + const s = new State(); + const h = new Item(); + const g = new Item(); + + assert(s.set(h), "first set binds"); + assert(!s.set(h), "same handle again is skipped"); + assert(s.set(g), "another handle binds"); + + assert(sameOpaque(h, h), "h == h"); + assert(!sameOpaque(h, g), "h == g"); + assert(!sameOpaque(h, null), "h == null"); + assert(sameOpaqueReversed(h, h), "h == h (union on the left)"); + + const item = new Item(); + assert(sameItem(item, item), "item === item"); + assert(!sameItem(item, null), "item === null"); + assert(differentItem(item, new Item()), "item !== other"); + + print("done."); +}