diff --git a/tests/Beam/UnionTypeTests.fs b/tests/Beam/UnionTypeTests.fs index a12f942397..0a88809eef 100644 --- a/tests/Beam/UnionTypeTests.fs +++ b/tests/Beam/UnionTypeTests.fs @@ -14,13 +14,13 @@ type Shape = | Square of side: float | Rectangle of width: float * height: float -type MyUnion = +type TestUnion = | Case0 | Case1 of string | Case2 of string * string | Case3 of string * string * string -type MyUnion2 = +type TestUnion2 = | Tag of string | NewTag of string @@ -33,20 +33,133 @@ type IntegrityLevel = | Untrusted -> "Untrusted" | Trusted -> "Trusted" -type IntUnion = - | IntCase1 of int - | IntCase2 of int - | IntCase3 of int +type T1 = T1 +type T2 = T2 +type T3 = T3 +type T4 = T4 +type T5 = T5 +type T6 = T6 +type T7 = T7 +type T8 = T8 +type T9 = T9 +type 'a more = More of 'a + +[] +type LangCode = + | EN + | ``pt-Br`` + +type S = S of string + +type Id = Id of string + +type UnionWithIdField = WithId of Id: Id + +type NamedFieldUnion = + | NamedCase of name: string * optional: string + +// [] +[] +type Example = + | First + | Second + | Other of string + + static member All = [| Example.First; Example.Second |] + static member Head = Some Example.First + static member Nested = [ [| Example.First |] ] + static member Pair = (Example.First, Example.Second) + static member Factory: unit -> Example = fun () -> Example.First [] type StructPoint2D = | Planar2D of float * float | Infinity +[] +type StructShape = + | Square of side: int + | Circle of radius: float + + // member this.Area = // TODO: support instance members + static member Area this = + match this with + | Square side -> float (side * side) + | Circle radius -> System.Math.PI * radius * radius + +[] +type StructShape2 = + | Square of float + | Circle of float + + // member this.Area = // TODO: support instance members + static member Area this = + match this with + | Square side -> side * side + | Circle radius -> System.Math.PI * radius * radius + +[] +type NestedMatchUnion = + | NestedA of int + | NestedB of int + +let private nestedUnionCaseAccess (u: NestedMatchUnion) = + match u with + | NestedA _ -> 0 + | NestedB _ -> + match u with + | NestedA value -> value + | _ -> -1 + +type IntUnion = + | IntCase1 of int + | IntCase2 of int + | IntCase3 of int + +let unionFnAlways1 = function + | IntCase1 x -> x + | _ -> -1 + +let unionFnRetNum = function + | IntCase1 a -> a + | IntCase2 b -> b + | IntCase3 c -> c + type WrappedUnion = | AString of string -type T1 = T1 +type DeepRecord = { Value: string } + +type DeepWrappedUnion = + | DeepWrappedA of string * DeepRecord + | DeepWrappedB of string + | DeepWrappedC of int + | DeepWrappedD of DeepRecord + | DeepWrappedE of int * int + | DeepWrappedF of WrappedUnion + | DeepWrappedG of {| X: DeepRecord; Y: int|} + +let matchStrings = function + | DeepWrappedA (s, d) -> d.Value + s + | DeepWrappedB s -> s + | DeepWrappedC c -> "nothing" + | DeepWrappedD d -> d.Value + | DeepWrappedE(a, b) -> "nothing2" + | DeepWrappedF (AString s) -> s + | DeepWrappedG x -> x.X.Value + +let matchNumbers = function + | DeepWrappedA (s, d) -> 0 + | DeepWrappedB s -> 0 + | DeepWrappedC c -> c + | DeepWrappedD d -> 0 + | DeepWrappedE(a, b) -> a + b + | DeepWrappedF _ -> 0 + | DeepWrappedG x -> x.Y + +let matchStringWhenStringNotHello = function + | DeepWrappedB s when s <> "hello" -> "not hello" + | _ -> "hello" type ForwardedValue = ForwardedValue of obj @@ -73,7 +186,7 @@ let private unwrapGenericForwardedValue<'T> (message: obj) = Some value | _ -> None -let private isMyUnion (message: obj) = message :? MyUnion +let private isMyUnion (message: obj) = message :? TestUnion let private isEither (message: obj) = message :? Either let private isGender (message: obj) = message :? Gender let private isT1 (message: obj) = message :? T1 @@ -186,17 +299,6 @@ type CollectionOrderItem = type CollectionArray = CollectionArray of int array -type DeepRecord = { Value: string } - -type DeepWrappedUnion = - | DeepWrappedA of string * DeepRecord - | DeepWrappedB of string - | DeepWrappedC of int - | DeepWrappedD of DeepRecord - | DeepWrappedE of int * int - | DeepWrappedF of WrappedUnion - | DeepWrappedG of {| X: DeepRecord; Y: int |} - let (|Functional|NonFunctional|) (s: string) = match s with | "fsharp" | "haskell" | "ocaml" -> Functional @@ -314,14 +416,14 @@ let ``test collection extrema preserve first ties and project each item once`` ( obj.ReferenceEquals(arrayMinValue, first) |> equal true [] -type MyUnion3 = -| Case1 -| Case2 -| Case3 +type MyUnion = + | Case1 + | Case2 + | Case3 type R = { Name: string - UnionCase: MyUnion3 + Case: MyUnion } [] @@ -378,11 +480,11 @@ let ``test Union case with no data works`` () = let ``test Shape union works`` () = let area shape = match shape with - | Circle r -> 3.14159 * r * r - | Square s -> s * s - | Rectangle(w, h) -> w * h - area (Square 5.0) |> equal 25.0 - area (Rectangle(3.0, 4.0)) |> equal 12.0 + | Shape.Circle r -> 3.14159 * r * r + | Shape.Square s -> s * s + | Shape.Rectangle(w, h) -> w * h + area (Shape.Square 5.0) |> equal 25.0 + area (Shape.Rectangle(3.0, 4.0)) |> equal 12.0 [] let ``test Either union works`` () = @@ -432,12 +534,12 @@ let ``test Union cases matches with many arguments can be generated`` () = [] let ``test Pattern matching with common targets works`` () = - let x = MyUnion.Case2("a", "b") + let x = TestUnion.Case2("a", "b") match x with - | MyUnion.Case0 -> failwith "unexpected" - | MyUnion.Case1 _ - | MyUnion.Case2 _ -> "a" - | MyUnion.Case3(a, b, c) -> a + b + c + | TestUnion.Case0 -> failwith "unexpected" + | TestUnion.Case1 _ + | TestUnion.Case2 _ -> "a" + | TestUnion.Case3(a, b, c) -> a + b + c |> equal "a" [] @@ -488,13 +590,75 @@ let ``test Active patterns can be combined with union case matching`` () = [] let ``test Equality works in filter`` () = - let original = [| { Name = "1"; UnionCase = MyUnion3.Case1 } ; { Name = "2"; UnionCase = MyUnion3.Case1 }; { Name = "3"; UnionCase = MyUnion3.Case2 }; { Name = "4"; UnionCase = MyUnion3.Case3 } |] + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] + original + |> Array.filter (fun r -> r.Case = MyUnion.Case1) + |> Array.length + |> equal 2 + +[] +let ``test Pattern matching works in filter`` () = + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] original - |> Array.filter (fun r -> r.UnionCase = MyUnion3.Case1) + |> Array.filter (fun r -> match r.Case with MyUnion.Case1 -> true | _ -> false) |> Array.length |> equal 2 -// --- Tests ported from Rust UnionTests --- +[] +let ``test Union field can have the same name as its type`` () = + let value = WithId(Id "foo") + match value with + | WithId id -> + match id with + | Id value -> value |> equal "foo" + +[] +let ``test Verify that union cases can have `-` in their names`` () = + // See https://github.com/fable-compiler/Fable/issues/4071 + let x = LangCode.``pt-Br`` + equal x LangCode.``pt-Br`` + +[] +let ``test sprintf formats strings cases correctly`` () = + let s = sprintf "%A" (S "1") + s |> equal "S \"1\"" + +[] +let ``test Union cases with no fields are physically equal`` () = + // Nullary cases compile to plain atoms, which the BEAM VM interns, so this matches fsc. + obj.ReferenceEquals(Gender.Male, Gender.Male) |> equal true + obj.ReferenceEquals(Gender.Male, Gender.Female) |> equal false + obj.ReferenceEquals(TestUnion.Case0, TestUnion.Case0) |> equal true +#if FABLE_COMPILER + // Unlike fsc, Erlang has no reference identity for compound terms (only atoms are + // interned), so `=:=` here is structural equality, unlike .NET reference equality. + obj.ReferenceEquals(TestUnion.Case1 "a", TestUnion.Case1 "a") |> equal true +#else + obj.ReferenceEquals(TestUnion.Case1 "a", TestUnion.Case1 "a") |> equal false +#endif + obj.ReferenceEquals(T1, T1) |> equal true + +[] +let ``Named union case fields work when a field is called name`` () = + let value = NamedCase("v1", "v2") + match value with + | NamedCase(name, optional) -> + name |> equal "v1" + optional |> equal "v2" + +[] +let ``test Static union member returning the union type works`` () = + Example.All |> Array.length |> equal 2 + Example.All.[0] |> equal Example.First + Example.All.[1] |> equal Example.Second + +[] +let ``test Static union member with nested self-reference works`` () = + Example.Head |> equal (Some Example.First) + Example.Nested |> List.head |> Array.head |> equal Example.First + Example.Pair |> fst |> equal Example.First + Example.Pair |> snd |> equal Example.Second + Example.Factory() |> equal Example.First [] let ``test Struct unions work`` () = @@ -506,6 +670,31 @@ let ``test Struct unions work`` () = let res = distance p res |> equal 5. +[] +let ``test Struct unions with fields work`` () = + let radius = 3.0 + let circle = StructShape.Circle radius + let square = StructShape.Square(int (2.0 * radius)) + // circle.Area |> equal (System.Math.PI * radius * radius) + // square.Area |> equal (4.0 * radius * radius) + StructShape.Area circle |> equal (System.Math.PI * radius * radius) + StructShape.Area square |> equal (4.0 * radius * radius) + +[] +let ``test Struct unions without fields work`` () = + let radius = 3.0 + let circle = StructShape2.Circle radius + let square = StructShape2.Square(2.0 * radius) + // circle.Area |> equal (System.Math.PI * radius * radius) + // square.Area |> equal (4.0 * radius * radius) + StructShape2.Area circle |> equal (System.Math.PI * radius * radius) + StructShape2.Area square |> equal (4.0 * radius * radius) + +[] +let ``test Nested union match keeps case-specific field access`` () = + nestedUnionCaseAccess (NestedA 7) |> equal 0 + nestedUnionCaseAccess (NestedB 11) |> equal -1 + [] let ``test Union case matching works`` () = let x = IntCase1 5 @@ -524,15 +713,6 @@ let ``test Union case equality works`` () = IntCase2 1 = IntCase2 1 |> equal true IntCase3 1 = IntCase3 1 |> equal true -let unionFnAlways1 = function - | IntCase1 x -> x - | _ -> -1 - -let unionFnRetNum = function - | IntCase1 a -> a - | IntCase2 b -> b - | IntCase3 c -> c - [] let ``test Union fn call works`` () = let x = IntCase1 3 @@ -551,24 +731,6 @@ let ``test Union with wrapped type works`` () = let b = match a with AString s -> s + " world" b |> equal "hello world" -let matchStrings = function - | DeepWrappedA (s, d) -> d.Value + s - | DeepWrappedB s -> s - | DeepWrappedC _ -> "nothing" - | DeepWrappedD d -> d.Value - | DeepWrappedE _ -> "nothing2" - | DeepWrappedF (AString s) -> s - | DeepWrappedG x -> x.X.Value - -let matchNumbers = function - | DeepWrappedA _ -> 0 - | DeepWrappedB _ -> 0 - | DeepWrappedC c -> c - | DeepWrappedD _ -> 0 - | DeepWrappedE(a, b) -> a + b - | DeepWrappedF _ -> 0 - | DeepWrappedG x -> x.Y - [] let ``test Deep union with wrapped type works`` () = let a = DeepWrappedA (" world", { Value = "hello" }) @@ -587,34 +749,21 @@ let ``test Deep union with wrapped type works`` () = [] let ``test Deep union with tuped prim type works`` () = let e = DeepWrappedE (3, 2) - let c = DeepWrappedC 42 + let c = DeepWrappedC 42 //todo to string! let g = DeepWrappedG {| X = { Value = "G" }; Y = 365 |} e |> matchNumbers |> equal 5 c |> matchNumbers |> equal 42 g |> matchNumbers |> equal 365 -let matchStringWhenNotHello = function - | DeepWrappedB s when s <> "hello" -> "not hello" - | _ -> "hello" +[] +let ``test Multi-case Union with wrapped type works`` () = + let b = DeepWrappedB "hello" + let res = match b with DeepWrappedB s -> s + " world" | _ -> "" + res |> equal "hello world" [] let ``test Match with condition works`` () = let b1 = DeepWrappedB "hello" let b2 = DeepWrappedB "not" - b1 |> matchStringWhenNotHello |> equal "hello" - b2 |> matchStringWhenNotHello |> equal "not hello" - -[] -let ``test Union cases with no fields are physically equal`` () = - // Nullary cases compile to plain atoms, which the BEAM VM interns, so this matches fsc. - obj.ReferenceEquals(Male, Male) |> equal true - obj.ReferenceEquals(Male, Female) |> equal false - obj.ReferenceEquals(Case0, Case0) |> equal true - obj.ReferenceEquals(T1, T1) |> equal true -#if FABLE_COMPILER - // Unlike fsc, Erlang has no reference identity for compound terms (only atoms are - // interned), so `=:=` here is structural equality, unlike .NET reference equality. - obj.ReferenceEquals(Case1 "a", Case1 "a") |> equal true -#else - obj.ReferenceEquals(Case1 "a", Case1 "a") |> equal false -#endif + b1 |> matchStringWhenStringNotHello |> equal "hello" + b2 |> matchStringWhenStringNotHello |> equal "not hello" diff --git a/tests/Dart/src/UnionTests.fs b/tests/Dart/src/UnionTests.fs index 11125bea46..55d70fefe9 100644 --- a/tests/Dart/src/UnionTests.fs +++ b/tests/Dart/src/UnionTests.fs @@ -18,7 +18,6 @@ type TestUnion = | Case2 of string * string | Case3 of string * string * string - type TestUnion2 = | Tag of string | NewTag of string @@ -64,9 +63,9 @@ type Wrapper(s: string) = [] type MyUnion = -| Case1 -| Case2 -| Case3 + | Case1 + | Case2 + | Case3 type R = { Name: string @@ -89,10 +88,123 @@ type T8 = T8 type T9 = T9 type 'a more = More of 'a +[] +type LangCode = + | EN + | ``pt-Br`` + +type S = S of string + type Id = Id of string type UnionWithIdField = WithId of Id: Id +type NamedFieldUnion = + | NamedCase of name: string * optional: string + +// [] +[] +type Example = + | First + | Second + | Other of string + + static member All = [| Example.First; Example.Second |] + static member Head = Some Example.First + static member Nested = [ [| Example.First |] ] + static member Pair = (Example.First, Example.Second) + static member Factory: unit -> Example = fun () -> Example.First + +[] +type StructPoint2D = + | Planar2D of float * float + | Infinity + +[] +type StructShape = + | Square of side: int + | Circle of radius: float + + // member this.Area = // TODO: support instance members + static member Area this = + match this with + | Square side -> float (side * side) + | Circle radius -> System.Math.PI * radius * radius + +[] +type StructShape2 = + | Square of float + | Circle of float + + // member this.Area = // TODO: support instance members + static member Area this = + match this with + | Square side -> side * side + | Circle radius -> System.Math.PI * radius * radius + +[] +type NestedMatchUnion = + | NestedA of int + | NestedB of int + +let private nestedUnionCaseAccess (u: NestedMatchUnion) = + match u with + | NestedA _ -> 0 + | NestedB _ -> + match u with + | NestedA value -> value + | _ -> -1 + +type IntUnion = + | IntCase1 of int + | IntCase2 of int + | IntCase3 of int + +let unionFnAlways1 = function + | IntCase1 x -> x + | _ -> -1 + +let unionFnRetNum = function + | IntCase1 a -> a + | IntCase2 b -> b + | IntCase3 c -> c + +type WrappedUnion = + | AString of string + +type DeepRecord = { Value: string } + +type DeepWrappedUnion = + | DeepWrappedA of string * DeepRecord + | DeepWrappedB of string + | DeepWrappedC of int + | DeepWrappedD of DeepRecord + | DeepWrappedE of int * int + | DeepWrappedF of WrappedUnion + | DeepWrappedG of {| X: DeepRecord; Y: int|} + +let matchStrings = function + | DeepWrappedA (s, d) -> d.Value + s + | DeepWrappedB s -> s + | DeepWrappedC c -> "nothing" + | DeepWrappedD d -> d.Value + | DeepWrappedE(a, b) -> "nothing2" + | DeepWrappedF (AString s) -> s + | DeepWrappedG x -> x.X.Value + +let matchNumbers = function + | DeepWrappedA (s, d) -> 0 + | DeepWrappedB s -> 0 + | DeepWrappedC c -> c + | DeepWrappedD d -> 0 + | DeepWrappedE(a, b) -> a + b + | DeepWrappedF _ -> 0 + | DeepWrappedG x -> x.Y + +let matchStringWhenStringNotHello = function + | DeepWrappedB s when s <> "hello" -> "not hello" + | _ -> "hello" + let tests() = testCase "Union cases matches with no arguments can be generated" <| fun () -> let x = Male @@ -191,14 +303,14 @@ let tests() = ex.Message.Contains("bar") |> equal false testCase "Equality works in filter" <| fun () -> - let original = [| { Name = "1"; Case = MyUnion.Case1 } ; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] original |> Array.filter (fun r -> r.Case = MyUnion.Case1) |> Array.length |> equal 2 testCase "Pattern matching works in filter" <| fun () -> - let original = [| { Name = "1"; Case = MyUnion.Case1 } ; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] original |> Array.filter (fun r -> match r.Case with MyUnion.Case1 -> true | _ -> false) |> Array.length @@ -218,3 +330,134 @@ let tests() = // | Str s -> "s: " + s // Str "foo" |> strify |> equal "s: foo" // Int 42 |> strify |> equal "i: 42" + + testCase "Verify that union cases can have `-` in their names" <| fun () -> + // See https://github.com/fable-compiler/Fable/issues/4071 + let x = LangCode.``pt-Br`` + equal x LangCode.``pt-Br`` + + // testCase "sprintf formats strings cases correctly" <| fun () -> + // let s = sprintf "%A" (S "1") + // equal s "S \"1\"" + + // testCase "Union cases with no fields are physically equal" <| fun () -> + // obj.ReferenceEquals(Gender.Male, Gender.Male) |> equal true + // obj.ReferenceEquals(Gender.Male, Gender.Female) |> equal false + // obj.ReferenceEquals(TestUnion.Case0, TestUnion.Case0) |> equal true + // obj.ReferenceEquals(TestUnion.Case1 "a", TestUnion.Case1 "a") |> equal false + // obj.ReferenceEquals(T1, T1) |> equal true + + testCase "Named union case fields work when a field is called name" <| fun () -> + let value = NamedCase("v1", "v2") + match value with + | NamedCase(name, optional) -> + name |> equal "v1" + optional |> equal "v2" + + testCase "Static union member returning the union type works" <| fun () -> + Example.All |> Array.length |> equal 2 + Example.All.[0] |> equal Example.First + Example.All.[1] |> equal Example.Second + + testCase "Static union member with nested self-reference works" <| fun () -> + Example.Head |> equal (Some Example.First) + Example.Nested |> List.head |> Array.head |> equal Example.First + Example.Pair |> fst |> equal Example.First + Example.Pair |> snd |> equal Example.Second + Example.Factory() |> equal Example.First + + testCase "Struct unions work" <| fun () -> + let distance p = + match p with + | Planar2D (x, y) -> sqrt (x * x + y * y) + | Infinity -> System.Double.PositiveInfinity //TODO: infinity literal + let p = Planar2D (3, 4) + let res = distance p + res |> equal 5. + + testCase "Struct unions with fields work" <| fun () -> + let radius = 3.0 + let circle = StructShape.Circle radius + let square = StructShape.Square(int (2.0 * radius)) + // circle.Area |> equal (System.Math.PI * radius * radius) + // square.Area |> equal (4.0 * radius * radius) + StructShape.Area circle |> equal (System.Math.PI * radius * radius) + StructShape.Area square |> equal (4.0 * radius * radius) + + testCase "Struct unions without fields work" <| fun () -> + let radius = 3.0 + let circle = StructShape2.Circle radius + let square = StructShape2.Square(2.0 * radius) + // circle.Area |> equal (System.Math.PI * radius * radius) + // square.Area |> equal (4.0 * radius * radius) + StructShape2.Area circle |> equal (System.Math.PI * radius * radius) + StructShape2.Area square |> equal (4.0 * radius * radius) + + testCase "Nested union match keeps case-specific field access" <| fun () -> + nestedUnionCaseAccess (NestedA 7) |> equal 0 + nestedUnionCaseAccess (NestedB 11) |> equal -1 + + testCase "Union case matching works" <| fun () -> + let x = IntCase1 5 + let res = + match x with + | IntCase1 a -> a + | IntCase2 b -> b + | IntCase3 c -> c + res |> equal 5 + + testCase "Union case equality works" <| fun () -> + IntCase1 5 = IntCase1 5 |> equal true + IntCase1 5 = IntCase2 5 |> equal false + IntCase3 2 = IntCase3 3 |> equal false + IntCase2 1 = IntCase2 1 |> equal true + IntCase3 1 = IntCase3 1 |> equal true + + testCase "Union fn call works" <| fun () -> + let x = IntCase1 3 + let res = unionFnAlways1 x + let res2 = unionFnAlways1 x //deliberately force clone/borrow break + let res3 = unionFnRetNum x + let res4 = unionFnRetNum (IntCase2 24) + res |> equal 3 + res2 |> equal 3 + res3 |> equal 3 + res4 |> equal 24 + + testCase "Union with wrapped type works" <| fun () -> + let a = AString "hello" + let b = match a with AString s -> s + " world" + b |> equal "hello world" + + testCase "Deep union with wrapped type works" <| fun () -> + let a = DeepWrappedA (" world", {Value = "hello"}) + let b = DeepWrappedB "world" + let c = DeepWrappedC 42 + let d = DeepWrappedD { Value = "hello" } + let f = DeepWrappedF (AString "doublewrapped") + let g = DeepWrappedG {| X = { Value = "G" }; Y = 365 |} + a |> matchStrings |> equal "hello world" + b |> matchStrings |> equal "world" + c |> matchStrings |> equal "nothing" + d |> matchStrings |> equal "hello" + f |> matchStrings |> equal "doublewrapped" + g |> matchStrings |> equal "G" + + testCase "Deep union with tuped prim type works" <| fun () -> + let e = DeepWrappedE (3, 2) + let c = DeepWrappedC 42 //todo to string! + let g = DeepWrappedG {| X = { Value = "G" }; Y = 365 |} + e |> matchNumbers |> equal 5 + c |> matchNumbers |> equal 42 + g |> matchNumbers |> equal 365 + + testCase "Multi-case Union with wrapped type works" <| fun () -> + let b = DeepWrappedB "hello" + let res = match b with DeepWrappedB s -> s + " world" | _ -> "" + res |> equal "hello world" + + testCase "Match with condition works" <| fun () -> + let b1 = DeepWrappedB "hello" + let b2 = DeepWrappedB "not" + b1 |> matchStringWhenStringNotHello |> equal "hello" + b2 |> matchStringWhenStringNotHello |> equal "not hello" diff --git a/tests/Js/Main/UnionTypeTests.fs b/tests/Js/Main/UnionTypeTests.fs index 002d57dcda..db48e9698c 100644 --- a/tests/Js/Main/UnionTypeTests.fs +++ b/tests/Js/Main/UnionTypeTests.fs @@ -18,7 +18,6 @@ type TestUnion = | Case2 of string * string | Case3 of string * string * string - type TestUnion2 = | Tag of string | NewTag of string @@ -64,9 +63,9 @@ type Wrapper(s: string) = [] type MyUnion = -| Case1 -| Case2 -| Case3 + | Case1 + | Case2 + | Case3 type R = { Name: string @@ -106,6 +105,114 @@ type LangCode = type S = S of string +type Id = Id of string + +type UnionWithIdField = WithId of Id: Id + +type NamedFieldUnion = + | NamedCase of name: string * optional: string + +[] +[] +type Example = + | First + | Second + | Other of string + + static member All = [| Example.First; Example.Second |] + static member Head = Some Example.First + static member Nested = [ [| Example.First |] ] + static member Pair = (Example.First, Example.Second) + static member Factory: unit -> Example = fun () -> Example.First + +[] +type StructPoint2D = + | Planar2D of float * float + | Infinity + +[] +type StructShape = + | Square of side: int + | Circle of radius: float + + member this.Area = + match this with + | Square side -> float (side * side) + | Circle radius -> System.Math.PI * radius * radius + +[] +type StructShape2 = + | Square of float + | Circle of float + + member this.Area = + match this with + | Square side -> side * side + | Circle radius -> System.Math.PI * radius * radius + +[] +type NestedMatchUnion = + | NestedA of int + | NestedB of int + +let private nestedUnionCaseAccess (u: NestedMatchUnion) = + match u with + | NestedA _ -> 0 + | NestedB _ -> + match u with + | NestedA value -> value + | _ -> -1 + +type IntUnion = + | IntCase1 of int + | IntCase2 of int + | IntCase3 of int + +let unionFnAlways1 = function + | IntCase1 x -> x + | _ -> -1 + +let unionFnRetNum = function + | IntCase1 a -> a + | IntCase2 b -> b + | IntCase3 c -> c + +type WrappedUnion = + | AString of string + +type DeepRecord = { Value: string } + +type DeepWrappedUnion = + | DeepWrappedA of string * DeepRecord + | DeepWrappedB of string + | DeepWrappedC of int + | DeepWrappedD of DeepRecord + | DeepWrappedE of int * int + | DeepWrappedF of WrappedUnion + | DeepWrappedG of {| X: DeepRecord; Y: int|} + +let matchStrings = function + | DeepWrappedA (s, d) -> d.Value + s + | DeepWrappedB s -> s + | DeepWrappedC c -> "nothing" + | DeepWrappedD d -> d.Value + | DeepWrappedE(a, b) -> "nothing2" + | DeepWrappedF (AString s) -> s + | DeepWrappedG x -> x.X.Value + +let matchNumbers = function + | DeepWrappedA (s, d) -> 0 + | DeepWrappedB s -> 0 + | DeepWrappedC c -> c + | DeepWrappedD d -> 0 + | DeepWrappedE(a, b) -> a + b + | DeepWrappedF _ -> 0 + | DeepWrappedG x -> x.Y + +let matchStringWhenStringNotHello = function + | DeepWrappedB s when s <> "hello" -> "not hello" + | _ -> "hello" + let tests = testList "Unions" [ testCase "Union cases matches with no arguments can be generated" <| fun () -> @@ -229,19 +336,26 @@ let tests = |> equal 2 testCase "Equality works in filter" <| fun () -> - let original = [| { Name = "1"; Case = MyUnion.Case1 } ; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] original |> Array.filter (fun r -> r.Case = MyUnion.Case1) |> Array.length |> equal 2 testCase "Pattern matching works in filter" <| fun () -> - let original = [| { Name = "1"; Case = MyUnion.Case1 } ; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] original |> Array.filter (fun r -> match r.Case with MyUnion.Case1 -> true | _ -> false) |> Array.length |> equal 2 + testCase "Union field can have the same name as its type" <| fun () -> + let value = WithId(Id "foo") + match value with + | WithId id -> + match id with + | Id value -> value |> equal "foo" + #if FABLE_COMPILER testCase "Case testing with generic erased unions works" <| fun () -> let strify (x: U2) = @@ -299,7 +413,6 @@ let tests = testCase "Verify that union cases can have `-` in their names" <| fun () -> // See https://github.com/fable-compiler/Fable/issues/4071 let x = LangCode.``pt-Br`` - equal x LangCode.``pt-Br`` testCase "sprintf formats strings cases correctly" <| fun () -> @@ -312,4 +425,116 @@ let tests = obj.ReferenceEquals(TestUnion.Case0, TestUnion.Case0) |> equal true obj.ReferenceEquals(TestUnion.Case1 "a", TestUnion.Case1 "a") |> equal false obj.ReferenceEquals(T1, T1) |> equal true + + testCase "Named union case fields work when a field is called name" <| fun () -> + let value = NamedCase("v1", "v2") + match value with + | NamedCase(name, optional) -> + name |> equal "v1" + optional |> equal "v2" + + testCase "Static union member returning the union type works" <| fun () -> + Example.All |> Array.length |> equal 2 + Example.All.[0] |> equal Example.First + Example.All.[1] |> equal Example.Second + + testCase "Static union member with nested self-reference works" <| fun () -> + Example.Head |> equal (Some Example.First) + Example.Nested |> List.head |> Array.head |> equal Example.First + Example.Pair |> fst |> equal Example.First + Example.Pair |> snd |> equal Example.Second + Example.Factory() |> equal Example.First + + testCase "Struct unions work" <| fun () -> + let distance p = + match p with + | Planar2D (x, y) -> sqrt (x * x + y * y) + | Infinity -> infinity + let p = Planar2D (3, 4) + let res = distance p + res |> equal 5. + + testCase "Struct unions with fields work" <| fun () -> + let radius = 3.0 + let circle = StructShape.Circle radius + let square = StructShape.Square(int (2.0 * radius)) + circle.Area |> equal (System.Math.PI * radius * radius) + square.Area |> equal (4.0 * radius * radius) + + testCase "Struct unions without fields work" <| fun () -> + let radius = 3.0 + let circle = StructShape2.Circle radius + let square = StructShape2.Square(2.0 * radius) + circle.Area |> equal (System.Math.PI * radius * radius) + square.Area |> equal (4.0 * radius * radius) + + testCase "Nested union match keeps case-specific field access" <| fun () -> + nestedUnionCaseAccess (NestedA 7) |> equal 0 + nestedUnionCaseAccess (NestedB 11) |> equal -1 + + testCase "Union case matching works" <| fun () -> + let x = IntCase1 5 + let res = + match x with + | IntCase1 a -> a + | IntCase2 b -> b + | IntCase3 c -> c + res |> equal 5 + + // testCase "Union case equality works" <| fun () -> + // IntCase1 5 = IntCase1 5 |> equal true + // IntCase1 5 = IntCase2 5 |> equal false + // IntCase3 2 = IntCase3 3 |> equal false + // IntCase2 1 = IntCase2 1 |> equal true + // IntCase3 1 = IntCase3 1 |> equal true + + testCase "Union fn call works" <| fun () -> + let x = IntCase1 3 + let res = unionFnAlways1 x + let res2 = unionFnAlways1 x //deliberately force clone/borrow break + let res3 = unionFnRetNum x + let res4 = unionFnRetNum (IntCase2 24) + res |> equal 3 + res2 |> equal 3 + res3 |> equal 3 + res4 |> equal 24 + + testCase "Union with wrapped type works" <| fun () -> + let a = AString "hello" + let b = match a with AString s -> s + " world" + b |> equal "hello world" + + testCase "Deep union with wrapped type works" <| fun () -> + let a = DeepWrappedA (" world", {Value = "hello"}) + let b = DeepWrappedB "world" + let c = DeepWrappedC 42 + let d = DeepWrappedD { Value = "hello" } + let f = DeepWrappedF (AString "doublewrapped") + let g = DeepWrappedG {| X = { Value = "G" }; Y = 365 |} + a |> matchStrings |> equal "hello world" + b |> matchStrings |> equal "world" + c |> matchStrings |> equal "nothing" + d |> matchStrings |> equal "hello" + f |> matchStrings |> equal "doublewrapped" + g |> matchStrings |> equal "G" + + testCase "Deep union with tuped prim type works" <| fun () -> + let e = DeepWrappedE (3, 2) + let c = DeepWrappedC 42 //todo to string! + let g = DeepWrappedG {| X = { Value = "G" }; Y = 365 |} + e |> matchNumbers |> equal 5 + c |> matchNumbers |> equal 42 + g |> matchNumbers |> equal 365 + + testCase "Multi-case Union with wrapped type works" <| fun () -> + let b = DeepWrappedB "hello" + let res = match b with DeepWrappedB s -> s + " world" | _ -> "" + res |> equal "hello world" + + testCase "Match with condition works" <| fun () -> + let b1 = DeepWrappedB "hello" + let b2 = DeepWrappedB "not" + b1 |> matchStringWhenStringNotHello |> equal "hello" + b2 |> matchStringWhenStringNotHello |> equal "not hello" + ] diff --git a/tests/Php/TestUnionType.fs b/tests/Php/TestUnionType.fs index 6d462a81de..e2e66ba784 100644 --- a/tests/Php/TestUnionType.fs +++ b/tests/Php/TestUnionType.fs @@ -18,7 +18,6 @@ type Either<'TL,'TR> = // | Case2 of string * string // | Case3 of string * string * string - // type TestUnion2 = // | Tag of string // | NewTag of string diff --git a/tests/Python/TestUnionType.fs b/tests/Python/TestUnionType.fs index 385e098bf2..bda4c2a22c 100644 --- a/tests/Python/TestUnionType.fs +++ b/tests/Python/TestUnionType.fs @@ -4,8 +4,6 @@ open Util.Testing type Gender = Male | Female -type T1 = T1 - type Either<'TL,'TR> = | Left of 'TL | Right of 'TR @@ -14,14 +12,13 @@ type Either<'TL,'TR> = | Left y -> y.ToString() | Right y -> y.ToString() -type MyUnion = +type TestUnion = | Case0 | Case1 of string | Case2 of string * string | Case3 of string * string * string - -type MyUnion2 = +type TestUnion2 = | Tag of string | NewTag of string @@ -65,16 +62,151 @@ type Wrapper(s: string) = member x.Value = s |> Seq.rev |> Seq.map string |> String.concat "" [] -type MyUnion3 = +type MyUnion = | Case1 | Case2 | Case3 type R = { Name: string - Case: MyUnion3 + Case: MyUnion } +type T1 = T1 +type T2 = T2 +type T3 = T3 +type T4 = T4 +type T5 = T5 +type T6 = T6 +type T7 = T7 +type T8 = T8 +type T9 = T9 +type 'a more = More of 'a + +// [] +// type LangCode = +// | EN +// | ``pt-Br`` + +type S = S of string + +type Id = Id of string + +type UnionWithIdField = WithId of Id: Id + +// See https://github.com/fable-compiler/Fable/issues/4645 +// A named union case field called `name` collided with the inherited `Union.name` +// property, which Python's @dataclass treated as a default value. +type NamedFieldUnion = + | NamedCase of name: string * optional: string + +// See https://github.com/fable-compiler/Fable/issues/4736 +// A static member whose type references the enclosing union used to emit a class-body +// `StaticLazyProperty[...]` subscript that evaluated the union type before its name was bound, +// raising `NameError` at import time. The reference can be nested in any type shape, so each +// member below exercises a different one: array, option (rendered with `|`), nested list/array, +// tuple, and a function type (`unit -> Example`). +[] +[] +type Example = + | First + | Second + | Other of string + + static member All = [| Example.First; Example.Second |] + static member Head = Some Example.First + static member Nested = [ [| Example.First |] ] + static member Pair = (Example.First, Example.Second) + static member Factory: unit -> Example = fun () -> Example.First + +[] +type StructPoint2D = + | Planar2D of float * float + | Infinity + +[] +type StructShape = + | Square of side: int + | Circle of radius: float + + member this.Area = + match this with + | Square side -> float (side * side) + | Circle radius -> System.Math.PI * radius * radius + +[] +type StructShape2 = + | Square of float + | Circle of float + + member this.Area = + match this with + | Square side -> side * side + | Circle radius -> System.Math.PI * radius * radius + +[] +type NestedMatchUnion = + | NestedA of int + | NestedB of int + +let private nestedUnionCaseAccess (u: NestedMatchUnion) = + match u with + | NestedA _ -> 0 + | NestedB _ -> + match u with + | NestedA value -> value + | _ -> -1 + +type IntUnion = + | IntCase1 of int + | IntCase2 of int + | IntCase3 of int + +let unionFnAlways1 = function + | IntCase1 x -> x + | _ -> -1 + +let unionFnRetNum = function + | IntCase1 a -> a + | IntCase2 b -> b + | IntCase3 c -> c + +type WrappedUnion = + | AString of string + +type DeepRecord = { Value: string } + +type DeepWrappedUnion = + | DeepWrappedA of string * DeepRecord + | DeepWrappedB of string + | DeepWrappedC of int + | DeepWrappedD of DeepRecord + | DeepWrappedE of int * int + | DeepWrappedF of WrappedUnion + | DeepWrappedG of {| X: DeepRecord; Y: int|} + +let matchStrings = function + | DeepWrappedA (s, d) -> d.Value + s + | DeepWrappedB s -> s + | DeepWrappedC c -> "nothing" + | DeepWrappedD d -> d.Value + | DeepWrappedE(a, b) -> "nothing2" + | DeepWrappedF (AString s) -> s + | DeepWrappedG x -> x.X.Value + +let matchNumbers = function + | DeepWrappedA (s, d) -> 0 + | DeepWrappedB s -> 0 + | DeepWrappedC c -> c + | DeepWrappedD d -> 0 + | DeepWrappedE(a, b) -> a + b + | DeepWrappedF _ -> 0 + | DeepWrappedG x -> x.Y + +let matchStringWhenStringNotHello = function + | DeepWrappedB s when s <> "hello" -> "not hello" + | _ -> "hello" + #if FABLE_COMPILER open Fable.Core @@ -132,12 +264,12 @@ let ``test Union cases matches with many arguments can be generated`` () = [] let ``test Pattern matching with common targets works`` () = - let x = MyUnion.Case2("a", "b") + let x = TestUnion.Case2("a", "b") match x with - | MyUnion.Case0 -> failwith "unexpected" - | MyUnion.Case1 _ - | MyUnion.Case2 _ -> "a" - | MyUnion.Case3(a, b, c) -> a + b + c + | TestUnion.Case0 -> failwith "unexpected" + | TestUnion.Case1 _ + | TestUnion.Case2 _ -> "a" + | TestUnion.Case3(a, b, c) -> a + b + c |> equal "a" [] @@ -231,23 +363,46 @@ let ``test Erased union payload fields can be read`` () = [] let ``test Equality works in filter`` () = - let original = [| { Name = "1"; Case = MyUnion3.Case1 } ; { Name = "2"; Case = MyUnion3.Case1 }; { Name = "3"; Case = MyUnion3.Case2 }; { Name = "4"; Case = MyUnion3.Case3 } |] + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] original - |> Array.filter (fun r -> r.Case = MyUnion3.Case1) + |> Array.filter (fun r -> r.Case = MyUnion.Case1) |> Array.length |> equal 2 -type S = S of string +[] +let ``test Pattern matching works in filter`` () = + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] + original + |> Array.filter (fun r -> match r.Case with MyUnion.Case1 -> true | _ -> false) + |> Array.length + |> equal 2 + +[] +let ``test Union field can have the same name as its type`` () = + let value = WithId(Id "foo") + match value with + | WithId id -> + match id with + | Id value -> value |> equal "foo" + +// [] +// let ``test Verify that union cases can have `-` in their names`` () = +// // See https://github.com/fable-compiler/Fable/issues/4071 +// let x = LangCode.``pt-Br`` +// equal x LangCode.``pt-Br`` + [] let ``test sprintf formats strings cases correctly`` () = let s = sprintf "%A" (S "1") s |> equal "S \"1\"" -// See https://github.com/fable-compiler/Fable/issues/4645 -// A named union case field called `name` collided with the inherited `Union.name` -// property, which Python's @dataclass treated as a default value. -type NamedFieldUnion = - | NamedCase of name: string * optional: string +[] +let ``test Union cases with no fields are physically equal`` () = + obj.ReferenceEquals(Gender.Male, Gender.Male) |> equal true + obj.ReferenceEquals(Gender.Male, Gender.Female) |> equal false + obj.ReferenceEquals(TestUnion.Case0, TestUnion.Case0) |> equal true + obj.ReferenceEquals(TestUnion.Case1 "a", TestUnion.Case1 "a") |> equal false + obj.ReferenceEquals(T1, T1) |> equal true [] let ``test Named union case fields work when a field is called name`` () = @@ -257,33 +412,6 @@ let ``test Named union case fields work when a field is called name`` () = name |> equal "v1" optional |> equal "v2" -[] -let ``test Union cases with no fields are physically equal`` () = - obj.ReferenceEquals(Gender.Male, Gender.Male) |> equal true - obj.ReferenceEquals(Gender.Male, Gender.Female) |> equal false - obj.ReferenceEquals(MyUnion.Case0, MyUnion.Case0) |> equal true - obj.ReferenceEquals(MyUnion.Case1 "a", MyUnion.Case1 "a") |> equal false - obj.ReferenceEquals(T1, T1) |> equal true - -// See https://github.com/fable-compiler/Fable/issues/4736 -// A static member whose type references the enclosing union used to emit a class-body -// `StaticLazyProperty[...]` subscript that evaluated the union type before its name was bound, -// raising `NameError` at import time. The reference can be nested in any type shape, so each -// member below exercises a different one: array, option (rendered with `|`), nested list/array, -// tuple, and a function type (`unit -> Example`). -[] -[] -type Example = - | First - | Second - | Other of string - - static member All = [| Example.First; Example.Second |] - static member Head = Some Example.First - static member Nested = [ [| Example.First |] ] - static member Pair = (Example.First, Example.Second) - static member Factory: unit -> Example = fun () -> Example.First - [] let ``test Static union member returning the union type works`` () = Example.All |> Array.length |> equal 2 @@ -301,3 +429,101 @@ let ``test Static union member with nested self-reference works`` () = Example.Pair |> snd |> equal Example.Second // Function type (`Callable[[], _Example]`) — missed by the original narrower detection. Example.Factory() |> equal Example.First + +[] +let ``test Struct unions work`` () = + let distance p = + match p with + | Planar2D (x, y) -> sqrt (x * x + y * y) + | Infinity -> infinity + let p = Planar2D (3, 4) + let res = distance p + res |> equal 5. + +[] +let ``test Struct unions with fields work`` () = + let radius = 3.0 + let circle = StructShape.Circle radius + let square = StructShape.Square(int (2.0 * radius)) + circle.Area |> equal (System.Math.PI * radius * radius) + square.Area |> equal (4.0 * radius * radius) + +[] +let ``test Struct unions without fields work`` () = + let radius = 3.0 + let circle = StructShape2.Circle radius + let square = StructShape2.Square(2.0 * radius) + circle.Area |> equal (System.Math.PI * radius * radius) + square.Area |> equal (4.0 * radius * radius) + +[] +let ``test Nested union match keeps case-specific field access`` () = + nestedUnionCaseAccess (NestedA 7) |> equal 0 + nestedUnionCaseAccess (NestedB 11) |> equal -1 + +[] +let ``test Union case matching works`` () = + let x = IntCase1 5 + let res = + match x with + | IntCase1 a -> a + | IntCase2 b -> b + | IntCase3 c -> c + res |> equal 5 + +[] +let ``test Union case equality works`` () = + IntCase1 5 = IntCase1 5 |> equal true + IntCase1 5 = IntCase2 5 |> equal false + IntCase3 2 = IntCase3 3 |> equal false + IntCase2 1 = IntCase2 1 |> equal true + IntCase3 1 = IntCase3 1 |> equal true + +[] +let ``test Union fn call works`` () = + let x = IntCase1 3 + let res = unionFnAlways1 x + let res2 = unionFnAlways1 x //deliberately force clone/borrow break + let res3 = unionFnRetNum x + let res4 = unionFnRetNum (IntCase2 24) + res |> equal 3 + res2 |> equal 3 + res3 |> equal 3 + res4 |> equal 24 + +[] +let ``test Deep union with wrapped type works`` () = + let a = DeepWrappedA (" world", {Value = "hello"}) + let b = DeepWrappedB "world" + let c = DeepWrappedC 42 + let d = DeepWrappedD { Value = "hello" } + let f = DeepWrappedF (AString "doublewrapped") + let g = DeepWrappedG {| X = { Value = "G" }; Y = 365 |} + a |> matchStrings |> equal "hello world" + b |> matchStrings |> equal "world" + c |> matchStrings |> equal "nothing" + d |> matchStrings |> equal "hello" + f |> matchStrings |> equal "doublewrapped" + g |> matchStrings |> equal "G" + +[] +let ``test Deep union with tuped prim type works`` () = + let e = DeepWrappedE (3, 2) + let c = DeepWrappedC 42 //todo to string! + let g = DeepWrappedG {| X = { Value = "G" }; Y = 365 |} + e |> matchNumbers |> equal 5 + c |> matchNumbers |> equal 42 + g |> matchNumbers |> equal 365 + +[] +let ``test Multi-case Union with wrapped type works`` () = + let b = DeepWrappedB "hello" + let res = match b with DeepWrappedB s -> s + " world" | _ -> "" + res |> equal "hello world" + +[] +let ``test Match with condition works`` () = + let b1 = DeepWrappedB "hello" + let b2 = DeepWrappedB "not" + b1 |> matchStringWhenStringNotHello |> equal "hello" + b2 |> matchStringWhenStringNotHello |> equal "not hello" diff --git a/tests/Rust/tests/src/UnionTests.fs b/tests/Rust/tests/src/UnionTests.fs index 1572ddf3f2..3b8378a02d 100644 --- a/tests/Rust/tests/src/UnionTests.fs +++ b/tests/Rust/tests/src/UnionTests.fs @@ -2,244 +2,7 @@ module Fable.Tests.UnionTests open Util.Testing -type IntUnion = - | IntCase1 of int - | IntCase2 of int - | IntCase3 of int - -[] -type Point2D = - | Planar2D of float * float - | Infinity - -[] -type Shape = - | Square of side: int - | Circle of radius: float - - member this.Area = - match this with - | Square side -> float (side * side) - | Circle radius -> System.Math.PI * radius * radius - -[] -type Shape2 = - | Square of float - | Circle of float - - member this.Area = - match this with - | Square side -> side * side - | Circle radius -> System.Math.PI * radius * radius - -[] -let ``Struct unions work`` () = - let distance p = - match p with - | Planar2D (x, y) -> sqrt (x * x + y * y) - | Infinity -> infinity - let p = Planar2D (3, 4) - let res = distance p - res |> equal 5. - -[] -let ``Struct unions with fields work`` () = - let radius = 3.0 - let circle = Shape.Circle radius - let square = Shape.Square(int (2.0 * radius)) - circle.Area |> equal (System.Math.PI * radius * radius) - square.Area |> equal (4.0 * radius * radius) - -[] -let ``Struct unions without fields work`` () = - let radius = 3.0 - let circle = Shape2.Circle radius - let square = Shape2.Square(2.0 * radius) - circle.Area |> equal (System.Math.PI * radius * radius) - square.Area |> equal (4.0 * radius * radius) - -[] -let ``Union case matching works`` () = - let x = IntCase1 5 - let res = - match x with - | IntCase1 a -> a - | IntCase2 b -> b - | IntCase3 c -> c - res |> equal 5 - -[] -let ``Union case equality works`` () = - IntCase1 5 = IntCase1 5 |> equal true - IntCase1 5 = IntCase2 5 |> equal false - IntCase3 2 = IntCase3 3 |> equal false - IntCase2 1 = IntCase2 1 |> equal true - IntCase3 1 = IntCase3 1 |> equal true - -let unionFnAlways1 = function - | IntCase1 x -> x - | _ -> -1 - -let unionFnRetNum = function - | IntCase1 a -> a - | IntCase2 b -> b - | IntCase3 c -> c - -[] -let ``Union fn call works`` () = - let x = IntCase1 3 - let res = unionFnAlways1 x - let res2 = unionFnAlways1 x //deliberately force clone/borrow break - let res3 = unionFnRetNum x - let res4 = unionFnRetNum (IntCase2 24) - res |> equal 3 - res2 |> equal 3 - res3 |> equal 3 - res4 |> equal 24 - -type WrappedUnion = - | AString of string - -[] -let ``Union with wrapped type works`` () = - let a = AString "hello" - let b = match a with AString s -> s + " world" - b |> equal "hello world" - -type DeepRecord = {Value: string} - -type DeepWrappedUnion = - | DeepWrappedA of string * DeepRecord - | DeepWrappedB of string - | DeepWrappedC of int - | DeepWrappedD of DeepRecord - | DeepWrappedE of int * int - | DeepWrappedF of WrappedUnion - | DeepWrappedG of {| X: DeepRecord; Y: int|} - -let matchStrings = function - | DeepWrappedA (s, d) -> d.Value + s - | DeepWrappedB s -> s - | DeepWrappedC c -> "nothing" - | DeepWrappedD d -> d.Value - | DeepWrappedE(a, b) -> "nothing2" - | DeepWrappedF (AString s) -> s - | DeepWrappedG x -> x.X.Value - -let matchNumbers = function - | DeepWrappedA (s, d) -> 0 - | DeepWrappedB s -> 0 - | DeepWrappedC c -> c - | DeepWrappedD d -> 0 - | DeepWrappedE(a, b) -> a + b - | DeepWrappedF _ -> 0 - | DeepWrappedG x -> x.Y - -[] -let ``Deep union with wrapped type works`` () = - let a = DeepWrappedA (" world", {Value = "hello"}) - let b = DeepWrappedB "world" - let c = DeepWrappedC 42 - let d = DeepWrappedD { Value = "hello" } - let f = DeepWrappedF (AString "doublewrapped") - let g = DeepWrappedG {|X={Value="G"}; Y=365|} - a |> matchStrings |> equal "hello world" - b |> matchStrings |> equal "world" - c |> matchStrings |> equal "nothing" - d |> matchStrings |> equal "hello" - f |> matchStrings |> equal "doublewrapped" - g |> matchStrings |> equal "G" - -[] -let ``Deep union with tuped prim type works`` () = - let e = DeepWrappedE (3, 2) - let c = DeepWrappedC 42//todo to string! - let g = DeepWrappedG {|X={Value="G"}; Y=365|} - e |> matchNumbers |> equal 5 - c |> matchNumbers |> equal 42 - g |> matchNumbers |> equal 365 - -[] -let ``Multi-case Union with wrapped type works`` () = - let b = DeepWrappedB "hello" - let res = match b with DeepWrappedB s -> s + " world" | _ -> "" - res |> equal "hello world" - -[] -let ``Should correctly import union in other file and allow creation`` () = - let r = Common.Imports.MyUnion.A 1 - let expected = Common.Imports.MyUnion.createA 1 - r |> equal expected - - -let matchStringWhenStringNotHello = function - | DeepWrappedB s when s <> "hello" -> "not hello" - | _ -> "hello" - -[] -let ``Match with condition works`` () = - let b1 = DeepWrappedB "hello" - let b2 = DeepWrappedB "not" - b1 |> matchStringWhenStringNotHello |> equal "hello" - b2 |> matchStringWhenStringNotHello |> equal "not hello" - -[] -type NestedMatchUnion = - | NestedA of int - | NestedB of int - -let private nestedUnionCaseAccess (u: NestedMatchUnion) = - match u with - | NestedA _ -> 0 - | NestedB _ -> - match u with - | NestedA value -> value - | _ -> -1 - -[] -let ``Nested union match keeps case-specific field access`` () = - nestedUnionCaseAccess (NestedA 7) |> equal 0 - nestedUnionCaseAccess (NestedB 11) |> equal -1 - -[] -type ArcUnion = - | ArcS of string - | ArcI of int - -let add1 = function - | ArcS s -> s + "1" |> ArcS - | ArcI i -> i + 1 |> ArcI - -[] -let ``Union with pointer type works`` () = - let a = ArcS "hello" - let b = ArcI 42 - let aa = add1 a - let bb = add1 b - aa |> equal (ArcS "hello1") - bb |> equal (ArcI 43) - -#if FABLE_COMPILER -open Fable.Core - -[")>] -let ensureMyUnionWrapped s = nativeOnly - -[] -let ensureIsStructUnionUnwrapped s = nativeOnly - -[] -let ``Normal union should be wrapped in a Lrc`` () = - IntCase1 1 |> ensureMyUnionWrapped |> ignore - -[] -let ``Struct union should not be wrapped in a Lrc`` () = - Infinity |> ensureIsStructUnionUnwrapped |> ignore - -#endif - - -// type Gender = Male | Female +type Gender = Male | Female // type Either<'TL,'TR> = // | Left of 'TL @@ -300,9 +63,9 @@ type Tree = [] type MyUnion = -| Case1 -| Case2 -| Case3 + | Case1 + | Case2 + | Case3 type R = { Name: string @@ -328,6 +91,119 @@ type T8 = T8 type T9 = T9 type 'a more = More of 'a +// [] +// type LangCode = +// | EN +// | ``pt-Br`` + +type S = S of string + +type Id = Id of string + +type UnionWithIdField = WithId of Id: Id +type NamedFieldUnion = + | NamedCase of name: string * optional: string + +[] +type Example = + | First + | Second + | Other of string + + static member All = [| Example.First; Example.Second |] + static member Head = Some Example.First + static member Nested = [ [| Example.First |] ] + static member Pair = (Example.First, Example.Second) + static member Factory: unit -> Example = fun () -> Example.First + +[] +type StructPoint2D = + | Planar2D of float * float + | Infinity + +[] +type StructShape = + | Square of side: int + | Circle of radius: float + + member this.Area = + match this with + | Square side -> float (side * side) + | Circle radius -> System.Math.PI * radius * radius + +[] +type StructShape2 = + | Square of float + | Circle of float + + member this.Area = + match this with + | Square side -> side * side + | Circle radius -> System.Math.PI * radius * radius + +[] +type NestedMatchUnion = + | NestedA of int + | NestedB of int + +let private nestedUnionCaseAccess (u: NestedMatchUnion) = + match u with + | NestedA _ -> 0 + | NestedB _ -> + match u with + | NestedA value -> value + | _ -> -1 + +type IntUnion = + | IntCase1 of int + | IntCase2 of int + | IntCase3 of int + +let unionFnAlways1 = function + | IntCase1 x -> x + | _ -> -1 + +let unionFnRetNum = function + | IntCase1 a -> a + | IntCase2 b -> b + | IntCase3 c -> c + +type WrappedUnion = + | AString of string + +type DeepRecord = { Value: string } + +type DeepWrappedUnion = + | DeepWrappedA of string * DeepRecord + | DeepWrappedB of string + | DeepWrappedC of int + | DeepWrappedD of DeepRecord + | DeepWrappedE of int * int + | DeepWrappedF of WrappedUnion + | DeepWrappedG of {| X: DeepRecord; Y: int|} + +let matchStrings = function + | DeepWrappedA (s, d) -> d.Value + s + | DeepWrappedB s -> s + | DeepWrappedC c -> "nothing" + | DeepWrappedD d -> d.Value + | DeepWrappedE(a, b) -> "nothing2" + | DeepWrappedF (AString s) -> s + | DeepWrappedG x -> x.X.Value + +let matchNumbers = function + | DeepWrappedA (s, d) -> 0 + | DeepWrappedB s -> 0 + | DeepWrappedC c -> c + | DeepWrappedD d -> 0 + | DeepWrappedE(a, b) -> a + b + | DeepWrappedF _ -> 0 + | DeepWrappedG x -> x.Y + +let matchStringWhenStringNotHello = function + | DeepWrappedB s when s <> "hello" -> "not hello" + | _ -> "hello" + // [] // let ``Union cases matches with no arguments can be generated`` () = // let x = Male @@ -436,7 +312,7 @@ let ``Union cases called Tag still work (bug due to Tag field)`` () = [] let ``Equality works in filter`` () = - let original = [| { Name = "1"; Case = MyUnion.Case1 } ; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] original |> Array.filter (fun r -> r.Case = MyUnion.Case1) |> Array.length @@ -444,12 +320,20 @@ let ``Equality works in filter`` () = [] let ``Pattern matching works in filter`` () = - let original = [| { Name = "1"; Case = MyUnion.Case1 } ; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] + let original = [| { Name = "1"; Case = MyUnion.Case1 }; { Name = "2"; Case = MyUnion.Case1 }; { Name = "3"; Case = MyUnion.Case2 }; { Name = "4"; Case = MyUnion.Case3 } |] original |> Array.filter (fun r -> match r.Case with MyUnion.Case1 -> true | _ -> false) |> Array.length |> equal 2 +[] +let ``Union field can have the same name as its type`` () = + let value = WithId(Id "foo") + match value with + | WithId id -> + match id with + | Id value -> value |> equal "foo" + // #if FABLE_COMPILER // [] // let ``Erased union type testing works`` () = @@ -520,3 +404,191 @@ let ``Implicit conversion to erased unions works`` () = b |> equal (U3.Case3 true) #endif + +// [] +// let ``Verify that union cases can have `-` in their names`` () = +// // See https://github.com/fable-compiler/Fable/issues/4071 +// let x = LangCode.``pt-Br`` +// equal x LangCode.``pt-Br`` + +// [] +// let ``sprintf formats strings cases correctly`` () = +// let s = sprintf "%A" (S "1") +// s |> equal "S \"1\"" + +// [] +// let ``Union cases with no fields are physically equal`` () = +// obj.ReferenceEquals(Gender.Male, Gender.Male) |> equal true +// obj.ReferenceEquals(Gender.Male, Gender.Female) |> equal false +// obj.ReferenceEquals(TestUnion.Case0, TestUnion.Case0) |> equal true +// obj.ReferenceEquals(TestUnion.Case1 "a", TestUnion.Case1 "a") |> equal false +// obj.ReferenceEquals(T1, T1) |> equal true + +[] +let ``Named union case fields work when a field is called name`` () = + let value = NamedCase("v1", "v2") + match value with + | NamedCase(name, optional) -> + name |> equal "v1" + optional |> equal "v2" + +[] +let ``Static union member returning the union type works`` () = + Example.All |> Array.length |> equal 2 + Example.All.[0] |> equal Example.First + Example.All.[1] |> equal Example.Second + +[] +let ``Static union member with nested self-reference works`` () = + Example.Head |> equal (Some Example.First) + Example.Nested |> List.head |> Array.head |> equal Example.First + Example.Pair |> fst |> equal Example.First + Example.Pair |> snd |> equal Example.Second + Example.Factory() |> equal Example.First + +[] +let ``Struct unions work`` () = + let distance p = + match p with + | Planar2D (x, y) -> sqrt (x * x + y * y) + | Infinity -> infinity + let p = Planar2D (3, 4) + let res = distance p + res |> equal 5. + +[] +let ``Struct unions with fields work`` () = + let radius = 3.0 + let circle = StructShape.Circle radius + let square = StructShape.Square(int (2.0 * radius)) + circle.Area |> equal (System.Math.PI * radius * radius) + square.Area |> equal (4.0 * radius * radius) + +[] +let ``Struct unions without fields work`` () = + let radius = 3.0 + let circle = StructShape2.Circle radius + let square = StructShape2.Square(2.0 * radius) + circle.Area |> equal (System.Math.PI * radius * radius) + square.Area |> equal (4.0 * radius * radius) + +[] +let ``Nested union match keeps case-specific field access`` () = + nestedUnionCaseAccess (NestedA 7) |> equal 0 + nestedUnionCaseAccess (NestedB 11) |> equal -1 + +[] +let ``Union case matching works`` () = + let x = IntCase1 5 + let res = + match x with + | IntCase1 a -> a + | IntCase2 b -> b + | IntCase3 c -> c + res |> equal 5 + +[] +let ``Union case equality works`` () = + IntCase1 5 = IntCase1 5 |> equal true + IntCase1 5 = IntCase2 5 |> equal false + IntCase3 2 = IntCase3 3 |> equal false + IntCase2 1 = IntCase2 1 |> equal true + IntCase3 1 = IntCase3 1 |> equal true + +[] +let ``Union fn call works`` () = + let x = IntCase1 3 + let res = unionFnAlways1 x + let res2 = unionFnAlways1 x //deliberately force clone/borrow break + let res3 = unionFnRetNum x + let res4 = unionFnRetNum (IntCase2 24) + res |> equal 3 + res2 |> equal 3 + res3 |> equal 3 + res4 |> equal 24 + +[] +let ``Union with wrapped type works`` () = + let a = AString "hello" + let b = match a with AString s -> s + " world" + b |> equal "hello world" + +[] +let ``Deep union with wrapped type works`` () = + let a = DeepWrappedA (" world", {Value = "hello"}) + let b = DeepWrappedB "world" + let c = DeepWrappedC 42 + let d = DeepWrappedD { Value = "hello" } + let f = DeepWrappedF (AString "doublewrapped") + let g = DeepWrappedG {| X = { Value = "G" }; Y = 365 |} + a |> matchStrings |> equal "hello world" + b |> matchStrings |> equal "world" + c |> matchStrings |> equal "nothing" + d |> matchStrings |> equal "hello" + f |> matchStrings |> equal "doublewrapped" + g |> matchStrings |> equal "G" + +[] +let ``Deep union with tuped prim type works`` () = + let e = DeepWrappedE (3, 2) + let c = DeepWrappedC 42 //todo to string! + let g = DeepWrappedG {| X = { Value = "G" }; Y = 365 |} + e |> matchNumbers |> equal 5 + c |> matchNumbers |> equal 42 + g |> matchNumbers |> equal 365 + +[] +let ``Multi-case Union with wrapped type works`` () = + let b = DeepWrappedB "hello" + let res = match b with DeepWrappedB s -> s + " world" | _ -> "" + res |> equal "hello world" + +[] +let ``Match with condition works`` () = + let b1 = DeepWrappedB "hello" + let b2 = DeepWrappedB "not" + b1 |> matchStringWhenStringNotHello |> equal "hello" + b2 |> matchStringWhenStringNotHello |> equal "not hello" + +[] +let ``Should correctly import union in other file and allow creation`` () = + let r = Common.Imports.MyUnion.A 1 + let expected = Common.Imports.MyUnion.createA 1 + r |> equal expected + +#if FABLE_COMPILER +open Fable.Core + +[] +type ArcUnion = + | ArcS of string + | ArcI of int + +let add1 = function + | ArcS s -> s + "1" |> ArcS + | ArcI i -> i + 1 |> ArcI + +[] +let ``Union with pointer type works`` () = + let a = ArcS "hello" + let b = ArcI 42 + let aa = add1 a + let bb = add1 b + aa |> equal (ArcS "hello1") + bb |> equal (ArcI 43) + +[")>] +let ensureMyUnionWrapped s = nativeOnly + +[] +let ensureIsStructUnionUnwrapped s = nativeOnly + +[] +let ``Normal union should be wrapped in a Lrc`` () = + IntCase1 1 |> ensureMyUnionWrapped |> ignore + +[] +let ``Struct union should not be wrapped in a Lrc`` () = + Infinity |> ensureIsStructUnionUnwrapped |> ignore + +#endif