Skip to content

Commit

Permalink
add constrains clause
Browse files Browse the repository at this point in the history
  • Loading branch information
johnazariah committed Jul 24, 2024
1 parent 6087203 commit 7f26b55
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 4 deletions.
12 changes: 11 additions & 1 deletion src/CSharp.UnionTypes.SourceGenerator/AST.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down
9 changes: 9 additions & 0 deletions src/CSharp.UnionTypes.SourceGenerator/CodeEmitter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 $"{{"

Expand Down
10 changes: 8 additions & 2 deletions src/CSharp.UnionTypes.TestApplication/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ namespace CSharp.UnionTypes.TestApplication
{
public static class Program
{

public static void Main (string[] args)
{
Maybe<int> m23 = new Maybe<int>.Some(23);
Expand All @@ -18,7 +17,14 @@ public static void Main (string[] args)

Console.WriteLine(str);
Console.WriteLine($"{m23}");
Console.WriteLine($"{new Result<int, Exception>.Return(18)}");

var red = new TrafficLights.Red();
var stopRed = (TrafficLightsToStopFor.Red)red;
Console.WriteLine($"{red}, {stopRed}, {(TrafficLights.Red)stopRed}");

var card = new PaymentMethod<string, int>.Card("1234");
AuditablePaymentMethod<string, int>.Card auditableCard = card;
Console.WriteLine($"{card}, {auditableCard}, {(PaymentMethod<string, int>.Card)auditableCard}");
}
}
}
7 changes: 6 additions & 1 deletion src/CSharp.UnionTypes.TestApplication/maybe.csunion
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
{
using System;

union Result<V,E> { Return<V> | Error<E> };
union Maybe<T> { Some<T> | None };

union PaymentMethod<TCard, TCheque> { Cash | Card<TCard> | Cheque<TCheque> };
union AuditablePaymentMethod<TCard, TCheque> constrains PaymentMethod<TCard, TCheque> { Card<TCard> | Cheque<TCheque> };

union TrafficLights { Red | Amber | Green };
union TrafficLightsToStopFor constrains TrafficLights { Red | Amber };
}
99 changes: 99 additions & 0 deletions tests/Tests.CSharp.UnionTypes.SourceGenerator/CodeEmitterTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,104 @@ let ``Maybe<T> is generated correctly`` () =
}
}
}
"""
Assert.Equal (expected.Replace("\r\n", "\n"), actual.Replace("\r\n", "\n"))

[<Fact>]
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"))


[<Fact>]
let ``Constrains clause for value constructed unions is generated correctly`` () =
let actual =
"""namespace CSharp.UnionTypes.TestApplication
{
union PaymentMethod<TCard, TCheque> { Cash | Card<TCard> | Cheque<TCheque> };
union AuditablePaymentMethod<TCard, TCheque> constrains PaymentMethod<TCard, TCheque> { Card<TCard> | Cheque<TCheque> };
}"""
|> GenerateNamespaceCode

let expected =
"""namespace CSharp.UnionTypes.TestApplication
{
public abstract partial record PaymentMethod<TCard, TCheque>
{
private PaymentMethod() { }
public sealed partial record Cash() : PaymentMethod<TCard, TCheque>
{
override public string ToString() => $"PaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Cash";
}
public sealed partial record Card(TCard Value) : PaymentMethod<TCard, TCheque>
{
override public string ToString() => $"PaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Card {Value}";
}
public sealed partial record Cheque(TCheque Value) : PaymentMethod<TCard, TCheque>
{
override public string ToString() => $"PaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Cheque {Value}";
}
}
public abstract partial record AuditablePaymentMethod<TCard, TCheque>
{
private AuditablePaymentMethod() { }
public sealed partial record Card(TCard Value) : AuditablePaymentMethod<TCard, TCheque>
{
override public string ToString() => $"AuditablePaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Card {Value}";
public static implicit operator Card(PaymentMethod<TCard, TCheque>.Card value) => new Card(value.Value);
public static implicit operator PaymentMethod<TCard, TCheque>.Card(Card value) => new PaymentMethod<TCard, TCheque>.Card(value.Value);
}
public sealed partial record Cheque(TCheque Value) : AuditablePaymentMethod<TCard, TCheque>
{
override public string ToString() => $"AuditablePaymentMethod<{typeof(TCard)}, {typeof(TCheque)}>.Cheque {Value}";
public static implicit operator Cheque(PaymentMethod<TCard, TCheque>.Cheque value) => new Cheque(value.Value);
public static implicit operator PaymentMethod<TCard, TCheque>.Cheque(Cheque value) => new PaymentMethod<TCard, TCheque>.Cheque(value.Value);
}
}
}
"""
Assert.Equal (expected.Replace("\r\n", "\n"), actual.Replace("\r\n", "\n"))

0 comments on commit 7f26b55

Please sign in to comment.