Skip to content

Commit

Permalink
Merge pull request #77 from MercuryTechnologies/tad/swift-sum-types
Browse files Browse the repository at this point in the history
swift: add Codable implementation for sum-of-product types
  • Loading branch information
tadfisher authored Feb 13, 2024
2 parents a37fe36 + bb72de5 commit 84c138d
Show file tree
Hide file tree
Showing 21 changed files with 695 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@Serializable
sealed class Enum : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ sealed class Enum : Parcelable {
@Serializable
@SerialName("dataCons1")
data class DataCons1(val contents: Record1) : Enum()

@Parcelize
@Serializable
@SerialName("dataCons2")
data object DataCons2 : Enum()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@Parcelize
@Serializable
data class Record0(
val record0Field0: Int,
val record0Field1: Int,
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@Parcelize
@Serializable
data class Record1(
val record1Field0: Int,
val record1Field1: Int,
) : Parcelable
33 changes: 33 additions & 0 deletions .golden/swiftEnumSumOfProductDocSpec/golden
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,37 @@ enum Enum: CaseIterable, Hashable, Codable {
case dataCons0(Record0)
/// Another constructor.
case dataCons1(Record1)

enum CodingKeys: String, CodingKey {
case tag
case contents
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let discriminator = try container.decode(String.self, forKey: .tag)
switch discriminator {
case "dataCons0":
self = .dataCons0(try container.decode(Record0.self, forKey: .contents))
case "dataCons1":
self = .dataCons1(try container.decode(Record1.self, forKey: .contents))
default:
throw DecodingError.typeMismatch(
CodingKeys.self,
.init(codingPath: decoder.codingPath, debugDescription: "Can't decode unknown tag: Enum.\(discriminator)")
)
}
}

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch (self) {
case let .dataCons0(contents):
try container.encode("dataCons0", forKey: .tag)
try container.encode(contents, forKey: .contents)
case let .dataCons1(contents):
try container.encode("dataCons1", forKey: .tag)
try container.encode(contents, forKey: .contents)
}
}
}
46 changes: 45 additions & 1 deletion .golden/swiftEnumSumOfProductSpec/golden
Original file line number Diff line number Diff line change
@@ -1,4 +1,48 @@
enum Enum: CaseIterable, Hashable, Codable {
enum Enum: Hashable, Codable {
case dataCons0(_ enumField0: Int, _ enumField1: Int)
case dataCons1(_ enumField2: String, _ enumField3: String)

enum CodingKeys: String, CodingKey {
case tag
case enumField0
case enumField1
case enumField2
case enumField3
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let discriminator = try container.decode(String.self, forKey: .tag)
switch discriminator {
case "dataCons0":
self = .dataCons0(
try container.decode(Int.self, forKey: .enumField0),
try container.decode(Int.self, forKey: .enumField1)
)
case "dataCons1":
self = .dataCons1(
try container.decode(String.self, forKey: .enumField2),
try container.decode(String.self, forKey: .enumField3)
)
default:
throw DecodingError.typeMismatch(
CodingKeys.self,
.init(codingPath: decoder.codingPath, debugDescription: "Can't decode unknown tag: Enum.\(discriminator)")
)
}
}

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch (self) {
case let .dataCons0:
try container.encode("dataCons0", forKey: .tag)
try container.encode(enumField0, forKey: .enumField0)
try container.encode(enumField1, forKey: .enumField1)
case let .dataCons1:
try container.encode("dataCons1", forKey: .tag)
try container.encode(enumField2, forKey: .enumField2)
try container.encode(enumField3, forKey: .enumField3)
}
}
}
32 changes: 32 additions & 0 deletions .golden/swiftEnumSumOfProductWithLinkEnumInterfaceSpec/golden
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
enum Enum: CaseIterable, Hashable, Codable {
case dataCons0(Record0)
case dataCons1(Record1)

enum CodingKeys: String, CodingKey {
case tag
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let discriminator = try container.decode(String.self, forKey: .tag)
switch discriminator {
case "dataCons0":
self = .dataCons0(try Record0.init(from: decoder))
case "dataCons1":
self = .dataCons1(try Record1.init(from: decoder))
default:
throw DecodingError.typeMismatch(
CodingKeys.self,
.init(codingPath: decoder.codingPath, debugDescription: "Can't decode unknown tag: Enum.\(discriminator)")
)
}
}

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch (self) {
case let .dataCons0(value):
try container.encode("dataCons0", forKey: .tag)
try value.encode(to: encoder)
case let .dataCons1(value):
try container.encode("dataCons1", forKey: .tag)
try value.encode(to: encoder)
}
}
}
44 changes: 44 additions & 0 deletions .golden/swiftEnumSumOfProductWithTaggedFlatObjectStyleSpec/golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
enum Enum: Codable {
case dataCons0(Record0)
case dataCons1(Record1)
case dataCons2
case _unknown

enum CodingKeys: String, CodingKey {
case tag
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let discriminator = try container.decode(String.self, forKey: .tag)
switch discriminator {
case "dataCons0":
self = .dataCons0(try Record0.init(from: decoder))
case "dataCons1":
self = .dataCons1(try Record1.init(from: decoder))
case "dataCons2":
self = .dataCons2
default:
self = ._unknown
}
}

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch (self) {
case let .dataCons0(value):
try container.encode("dataCons0", forKey: .tag)
try value.encode(to: encoder)
case let .dataCons1(value):
try container.encode("dataCons1", forKey: .tag)
try value.encode(to: encoder)
case .dataCons2:
try container.encode("dataCons2", forKey: .tag)
case ._unknown:
throw EncodingError.invalidValue(
self,
.init(codingPath: encoder.codingPath, debugDescription: "Can't encode value: Enum._unknown")
)
}
}
}
42 changes: 42 additions & 0 deletions .golden/swiftEnumSumOfProductWithTaggedObjectStyleSpec/golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
enum Enum: Codable {
case dataCons0(Record0)
case dataCons1(Record1)
case dataCons2

enum CodingKeys: String, CodingKey {
case tag
case contents
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let discriminator = try container.decode(String.self, forKey: .tag)
switch discriminator {
case "dataCons0":
self = .dataCons0(try container.decode(Record0.self, forKey: .contents))
case "dataCons1":
self = .dataCons1(try container.decode(Record1.self, forKey: .contents))
case "dataCons2":
self = .dataCons2
default:
throw DecodingError.typeMismatch(
CodingKeys.self,
.init(codingPath: decoder.codingPath, debugDescription: "Can't decode unknown tag: Enum.\(discriminator)")
)
}
}

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch (self) {
case let .dataCons0(contents):
try container.encode("dataCons0", forKey: .tag)
try container.encode(contents, forKey: .contents)
case let .dataCons1(contents):
try container.encode("dataCons1", forKey: .tag)
try container.encode(contents, forKey: .contents)
case .dataCons2:
try container.encode("dataCons2", forKey: .tag)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
struct Record0: Codable {
var record0Field0: Int
var record0Field1: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
struct Record0: Codable {
var record0Field0: Int
var record0Field1: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
struct Record1: Codable {
var record1Field0: Int
var record1Field1: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
struct Record1: Codable {
var record1Field0: Int
var record1Field1: Int
}
33 changes: 33 additions & 0 deletions .golden/swiftSumOfProductWithTypeParameterSpec/golden
Original file line number Diff line number Diff line change
@@ -1,4 +1,37 @@
enum CursorInput<A: Hashable & Codable>: CaseIterable, Hashable, Codable {
case nextPage(A?)
case previousPage(A)

enum CodingKeys: String, CodingKey {
case direction
case key
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let discriminator = try container.decode(String.self, forKey: .direction)
switch discriminator {
case "nextPage":
self = .nextPage(try container.decode(A?.self, forKey: .key))
case "previousPage":
self = .previousPage(try container.decode(A.self, forKey: .key))
default:
throw DecodingError.typeMismatch(
CodingKeys.self,
.init(codingPath: decoder.codingPath, debugDescription: "Can't decode unknown direction: CursorInput.\(discriminator)")
)
}
}

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch (self) {
case let .nextPage(key):
try container.encode("nextPage", forKey: .direction)
try container.encode(key, forKey: .key)
case let .previousPage(key):
try container.encode("previousPage", forKey: .direction)
try container.encode(key, forKey: .key)
}
}
}
1 change: 1 addition & 0 deletions moat.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ test-suite spec
SumOfProductDocSpec
SumOfProductSpec
SumOfProductWithLinkEnumInterfaceSpec
SumOfProductWithTaggedFlatObjectStyleSpec
SumOfProductWithTaggedObjectAndNonConcreteCasesSpec
SumOfProductWithTaggedObjectAndSingleNullarySpec
SumOfProductWithTaggedObjectStyleSpec
Expand Down
12 changes: 8 additions & 4 deletions src/Moat.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ module Moat
makeBase,
sumOfProductEncodingOptions,
enumEncodingStyle,
enumUnknownCase,

-- * Pretty-printing

Expand Down Expand Up @@ -759,7 +760,7 @@ consToMoatType o@Options {..} parentName parentDoc instTys variant ts bs = \case
cases <- forM cons' (mkCase o)
ourMatch <-
matchProxy
=<< lift (enumExp parentName parentDoc instTys dataInterfaces dataProtocols dataAnnotations cases dataRawValue ts bs sumOfProductEncodingOptions enumEncodingStyle)
=<< lift (enumExp parentName parentDoc instTys dataInterfaces dataProtocols dataAnnotations cases dataRawValue ts bs sumOfProductEncodingOptions enumEncodingStyle enumUnknownCase)
pure [pure ourMatch]
else throwError $ MissingStrictCases missingConstructors

Expand Down Expand Up @@ -914,7 +915,7 @@ mkTypeTag Options {..} typName instTys = \case
mkName
(nameStr typName ++ "Tag")
let tag = tagExp typName parentName field False
matchProxy =<< lift (enumExp parentName Nothing instTys dataInterfaces dataProtocols dataAnnotations [] dataRawValue [tag] (False, Nothing, []) sumOfProductEncodingOptions enumEncodingStyle)
matchProxy =<< lift (enumExp parentName Nothing instTys dataInterfaces dataProtocols dataAnnotations [] dataRawValue [tag] (False, Nothing, []) sumOfProductEncodingOptions enumEncodingStyle enumUnknownCase)
_ -> throwError $ NotANewtype typName

-- make a newtype into a type alias
Expand Down Expand Up @@ -955,7 +956,7 @@ mkVoid ::
MoatM Match
mkVoid Options {..} typName instTys ts =
matchProxy
=<< lift (enumExp typName Nothing instTys [] [] [] [] Nothing ts (False, Nothing, []) sumOfProductEncodingOptions enumEncodingStyle)
=<< lift (enumExp typName Nothing instTys [] [] [] [] Nothing ts (False, Nothing, []) sumOfProductEncodingOptions enumEncodingStyle enumUnknownCase)

mkNewtype ::
() =>
Expand Down Expand Up @@ -1577,14 +1578,16 @@ enumExp ::
(Bool, Maybe MoatType, [Protocol]) ->
SumOfProductEncodingOptions ->
EnumEncodingStyle ->
Maybe String ->
Q Exp
enumExp parentName parentDoc tyVars ifaces protos anns cases raw tags bs sop ees =
enumExp parentName parentDoc tyVars ifaces protos anns cases raw tags bs sop ees euc =
do
enumInterfaces_ <- Syntax.lift ifaces
enumAnnotations_ <- Syntax.lift anns
enumProtocols_ <- Syntax.lift protos
sumOfProductEncodingOptions_ <- Syntax.lift sop
enumEnumEncodingStyle_ <- Syntax.lift ees
enumEnumUnknownCase_ <- Syntax.lift euc
applyBase bs $
RecConE
'MoatEnum
Expand All @@ -1600,6 +1603,7 @@ enumExp parentName parentDoc tyVars ifaces protos anns cases raw tags bs sop ees
, ('enumTags, ListE tags)
, ('enumSumOfProductEncodingOption, sumOfProductEncodingOptions_)
, ('enumEnumEncodingStyle, enumEnumEncodingStyle_)
, ('enumEnumUnknownCase, enumEnumUnknownCase_)
]

newtypeExp ::
Expand Down
Loading

0 comments on commit 84c138d

Please sign in to comment.