diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h b/tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h index d88f0a948..d6eea410a 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h @@ -1701,6 +1701,50 @@ class MLIRTypeHelper return mlir::Type(); } + // index or a signless integer (a constant string's folded length is i32) with a signed integer (or a + // signed integer literal) merges to a signed integer that holds both + mlir::Type mergeIndexWithSignedInt(mlir::Type typeLeft, mlir::Type typeRight, bool& found) + { + found = false; + + auto intOf = [](mlir::Type type) -> mlir::IntegerType { + if (auto literalType = dyn_cast(type)) + { + type = literalType.getElementType(); + } + + return dyn_cast(type); + }; + + auto isIndexOrSignless = [&](mlir::Type type) { + auto intType = intOf(type); + return type.isIndex() || (intType && intType.isSignless()); + }; + + auto signedIntOf = [&](mlir::Type type) { + auto intType = intOf(type); + return intType && intType.isSigned() ? intType : mlir::IntegerType(); + }; + + mlir::IntegerType signedType; + if (isIndexOrSignless(typeLeft)) + { + signedType = signedIntOf(typeRight); + } + else if (isIndexOrSignless(typeRight)) + { + signedType = signedIntOf(typeLeft); + } + + if (!signedType) + { + return mlir::Type(); + } + + found = true; + return mlir::IntegerType::get(context, std::max(64u, signedType.getIntOrFloatBitWidth()), mlir::IntegerType::Signed); + } + mlir::Type mergeFuncTypes(mlir::Type typeLeft, mlir::Type typeRight, bool& found) { found = false; @@ -1785,6 +1829,14 @@ class MLIRTypeHelper return typeRight; } + // `c ? s.length : -1` - see mergeType + auto mergedIndex = false; + auto resIndexType = mergeIndexWithSignedInt(typeLeft, typeRight, mergedIndex); + if (mergedIndex) + { + return resIndexType; + } + if (canWideTypeWithoutDataLoss(typeLeft, typeRight)) { return typeRight; @@ -3575,6 +3627,16 @@ class MLIRTypeHelper return existType; } + // index (a length, a count) passes for signless in canCastFromTo but holds an unsigned value, + // so picking it for `index | si32` wraps a negative value: `return s.length` + `return -1` gave 4294967295 + auto mergedIndex = false; + auto resIndexType = mergeIndexWithSignedInt(existType, currentType, mergedIndex); + if (mergedIndex) + { + merged = true; + return resIndexType; + } + if (canCastFromTo(location, currentType, existType)) { merged = true; diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index af1500be9..8a2b12ce3 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -6039,7 +6039,7 @@ class MLIRGenImpl { auto isBoolean = [](mlir::Type type) { return isa(type); }; auto isString = [](mlir::Type type) { return isa(type); }; - auto isNumeric = [](mlir::Type type) { return type.isIntOrIndexOrFloat() && !isa(type); }; + auto isNumeric = [](mlir::Type type) { return isa(type) || (type.isIntOrIndexOrFloat() && !isa(type)); }; auto leftIsBoolean = isBoolean(leftType); auto rightIsBoolean = isBoolean(rightType); @@ -6072,8 +6072,9 @@ class MLIRGenImpl if ((opCode == SyntaxKind::EqualsEqualsEqualsToken || opCode == SyntaxKind::ExclamationEqualsEqualsToken) && isDefinitelyMismatchedForStrictEquals(leftExpressionValue.getType(), rightExpressionValue.getType())) { - auto result = opCode == SyntaxKind::ExclamationEqualsEqualsToken; - leftExpressionValue = builder.create(location, getBooleanType(), builder.getBoolAttr(result)); + // the operator itself still runs on the replaced operands, so they must be two + // unequal values: `false === true` is false and `false !== true` is true + leftExpressionValue = builder.create(location, getBooleanType(), builder.getBoolAttr(false)); rightExpressionValue = builder.create(location, getBooleanType(), builder.getBoolAttr(true)); return mlir::success(); } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 405c5de1a..c14eeccf5 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -390,6 +390,8 @@ tslang_add_test(NAME test-compile-00-optional-loose-null COMMAND test-runner "${ 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-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-length-with-negative-merge COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00length_with_negative_merge.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") @@ -863,6 +865,8 @@ tslang_add_test(NAME test-jit-00-optional-loose-null COMMAND test-runner -jit "$ 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-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-length-with-negative-merge COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00length_with_negative_merge.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") diff --git a/tslang/test/tester/tests/00length_with_negative_merge.ts b/tslang/test/tester/tests/00length_with_negative_merge.ts new file mode 100644 index 000000000..c36183356 --- /dev/null +++ b/tslang/test/tester/tests/00length_with_negative_merge.ts @@ -0,0 +1,49 @@ +// A length (index, unsigned) and a negative integer in one inferred type: a return, a conditional +// and an array literal must all keep -1, not wrap it to 4294967295 +function lengthFirst(p: string, found: boolean) { + if (found) return p.length; + return -1; +} + +function negativeFirst(p: number[], found: boolean) { + if (!found) return -1; + return p.length; +} + +function returns() { + assert(lengthFirst("abc", true) == 3, "length first: length"); + assert(lengthFirst("abc", false) == -1, "length first: -1"); + assert(lengthFirst("abc", false) < 0, "length first: -1 < 0"); + assert(negativeFirst([1, 2], true) == 2, "-1 first: length"); + assert(negativeFirst([1, 2], false) == -1, "-1 first: -1"); +} + +function conditionals(p: string, found: boolean) { + const a = found ? p.length : -1; + const b = found ? -1 : p.length; + assert(a == -1, "found ? length : -1"); + assert(b == 3, "found ? -1 : length"); +} + +function arrays(p: string) { + const a = [p.length, -1]; + const b = [-1, p.length]; + assert(a[0] == 3 && a[1] == -1, "[length, -1]"); + assert(b[0] == -1 && b[1] == 3, "[-1, length]"); +} + +function constantString(found: boolean) { + // a constant string's length folds to a constant + const p = "abc"; + const a = found ? p.length : -1; + assert(a == -1, "constant: found ? length : -1"); + assert([p.length, -1][1] == -1, "constant: [length, -1]"); +} + +function main() { + returns(); + conditionals("abc", false); + arrays("abc"); + constantString(false); + print("done."); +} diff --git a/tslang/test/tester/tests/00strict_equals_mismatched_types.ts b/tslang/test/tester/tests/00strict_equals_mismatched_types.ts new file mode 100644 index 000000000..1eb8ee973 --- /dev/null +++ b/tslang/test/tester/tests/00strict_equals_mismatched_types.ts @@ -0,0 +1,41 @@ +// `===` / `!==` between a boolean, a number and a string never coerce: the values are simply unequal, +// so `===` is false and `!==` is true - for literals, for consts and for values typed `number` +function literals() { + assert(1 !== true, "1 !== true"); + assert("a" !== 1, "\"a\" !== 1"); + assert(true !== "x", "true !== \"x\""); + assert(!(1 === true), "1 === true"); + assert(!("a" === 1), "\"a\" === 1"); + assert(!(true === "x"), "true === \"x\""); +} + +function consts() { + const n = 1, b = true, s = "a"; + assert(n !== b, "n !== b"); + assert(s !== n, "s !== n"); + assert(b !== s, "b !== s"); + assert(!(n === b), "n === b"); + assert(!(s === n), "s === n"); + assert(!(b === s), "b === s"); +} + +function typed() { + let x: number = 1; + let b: boolean = true; + let s: string = "1"; + assert(x !== b, "number !== boolean"); + assert(x !== s, "number !== string"); + assert(s !== x, "string !== number"); + assert(!(x === b), "number === boolean"); + assert(!(x === s), "number === string"); + + // loose equality still coerces + assert(x == b, "1 == true"); +} + +function main() { + literals(); + consts(); + typed(); + print("done."); +}