diff --git a/src/CSharp.UnionTypes.SourceGenerator/AST.fs b/src/CSharp.UnionTypes.SourceGenerator/AST.fs index 3fe6d99..ddd6ceb 100644 --- a/src/CSharp.UnionTypes.SourceGenerator/AST.fs +++ b/src/CSharp.UnionTypes.SourceGenerator/AST.fs @@ -155,11 +155,21 @@ module AST = in sprintf "%s%s" bareTypeName typeParameters + member this.ValueMember = + match this.MemberArgumentType with + | Some _ -> "Value" + | None -> "" + member this.UnionMemberValueMember = match this.MemberArgumentType with - | Some mat -> sprintf "(%s Value)" mat.CSharpTypeName + | Some mat -> sprintf "(%s %s)" mat.CSharpTypeName this.ValueMember | None -> "()" + member this.UnionMemberValueAccessor(varName) = + match this.MemberArgumentType with + | Some _ -> sprintf "%s.%s" varName this.ValueMember + | None -> "" + override this.ToString() = this.MemberArgumentType |> Option.fold (fun _ s -> sprintf "%s of %s" this.MemberName.unapply (s.ToString())) diff --git a/src/CSharp.UnionTypes.SourceGenerator/CodeEmitter.fs b/src/CSharp.UnionTypes.SourceGenerator/CodeEmitter.fs index 04041ee..61a218a 100644 --- a/src/CSharp.UnionTypes.SourceGenerator/CodeEmitter.fs +++ b/src/CSharp.UnionTypes.SourceGenerator/CodeEmitter.fs @@ -25,9 +25,18 @@ module CodeEmitter = | Some _ -> $" {{Value}}" | None -> "" indentAndWriteLine $"override public string ToString() => $\"{union.UnionClassNameWithTypeofTypeArgs}.{unionMember.MemberName.unapply}{memberValuePattern}\";" + match union.BaseType with + | Some baseType -> + let valueAccessor = unionMember.UnionMemberValueAccessor("value") + indentAndWriteLine $"public static implicit operator {unionMember.MemberName.unapply}({baseType.CSharpTypeName}.{unionMember.MemberName.unapply} value) => new {unionMember.MemberName.unapply}({valueAccessor});" + indentAndWriteLine $"public static implicit operator {baseType.CSharpTypeName}.{unionMember.MemberName.unapply}({unionMember.MemberName.unapply} value) => new {baseType.CSharpTypeName}.{unionMember.MemberName.unapply}({valueAccessor});" + | None -> + () + indentWriter.Indent <- indentWriter.Indent - 1 indentAndWriteLine $"}}" + indentAndWriteLine $"public abstract partial record {union.UnionClassNameWithTypeArgs}" indentAndWriteLine $"{{" diff --git a/src/CSharp.UnionTypes.TestApplication/Program.cs b/src/CSharp.UnionTypes.TestApplication/Program.cs index 9953ed2..ae04662 100644 --- a/src/CSharp.UnionTypes.TestApplication/Program.cs +++ b/src/CSharp.UnionTypes.TestApplication/Program.cs @@ -4,7 +4,6 @@ namespace CSharp.UnionTypes.TestApplication { public static class Program { - public static void Main (string[] args) { Maybe m23 = new Maybe.Some(23); @@ -18,7 +17,14 @@ public static void Main (string[] args) Console.WriteLine(str); Console.WriteLine($"{m23}"); - Console.WriteLine($"{new Result.Return(18)}"); + + var red = new TrafficLights.Red(); + var stopRed = (TrafficLightsToStopFor.Red)red; + Console.WriteLine($"{red}, {stopRed}, {(TrafficLights.Red)stopRed}"); + + var card = new PaymentMethod.Card("1234"); + AuditablePaymentMethod.Card auditableCard = card; + Console.WriteLine($"{card}, {auditableCard}, {(PaymentMethod.Card)auditableCard}"); } } } diff --git a/src/CSharp.UnionTypes.TestApplication/maybe.csunion b/src/CSharp.UnionTypes.TestApplication/maybe.csunion index 967c19b..cd17cbf 100644 --- a/src/CSharp.UnionTypes.TestApplication/maybe.csunion +++ b/src/CSharp.UnionTypes.TestApplication/maybe.csunion @@ -2,6 +2,11 @@ { using System; - union Result { Return | Error }; union Maybe { Some | None }; + + union PaymentMethod { Cash | Card | Cheque }; + union AuditablePaymentMethod constrains PaymentMethod { Card | Cheque }; + + union TrafficLights { Red | Amber | Green }; + union TrafficLightsToStopFor constrains TrafficLights { Red | Amber }; } \ No newline at end of file diff --git a/tests/Tests.CSharp.UnionTypes.SourceGenerator/CodeEmitterTests.fs b/tests/Tests.CSharp.UnionTypes.SourceGenerator/CodeEmitterTests.fs index dddfe63..6399b18 100644 --- a/tests/Tests.CSharp.UnionTypes.SourceGenerator/CodeEmitterTests.fs +++ b/tests/Tests.CSharp.UnionTypes.SourceGenerator/CodeEmitterTests.fs @@ -28,5 +28,104 @@ let ``Maybe is generated correctly`` () = } } } +""" + Assert.Equal (expected.Replace("\r\n", "\n"), actual.Replace("\r\n", "\n")) + +[] +let ``Constrains clause for enumerations is generated correctly`` () = + let actual = + """namespace CSharp.UnionTypes.TestApplication +{ + union TrafficLights { Red | Amber | Green }; + union TrafficLightsToStopFor constrains TrafficLights { Red | Amber }; +}""" + |> GenerateNamespaceCode + + let expected = + """namespace CSharp.UnionTypes.TestApplication +{ + public abstract partial record TrafficLights + { + private TrafficLights() { } + public sealed partial record Red() : TrafficLights + { + override public string ToString() => $"TrafficLights.Red"; + } + public sealed partial record Amber() : TrafficLights + { + override public string ToString() => $"TrafficLights.Amber"; + } + public sealed partial record Green() : TrafficLights + { + override public string ToString() => $"TrafficLights.Green"; + } + } + public abstract partial record TrafficLightsToStopFor + { + private TrafficLightsToStopFor() { } + public sealed partial record Red() : TrafficLightsToStopFor + { + override public string ToString() => $"TrafficLightsToStopFor.Red"; + public static implicit operator Red(TrafficLights.Red value) => new Red(); + public static implicit operator TrafficLights.Red(Red value) => new TrafficLights.Red(); + } + public sealed partial record Amber() : TrafficLightsToStopFor + { + override public string ToString() => $"TrafficLightsToStopFor.Amber"; + public static implicit operator Amber(TrafficLights.Amber value) => new Amber(); + public static implicit operator TrafficLights.Amber(Amber value) => new TrafficLights.Amber(); + } + } +} +""" + Assert.Equal (expected.Replace("\r\n", "\n"), actual.Replace("\r\n", "\n")) + + +[] +let ``Constrains clause for value constructed unions is generated correctly`` () = + let actual = + """namespace CSharp.UnionTypes.TestApplication +{ + union PaymentMethod { Cash | Card | Cheque }; + union AuditablePaymentMethod constrains PaymentMethod { Card | Cheque }; +}""" + |> GenerateNamespaceCode + + let expected = + """namespace CSharp.UnionTypes.TestApplication +{ + public abstract partial record PaymentMethod + { + private PaymentMethod() { } + public sealed partial record Cash() : PaymentMethod + { + override public string ToString() => $"PaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Cash"; + } + public sealed partial record Card(TCard Value) : PaymentMethod + { + override public string ToString() => $"PaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Card {Value}"; + } + public sealed partial record Cheque(TCheque Value) : PaymentMethod + { + override public string ToString() => $"PaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Cheque {Value}"; + } + } + public abstract partial record AuditablePaymentMethod + { + private AuditablePaymentMethod() { } + public sealed partial record Card(TCard Value) : AuditablePaymentMethod + { + override public string ToString() => $"AuditablePaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Card {Value}"; + public static implicit operator Card(PaymentMethod.Card value) => new Card(value.Value); + public static implicit operator PaymentMethod.Card(Card value) => new PaymentMethod.Card(value.Value); + } + public sealed partial record Cheque(TCheque Value) : AuditablePaymentMethod + { + override public string ToString() => $"AuditablePaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Cheque {Value}"; + public static implicit operator Cheque(PaymentMethod.Cheque value) => new Cheque(value.Value); + public static implicit operator PaymentMethod.Cheque(Cheque value) => new PaymentMethod.Cheque(value.Value); + } + } +} """ Assert.Equal (expected.Replace("\r\n", "\n"), actual.Replace("\r\n", "\n")) \ No newline at end of file