From 4ab40a512773866afa1cd4b04a083f602997a8e5 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Thu, 1 Oct 2026 17:12:00 +0100 Subject: [PATCH] A length holds and prints -1: index converts from a signed integer and to text as signed `let n = s.length; n = -1` printed 4294967295, and so did `n` as a number: the si32 -> index cast fell through to the zext in castLLVMTypesLogic, so -1 became 0x00000000FFFFFFFF. It now sign-extends. index -> string printed unsigned, while index already compares and converts to number as signed: `s.length - 3` printed 18446744073709551615 though `s.length - 3 < 0` was true. It now prints signed (-1). Co-Authored-By: Claude Opus 5.5 --- .../TypeScript/LowerToLLVM/CastLogicHelper.h | 10 ++++- tslang/test/tester/CMakeLists.txt | 2 + .../tests/00length_signed_arithmetic.ts | 45 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tslang/test/tester/tests/00length_signed_arithmetic.ts diff --git a/tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h b/tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h index 1349d4ac3..924e8a976 100644 --- a/tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h +++ b/tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h @@ -102,7 +102,8 @@ class CastLogicHelper if (inType.isIndex() && isResString) { - return castIntToString(in, tch.getIndexTypeBitwidth(), false); + // signed, as index compares and converts to number: `s.length - 3` is -1, not 18446744073709551615 + return castIntToString(in, tch.getIndexTypeBitwidth(), true); } if (isa(inType) && isResString) @@ -150,6 +151,13 @@ class CastLogicHelper } } + // a signed integer keeps its sign as an index: `let n = s.length; n = -1` held 4294967295 under the + // zext in castLLVMTypesLogic + if (inType.isSignedInteger() && resType.isIndex() && resLLVMType.getIntOrFloatBitWidth() > inType.getIntOrFloatBitWidth()) + { + return rewriter.create(loc, resLLVMType, in); + } + // TODO: should be in LLVM cast? if (inType.isSignedInteger() && resType.isSignedInteger() && resType.getIntOrFloatBitWidth() > inType.getIntOrFloatBitWidth()) { diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index c14eeccf5..f570460d0 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -392,6 +392,7 @@ tslang_add_test(NAME test-compile-00-union-nullable-value COMMAND test-runner "$ 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-length-signed-arithmetic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00length_signed_arithmetic.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") @@ -867,6 +868,7 @@ tslang_add_test(NAME test-jit-00-union-nullable-value COMMAND test-runner -jit " 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-length-signed-arithmetic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00length_signed_arithmetic.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_signed_arithmetic.ts b/tslang/test/tester/tests/00length_signed_arithmetic.ts new file mode 100644 index 000000000..5ee3fe1f1 --- /dev/null +++ b/tslang/test/tester/tests/00length_signed_arithmetic.ts @@ -0,0 +1,45 @@ +// A length is an index; it holds, compares and prints as a signed value, as a JS number would: +// a variable initialised from one keeps -1 when assigned it, and a length minus more is negative +function assigned(s: string) { + let n = s.length; + n = -1; + assert(n == -1, "n == -1"); + assert(n < 0, "n < 0"); + const asNumber: number = n; + assert(asNumber == -1, "as number"); + assert(`${n}` == "-1", "as text"); + assert("" + n == "-1", "concatenated"); + const boxed: any = n; + assert(`${boxed}` == "-1", "boxed"); +} + +function subtracted(s: string, a: number[]) { + assert(`${s.length - 3}` == "-1", "string length - 3"); + assert(`${a.length - 5}` == "-3", "array length - 5"); + assert(a.length - 5 < 0, "array length - 5 < 0"); + + let i = s.length; + i--; i--; i--; i--; + assert(`${i}` == "-2", "decremented"); + + let w = a.length; + w -= 5; + assert(`${w}` == "-3", "-="); +} + +function countdown(s: string) { + let visited = 0; + for (let k = s.length - 1; k >= 0; k--) { + visited++; + if (visited > 10) break; + } + + assert(visited == 2, "countdown"); +} + +function main() { + assigned("ab"); + subtracted("ab", [1, 2]); + countdown("ab"); + print("done."); +}