From edde51cee700317670725eba5fabeab4df933e5a Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Thu, 15 Oct 2026 22:51:34 +0200 Subject: [PATCH 1/2] fix(python): emit widening integer casts --- src/Fable.Transforms/Python/Fable2Python.Transforms.fs | 8 ++++++++ tests/Python/TestArithmetic.fs | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs index c445abfde4..180e9855e1 100644 --- a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs +++ b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs @@ -382,6 +382,14 @@ let transformCast (com: IPythonCompiler) (ctx: Context) t e : Expression * State let value, stmts = com.TransformAsExpr(ctx, e) libCall com ctx None "core" "int32" [ value ], stmts | _ -> com.TransformAsExpr(ctx, e) + // The other integer widths are wrapper classes too, so `uint32 (x: byte)` needs the same explicit constructor call + | Fable.Number((Int8 | UInt8 | Int16 | UInt16 | UInt32) as kind, _), _ -> + match e.Type with + | Fable.Number(sourceKind, _) when sourceKind = kind -> com.TransformAsExpr(ctx, e) + | Fable.Number _ -> + let value, stmts = com.TransformAsExpr(ctx, e) + libCall com ctx None "core" (Py.Replacements.getIntTypeName kind) [ value ], stmts + | _ -> com.TransformAsExpr(ctx, e) // Float64 is a plain Python `float`, and the same reasoning applies: // `Replacements.toFloat` emits a bare `TypeCast` for every numeric source that is // not bigint/decimal/int64, so `float (x: float32)` would otherwise stay a diff --git a/tests/Python/TestArithmetic.fs b/tests/Python/TestArithmetic.fs index 2cba8c54c2..65e947babd 100644 --- a/tests/Python/TestArithmetic.fs +++ b/tests/Python/TestArithmetic.fs @@ -1514,6 +1514,16 @@ let ``test narrowing conversions of summed products work`` () = mulSum (id Int32.MinValue) (id Int32.MinValue) |> equal 0u mulSum (id 99991) (id 99991) |> equal 2816530978u +let private widenByte (x: byte) = uint32 x +let private widenUInt16 (x: uint16) = uint32 x +let private widenSByte (x: sbyte) = int16 x + +[] +let ``test widening integer conversions compute at the target width`` () = + widenByte (id 200uy) * 300u |> equal 60000u + widenUInt16 (id 60000us) * 3u |> equal 180000u + widenSByte (id 100y) * 2s |> equal 200s + #if FABLE_COMPILER // An `[]` macro splices its arguments into the text verbatim, and may put one in From c4bc5069ab9bdda8dc8d2c862f013d955bd56ce7 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 15 Oct 2026 22:59:39 +0200 Subject: [PATCH 2/2] test(python): strengthen integer widening regressions --- tests/Python/TestArithmetic.fs | 10 ---------- tests/Python/TestConvert.fs | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/Python/TestArithmetic.fs b/tests/Python/TestArithmetic.fs index 65e947babd..2cba8c54c2 100644 --- a/tests/Python/TestArithmetic.fs +++ b/tests/Python/TestArithmetic.fs @@ -1514,16 +1514,6 @@ let ``test narrowing conversions of summed products work`` () = mulSum (id Int32.MinValue) (id Int32.MinValue) |> equal 0u mulSum (id 99991) (id 99991) |> equal 2816530978u -let private widenByte (x: byte) = uint32 x -let private widenUInt16 (x: uint16) = uint32 x -let private widenSByte (x: sbyte) = int16 x - -[] -let ``test widening integer conversions compute at the target width`` () = - widenByte (id 200uy) * 300u |> equal 60000u - widenUInt16 (id 60000us) * 3u |> equal 180000u - widenSByte (id 100y) * 2s |> equal 200s - #if FABLE_COMPILER // An `[]` macro splices its arguments into the text verbatim, and may put one in diff --git a/tests/Python/TestConvert.fs b/tests/Python/TestConvert.fs index 781b711289..a77a50fcaf 100644 --- a/tests/Python/TestConvert.fs +++ b/tests/Python/TestConvert.fs @@ -418,6 +418,26 @@ let private toIntFromInt16 (x: int16) = int x let private toFloatFromSingle (x: float32) = float x let private toFloatFromByte (x: byte) = float x let private toFloatFromInt (x: int) = float x +let private toInt16FromSByte (x: sbyte) = int16 x +let private toUInt16FromByte (x: byte) = uint16 x +let private toUInt32FromByte (x: byte) = uint32 x +let private toUInt32FromUInt16 (x: uint16) = uint32 x + +[] +let ``test Widening between integer wrappers uses the target width`` () = + // Compare as int so wrapper equality cannot truncate the expected value to the source width. + toInt16FromSByte 100y <<< 1 |> int |> equal 200 + toInt16FromSByte -100y <<< 1 |> int |> equal -200 + toUInt16FromByte 200uy <<< 1 |> int |> equal 400 + toUInt32FromByte 200uy <<< 1 |> int |> equal 400 + toUInt32FromUInt16 60000us <<< 1 |> int |> equal 120000 + +[] +let ``test Widening between integer wrappers has the target runtime type`` () = + box (toInt16FromSByte 5y) :? int16 |> equal true + box (toUInt16FromByte 5uy) :? uint16 |> equal true + box (toUInt32FromByte 5uy) :? uint32 |> equal true + box (toUInt32FromUInt16 5us) :? uint32 |> equal true [] let ``test Widening to int does not keep the source width`` () =