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
63 changes: 63 additions & 0 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5861,6 +5861,54 @@ class MLIRGenImpl
return mlir::success();
}

// An optional number compared with a number. The lowering compares them only when the optional
// holds a value (OptionalLogicHelper), so it stays optional here: synced as a plain value it was
// cast out of the optional first, an empty one reading as 0 - and the promotion saw only the other
// operand's type, so against an integer literal 0.5 became an s32 and `0.5 === 0` was true. Both
// sides take the wider of the optional's element type and the other operand's type.
ValueOrLogicalResult syncOptionalNumericTypes(mlir::Location location, mlir::Value &leftExpressionValue, mlir::Value &rightExpressionValue, const GenContext &genContext)
{
auto leftOptType = dyn_cast<mlir_ts::OptionalType>(leftExpressionValue.getType());
auto rightOptType = dyn_cast<mlir_ts::OptionalType>(rightExpressionValue.getType());
if (!leftOptType == !rightOptType)
{
return mlir::success();
}

auto &optValue = leftOptType ? leftExpressionValue : rightExpressionValue;
auto &otherValue = leftOptType ? rightExpressionValue : leftExpressionValue;
auto elementType = (leftOptType ? leftOptType : rightOptType).getElementType();
auto otherType = otherValue.getType();
if (auto literalType = dyn_cast<mlir_ts::LiteralType>(otherType))
{
otherType = literalType.getElementType();
}

if (!isPromotableNumeric(elementType) || !isPromotableNumeric(otherType))
{
return mlir::success();
}

auto types = numericPromotionOrder();
auto commonType = llvm::find_if(types, [&](mlir::Type type) { return type == elementType || type == otherType; });
if (commonType == types.end())
{
return mlir::success();
}

if (elementType != *commonType)
{
CAST(optValue, location, getOptionalType(*commonType), optValue, genContext);
}

if (otherValue.getType() != *commonType)
{
CAST(otherValue, location, *commonType, otherValue, genContext);
}

return optValue;
}

// The order the arithmetic operators promote in, widest first: the first type either
// operand already has is the one both are cast to. Shared by `+` and by the general
// arithmetic/comparison path below, which is the point -- they disagreed, and `+` was
Expand Down Expand Up @@ -6169,6 +6217,21 @@ class MLIRGenImpl
// TODO: do we need to sync type for all Ops?
auto types = numericPromotionOrder();

// `-` and `*` fall into this case too: arithmetic on an optional is left as it was
if (opCode != SyntaxKind::MinusToken && opCode != SyntaxKind::AsteriskToken)
{
auto o = syncOptionalNumericTypes(location, leftExpressionValue, rightExpressionValue, genContext);
if (o.value)
{
break;
}

if (mlir::failed(o.result))
{
return mlir::failure();
}
}

auto r = syncUnionTypes(location, leftExpressionValue, rightExpressionValue, genContext);
if (r.value)
{
Expand Down
2 changes: 2 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ tslang_add_test(NAME test-compile-00-union-errors COMMAND test-runner "${PROJECT
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-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")
tslang_add_test(NAME test-compile-00-intersection-type COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00intersection_type.ts")
tslang_add_test(NAME test-compile-00-intersection-type-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00intersection_type_generic.ts")
tslang_add_test(NAME test-compile-00-length COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00length.ts")
Expand Down Expand Up @@ -853,6 +854,7 @@ tslang_add_test(NAME test-jit-00-union-errors COMMAND test-runner -jit "${PROJEC
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-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")
tslang_add_test(NAME test-jit-00-union-global-init COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_global_init.ts")
tslang_add_test(NAME test-jit-00-intersection-type COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00intersection_type.ts")
tslang_add_test(NAME test-jit-00-intersection-type-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00intersection_type_generic.ts")
Expand Down
56 changes: 56 additions & 0 deletions tslang/test/tester/tests/00optional_number_compare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// An optional number compares as the wider of its own type and the other operand's - not as the
// other operand's alone, which made 0.5 an s32 against an integer literal - and only when it holds a
// value: an empty one is not 0, it is less than, greater than and equal to nothing
function holdsFraction() {
let n: number | undefined = 0.5;
assert(!(n === 0), "0.5 === 0");
assert(n === 0.5, "0.5 === 0.5");
assert(n !== 0, "0.5 !== 0");
assert(n < 1, "0.5 < 1");
assert(n > 0, "0.5 > 0");
assert(!(0 === n), "0 === 0.5");
assert(0.5 == n, "0.5 == 0.5");

let zero: number = 0;
let half: number = 0.5;
assert(!(n === zero), "0.5 === zero");
assert(n === half, "0.5 === half");
assert(n > zero, "0.5 > zero");
}

function empty() {
let n: number | undefined = undefined;
assert(!(n === 0), "undefined === 0");
assert(!(n == 0), "undefined == 0");
assert(n !== 0, "undefined !== 0");
assert(!(n < 1), "undefined < 1");
assert(!(n >= 0), "undefined >= 0");
assert(!(0 === n), "0 === undefined");

let zero: number = 0;
assert(!(n === zero), "undefined === zero");
assert(!(n <= zero), "undefined <= zero");
}

function integers() {
let i: s32 | undefined = 3;
assert(i === 3, "s32 3 === 3");
assert(!(i === 0), "s32 3 === 0");
assert(i > 2, "s32 3 > 2");
assert(i < 3.5, "s32 3 < 3.5");

i = undefined;
assert(!(i === 0), "s32 undefined === 0");
assert(!(i < 1), "s32 undefined < 1");

let f: number | undefined = 3;
assert(f === 3, "3.0 === 3");
assert(f > 2.5, "3.0 > 2.5");
}

function main() {
holdsFraction();
empty();
integers();
print("done.");
}