From a369d29e9dac8bf05c446c1ca1712b0d73d4fadd Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 23 Sep 2026 23:28:49 +0100 Subject: [PATCH] Throw a u32 as an unsigned int; convert u32 to number as unsigned A thrown u32 went out as a signed int (`.H` on Windows, `_ZTIi` on Linux), and every reader took its top bit for a sign: `throw 0xFFFFFFFF` caught by `catch (e: number)` or `catch (e)` gave -1. On Windows the int's widening and boxing thunks were also shared by name, so a module throwing both an s32 and a u32 used whichever was built first. A u32 now has RTTI of its own: `.I` records with their own `.PEAX` box and `.N` widening thunks on Windows, `_ZTIj` on Linux, listed by `catch (e: number)`, bound by loadCaughtNumber (uitofp) and boxed as a u32 by an untyped catch. The thunk alone did not fix it: every u32-to-number cast lowered to sitofp, because castLLVMTypesLogic only sees the signless LLVM type. An unsigned integer now converts to a float with uitofp. That exposed two more signedness gaps that had cancelled it out in 02numbers.ts: - number to a narrow integer was fptosi straight to i32, poison above 0x7FFFFFFF, so `0x80000001 << 1` was garbage once the literal arrived as 2147483649. It now goes through i64 and truncates, which wraps modulo 2^32 as ToInt32/ToUint32 do. - `>>>` produced a signless i32 that widened as signed; its result is now a u32, so `-1 >>> 0` is 4294967295. Co-Authored-By: Claude Opus 5.5 --- .../TypeScript/LowerToLLVM/CastLogicHelper.h | 21 +++- .../LowerToLLVM/LLVMRTTIHelperVCLinux.h | 14 ++- .../LowerToLLVM/LLVMRTTIHelperVCLinuxConst.h | 6 ++ .../LowerToLLVM/LLVMRTTIHelperVCWin32.h | 17 +++- .../LowerToLLVM/LLVMRTTIHelperVCWin32Const.h | 19 ++++ .../MLIRLogic/MLIRRTTIHelperVCLinux.h | 27 +++++- .../MLIRLogic/MLIRRTTIHelperVCWin32.h | 34 ++++++- tslang/lib/TypeScript/LowerToAffineLoops.cpp | 5 +- tslang/lib/TypeScript/LowerToLLVM.cpp | 23 +++-- tslang/lib/TypeScript/MLIRGenImpl.h | 7 ++ tslang/test/tester/CMakeLists.txt | 3 + tslang/test/tester/tests/00throw_u32.ts | 97 +++++++++++++++++++ 12 files changed, 257 insertions(+), 16 deletions(-) create mode 100644 tslang/test/tester/tests/00throw_u32.ts diff --git a/tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h b/tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h index 09bdb1aee..755a39fa1 100644 --- a/tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h +++ b/tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h @@ -154,7 +154,15 @@ class CastLogicHelper if (inType.isSignedInteger() && resType.isSignedInteger() && resType.getIntOrFloatBitWidth() > inType.getIntOrFloatBitWidth()) { return rewriter.create(loc, resLLVMType, in); - } + } + + // an unsigned integer is converted to a float as unsigned: castLLVMTypesLogic only sees the + // signless LLVM type, and its sitofp read a u32 of 0xFFFFFFFF as -1 + if (inType.isUnsignedInteger() && isFloat(resLLVMType)) + { + auto inValue = in.getType() == inLLVMType ? in : rewriter.create(loc, inLLVMType, in); + return rewriter.create(loc, resLLVMType, inValue); + } auto isResAny = isa(resType); if (isResAny) @@ -691,6 +699,17 @@ class CastLogicHelper if (isFloat(inLLVMType) && isInt(resLLVMType)) { + // to a narrower integer through i64, then truncated: fptosi straight to i32 is poison + // outside the signed range, so a number holding a u32 above 0x7FFFFFFF (2147483649, + // say, which the unsigned int-to-float conversion now gives) came back as garbage. + // Truncating wraps modulo 2^32, as ToInt32/ToUint32 do. + auto width = resLLVMType.getIntOrFloatBitWidth(); + if (width < 64) + { + auto wideValue = rewriter.create(loc, rewriter.getI64Type(), in); + return rewriter.create(loc, resLLVMType, wideValue); + } + return rewriter.create(loc, resLLVMType, in); } diff --git a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinux.h b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinux.h index 7ffa01096..04ef403f9 100644 --- a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinux.h +++ b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinux.h @@ -64,6 +64,11 @@ class LLVMRTTIHelperVCLinux types.push_back({linux::I32Type::typeName}); } + void setU32AsCatchType() + { + types.push_back({linux::U32Type::typeName}); + } + void setBoolAsCatchType() { types.push_back({linux::BoolType::typeName}); @@ -116,7 +121,14 @@ class LLVMRTTIHelperVCLinux .Case([&](auto intType) { if (intType.getIntOrFloatBitWidth() == 32) { - setI32AsCatchType(); + if (intType.isUnsigned()) + { + setU32AsCatchType(); + } + else + { + setI32AsCatchType(); + } } else if (intType.getIntOrFloatBitWidth() == 64) { diff --git a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinuxConst.h b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinuxConst.h index 493f4a8d8..17c6e60c9 100644 --- a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinuxConst.h +++ b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCLinuxConst.h @@ -23,6 +23,12 @@ namespace I32Type constexpr const auto *typeName = "_ZTIi"; } // namespace I32Type +// `unsigned int` - a u32 used to be thrown as an `int`, and read back with its top bit as a sign +namespace U32Type +{ +constexpr const auto *typeName = "_ZTIj"; +} // namespace U32Type + namespace BoolType { constexpr const auto *typeName = "_ZTIb"; diff --git a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCWin32.h b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCWin32.h index f3982f25a..4a2d5329d 100644 --- a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCWin32.h +++ b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCWin32.h @@ -88,6 +88,14 @@ class LLVMRTTIHelperVCWin32 throwInfoRef = I32Type::throwInfoRef; } + void setU32AsCatchType() + { + types.push_back({U32Type::typeName, U32Type::typeInfoRef, U32Type::catchableTypeInfoRef, U32Type::catchableTypeSize}); + + catchableTypeInfoArrayRef = U32Type::catchableTypeInfoArrayRef; + throwInfoRef = U32Type::throwInfoRef; + } + void setBoolAsCatchType() { types.push_back({BoolType::typeName, BoolType::typeInfoRef, BoolType::catchableTypeInfoRef, BoolType::catchableTypeSize}); @@ -172,7 +180,14 @@ class LLVMRTTIHelperVCWin32 .Case([&](auto intType) { if (intType.getIntOrFloatBitWidth() == 32) { - setI32AsCatchType(); + if (intType.isUnsigned()) + { + setU32AsCatchType(); + } + else + { + setI32AsCatchType(); + } } else if (intType.getIntOrFloatBitWidth() == 64) { diff --git a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCWin32Const.h b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCWin32Const.h index 5c701c46d..a3fc9bf34 100644 --- a/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCWin32Const.h +++ b/tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCWin32Const.h @@ -120,6 +120,25 @@ constexpr const auto *throwInfoRef = "_TI3H"; constexpr int catchableTypeSize = 4; } // namespace I32Type +// `unsigned int`. A u32 used to go out as an `int` (`.H`), so every reader - the `.N` widening +// thunk, the `.PEAX` box thunk - took its top bit for a sign and 0xFFFFFFFF came back as -1. +// Its records have names of their own, so they never share a thunk with an `int` throw. +namespace U32Type +{ +constexpr const auto *typeName = ".I"; +constexpr const auto *typeInfoRef = "??_R0I@8"; +constexpr const auto *catchableTypeInfoRef = "_CT??_R0I@84"; +constexpr const auto *catchableTypeInfoRef2 = "_CT??_R0PEAX@I84.box"; +constexpr const auto *copyThunk2 = ".eh.copy.box.I"; +constexpr const auto *typeName3 = ".N"; +constexpr const auto *typeInfoRef3 = "??_R0N@8"; +constexpr const auto *catchableTypeInfoRef3 = "_CT??_R0N@88.fromI"; +constexpr const auto *copyThunk3 = ".eh.copy.num.I"; +constexpr const auto *catchableTypeInfoArrayRef = "_CTA3I"; +constexpr const auto *throwInfoRef = "_TI3I"; +constexpr int catchableTypeSize = 4; +} // namespace U32Type + // `bool` - a boolean is an i1, stored as a byte namespace BoolType { diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCLinux.h b/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCLinux.h index 6f73c4d2f..7efd6f190 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCLinux.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCLinux.h @@ -90,6 +90,11 @@ class MLIRRTTIHelperVCLinux types.push_back({linux::I32Type::typeName, TypeInfo::Value, -1}); } + void setU32AsCatchType() + { + types.push_back({linux::U32Type::typeName, TypeInfo::Value, -1}); + } + void setBoolAsCatchType() { types.push_back({linux::BoolType::typeName, TypeInfo::Value, -1}); @@ -157,7 +162,14 @@ class MLIRRTTIHelperVCLinux auto width = intType.getIntOrFloatBitWidth(); if (width == 32) { - setI32AsCatchType(); + if (intType.isUnsigned()) + { + setU32AsCatchType(); + } + else + { + setI32AsCatchType(); + } } else if (width == 64) { @@ -191,9 +203,10 @@ class MLIRRTTIHelperVCLinux #else setF32AsCatchType(); #endif - // a `catch (e: number)` also takes a thrown int (`throw 1`) or f32, and binding + // a `catch (e: number)` also takes a thrown int (`throw 1`), u32 or f32, and binding // it compares against their type_infos - see TryOpLowering, SaveCatchVarOpLowering setI32AsCatchType(); + setU32AsCatchType(); setF32AsCatchType(); }) .Case([&](auto boolType) { setBoolAsCatchType(); }) @@ -216,6 +229,7 @@ class MLIRRTTIHelperVCLinux // see linux::SaveCatchVarOpLowering. `_ZTIPv` is also what a `throw e` of the // caught value throws again. setI32AsCatchType(); + setU32AsCatchType(); setF64AsCatchType(); setF32AsCatchType(); setBoolAsCatchType(); @@ -247,7 +261,14 @@ class MLIRRTTIHelperVCLinux auto width = intType.getIntOrFloatBitWidth(); if (width == 32) { - setI32AsCatchType(); + if (intType.isUnsigned()) + { + setU32AsCatchType(); + } + else + { + setI32AsCatchType(); + } } else if (width == 64) { diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCWin32.h b/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCWin32.h index 0eedaf857..caeb893fe 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCWin32.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCWin32.h @@ -138,6 +138,22 @@ class MLIRRTTIHelperVCWin32 throwInfoRef = windows::I32Type::throwInfoRef; } + void setU32AsCatchType() + { + types.push_back({windows::U32Type::typeName, windows::U32Type::typeInfoRef, windows::U32Type::catchableTypeInfoRef, + windows::U32Type::catchableTypeSize}); + types.push_back(boxEntry(windows::U32Type::catchableTypeInfoRef2, windows::U32Type::copyThunk2)); + + TypeNames toNumber{windows::U32Type::typeName3, windows::U32Type::typeInfoRef3, windows::U32Type::catchableTypeInfoRef3, + windows::F64Type::catchableTypeSize}; + toNumber.copyThunk = windows::U32Type::copyThunk3; + toNumber.copyThunkTarget = mlir_ts::NumberType::get(rewriter.getContext()); + types.push_back(toNumber); + + catchableTypeInfoArrayRef = windows::U32Type::catchableTypeInfoArrayRef; + throwInfoRef = windows::U32Type::throwInfoRef; + } + void setBoolAsCatchType() { types.push_back({windows::BoolType::typeName, windows::BoolType::typeInfoRef, windows::BoolType::catchableTypeInfoRef, @@ -253,7 +269,14 @@ class MLIRRTTIHelperVCWin32 .Case([&](auto intType) { if (intType.getIntOrFloatBitWidth() == 32) { - setI32AsCatchType(); + if (intType.isUnsigned()) + { + setU32AsCatchType(); + } + else + { + setI32AsCatchType(); + } } else if (intType.getIntOrFloatBitWidth() == 64) { @@ -321,7 +344,14 @@ class MLIRRTTIHelperVCWin32 .Case([&](auto intType) { if (intType.getIntOrFloatBitWidth() == 32) { - setI32AsCatchType(); + if (intType.isUnsigned()) + { + setU32AsCatchType(); + } + else + { + setI32AsCatchType(); + } } else if (intType.getIntOrFloatBitWidth() == 64) { diff --git a/tslang/lib/TypeScript/LowerToAffineLoops.cpp b/tslang/lib/TypeScript/LowerToAffineLoops.cpp index b20d19f25..598f6e858 100644 --- a/tslang/lib/TypeScript/LowerToAffineLoops.cpp +++ b/tslang/lib/TypeScript/LowerToAffineLoops.cpp @@ -1654,14 +1654,15 @@ struct TryOpLowering : public TsPattern : /*catch all*/ (mlir::Value)rewriter.create(loc, mth.getNullType()); // `catch (e: number)` has to take `throw 1` too - an integer literal is an int here - and - // a thrown f32. On Windows their ThrowInfos list a `.N` entry that converts (see + // a thrown u32 or f32. On Windows their ThrowInfos list a `.N` entry that converts (see // copyThunkPrefix in LLVMRTTIHelperVCWin32Const.h); on the Itanium path the catch lists // their type_infos as further clauses, accepts any of them, and // linux::SaveCatchVarOpLowering converts. SmallVector widenedTypeInfos; if (!tsContext->compileOptions.isWindows && catchVarType && isa(catchVarType)) { - for (auto typeInfoName : {::typescript::linux::I32Type::typeName, ::typescript::linux::F32Type::typeName}) + for (auto typeInfoName : {::typescript::linux::I32Type::typeName, ::typescript::linux::U32Type::typeName, + ::typescript::linux::F32Type::typeName}) { widenedTypeInfos.push_back(rewriter.create( loc, mth.getRefType(mth.getOpaqueType()), mlir::FlatSymbolRefAttr::get(rewriter.getContext(), typeInfoName))); diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 30a50493a..1f1db9ce3 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -5081,16 +5081,26 @@ struct SaveCatchVarOpLowering : public TsLlvmPattern return rewriter.create(loc, llvmNumberType, intValue); }, [&](OpBuilder &, Location) -> mlir::Value { - auto floatTypeInfo = rewriter.create(loc, ptrTy, ::typescript::linux::F32Type::typeName); - auto isFloat = rewriter.create(loc, LLVM::ICmpPredicate::eq, typeInfo, floatTypeInfo); + auto uintTypeInfo = rewriter.create(loc, ptrTy, ::typescript::linux::U32Type::typeName); + auto isUInt = rewriter.create(loc, LLVM::ICmpPredicate::eq, typeInfo, uintTypeInfo); return clh.conditionalExpressionLowering( - loc, llvmNumberType, isFloat, + loc, llvmNumberType, isUInt, [&](OpBuilder &, Location) -> mlir::Value { - auto floatValue = rewriter.create(loc, rewriter.getF32Type(), exceptionInfo); - return rewriter.create(loc, llvmNumberType, floatValue); + auto uintValue = rewriter.create(loc, th.getI32Type(), exceptionInfo); + return rewriter.create(loc, llvmNumberType, uintValue); }, [&](OpBuilder &, Location) -> mlir::Value { - return rewriter.create(loc, llvmNumberType, exceptionInfo); + auto floatTypeInfo = rewriter.create(loc, ptrTy, ::typescript::linux::F32Type::typeName); + auto isFloat = rewriter.create(loc, LLVM::ICmpPredicate::eq, typeInfo, floatTypeInfo); + return clh.conditionalExpressionLowering( + loc, llvmNumberType, isFloat, + [&](OpBuilder &, Location) -> mlir::Value { + auto floatValue = rewriter.create(loc, rewriter.getF32Type(), exceptionInfo); + return rewriter.create(loc, llvmNumberType, floatValue); + }, + [&](OpBuilder &, Location) -> mlir::Value { + return rewriter.create(loc, llvmNumberType, exceptionInfo); + }); }); }); } @@ -5135,6 +5145,7 @@ struct SaveCatchVarOpLowering : public TsLlvmPattern // each type tslang throws, by the type_info a throw of it carries SmallVector> candidates{ {::typescript::linux::I32Type::typeName, mlir::IntegerType::get(ctx, 32, mlir::IntegerType::Signed)}, + {::typescript::linux::U32Type::typeName, mlir::IntegerType::get(ctx, 32, mlir::IntegerType::Unsigned)}, {::typescript::linux::F64Type::typeName, mlir_ts::NumberType::get(ctx)}, {::typescript::linux::F32Type::typeName, mlir::Float32Type::get(ctx)}, {::typescript::linux::BoolType::typeName, mlir_ts::BooleanType::get(ctx)}, diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 112f6124a..b64b8158a 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -6114,6 +6114,13 @@ class MLIRGenImpl builder.getI32IntegerAttr((int)opCode), leftExpressionValue, rightExpressionValue); + // `>>>` yields an unsigned 32-bit value; left as a signless i32 it widened to a number + // as signed, and `-1 >>> 0` read -1 instead of 4294967295 + if (opCode == SyntaxKind::GreaterThanGreaterThanGreaterThanToken && result.getType() == builder.getI32Type()) + { + result = builder.create(location, builder.getIntegerType(32, /*isSigned=*/false), result); + } + // `+` on strings is the one arithmetic operator that allocates: it becomes // `ts.StringConcat`, which builds a new string. A receiver takes that reference // over; `("a" + b).length`, where there is no receiver, gives it back at the end of diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index e5d4088fc..bb094898e 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -393,6 +393,7 @@ tslang_add_test(NAME test-compile-00-throw-number-untyped-catch COMMAND test-run tslang_add_test(NAME test-compile-00-catch-untyped-value COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00catch_untyped_value.ts") tslang_add_test(NAME test-compile-00-throw-bool-bigint COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_bool_bigint.ts") tslang_add_test(NAME test-compile-00-throw-f32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_f32.ts") +tslang_add_test(NAME test-compile-00-throw-u32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_u32.ts") tslang_add_test(NAME test-compile-00-cast-to-f32 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00cast_to_f32.ts") tslang_add_test(NAME test-compile-00-instanceof-namespace-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof_namespace_narrowing.ts") tslang_add_test(NAME test-compile-00-using-nested-scopes COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_nested_scopes.ts") @@ -823,6 +824,7 @@ tslang_add_test(NAME test-jit-00-throw-number-untyped-catch COMMAND test-runner tslang_add_test(NAME test-jit-00-catch-untyped-value COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00catch_untyped_value.ts") tslang_add_test(NAME test-jit-00-throw-bool-bigint COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_bool_bigint.ts") tslang_add_test(NAME test-jit-00-throw-f32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_f32.ts") +tslang_add_test(NAME test-jit-00-throw-u32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00throw_u32.ts") tslang_add_test(NAME test-jit-00-cast-to-f32 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00cast_to_f32.ts") tslang_add_test(NAME test-jit-00-instanceof-namespace-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00instanceof_namespace_narrowing.ts") tslang_add_test(NAME test-jit-00-using-nested-scopes COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00using_nested_scopes.ts") @@ -1722,6 +1724,7 @@ set(TSLANG_CORPUS 00throw_in_catch.ts 00throw_bool_bigint.ts 00throw_f32.ts + 00throw_u32.ts 00throw_inlined.ts 00throw_number_untyped_catch.ts 00to_primitive_ops.ts diff --git a/tslang/test/tester/tests/00throw_u32.ts b/tslang/test/tester/tests/00throw_u32.ts new file mode 100644 index 000000000..f850c0f7e --- /dev/null +++ b/tslang/test/tester/tests/00throw_u32.ts @@ -0,0 +1,97 @@ +// Throwing and catching a u32. +// +// A thrown u32 described itself as a signed `int` (`.H` on Windows, `_ZTIi` on Linux), so every +// reader took its top bit for a sign: `catch (e: number)` and an untyped catch both saw +// 0xFFFFFFFF as -1. On Windows the widening and boxing thunks were also shared by name with an +// `int` throw, so a module throwing both used whichever was built first. It is now thrown as a +// C++ `unsigned int` (`.I` / `_ZTIj`), with records and thunks of its own. +// +// This module throws both, on purpose. The values are checked by sign, which cannot mistake -1 +// for 0xFFFFFFFF - an equality test against 4294967295 passed on the broken build. + +const big: u32 = 0xFFFFFFFF; + +function typedU32() { + let r: u32 = 0; + try { + throw big; + } catch (e: u32) { + r = e; + } + + return r; +} + +function untypedU32() { + let r: number = 0; + try { + throw big; + } catch (e) { + r = e; + } + + return r; +} + +function u32AsNumber() { + let r: number = 0; + try { + throw big; + } catch (e: number) { + r = e; + } + + return r; +} + +function s32AsNumber() { + let r: number = 0; + try { + throw -1; + } catch (e: number) { + r = e; + } + + return r; +} + +function untypedS32() { + let r: s32 = 0; + try { + throw -1; + } catch (e) { + r = e; + } + + return r; +} + +function s32PassesU32Catch() { + let r: number = 0; + try { + try { + throw -2; + } catch (e: u32) { + r = 1; + } + } catch (e2: number) { + r = e2; + } + + return r; +} + +function main() { + // the conversion every reader below goes through: sitofp made it -1 + const widened: number = big; + assert(widened > 0, "u32 to number is unsigned"); + + assert(typedU32() == big, "catch (e: u32) catches a thrown u32"); + assert(untypedU32() > 0, "an untyped catch reads a thrown u32 unsigned"); + assert(u32AsNumber() > 0, "catch (e: number) widens a thrown u32 unsigned"); + assert(s32AsNumber() < 0, "catch (e: number) still widens a thrown s32 signed"); + assert(untypedS32() < 0, "an untyped catch still reads a thrown s32 signed"); + assert(s32PassesU32Catch() == -2, "a thrown s32 passes a u32 catch"); + + print("done."); +}