From a993f19604d1d3bf18f625bdb470620ea2c1aff2 Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Tue, 27 Oct 2026 12:07:05 +0100 Subject: [PATCH] feat(js/ts/python/dart): support Nullable conversions --- src/Fable.Transforms/Dart/Replacements.fs | 38 +++++ src/Fable.Transforms/Python/Replacements.fs | 40 +++++ src/Fable.Transforms/Replacements.fs | 30 ++++ tests/Dart/main.dart | 2 + tests/Dart/src/Fable.Tests.Dart.fsproj | 1 + tests/Dart/src/NullableTests.fs | 60 +++++++ tests/Js/Main/Fable.Tests.fsproj | 1 + tests/Js/Main/Main.fs | 1 + tests/Js/Main/NullableTests.fs | 170 ++++++++++++++++++++ tests/Python/Fable.Tests.Python.fsproj | 1 + tests/Python/TestNullable.fs | 72 +++++++++ 11 files changed, 416 insertions(+) create mode 100644 tests/Dart/src/NullableTests.fs create mode 100644 tests/Js/Main/NullableTests.fs create mode 100644 tests/Python/TestNullable.fs diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index 062403f731..66b24c4b58 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -2055,6 +2055,43 @@ let nullables (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg: | "get_HasValue", Some c -> makeEqOp r c (makeNull ()) BinaryUnequal |> Some | _ -> None +let nullableModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr option) (args: Expr list) = + let convert (targetType: Type) (value: Expr) = + match targetType with + | Char -> toChar com ctx r value + | Number((Float16 | Float32 | Float64), _) -> toFloat com ctx r targetType [ value ] + | Number(Decimal, _) -> toDecimal com ctx r targetType [ value ] + | Number _ -> toInt com ctx r targetType [ value ] + | _ -> TypeCast(value, targetType) + + match i.CompiledName.Replace("$W", ""), List.tryLast args, t with + | ("ToByte" | "ToUInt8" | "ToSByte" | "ToInt8" | "ToInt16" | "ToUInt16" | "ToInt" | "ToInt32" | "ToUInt" | "ToUInt32" | "ToInt64" | "ToUInt64" | "ToIntPtr" | "ToUIntPtr" | "ToSingle" | "ToFloat32" | "ToDouble" | "ToFloat" | "ToDecimal" | "ToChar" | "ToEnum"), + Some value, + Nullable(targetType, _) -> + match value with + | Value(Null _, _) -> makeNull () |> Some + | Value((NumberConstant _ | CharConstant _), _) -> convert targetType value |> Some + | _ -> + let sourceType = + match value.Type with + | Nullable(sourceType, _) -> sourceType + | sourceType -> sourceType + + let v = makeUniqueIdent com ctx value.Type "v" + + Let( + v, + value, + IfThenElse( + makeEqOp None (IdentExpr v) (makeNull ()) BinaryUnequal, + TypeCast(convert targetType (TypeCast(IdentExpr v, sourceType)), t), + makeNull (), + r + ) + ) + |> Some + | _ -> None + let options isStruct (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg with | "Some", _ -> NewOption(List.tryHead args, t.Generics.Head, isStruct) |> makeValue r |> Some @@ -4022,6 +4059,7 @@ let private replacedModules = "System.Math", operators "System.MathF", operators "Microsoft.FSharp.Core.Operators", operators + "Microsoft.FSharp.Linq.NullableModule", nullableModule "Microsoft.FSharp.Core.Operators.Checked", operators "Microsoft.FSharp.Core.Operators.Unchecked", unchecked "Microsoft.FSharp.Core.Operators.OperatorIntrinsics", intrinsicFunctions diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 694e9c69c7..3da6ef0dfd 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -2247,6 +2247,45 @@ let nullables (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg: | "get_HasValue", Some c -> Test(c, OptionTest true, r) |> Some | _ -> None +let nullableModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr option) (args: Expr list) = + let convert (targetType: Type) (value: Expr) = + match targetType with + | Char -> toChar com ctx r value + | Number(UInt64, _) -> toLong com ctx r true targetType [ value ] + | Number(Int64, _) -> toLong com ctx r false targetType [ value ] + | Number((Float16 | Float32 | Float64), _) -> toFloat com ctx r targetType [ value ] + | Number(Decimal, _) -> toDecimal com ctx r targetType [ value ] + | Number _ -> toInt com ctx r targetType [ value ] + | _ -> TypeCast(value, targetType) + + match i.CompiledName.Replace("$W", ""), List.tryLast args, t with + | ("ToByte" | "ToUInt8" | "ToSByte" | "ToInt8" | "ToInt16" | "ToUInt16" | "ToInt" | "ToInt32" | "ToUInt" | "ToUInt32" | "ToInt64" | "ToUInt64" | "ToIntPtr" | "ToUIntPtr" | "ToSingle" | "ToFloat32" | "ToDouble" | "ToFloat" | "ToDecimal" | "ToChar" | "ToEnum"), + Some value, + Nullable(targetType, _) -> + match value with + | Value(Null _, _) -> Value(Null t, None) |> Some + | Value((NumberConstant _ | CharConstant _), _) -> convert targetType value |> Some + | _ -> + let sourceType = + match value.Type with + | Nullable(sourceType, _) -> sourceType + | sourceType -> sourceType + + let v = makeUniqueIdent com ctx value.Type "v" + + Let( + v, + value, + IfThenElse( + Test(IdentExpr v, OptionTest true, None), + TypeCast(convert targetType (TypeCast(IdentExpr v, sourceType)), t), + Value(Null t, None), + r + ) + ) + |> Some + | _ -> None + // See fable-library-ts/Option.ts for more info on how options behave in Fable runtime let options isStruct (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg with @@ -4193,6 +4232,7 @@ let private replacedModules = "System.Math", operators "System.MathF", operators "Microsoft.FSharp.Core.Operators", operators + "Microsoft.FSharp.Linq.NullableModule", nullableModule "Microsoft.FSharp.Core.Operators.Checked", operators "Microsoft.FSharp.Core.Operators.Unchecked", unchecked "Microsoft.FSharp.Core.Operators.OperatorIntrinsics", intrinsicFunctions diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 0c696080db..cf7eccd048 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -2563,6 +2563,35 @@ let nullables (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg: | "get_HasValue", Some c -> Test(c, OptionTest true, r) |> Some | _ -> None +let nullableModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr option) (args: Expr list) = + let convert (targetType: Type) (value: Expr) = + match targetType with + | Char -> toChar com ctx r value + | Number(BigIntegers _, _) -> toLong com ctx r targetType [ value ] + | Number(Integers _, _) -> toInt com ctx r targetType [ value ] + | Number(Floats _, _) -> toFloat com ctx r targetType [ value ] + | Number(Decimal, _) -> toDecimal com ctx r targetType [ value ] + | _ -> TypeCast(value, targetType) + + match i.CompiledName.Replace("$W", ""), List.tryLast args, t with + | ("ToByte" | "ToUInt8" | "ToSByte" | "ToInt8" | "ToInt16" | "ToUInt16" | "ToInt" | "ToInt32" | "ToUInt" | "ToUInt32" | "ToInt64" | "ToUInt64" | "ToIntPtr" | "ToUIntPtr" | "ToSingle" | "ToFloat32" | "ToDouble" | "ToFloat" | "ToDecimal" | "ToChar" | "ToEnum"), + Some value, + Nullable(targetType, _) -> + match value with + | Value(Null _, _) -> Value(Null t, None) |> Some + | Value((NumberConstant _ | CharConstant _), _) -> convert targetType value |> Some + | _ -> + let sourceType = + match value.Type with + | Nullable(sourceType, _) -> sourceType + | sourceType -> sourceType + + let v = makeUniqueIdent com ctx sourceType "v" + let mapping = Delegate([ v ], convert targetType (IdentExpr v), None, Tags.empty) + + Helper.LibCall(com, "Option", "map", t, [ mapping; value ], ?loc = r) |> Some + | _ -> None + // See fable-library-ts/Option.ts for more info on how options behave in Fable runtime let options isStruct (com: ICompiler) (_: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg with @@ -4839,6 +4868,7 @@ let private replacedModules = "System.Math", operators "System.MathF", operators "Microsoft.FSharp.Core.Operators", operators + "Microsoft.FSharp.Linq.NullableModule", nullableModule "Microsoft.FSharp.Core.Operators.Checked", operators "Microsoft.FSharp.Core.Operators.Unchecked", unchecked "Microsoft.FSharp.Core.Operators.OperatorIntrinsics", intrinsicFunctions diff --git a/tests/Dart/main.dart b/tests/Dart/main.dart index 9149d8cba7..3a2855f4a6 100644 --- a/tests/Dart/main.dart +++ b/tests/Dart/main.dart @@ -14,6 +14,7 @@ import './src/ListTests.dart' as list; import './src/MapTests.dart' as map; import './src/MiscTests.dart' as misc; // import './src/NullnessTests.dart' as nullness; +import './src/NullableTests.dart' as nullable; import './src/OptionTests.dart' as option; import './src/QuotationTests.dart' as quotation; import './src/RecordTests.dart' as record; @@ -49,6 +50,7 @@ void main() { map.tests(); misc.tests(); // nullness.tests(); + nullable.tests(); option.tests(); quotation.tests(); record.tests(); diff --git a/tests/Dart/src/Fable.Tests.Dart.fsproj b/tests/Dart/src/Fable.Tests.Dart.fsproj index 4cbb0780d8..47da2d9ccf 100644 --- a/tests/Dart/src/Fable.Tests.Dart.fsproj +++ b/tests/Dart/src/Fable.Tests.Dart.fsproj @@ -24,6 +24,7 @@ + diff --git a/tests/Dart/src/NullableTests.fs b/tests/Dart/src/NullableTests.fs new file mode 100644 index 0000000000..1a8b13689b --- /dev/null +++ b/tests/Dart/src/NullableTests.fs @@ -0,0 +1,60 @@ +module Fable.Tests.Dart.Nullable + +open System +open Util +open FSharp.Linq + +let private none<'T when 'T: struct and 'T: (new: unit -> 'T) and 'T :> ValueType> = Nullable<'T>() + +let tests() = + testCase "Nullable.char works" <| fun () -> + Nullable.char (Nullable 49uy) |> equal (Nullable '1') + Nullable.char (Nullable 53) |> equal (Nullable '5') + Nullable.char (Nullable 55L) |> equal (Nullable '7') + Nullable.char (Nullable 58.0) |> equal (Nullable ':') + Nullable.char (Nullable 'a') |> equal (Nullable 'a') + Nullable.char none |> equal none + + testCase "Nullable.enum works" <| fun () -> + (Nullable.enum (Nullable 2): Nullable) |> equal (Nullable DayOfWeek.Tuesday) + (Nullable.enum none: Nullable) |> equal none + + testCase "Nullable.float and Nullable.float32 work" <| fun () -> + Nullable.float (Nullable 1uy) |> equal (Nullable 1.0) + Nullable.float (Nullable 5) |> equal (Nullable 5.0) + Nullable.float (Nullable 7L) |> equal (Nullable 7.0) + Nullable.float (Nullable 'c') |> equal (Nullable 99.0) + Nullable.float none |> equal none + Nullable.float32 (Nullable 5) |> equal (Nullable 5.0f) + Nullable.float32 (Nullable 10.0) |> equal (Nullable 10.0f) + Nullable.float32 none |> equal none + Nullable.double (Nullable 5) |> equal (Nullable 5.0) + Nullable.single (Nullable 9.5) |> equal (Nullable 9.5f) + + testCase "Nullable integer conversions work" <| fun () -> + Nullable.int (Nullable 1uy) |> equal (Nullable 1) + Nullable.int (Nullable 7L) |> equal (Nullable 7) + Nullable.int (Nullable 10.9) |> equal (Nullable 10) + Nullable.int (Nullable -11.9) |> equal (Nullable -11) + Nullable.int (Nullable 'c') |> equal (Nullable 99) + Nullable.int none |> equal none + Nullable.int16 (Nullable -7L) |> equal (Nullable -7s) + Nullable.int32 (Nullable 5L) |> equal (Nullable 5) + Nullable.int64 (Nullable -9.9) |> equal (Nullable -9L) + Nullable.int64 none |> equal none + Nullable.int8 (Nullable 9.9) |> equal (Nullable 9y) + Nullable.sbyte (Nullable -7.9) |> equal (Nullable -7y) + Nullable.byte (Nullable 5) |> equal (Nullable 5uy) + Nullable.uint8 (Nullable 7L) |> equal (Nullable 7uy) + Nullable.uint16 (Nullable 9.9) |> equal (Nullable 9us) + Nullable.uint (Nullable 5) |> equal (Nullable 5u) + Nullable.uint32 (Nullable 7L) |> equal (Nullable 7u) + Nullable.uint64 none |> equal none + + testCase "Nullable conversions evaluate their argument once" <| fun () -> + let mutable calls = 0 + let next () = + calls <- calls + 1 + Nullable 5 + Nullable.float (next ()) |> equal (Nullable 5.0) + calls |> equal 1 diff --git a/tests/Js/Main/Fable.Tests.fsproj b/tests/Js/Main/Fable.Tests.fsproj index ce2d68b963..1fbca90fc9 100644 --- a/tests/Js/Main/Fable.Tests.fsproj +++ b/tests/Js/Main/Fable.Tests.fsproj @@ -68,6 +68,7 @@ + diff --git a/tests/Js/Main/Main.fs b/tests/Js/Main/Main.fs index f42d889826..bcf67edab3 100644 --- a/tests/Js/Main/Main.fs +++ b/tests/Js/Main/Main.fs @@ -34,6 +34,7 @@ let allTests = Maps.tests Misc.tests Nullness.tests + Nullable.tests NestedAndRecursivePatternTests.tests Observable.tests Option.tests diff --git a/tests/Js/Main/NullableTests.fs b/tests/Js/Main/NullableTests.fs new file mode 100644 index 0000000000..553e519989 --- /dev/null +++ b/tests/Js/Main/NullableTests.fs @@ -0,0 +1,170 @@ +module Fable.Tests.Nullable + +open Util.Testing +open System +open FSharp.Linq + +let private none<'T when 'T: struct and 'T: (new: unit -> 'T) and 'T :> ValueType> = Nullable<'T>() + +let tests = + testList "Nullable" [ + testCase "Nullable.char" <| fun () -> + Nullable.char (Nullable 49uy) |> equal (Nullable '1') + Nullable.char (Nullable 50y) |> equal (Nullable '2') + Nullable.char (Nullable 51s) |> equal (Nullable '3') + Nullable.char (Nullable 52us) |> equal (Nullable '4') + Nullable.char (Nullable 53) |> equal (Nullable '5') + Nullable.char (Nullable 54u) |> equal (Nullable '6') + Nullable.char (Nullable 55L) |> equal (Nullable '7') + Nullable.char (Nullable 56UL) |> equal (Nullable '8') + Nullable.char (Nullable 57m) |> equal (Nullable '9') + Nullable.char (Nullable 58.0) |> equal (Nullable ':') + Nullable.char (Nullable 59.0f) |> equal (Nullable ';') + Nullable.char (Nullable 'a') |> equal (Nullable 'a') + Nullable.char none |> equal none + + testCase "Nullable.decimal" <| fun () -> + Nullable.decimal (Nullable 1uy) |> equal (Nullable 1m) + Nullable.decimal (Nullable 2y) |> equal (Nullable 2m) + Nullable.decimal (Nullable 3s) |> equal (Nullable 3m) + Nullable.decimal (Nullable 4us) |> equal (Nullable 4m) + Nullable.decimal (Nullable 5) |> equal (Nullable 5m) + Nullable.decimal (Nullable 6u) |> equal (Nullable 6m) + Nullable.decimal (Nullable 7L) |> equal (Nullable 7m) + Nullable.decimal (Nullable 8UL) |> equal (Nullable 8m) + Nullable.decimal (Nullable 9m) |> equal (Nullable 9m) + Nullable.decimal (Nullable 10.5) |> equal (Nullable 10.5m) + Nullable.decimal (Nullable 11.5f) |> equal (Nullable 11.5m) + Nullable.decimal none |> equal none + + testCase "Nullable.double" <| fun () -> + Nullable.double (Nullable 5) |> equal (Nullable 5.0) + Nullable.double (Nullable 7L) |> equal (Nullable 7.0) + Nullable.double (Nullable 9m) |> equal (Nullable 9.0) + Nullable.double (Nullable 11.5f) |> equal (Nullable 11.5) + Nullable.double none |> equal none + + testCase "Nullable.enum" <| fun () -> + (Nullable.enum (Nullable 2): Nullable) |> equal (Nullable DayOfWeek.Tuesday) + (Nullable.enum none: Nullable) |> equal none + + testCase "Nullable.float" <| fun () -> + Nullable.float (Nullable 1uy) |> equal (Nullable 1.0) + Nullable.float (Nullable 2y) |> equal (Nullable 2.0) + Nullable.float (Nullable 3s) |> equal (Nullable 3.0) + Nullable.float (Nullable 4us) |> equal (Nullable 4.0) + Nullable.float (Nullable 5) |> equal (Nullable 5.0) + Nullable.float (Nullable 6u) |> equal (Nullable 6.0) + Nullable.float (Nullable 7L) |> equal (Nullable 7.0) + Nullable.float (Nullable 8UL) |> equal (Nullable 8.0) + Nullable.float (Nullable 9m) |> equal (Nullable 9.0) + Nullable.float (Nullable 10.0) |> equal (Nullable 10.0) + Nullable.float (Nullable 11.0f) |> equal (Nullable 11.0) + Nullable.float (Nullable 'c') |> equal (Nullable 99.0) + Nullable.float none |> equal none + + testCase "Nullable.float32" <| fun () -> + Nullable.float32 (Nullable 1uy) |> equal (Nullable 1.0f) + Nullable.float32 (Nullable 2y) |> equal (Nullable 2.0f) + Nullable.float32 (Nullable 3s) |> equal (Nullable 3.0f) + Nullable.float32 (Nullable 4us) |> equal (Nullable 4.0f) + Nullable.float32 (Nullable 5) |> equal (Nullable 5.0f) + Nullable.float32 (Nullable 6u) |> equal (Nullable 6.0f) + Nullable.float32 (Nullable 7L) |> equal (Nullable 7.0f) + Nullable.float32 (Nullable 8UL) |> equal (Nullable 8.0f) + Nullable.float32 (Nullable 9m) |> equal (Nullable 9.0f) + Nullable.float32 (Nullable 10.0) |> equal (Nullable 10.0f) + Nullable.float32 (Nullable 11.0f) |> equal (Nullable 11.0f) + Nullable.float32 (Nullable 'c') |> equal (Nullable 99.0f) + Nullable.float32 none |> equal none + + testCase "Nullable.int" <| fun () -> + Nullable.int (Nullable 1uy) |> equal (Nullable 1) + Nullable.int (Nullable 2y) |> equal (Nullable 2) + Nullable.int (Nullable 3s) |> equal (Nullable 3) + Nullable.int (Nullable 4us) |> equal (Nullable 4) + Nullable.int (Nullable 5) |> equal (Nullable 5) + Nullable.int (Nullable 6u) |> equal (Nullable 6) + Nullable.int (Nullable 7L) |> equal (Nullable 7) + Nullable.int (Nullable 8UL) |> equal (Nullable 8) + Nullable.int (Nullable 9m) |> equal (Nullable 9) + Nullable.int (Nullable 10.9) |> equal (Nullable 10) + Nullable.int (Nullable -11.9f) |> equal (Nullable -11) + Nullable.int (Nullable 'c') |> equal (Nullable 99) + Nullable.int none |> equal none + + testCase "Nullable.int16" <| fun () -> + Nullable.int16 (Nullable 5) |> equal (Nullable 5s) + Nullable.int16 (Nullable -7L) |> equal (Nullable -7s) + Nullable.int16 (Nullable 9.9) |> equal (Nullable 9s) + Nullable.int16 none |> equal none + + testCase "Nullable.int32" <| fun () -> + Nullable.int32 (Nullable 5L) |> equal (Nullable 5) + Nullable.int32 (Nullable -7.9) |> equal (Nullable -7) + Nullable.int32 (Nullable 9m) |> equal (Nullable 9) + Nullable.int32 none |> equal none + + testCase "Nullable.int64" <| fun () -> + Nullable.int64 (Nullable 5) |> equal (Nullable 5L) + Nullable.int64 (Nullable 7UL) |> equal (Nullable 7L) + Nullable.int64 (Nullable -9.9) |> equal (Nullable -9L) + Nullable.int64 (Nullable 11m) |> equal (Nullable 11L) + Nullable.int64 none |> equal none + + testCase "Nullable.int8" <| fun () -> + Nullable.int8 (Nullable 5) |> equal (Nullable 5y) + Nullable.int8 (Nullable -7L) |> equal (Nullable -7y) + Nullable.int8 (Nullable 9.9) |> equal (Nullable 9y) + Nullable.int8 none |> equal none + + testCase "Nullable.sbyte" <| fun () -> + Nullable.sbyte (Nullable 5) |> equal (Nullable 5y) + Nullable.sbyte (Nullable -7.9) |> equal (Nullable -7y) + Nullable.sbyte none |> equal none + + testCase "Nullable.single" <| fun () -> + Nullable.single (Nullable 5) |> equal (Nullable 5.0f) + Nullable.single (Nullable 7L) |> equal (Nullable 7.0f) + Nullable.single (Nullable 9.5) |> equal (Nullable 9.5f) + Nullable.single none |> equal none + + testCase "Nullable.uint" <| fun () -> + Nullable.uint (Nullable 5) |> equal (Nullable 5u) + Nullable.uint (Nullable 7L) |> equal (Nullable 7u) + Nullable.uint (Nullable 9.9) |> equal (Nullable 9u) + Nullable.uint none |> equal none + + testCase "Nullable.uint16" <| fun () -> + Nullable.uint16 (Nullable 5) |> equal (Nullable 5us) + Nullable.uint16 (Nullable 7L) |> equal (Nullable 7us) + Nullable.uint16 (Nullable 9.9) |> equal (Nullable 9us) + Nullable.uint16 none |> equal none + + testCase "Nullable.uint32" <| fun () -> + Nullable.uint32 (Nullable 5) |> equal (Nullable 5u) + Nullable.uint32 (Nullable 7L) |> equal (Nullable 7u) + Nullable.uint32 (Nullable 9.9) |> equal (Nullable 9u) + Nullable.uint32 none |> equal none + + testCase "Nullable.uint64" <| fun () -> + Nullable.uint64 (Nullable 5) |> equal (Nullable 5UL) + Nullable.uint64 (Nullable 7L) |> equal (Nullable 7UL) + Nullable.uint64 (Nullable 9.9) |> equal (Nullable 9UL) + Nullable.uint64 (Nullable 11m) |> equal (Nullable 11UL) + Nullable.uint64 none |> equal none + + testCase "Nullable.byte and Nullable.uint8" <| fun () -> + Nullable.byte (Nullable 5) |> equal (Nullable 5uy) + Nullable.uint8 (Nullable 7L) |> equal (Nullable 7uy) + Nullable.uint8 (Nullable 9.9) |> equal (Nullable 9uy) + Nullable.byte none |> equal none + + testCase "Nullable conversions evaluate their argument once" <| fun () -> + let mutable calls = 0 + let next () = + calls <- calls + 1 + Nullable 5 + Nullable.float (next ()) |> equal (Nullable 5.0) + calls |> equal 1 + ] diff --git a/tests/Python/Fable.Tests.Python.fsproj b/tests/Python/Fable.Tests.Python.fsproj index b5f130a704..1feeccc0dd 100644 --- a/tests/Python/Fable.Tests.Python.fsproj +++ b/tests/Python/Fable.Tests.Python.fsproj @@ -64,6 +64,7 @@ + diff --git a/tests/Python/TestNullable.fs b/tests/Python/TestNullable.fs new file mode 100644 index 0000000000..0370bddd71 --- /dev/null +++ b/tests/Python/TestNullable.fs @@ -0,0 +1,72 @@ +module Fable.Tests.Nullable + +open System +open Util.Testing +open FSharp.Linq + +[] +let ``test Nullable.char works`` () = + Nullable.char (Nullable 49uy) |> equal (Nullable '1') + Nullable.char (Nullable 53) |> equal (Nullable '5') + Nullable.char (Nullable 55L) |> equal (Nullable '7') + Nullable.char (Nullable 57m) |> equal (Nullable '9') + Nullable.char (Nullable 58.0) |> equal (Nullable ':') + Nullable.char (Nullable 'a') |> equal (Nullable 'a') + Nullable.char (Nullable()) |> equal (Nullable()) + +[] +let ``test Nullable.decimal works`` () = + Nullable.decimal (Nullable 5) |> equal (Nullable 5m) + Nullable.decimal (Nullable 7L) |> equal (Nullable 7m) + Nullable.decimal (Nullable 10.5) |> equal (Nullable 10.5m) + Nullable.decimal (Nullable()) |> equal (Nullable()) + +[] +let ``test Nullable.enum works`` () = + (Nullable.enum (Nullable 2): Nullable) |> equal (Nullable DayOfWeek.Tuesday) + (Nullable.enum (Nullable()): Nullable) |> equal (Nullable()) + +[] +let ``test Nullable.float and Nullable.float32 work`` () = + Nullable.float (Nullable 1uy) |> equal (Nullable 1.0) + Nullable.float (Nullable 5) |> equal (Nullable 5.0) + Nullable.float (Nullable 7L) |> equal (Nullable 7.0) + Nullable.float (Nullable 9m) |> equal (Nullable 9.0) + Nullable.float (Nullable 'c') |> equal (Nullable 99.0) + Nullable.float (Nullable()) |> equal (Nullable()) + Nullable.float32 (Nullable 5) |> equal (Nullable 5.0f) + Nullable.float32 (Nullable 10.0) |> equal (Nullable 10.0f) + Nullable.float32 (Nullable()) |> equal (Nullable()) + Nullable.double (Nullable 5) |> equal (Nullable 5.0) + Nullable.single (Nullable 9.5) |> equal (Nullable 9.5f) + +[] +let ``test Nullable integer conversions work`` () = + Nullable.int (Nullable 1uy) |> equal (Nullable 1) + Nullable.int (Nullable 7L) |> equal (Nullable 7) + Nullable.int (Nullable 9m) |> equal (Nullable 9) + Nullable.int (Nullable 10.9) |> equal (Nullable 10) + Nullable.int (Nullable -11.9) |> equal (Nullable -11) + Nullable.int (Nullable 'c') |> equal (Nullable 99) + Nullable.int (Nullable()) |> equal (Nullable()) + Nullable.int16 (Nullable -7L) |> equal (Nullable -7s) + Nullable.int32 (Nullable 5L) |> equal (Nullable 5) + Nullable.int64 (Nullable -9.9) |> equal (Nullable -9L) + Nullable.int64 (Nullable()) |> equal (Nullable()) + Nullable.int8 (Nullable 9.9) |> equal (Nullable 9y) + Nullable.sbyte (Nullable -7.9) |> equal (Nullable -7y) + Nullable.byte (Nullable 5) |> equal (Nullable 5uy) + Nullable.uint8 (Nullable 7L) |> equal (Nullable 7uy) + Nullable.uint16 (Nullable 9.9) |> equal (Nullable 9us) + Nullable.uint (Nullable 5) |> equal (Nullable 5u) + Nullable.uint32 (Nullable 7L) |> equal (Nullable 7u) + Nullable.uint64 (Nullable()) |> equal (Nullable()) + +[] +let ``test Nullable conversions evaluate their argument once`` () = + let mutable calls = 0 + let next () = + calls <- calls + 1 + Nullable 5 + Nullable.float (next ()) |> equal (Nullable 5.0) + calls |> equal 1