Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JS/TS] Fix decimal to char conversion checks #4054

Merged
merged 3 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading