Skip to content

Commit 4eb999c

Browse files
authored
fix: fixed default value not respected for optional types (#36)
1 parent db55d96 commit 4eb999c

File tree

2 files changed

+305
-3
lines changed

2 files changed

+305
-3
lines changed

Sources/CodableMacroPlugin/Variables/DefaultValueVariable.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,14 @@ where Var.Initialization == RequiredInitialization {
8989
let catchClauses = CatchClauseListSyntax {
9090
CatchClauseSyntax { "self.\(name) = \(options.expr)" }
9191
}
92+
var doClauses = base.decoding(in: context, from: location)
93+
if type.isOptional, !doClauses.isEmpty {
94+
let lastIndex = doClauses.index(before: doClauses.endIndex)
95+
let assignmentblock = doClauses.remove(at: lastIndex)
96+
doClauses.append("\(assignmentblock) ?? \(options.expr)")
97+
}
9298
return CodeBlockItemListSyntax {
93-
DoStmtSyntax(catchClauses: catchClauses) {
94-
base.decoding(in: context, from: location)
95-
}
99+
DoStmtSyntax(catchClauses: catchClauses) { doClauses }
96100
}
97101
}
98102
}

Tests/MetaCodableTests/CodedAtTests.swift

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,51 @@ final class CodedAtTests: XCTestCase {
217217
)
218218
}
219219

