diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 4345cca8f6..019713e51f 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -155,12 +155,6 @@ let makeRefFromMutableFunc com ctx r t (value: Expr) = makeRefCell com r t [ getter; setter ] -let toChar (arg: Expr) = - match arg.Type with - | Char -> arg - | String -> TypeCast(arg, Char) - | _ -> Helper.GlobalCall("String", Char, [ arg ], memb = "fromCharCode") - let toString com (ctx: Context) r (args: Expr list) = match args with | [] -> @@ -232,19 +226,44 @@ let needToCast fromKind toKind = /// Conversions to floating point let toFloat com (ctx: Context) r targetType (args: Expr list) : Expr = - match args.Head.Type with - | Char -> Helper.InstanceCall(args.Head, "charCodeAt", Int32.Number, [ makeIntConst 0 ]) - | String -> Helper.LibCall(com, "Double", "parse", targetType, args) - | Number(kind, _) -> - match kind with - | Decimal -> Helper.LibCall(com, "Decimal", "toNumber", targetType, args) - | BigIntegers _ -> Helper.LibCall(com, "BigInt", "toFloat64", targetType, args) - | _ -> TypeCast(args.Head, targetType) - | _ -> + let warn () = addWarning com ctx.InlinePath r "Cannot make conversion because source type is unknown" TypeCast(args.Head, targetType) + let convertType (typ: Type) = + match typ with + | Char -> Helper.InstanceCall(args.Head, "charCodeAt", Int32.Number, [ makeIntConst 0 ]) + | String -> Helper.LibCall(com, "Double", "parse", targetType, args) + | Number(kind, _) -> + match kind with + | Decimal -> Helper.LibCall(com, "Decimal", "toNumber", targetType, args) + | BigIntegers _ -> + let coreMember = + match targetType with + | Number(Float16, _) -> "toFloat16" + | Number(Float32, _) -> "toFloat32" + | Number(Float64, _) + | _ -> "toFloat64" + + Helper.LibCall(com, "BigInt", coreMember, targetType, args) + | _ -> TypeCast(args.Head, targetType) + | _ -> + addWarning com ctx.InlinePath r "Cannot make conversion because source type is unknown" + + TypeCast(args.Head, targetType) + + match args.Head.Type with + | DeclaredType(entityRef, genericArgs) -> + if + entityRef.FullName = "System.Nullable`1" + && entityRef.Path = CoreAssemblyName "System.Runtime" + then + convertType genericArgs.Head + else + warn () + | _ -> convertType args.Head.Type + let toDecimal com (ctx: Context) r targetType (args: Expr list) : Expr = match args.Head.Type with | Char -> @@ -339,6 +358,19 @@ let toInt com (ctx: Context) r targetType (args: Expr list) = addWarning com ctx.InlinePath r "Cannot make conversion because source type is unknown" TypeCast(args.Head, targetType) +let toChar com (ctx: Context) r targetType (args: Expr list) = + match args.Head.Type with + | Char -> args.Head + | String -> TypeCast(args.Head, Char) + // We need to convert BigInt to number first + | Number(Int64, _) + | Number(UInt64, _) -> + let valueToNumber = toInt com ctx None Int32.Number [ args.Head ] + + Helper.GlobalCall("String", Char, [ valueToNumber ], memb = "fromCharCode") + + | _ -> Helper.GlobalCall("String", Char, [ args.Head ], memb = "fromCharCode") + let round com (args: Expr list) = match args.Head.Type with | Number(Decimal, _) -> @@ -370,7 +402,8 @@ 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 + |> List.singleton + |> toChar com ctx r t let truncateUnsigned operation = // see #1550 match t with @@ -1206,7 +1239,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 |> Some | "ToString", _ -> toString com ctx r args |> Some | "CreateSequence", [ xs ] -> TypeCast(xs, t) |> Some | ("CreateDictionary" | "CreateReadOnlyDictionary"), [ arg ] -> makeDictionary com ctx r t arg |> Some @@ -2195,12 +2228,22 @@ let results (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr o Helper.LibCall(com, "Result", meth, t, args, i.SignatureArgTypes, genArgs = i.GenericArgs, ?loc = r) ) -let nullables (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = +let nullables (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg with | ".ctor", None -> List.tryHead args // | "get_Value", Some c -> Get(c, OptionValue, t, r) |> Some // Get(OptionValue) doesn't do a null check | "get_Value", Some c -> Helper.LibCall(com, "Option", "value", t, [ c ], ?loc = r) |> Some | "get_HasValue", Some c -> Test(c, OptionTest true, r) |> Some + | "op_Explicit", _ -> + match t with + | Number(kind, _) -> + match kind with + | BigIntegers _ -> toLong com ctx r t args |> Some + | Integers _ -> toInt com ctx r t args |> Some + | Floats _ -> toFloat com ctx r t args |> Some + | Decimal -> toDecimal com ctx r t args |> Some + | _ -> None + | _ -> None | _ -> None // See fable-library-ts/Option.ts for more info on how options behave in Fable runtime @@ -2451,6 +2494,11 @@ 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 -> + let decimalToNumber = + Helper.LibCall(com, "Decimal", "toNumber", UInt16.Number, args) + + Some(Helper.GlobalCall("String", Char, [ decimalToNumber ], memb = "fromCharCode")) | _ -> None | ("Ceiling" | "Floor" | "Round" | "Truncate" | "Min" | "Max" | "MinMagnitude" | "MaxMagnitude" | "Clamp" | "Add" | "Subtract" | "Multiply" | "Divide" | "Remainder" | "Negate" as meth), _ -> @@ -2915,7 +2963,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 |> Some | "ToString" -> toString com ctx r args |> Some | "ToBase64String" | "FromBase64String" -> @@ -3953,6 +4001,13 @@ let makeMethodInfo com r (name: string) (parameters: (string * Type) list) (retu Helper.LibCall(com, "Reflection", "MethodInfo", t, args, isConstructor = true, ?loc = r) +let linqNullableModule (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = + match i.CompiledName with + | "ToFloat32" + | "ToFloat" -> toFloat com ctx r t args |> Some + | "ToChar" -> toChar com ctx r t args |> Some + | _ -> None + let tryField com returnTyp ownerTyp fieldName = match ownerTyp, fieldName with | Number(Decimal, _), _ -> Helper.LibValue(com, "Decimal", "get_" + fieldName, returnTyp) |> Some @@ -4110,6 +4165,7 @@ let private replacedModules = "Microsoft.FSharp.Control.ObservableModule", observable Types.type_, types "System.Reflection.TypeInfo", types + "Microsoft.FSharp.Linq.NullableModule", linqNullableModule ] let tryCall (com: ICompiler) (ctx: Context) r t (info: CallInfo) (thisArg: Expr option) (args: Expr list) = diff --git a/src/fable-library-ts/BigInt.ts b/src/fable-library-ts/BigInt.ts index a0c5f0aec6..5decc7cd2e 100644 --- a/src/fable-library-ts/BigInt.ts +++ b/src/fable-library-ts/BigInt.ts @@ -143,7 +143,7 @@ export function toUInt128(x: bigint): uint128 { return BigInt.asUintN(128, x); } export function toNativeInt(x: bigint): nativeint { return BigInt.asIntN(64, x); } export function toUNativeInt(x: bigint): unativeint { return BigInt.asUintN(64, x); } -export function toFloat16(x: bigint): float32 { return Number(x); } +export function toFloat16(x: bigint): float16 { return Number(x); } export function toFloat32(x: bigint): float32 { return Number(x); } export function toFloat64(x: bigint): float64 { return Number(x); } diff --git a/src/quicktest/QuickTest.fs b/src/quicktest/QuickTest.fs index 7bd62ccbf2..335ddd09af 100644 --- a/src/quicktest/QuickTest.fs +++ b/src/quicktest/QuickTest.fs @@ -11,90 +11,85 @@ open Fable.Core open Fable.Core.JsInterop open Fable.Core.Testing -let log (o: obj) = JS.console.log (o) -// printfn "%A" o - let equal expected actual = let areEqual = expected = actual - printfn "%A = %A > %b" expected actual areEqual + printfn "%A = %A > 2dd%b" expected actual areEqual if not areEqual then failwithf "[ASSERT ERROR] Expected %A but got %A" expected actual -let throwsError (expected: string) (f: unit -> 'a) : unit = - let success = - try - f () |> ignore - true - with e -> - if not <| String.IsNullOrEmpty(expected) then - equal e.Message expected - - false - // TODO better error messages - equal false success - -let testCase (msg: string) f : unit = - try - printfn "%s" msg - f () - with ex -> - printfn "%s" ex.Message - - if - ex.Message <> null - && ex.Message.StartsWith("[ASSERT ERROR]", StringComparison.Ordinal) |> not - then - printfn "%s" (ex.StackTrace ??= "") - - printfn "" - -let testCaseAsync msg f = - testCase - msg - (fun () -> - async { - try - do! f () - with ex -> - printfn "%s" ex.Message - - if - ex.Message <> null - && ex.Message.StartsWith("[ASSERT ERROR]", StringComparison.Ordinal) |> not - then - printfn "%s" (ex.StackTrace ??= "") - } - |> Async.StartImmediate - ) - -let throwsAnyError (f: unit -> 'a) : unit = - let success = - try - f () |> ignore - true - with e -> - printfn "Got expected error: %s" e.Message - false - - if success then - printfn "[ERROR EXPECTED]" - -let measureTime (f: unit -> unit) : unit = - emitJsStatement - () - """ - //js - const startTime = process.hrtime(); - f(); - const elapsed = process.hrtime(startTime); - console.log("Ms:", elapsed[0] * 1e3 + elapsed[1] / 1e6); - //!js -""" - -printfn "Running quick tests..." - -// Write here your unit test, you can later move it -// to Fable.Tests project. For example: -// testCase "Addition works" <| fun () -> -// 2 + 2 |> equal 4 +// let x = 12 +// let nullableX = Nullable x + +// let x2 = 12 +// let nullableX2 = Nullable x + +// let mutable a = Nullable 42 +// a <- Nullable() + +// open System +open FSharp.Linq + +// let nullableFloat = Nullable 10.0 +// let standardString = float nullableFloat +// +// // let standardString = string nullableString +// equal (Nullable.float (Nullable 1uy)) (Nullable 1.0) +// equal (Nullable.float (Nullable 2y)) (Nullable 2.0) +// equal (Nullable.float (Nullable 3s)) (Nullable 3.0) +// equal (Nullable.float (Nullable 4us)) (Nullable 4.0) +// equal (Nullable.float (Nullable 5)) (Nullable 5.0) +// equal (Nullable.float (Nullable 6u)) (Nullable 6.0) +// equal (Nullable.float (Nullable 7L)) (Nullable 7.0) +// equal (Nullable.float (Nullable 8UL)) (Nullable 8.0) +// equal (Nullable.float (Nullable 9m)) (Nullable 9.0) +// equal (Nullable.float (Nullable 10.0)) (Nullable 10.0) +// equal (Nullable.float (Nullable 11.0f)) (Nullable 11.0) +// equal (Nullable.float (Nullable 'c')) (Nullable 99.0) +// equal (Nullable.float (Nullable.enum(Nullable 2 ): Nullable)) (Nullable 2.0) +// +// +// equal (Nullable.float32 (Nullable 1uy)) (Nullable 1.0f) +// equal (Nullable.float32 (Nullable 2y)) (Nullable 2.0f) +// equal (Nullable.float32 (Nullable 3s)) (Nullable 3.0f) +// equal (Nullable.float32 (Nullable 4us)) (Nullable 4.0f) +// equal (Nullable.float32 (Nullable 5)) (Nullable 5.0f) +// equal (Nullable.float32 (Nullable 6u)) (Nullable 6.0f) +// equal (Nullable.float32 (Nullable 7L)) (Nullable 7.0f) +// equal (Nullable.float32 (Nullable 8UL)) (Nullable 8.0f) +// equal (Nullable.float32 (Nullable 9m)) (Nullable 9.0f) +// equal (Nullable.float32 (Nullable 10.0)) (Nullable 10.0f) +// equal (Nullable.float32 (Nullable 11.0f)) (Nullable 11.0f) +// equal (Nullable.float32 (Nullable 'c')) (Nullable 99.0f) +// equal (Nullable.float32 (Nullable.enum(Nullable 2 ): Nullable)) (Nullable 2.0f) +// +// let float32Value = float32 2L +// // float32(1y) |> equal 2.0f + +// equal (Nullable.char (Nullable 49uy)) (Nullable '1') +// equal (Nullable.char (Nullable 50y)) (Nullable '2') +// equal (Nullable.char (Nullable 51s)) (Nullable '3') +// equal (Nullable.char (Nullable 52us)) (Nullable '4') +// equal (Nullable.char (Nullable 53)) (Nullable '5') +// equal (Nullable.char (Nullable 54u)) (Nullable '6') +// equal (Nullable.char (Nullable 55L)) (Nullable '7') +// equal (Nullable.char (Nullable 56UL)) (Nullable '8') +// equal (Nullable.char (Nullable 57m)) (Nullable '9') +// equal (Nullable.char (Nullable 58.0)) (Nullable ':') +// equal (Nullable.char (Nullable 59.0f)) (Nullable ';') +// equal (Nullable.char (Nullable 'a')) (Nullable 'a') +// + +// let c = char 57m +// +// let test : decimal = 57m +// +// let t : char = Decimal.op_Explicit 57m + +let test2 = uint16 655356 + +let test = 65535us + +let te = char 57m + +printfn "%A" te diff --git a/src/quicktest/QuickTest.fsproj b/src/quicktest/QuickTest.fsproj index 42c507a85e..5fe4061810 100644 --- a/src/quicktest/QuickTest.fsproj +++ b/src/quicktest/QuickTest.fsproj @@ -4,6 +4,7 @@ net9.0 Major Preview + enable diff --git a/tests/Js/Main/Fable.Tests.fsproj b/tests/Js/Main/Fable.Tests.fsproj index 36670c2b88..d45e04a854 100644 --- a/tests/Js/Main/Fable.Tests.fsproj +++ b/tests/Js/Main/Fable.Tests.fsproj @@ -81,6 +81,7 @@ + diff --git a/tests/Js/Main/Main.fs b/tests/Js/Main/Main.fs index 5882ee29fc..ac9fe82caa 100644 --- a/tests/Js/Main/Main.fs +++ b/tests/Js/Main/Main.fs @@ -52,6 +52,7 @@ let allTests = UnionTypes.tests Uri.tests ListCollector.tests + Nullable.tests |] #if FABLE_COMPILER diff --git a/tests/Js/Main/NullableTests.fs b/tests/Js/Main/NullableTests.fs new file mode 100644 index 0000000000..bbb0780c80 --- /dev/null +++ b/tests/Js/Main/NullableTests.fs @@ -0,0 +1,106 @@ +module Fable.Tests.Nullable + +open Util.Testing +open System + +module Conversions = + + open FSharp.Linq + + let tests = + testList "Conversions" [ + testCase "Nullable.char" <| fun () -> + equal (Nullable.char (Nullable 49uy)) (Nullable '1') + equal (Nullable.char (Nullable 50y)) (Nullable '2') + equal (Nullable.char (Nullable 51s)) (Nullable '3') + equal (Nullable.char (Nullable 52us)) (Nullable '4') + equal (Nullable.char (Nullable 53)) (Nullable '5') + equal (Nullable.char (Nullable 54u)) (Nullable '6') + equal (Nullable.char (Nullable 55L)) (Nullable '7') + equal (Nullable.char (Nullable 56UL)) (Nullable '8') + equal (Nullable.char (Nullable 57m)) (Nullable '9') + equal (Nullable.char (Nullable 58.0)) (Nullable ':') + equal (Nullable.char (Nullable 59.0f)) (Nullable ';') + equal (Nullable.char (Nullable 'a')) (Nullable 'a') + // equal (Nullable.float (Nullable.enum(Nullable 2 ): Nullable)) (Nullable 2.0) + + testCase "Nullable.decimal" <| fun () -> + () + + testCase "Nullable.double" <| fun () -> + () + + testCase "Nullable.enum" <| fun () -> + () + + testCase "Nullable.float" <| fun () -> + equal (Nullable.float (Nullable 1uy)) (Nullable 1.0) + equal (Nullable.float (Nullable 2y)) (Nullable 2.0) + equal (Nullable.float (Nullable 3s)) (Nullable 3.0) + equal (Nullable.float (Nullable 4us)) (Nullable 4.0) + equal (Nullable.float (Nullable 5)) (Nullable 5.0) + equal (Nullable.float (Nullable 6u)) (Nullable 6.0) + equal (Nullable.float (Nullable 7L)) (Nullable 7.0) + equal (Nullable.float (Nullable 8UL)) (Nullable 8.0) + equal (Nullable.float (Nullable 9m)) (Nullable 9.0) + equal (Nullable.float (Nullable 10.0)) (Nullable 10.0) + equal (Nullable.float (Nullable 11.0f)) (Nullable 11.0) + equal (Nullable.float (Nullable 'c')) (Nullable 99.0) + // equal (Nullable.float (Nullable.enum(Nullable 2 ): Nullable)) (Nullable 2.0) + + testCase "Nullable.float32" <| fun () -> + equal (Nullable.float32 (Nullable 1uy)) (Nullable 1.0f) + equal (Nullable.float32 (Nullable 2y)) (Nullable 2.0f) + equal (Nullable.float32 (Nullable 3s)) (Nullable 3.0f) + equal (Nullable.float32 (Nullable 4us)) (Nullable 4.0f) + equal (Nullable.float32 (Nullable 5)) (Nullable 5.0f) + equal (Nullable.float32 (Nullable 6u)) (Nullable 6.0f) + equal (Nullable.float32 (Nullable 7L)) (Nullable 7.0f) + equal (Nullable.float32 (Nullable 8UL)) (Nullable 8.0f) + equal (Nullable.float32 (Nullable 9m)) (Nullable 9.0f) + equal (Nullable.float32 (Nullable 10.0)) (Nullable 10.0f) + equal (Nullable.float32 (Nullable 11.0f)) (Nullable 11.0f) + equal (Nullable.float32 (Nullable 'c')) (Nullable 99.0f) + // equal (Nullable.float32 (Nullable.enum(Nullable 2 ): Nullable)) (Nullable 2.0f) + + testCase "Nullable.int" <| fun () -> + () + + testCase "Nullable.int16" <| fun () -> + () + + testCase "Nullable.int32" <| fun () -> + () + + testCase "Nullable.int64" <| fun () -> + () + + testCase "Nullable.int8" <| fun () -> + () + + testCase "Nullable.sbyte" <| fun () -> + () + + testCase "Nullable.single" <| fun () -> + () + + testCase "Nullable.uint" <| fun () -> + () + + testCase "Nullable.uint16" <| fun () -> + () + + testCase "Nullable.uint32" <| fun () -> + () + + testCase "Nullable.uint64" <| fun () -> + () + + testCase "Nullable.uint8" <| fun () -> + () + ] + +let tests = + testList "Nullable" [ + Conversions.tests + ]