Skip to content

Commit 485b74d

Browse files
authored
fix: fixed enum cases encoding without associated values (#158)
* fix #157 * fixed an error and added a test * add coding test * Fix unused variable * Added test * conditional break * fix tests for last commit * fix old tests * add new tests so this bug can't slip in again
1 parent ee54b7d commit 485b74d

6 files changed

Lines changed: 216 additions & 13 deletions

File tree

Sources/PluginCore/Variables/Enum/Switcher/AdjacentlyTaggableSwitcher.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ extension InternallyTaggedEnumSwitcher: AdjacentlyTaggableSwitcher {
388388
let switchExpr = self.encodeSwitchExpression(
389389
over: location.selfValue, at: location, from: encoder,
390390
in: context, withDefaultCase: location.hasDefaultCase
391-
) { name in
391+
) { name, _ in
392392
let (variable, _) = identifierVariableAndKey(
393393
name, withType: "_", context: context
394394
)

Sources/PluginCore/Variables/Enum/Switcher/ExternallyTaggedEnumSwitcher.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,9 @@ package struct ExternallyTaggedEnumSwitcher: TaggedEnumSwitcherVariable {
170170
let switchExpr = self.encodeSwitchExpression(
171171
over: location.selfValue, at: location, from: contentEncoder,
172172
in: context, withDefaultCase: location.hasDefaultCase
173-
) { name in
173+
) { name, hasContent in
174174
"""
175-
let \(contentEncoder) = \(container).superEncoder(forKey: \(name))
175+
let \(hasContent ? contentEncoder : "_") = \(container).superEncoder(forKey: \(name))
176176
"""
177177
}
178178
if let switchExpr = switchExpr {

Sources/PluginCore/Variables/Enum/Switcher/TaggedEnumSwitcherVariable.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ extension EnumSwitcherVariable {
117117
from coder: TokenSyntax,
118118
in context: some MacroExpansionContext,
119119
withDefaultCase default: Bool,
120-
preSyntax: (TokenSyntax) -> CodeBlockItemListSyntax
120+
preSyntax: ((TokenSyntax, hasContent: Bool)) -> CodeBlockItemListSyntax
121121
) -> SwitchExprSyntax? {
122122
let cases = location.cases
123123
let allEncodable = cases.allSatisfy { $0.variable.encode ?? true }
@@ -144,12 +144,12 @@ extension EnumSwitcherVariable {
144144

145145
let generatedCode = generated.code.combined()
146146
SwitchCaseSyntax(label: .case(label)) {
147+
let preSyntax = preSyntax(("\(values.first!)", !generatedCode.isEmpty))
148+
preSyntax
147149
if !generatedCode.isEmpty {
148-
CodeBlockItemListSyntax {
149-
preSyntax("\(values.first!)")
150-
generatedCode
151-
}
152-
} else {
150+
generatedCode
151+
}
152+
if preSyntax.isEmpty && generatedCode.isEmpty {
153153
"break"
154154
}
155155
}

Tests/MetaCodableTests/CodableTests.swift

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,88 @@ struct CodableTests {
561561
)
562562
}
563563
}
564+
565+
struct EnumWithoutAssociatedVariables {
566+
@Codable
567+
enum Foo {
568+
case foo
569+
}
570+
571+
@Test
572+
func expansion() throws {
573+
assertMacroExpansion(
574+
"""
575+
@Codable
576+
enum Foo {
577+
case foo
578+
}
579+
""",
580+
expandedSource:
581+
"""
582+
enum Foo {
583+
case foo
584+
}
585+
586+
extension Foo: Decodable {
587+
init(from decoder: any Decoder) throws {
588+
let container = try decoder.container(keyedBy: DecodingKeys.self)
589+
guard container.allKeys.count == 1 else {
590+
let context = DecodingError.Context(
591+
codingPath: container.codingPath,
592+
debugDescription: "Invalid number of keys found, expected one."
593+
)
594+
throw DecodingError.typeMismatch(Self.self, context)
595+
}
596+
let contentDecoder = try container.superDecoder(forKey: container.allKeys.first.unsafelyUnwrapped)
597+
switch container.allKeys.first.unsafelyUnwrapped {
598+
case DecodingKeys.foo:
599+
self = .foo
600+
}
601+
}
602+
}
603+
604+
extension Foo: Encodable {
605+
func encode(to encoder: any Encoder) throws {
606+
var container = encoder.container(keyedBy: CodingKeys.self)
607+
switch self {
608+
case .foo:
609+
let _ = container.superEncoder(forKey: CodingKeys.foo)
610+
}
611+
}
612+
}
613+
614+
extension Foo {
615+
enum CodingKeys: String, CodingKey {
616+
case foo = "foo"
617+
}
618+
enum DecodingKeys: String, CodingKey {
619+
case foo = "foo"
620+
}
621+
}
622+
"""
623+
)
624+
}
625+
626+
@Test
627+
func decodingFromJSON() throws {
628+
let jsonStr = """
629+
{
630+
"foo": {}
631+
}
632+
"""
633+
let jsonData = try #require(jsonStr.data(using: .utf8))
634+
let decoded = try JSONDecoder().decode(Foo.self, from: jsonData)
635+
#expect(decoded == Foo.foo)
636+
}
637+
638+
@Test
639+
func encodingToJSON() throws {
640+
let original = Foo.foo
641+
let encoded = try JSONEncoder().encode(original)
642+
let json = try JSONSerialization.jsonObject(with: encoded) as? [String: Any]
643+
#expect((json?["foo"] as? [String: Any])?.isEmpty == true)
644+
}
645+
}
564646
}
565647

566648
#if canImport(MacroPlugin)

Tests/MetaCodableTests/CodedAt/CodedAtEnumTests.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,4 +802,102 @@ struct CodedAtEnumTests {
802802
#expect(json["int"] as? Int == 42)
803803
}
804804
}
805+
806+
struct WithoutAssociatedVariables {
807+
@Codable
808+
@CodedAt("type")
809+
enum Foo {
810+
case foo
811+
}
812+
813+
@Test
814+
func expansion() throws {
815+
assertMacroExpansion(
816+
"""
817+
@Codable
818+
@CodedAt("type")
819+
enum Foo {
820+
case foo
821+
}
822+
""",
823+
expandedSource:
824+
"""
825+
enum Foo {
826+
case foo
827+
}
828+
829+
extension Foo: Decodable {
830+
init(from decoder: any Decoder) throws {
831+
var typeContainer: KeyedDecodingContainer<CodingKeys>?
832+
let container = try? decoder.container(keyedBy: CodingKeys.self)
833+
if let container = container {
834+
typeContainer = container
835+
} else {
836+
typeContainer = nil
837+
}
838+
if let typeContainer = typeContainer {
839+
let typeString: String?
840+
do {
841+
typeString = try typeContainer.decodeIfPresent(String.self, forKey: CodingKeys.type) ?? nil
842+
} catch {
843+
typeString = nil
844+
}
845+
if let typeString = typeString {
846+
switch typeString {
847+
case "foo":
848+
self = .foo
849+
return
850+
default:
851+
break
852+
}
853+
}
854+
}
855+
let context = DecodingError.Context(
856+
codingPath: decoder.codingPath,
857+
debugDescription: "Couldn't match any cases."
858+
)
859+
throw DecodingError.typeMismatch(Self.self, context)
860+
}
861+
}
862+
863+
extension Foo: Encodable {
864+
func encode(to encoder: any Encoder) throws {
865+
let container = encoder.container(keyedBy: CodingKeys.self)
866+
var typeContainer = container
867+
switch self {
868+
case .foo:
869+
try typeContainer.encode("foo", forKey: CodingKeys.type)
870+
}
871+
}
872+
}
873+
874+
extension Foo {
875+
enum CodingKeys: String, CodingKey {
876+
case type = "type"
877+
}
878+
}
879+
"""
880+
)
881+
}
882+
883+
@Test
884+
func decodingFromJSON() throws {
885+
let jsonStr = """
886+
{
887+
"type": "foo"
888+
}
889+
"""
890+
let jsonData = try #require(jsonStr.data(using: .utf8))
891+
let decoded = try JSONDecoder().decode(Foo.self, from: jsonData)
892+
#expect(decoded == Foo.foo)
893+
}
894+
895+
@Test
896+
func encodingToJSON() throws {
897+
let original = Foo.foo
898+
let encoded = try JSONEncoder().encode(original)
899+
let json = try JSONSerialization.jsonObject(with: encoded) as? [String: Any]
900+
#expect(json?["type"] as? String == "foo")
901+
}
902+
}
805903
}

Tests/MetaCodableTests/IgnoreInitializedTests.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Foundation
12
import MetaCodable
23
import Testing
34

@@ -124,6 +125,17 @@ struct IgnoreInitializedTests {
124125
case multi(_ variable: Bool, val: Int, String = "text")
125126
}
126127

128+
@Test func coding() async throws {
129+
let encoded = try JSONEncoder().encode(SomeEnum.bool(false))
130+
let expected = try JSONSerialization.data(withJSONObject: ["bool": [:]])
131+
#expect(encoded == expected)
132+
let decoded = try JSONDecoder().decode(SomeEnum.self, from: encoded)
133+
switch decoded {
134+
case SomeEnum.bool(true): break
135+
default: Issue.record("Incorrect default value")
136+
}
137+
}
138+
127139
@Test
128140
func expansion() throws {
129141
assertMacroExpansion(
@@ -185,9 +197,9 @@ struct IgnoreInitializedTests {
185197
var container = encoder.container(keyedBy: CodingKeys.self)
186198
switch self {
187199
case .bool(_: _):
188-
break
200+
let _ = container.superEncoder(forKey: CodingKeys.bool)
189201
case .int(val: _):
190-
break
202+
let _ = container.superEncoder(forKey: CodingKeys.int)
191203
case .string(let _0):
192204
let contentEncoder = container.superEncoder(forKey: CodingKeys.string)
193205
try _0.encode(to: contentEncoder)
@@ -234,6 +246,17 @@ struct IgnoreInitializedTests {
234246
case multi(_ variable: Bool, val: Int, String = "text")
235247
}
236248

249+
@Test func coding() async throws {
250+
let encoded = try JSONEncoder().encode(SomeEnum.int(val: 0))
251+
let expected = try JSONSerialization.data(withJSONObject: ["altInt": [:]])
252+
#expect(encoded == expected)
253+
let decoded = try JSONDecoder().decode(SomeEnum.self, from: encoded)
254+
switch decoded {
255+
case SomeEnum.int(6): break
256+
default: Issue.record("Incorrect default value")
257+
}
258+
}
259+
237260
@Test
238261
func expansion() throws {
239262
assertMacroExpansion(
@@ -297,9 +320,9 @@ struct IgnoreInitializedTests {
297320
var container = encoder.container(keyedBy: CodingKeys.self)
298321
switch self {
299322
case .bool(_: _):
300-
break
323+
let _ = container.superEncoder(forKey: CodingKeys.bool)
301324
case .int(val: _):
302-
break
325+
let _ = container.superEncoder(forKey: CodingKeys.int)
303326
case .string(let _0):
304327
let contentEncoder = container.superEncoder(forKey: CodingKeys.string)
305328
try _0.encode(to: contentEncoder)

0 commit comments

Comments
 (0)