Skip to content

Commit

Permalink
[JS/TS] Fix decimal to char conversion checks (#4054)
Browse files Browse the repository at this point in the history
* [JS/TS] Fix `decimal` to `char` conversion checks

* simplify the test a little

* add placeholder tests for Python and Rust
  • Loading branch information
MangelMaxime authored Feb 19, 2025
1 parent 5271a71 commit 8e69e80
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* [JS/TS] Fix #4025: No reflection info for pojos (by @alfonsogarciacaro)
* [JS/TS] Fix #4049: decimal/bigint to integer conversion checks (by @ncave)
* [JS/TS] Fix `decimal` to `char` conversion checks (by @ManngelMaxime)

## 5.0.0-alpha.10 - 2025-02-16

Expand Down
1 change: 1 addition & 0 deletions src/Fable.Compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* [JS/TS] Fix #4025: No reflection info for pojos (by @alfonsogarciacaro)
* [JS/TS] Fix #4049: decimal/bigint to integer conversion checks (by @ncave)
* [JS/TS] Fix `decimal` to `char` conversion checks (by @ManngelMaxime)

## 5.0.0-alpha.10 - 2025-02-16

Expand Down
10 changes: 6 additions & 4 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,11 @@ let makeRefFromMutableFunc com ctx r t (value: Expr) =

makeRefCell com r t [ getter; setter ]

let toChar (arg: Expr) =
let toChar com (ctx: Context) r targetType (arg: Expr) =
match arg.Type with
| Char -> arg
| String -> TypeCast(arg, Char)
| Number(Decimal, _) -> Helper.LibCall(com, "Decimal", "toChar", Char, [ arg ], ?loc = r)
| _ -> Helper.GlobalCall("String", Char, [ arg ], memb = "fromCharCode")

let toString com (ctx: Context) r (args: Expr list) =
Expand Down Expand Up @@ -380,7 +381,7 @@ let applyOp (com: ICompiler) (ctx: Context) r t opName (args: Expr list) =
let toUInt16 e = toInt com ctx None UInt16.Number [ e ]

Operation(Binary(op, toUInt16 left, toUInt16 right), Tags.empty, UInt16.Number, r)
|> toChar
|> toChar com ctx r t

let truncateUnsigned operation = // see #1550
match t with
Expand Down Expand Up @@ -1216,7 +1217,7 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o
| ("ToInt64" | "ToUInt64" | "ToIntPtr" | "ToUIntPtr"), _ -> toLong com ctx r t args |> Some
| ("ToSingle" | "ToDouble"), _ -> toFloat com ctx r t args |> Some
| "ToDecimal", _ -> toDecimal com ctx r t args |> Some
| "ToChar", _ -> toChar args.Head |> Some
| "ToChar", _ -> toChar com ctx r t args.Head |> Some
| "ToString", _ -> toString com ctx r args |> Some
| "CreateSequence", [ xs ] -> TypeCast(xs, t) |> Some
| ("CreateDictionary" | "CreateReadOnlyDictionary"), [ arg ] -> makeDictionary com ctx r t arg |> Some
Expand Down Expand Up @@ -2461,6 +2462,7 @@ let decimals (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg:
| Floats _ -> toFloat com ctx r t args |> Some
| Decimal -> toDecimal com ctx r t args |> Some
| _ -> None
| Char -> toChar com ctx r t args.Head |> Some
| _ -> None
| ("Ceiling" | "Floor" | "Round" | "Truncate" | "Min" | "Max" | "MinMagnitude" | "MaxMagnitude" | "Clamp" | "Add" | "Subtract" | "Multiply" | "Divide" | "Remainder" | "Negate" as meth),
_ ->
Expand Down Expand Up @@ -2925,7 +2927,7 @@ let convert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (
| "ToSingle"
| "ToDouble" -> toFloat com ctx r t args |> Some
| "ToDecimal" -> toDecimal com ctx r t args |> Some
| "ToChar" -> toChar args.Head |> Some
| "ToChar" -> toChar com ctx r t args.Head |> Some
| "ToString" -> toString com ctx r args |> Some
| "ToBase64String"
| "FromBase64String" ->
Expand Down
3 changes: 3 additions & 0 deletions src/fable-library-ts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

* [JS/TS] Fix #4049: decimal/bigint to integer conversion checks (by @ncave)
* [JS/TS] Fix `decimal` to `char` conversion checks (by @ManngelMaxime)

## 2.0.0-beta.1 - 2025-02-16

* Compiled with Fable 5.0.0-alpha.10
Expand Down
8 changes: 8 additions & 0 deletions src/fable-library-ts/Decimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ export function toNumber(x: Decimal): number {
return +x;
}

export function toChar(x: Decimal): string {
const n = toNumber(x);
if (n < 0 || n > 65535 || isNaN(n)) {
throw new Error("Value was either too large or too small for a character.");
}
return String.fromCharCode(n);
}

export function toInt8(x: Decimal): int8 { return bigInt.toInt8(fromDecimal(x)); }
export function toUInt8(x: Decimal): uint8 { return bigInt.toUInt8(fromDecimal(x)); }
export function toInt16(x: Decimal): int16 { return bigInt.toInt16(fromDecimal(x)); }
Expand Down
6 changes: 6 additions & 0 deletions tests/Js/Main/ConvertTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,10 @@ let tests =
// System.Decimal
//-------------------------------------

testCase "Decimal.ToChar works" <| fun () ->
let value = 'A'
char (decimal (int value)) |> equal value

testCase "Decimal.ToSByte works" <| fun () ->
let value = 0x02y
sbyte (decimal (int32 value)) |> equal value
Expand Down Expand Up @@ -1107,6 +1111,7 @@ let tests =

testCase "Decimal to integer conversions are min-checked" <| fun () ->
let x = Decimal.MinValue
throwsAnyError (fun () -> char x)
throwsAnyError (fun () -> int8 x)
throwsAnyError (fun () -> uint8 x)
throwsAnyError (fun () -> int16 x)
Expand All @@ -1120,6 +1125,7 @@ let tests =

testCase "Decimal to integer conversions are max-checked" <| fun () ->
let x = Decimal.MaxValue
throwsAnyError (fun () -> char x)
throwsAnyError (fun () -> int8 x)
throwsAnyError (fun () -> uint8 x)
throwsAnyError (fun () -> int16 x)
Expand Down
7 changes: 7 additions & 0 deletions tests/Python/TestConvert.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,11 @@ let ``test BitConverter.ToString 3 works`` () =
// System.Decimal
//-------------------------------------

// [<Fact>]
// let ``Decimal.ToChar works`` () =
// let value = 'A'
// char (decimal (int value)) |> equal value

[<Fact>]
let ``test Decimal.ToSByte works`` () =
let value = 0x02y
Expand Down Expand Up @@ -1209,6 +1214,7 @@ let ``test Decimal.ToDouble works`` () =
// [<Fact>]
// let ``test Decimal to integer conversions are min-checked`` () =
// let x = Decimal.MinValue
// throwsAnyError (fun () -> char x)
// throwsAnyError (fun () -> int8 x)
// throwsAnyError (fun () -> uint8 x)
// throwsAnyError (fun () -> int16 x)
Expand All @@ -1223,6 +1229,7 @@ let ``test Decimal.ToDouble works`` () =
// [<Fact>]
// let ``test Decimal to integer conversions are max-checked`` () =
// let x = Decimal.MaxValue
// throwsAnyError (fun () -> char x)
// throwsAnyError (fun () -> int8 x)
// throwsAnyError (fun () -> uint8 x)
// throwsAnyError (fun () -> int16 x)
Expand Down
7 changes: 7 additions & 0 deletions tests/Rust/tests/src/ConvertTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,11 @@ let ``BitConverter.ToString 3 works`` () =
// System.Decimal
//-------------------------------------

// [<Fact>]
// let ``Decimal.ToChar works`` () =
// let value = 'A'
// char (decimal (int value)) |> equal value

[<Fact>]
let ``Decimal.ToSByte works`` () =
let value = 0x02y
Expand Down Expand Up @@ -1257,6 +1262,7 @@ let ``Decimal.ToDouble works`` () =
[<Fact>]
let ``Decimal to integer conversions are min-checked`` () =
let x = Decimal.MinValue
// throwsAnyError (fun () -> char x)
throwsAnyError (fun () -> int8 x)
throwsAnyError (fun () -> uint8 x)
throwsAnyError (fun () -> int16 x)
Expand All @@ -1271,6 +1277,7 @@ let ``Decimal to integer conversions are min-checked`` () =
[<Fact>]
let ``Decimal to integer conversions are max-checked`` () =
let x = Decimal.MaxValue
// throwsAnyError (fun () -> char x)
throwsAnyError (fun () -> int8 x)
throwsAnyError (fun () -> uint8 x)
throwsAnyError (fun () -> int16 x)
Expand Down

0 comments on commit 8e69e80

Please sign in to comment.