220+
func testWithNoPathAndDefaultValueOnOptionalType() throws {
221+
assertMacroExpansion(
222+
"""
223+
@Codable
224+
@MemberInit
225+
struct SomeCodable {
226+
@Default("some")
227+
@CodedAt
228+
let value: String?
229+
}
230+
""",
231+
expandedSource:
232+
"""
233+
struct SomeCodable {
234+
let value: String?
235+
236+
init(value: String? = "some") {
237+
self.value = value
238+
}
239+
}
240+
241+
extension SomeCodable: Decodable {
242+
init(from decoder: any Decoder) throws {
243+
do {
244+
self.value = try String?(from: decoder) ?? "some"
245+
} catch {
246+
self.value = "some"
247+
}
248+
}
249+
}
250+
251+
extension SomeCodable: Encodable {
252+
func encode(to encoder: any Encoder) throws {
253+
try self.value.encode(to: encoder)
254+
}
255+
}
256+
257+
extension SomeCodable {
258+
enum CodingKeys: String, CodingKey {
259+
}
260+
}
261+
"""
262+
)
263+
}
264+
220265
func testWithNoPathWithHelperInstance() throws {
221266
assertMacroExpansion(
222267
"""
@@ -304,6 +349,52 @@ final class CodedAtTests: XCTestCase {
304349
)
305350
}
306351

352+
func testWithNoPathWithHelperInstanceAndDefaultValueOnOptional() throws {
353+
assertMacroExpansion(
354+
"""
355+
@Codable
356+
@MemberInit
357+
struct SomeCodable {
358+
@Default(["some"])
359+
@CodedBy(LossySequenceCoder<[String]>())
360+
@CodedAt
361+
let value: [String]?
362+
}
363+
""",
364+
expandedSource:
365+
"""
366+
struct SomeCodable {
367+
let value: [String]?
368+
369+
init(value: [String]? = ["some"]) {
370+
self.value = value
371+
}
372+
}
373+
374+
extension SomeCodable: Decodable {
375+
init(from decoder: any Decoder) throws {
376+
do {
377+
self.value = try LossySequenceCoder<[String]>().decodeIfPresent(from: decoder) ?? ["some"]
378+
} catch {
379+
self.value = ["some"]
380+
}
381+
}
382+
}
383+
384+
extension SomeCodable: Encodable {
385+
func encode(to encoder: any Encoder) throws {
386+
try LossySequenceCoder<[String]>().encodeIfPresent(self.value, to: encoder)
387+
}
388+
}
389+
390+
extension SomeCodable {
391+
enum CodingKeys: String, CodingKey {
392+
}
393+
}
394+
"""
395+
)
396+
}
397+
307398
func testWithSinglePath() throws {
308399
assertMacroExpansion(
309400
"""
@@ -395,6 +486,54 @@ final class CodedAtTests: XCTestCase {
395486
)
396487
}
397488

489+
func testWithSinglePathAndDefaultValueOnOptionalType() throws {
490+
assertMacroExpansion(
491+
"""
492+
@Codable
493+
@MemberInit
494+
struct SomeCodable {
495+
@Default("some")
496+
@CodedAt("key")
497+
let value: String?
498+
}
499+
""",
500+
expandedSource:
501+
"""
502+
struct SomeCodable {
503+
let value: String?
504+
505+
init(value: String? = "some") {
506+
self.value = value
507+
}
508+
}
509+
510+
extension SomeCodable: Decodable {
511+
init(from decoder: any Decoder) throws {
512+
let container = try decoder.container(keyedBy: CodingKeys.self)
513+
do {
514+
self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some"
515+
} catch {
516+
self.value = "some"
517+
}
518+
}
519+
}
520+
521+
extension SomeCodable: Encodable {
522+
func encode(to encoder: any Encoder) throws {
523+
var container = encoder.container(keyedBy: CodingKeys.self)
524+
try container.encodeIfPresent(self.value, forKey: CodingKeys.value)
525+
}
526+
}
527+
528+
extension SomeCodable {
529+
enum CodingKeys: String, CodingKey {
530+
case value = "key"
531+
}
532+
}
533+
"""
534+
)
535+
}
536+
398537
func testWithSinglePathWithHelperInstance() throws {
399538
assertMacroExpansion(
400539
"""
@@ -488,6 +627,55 @@ final class CodedAtTests: XCTestCase {
488627
)
489628
}
490629

630+
func testWithOnePathWithHelperInstanceAndDefaultValueOnOptional() throws {
631+
assertMacroExpansion(
632+
"""
633+
@Codable
634+
@MemberInit
635+
struct SomeCodable {
636+
@Default(["some"])
637+
@CodedBy(LossySequenceCoder<[String]>())
638+
@CodedAt("key")
639+
let value: [String]?
640+
}
641+
""",
642+
expandedSource:
643+
"""
644+
struct SomeCodable {
645+
let value: [String]?
646+
647+
init(value: [String]? = ["some"]) {
648+
self.value = value
649+
}
650+
}
651+
652+
extension SomeCodable: Decodable {
653+
init(from decoder: any Decoder) throws {
654+
let container = try decoder.container(keyedBy: CodingKeys.self)
655+
do {
656+
self.value = try LossySequenceCoder<[String]>().decodeIfPresent(from: container, forKey: CodingKeys.value) ?? ["some"]
657+
} catch {
658+
self.value = ["some"]
659+
}
660+
}
661+
}
662+
663+
extension SomeCodable: Encodable {
664+
func encode(to encoder: any Encoder) throws {
665+
var container = encoder.container(keyedBy: CodingKeys.self)
666+
try LossySequenceCoder<[String]>().encodeIfPresent(self.value, to: &container, atKey: CodingKeys.value)
667+
}
668+
}
669+
670+
extension SomeCodable {
671+
enum CodingKeys: String, CodingKey {
672+
case value = "key"
673+
}
674+
}
675+
"""
676+
)
677+
}
678+
491679
func testWithNestedPath() throws {
492680
assertMacroExpansion(
493681
"""
@@ -591,6 +779,60 @@ final class CodedAtTests: XCTestCase {
591779
)
592780
}
593781

782+
func testWithNestedPathAndDefaultValueOnOptionalType() throws {
783+
assertMacroExpansion(
784+
"""
785+
@Codable
786+
@MemberInit
787+
struct SomeCodable {
788+
@Default("some")
789+
@CodedAt("deeply", "nested", "key")
790+
let value: String?
791+
}
792+
""",
793+
expandedSource:
794+
"""
795+
struct SomeCodable {
796+
let value: String?
797+
798+
init(value: String? = "some") {
799+
self.value = value
800+
}
801+
}
802+
803+
extension SomeCodable: Decodable {
804+
init(from decoder: any Decoder) throws {
805+
let container = try decoder.container(keyedBy: CodingKeys.self)
806+
let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
807+
let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
808+
do {
809+
self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some"
810+
} catch {
811+
self.value = "some"
812+
}
813+
}
814+
}
815+
816+
extension SomeCodable: Encodable {
817+
func encode(to encoder: any Encoder) throws {
818+
var container = encoder.container(keyedBy: CodingKeys.self)
819+
var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
820+
var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
821+
try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value)
822+
}
823+
}
824+
825+
extension SomeCodable {
826+
enum CodingKeys: String, CodingKey {
827+
case value = "key"
828+
case deeply = "deeply"
829+
case nested = "nested"
830+
}
831+
}
832+
"""
833+
)
834+
}
835+
594836
func testWithNestedPathWithHelperInstance() throws {
595837
assertMacroExpansion(
596838
"""
@@ -695,5 +937,61 @@ final class CodedAtTests: XCTestCase {
695937
"""
696938
)
697939
}
940+
941+
func testWithNestedPathWithHelperInstanceAndDefaultValueOnOptional() throws
942+
{
943+
assertMacroExpansion(
944+
"""
945+
@Codable
946+
@MemberInit
947+
struct SomeCodable {
948+
@Default(["some"])
949+
@CodedBy(LossySequenceCoder<[String]>())
950+
@CodedAt("deeply", "nested", "key")
951+
let value: [String]?
952+
}
953+
""",
954+
expandedSource:
955+
"""
956+
struct SomeCodable {
957+
let value: [String]?
958+
959+
init(value: [String]? = ["some"]) {
960+
self.value = value
961+
}
962+
}
963+
964+
extension SomeCodable: Decodable {
965+
init(from decoder: any Decoder) throws {
966+
let container = try decoder.container(keyedBy: CodingKeys.self)
967+
let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
968+
let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
969+
do {
970+
self.value = try LossySequenceCoder<[String]>().decodeIfPresent(from: nested_deeply_container, forKey: CodingKeys.value) ?? ["some"]
971+
} catch {
972+
self.value = ["some"]
973+
}
974+
}
975+
}
976+
977+
extension SomeCodable: Encodable {
978+
func encode(to encoder: any Encoder) throws {
979+
var container = encoder.container(keyedBy: CodingKeys.self)
980+
var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
981+
var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
982+
try LossySequenceCoder<[String]>().encodeIfPresent(self.value, to: &nested_deeply_container, atKey: CodingKeys.value)
983+
}
984+
}
985+
986+
extension SomeCodable {
987+
enum CodingKeys: String, CodingKey {
988+
case value = "key"
989+
case deeply = "deeply"
990+
case nested = "nested"
991+
}
992+
}
993+
"""
994+
)
995+
}
698996
}
699997
#endif

0 commit comments

Comments
 (0)