diff --git a/Sources/PluginCore/Variables/Property/BasicPropertyVariable.swift b/Sources/PluginCore/Variables/Property/BasicPropertyVariable.swift index 65866ec36..29024e0d4 100644 --- a/Sources/PluginCore/Variables/Property/BasicPropertyVariable.swift +++ b/Sources/PluginCore/Variables/Property/BasicPropertyVariable.swift @@ -145,12 +145,13 @@ struct BasicPropertyVariable: DefaultPropertyVariable, DeclaredVariable { ) -> CodeBlockItemListSyntax { switch location { case .coder(let decoder, let passedMethod): - let optionalToken: TokenSyntax = - if passedMethod?.trimmedDescription == "decodeIfPresent" { - "?" - } else { - "" - } + let optionalToken: TokenSyntax = passedMethod?.trimmedDescription == "decodeIfPresent" ? "?" : "" + + var type = type + if let implicitlyUnwrappedType = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) { + type = TypeSyntax(OptionalTypeSyntax(wrappedType: implicitlyUnwrappedType.wrappedType)) + } + return CodeBlockItemListSyntax { """ \(decodePrefix)\(name) = try \(type)\(optionalToken)(from: \(decoder)) diff --git a/Sources/PluginCore/Variables/Property/PropertyVariable.swift b/Sources/PluginCore/Variables/Property/PropertyVariable.swift index 9e24d04dd..f89a2e5ef 100644 --- a/Sources/PluginCore/Variables/Property/PropertyVariable.swift +++ b/Sources/PluginCore/Variables/Property/PropertyVariable.swift @@ -119,11 +119,14 @@ extension PropertyVariable { /// /// Checks whether the type syntax uses /// `?` optional type syntax (i.e. `Type?`) or + /// `!` implicitly unwrapped optional type syntax (i.e. `Type!`) or /// generic optional syntax (i.e. `Optional`). var hasOptionalType: Bool { if type.is(OptionalTypeSyntax.self) { return true - } else if let type = type.as(IdentifierTypeSyntax.self), + } else if type.is(ImplicitlyUnwrappedOptionalTypeSyntax.self) { + return true + } else if let type = type.as(IdentifierTypeSyntax.self), type.name.text == "Optional", let gArgs = type.genericArgumentClause?.arguments, gArgs.count == 1 @@ -152,6 +155,9 @@ extension PropertyVariable { if let type = type.as(OptionalTypeSyntax.self) { dType = type.wrappedType dMethod = "\(method)IfPresent" + } else if let type = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) { + dType = type.wrappedType + dMethod = "\(method)IfPresent" } else if let type = type.as(IdentifierTypeSyntax.self), type.name.text == "Optional", let gArgs = type.genericArgumentClause?.arguments, diff --git a/Tests/MetaCodableTests/CodableTests.swift b/Tests/MetaCodableTests/CodableTests.swift index bb7298e3c..6d420d576 100644 --- a/Tests/MetaCodableTests/CodableTests.swift +++ b/Tests/MetaCodableTests/CodableTests.swift @@ -47,6 +47,53 @@ final class CodableTests: XCTestCase { ) } + func testOptionalWithoutAnyCustomization() throws { + assertMacroExpansion( + """ + @Codable + struct SomeCodable { + let value1: String? + let value2: String! + let value3: Optional + } + """, + expandedSource: + """ + struct SomeCodable { + let value1: String? + let value2: String! + let value3: Optional + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.value1 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value1) + self.value2 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value2) + self.value3 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(self.value1, forKey: CodingKeys.value1) + try container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + try container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value1 = "value1" + case value2 = "value2" + case value3 = "value3" + } + } + """ + ) + } + func testWithoutAnyCustomizationWithStaticVar() throws { assertMacroExpansion( """ diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultChoiceTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultChoiceTests.swift index b8ad45c3d..b6000db32 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultChoiceTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultChoiceTests.swift @@ -84,6 +84,44 @@ final class CodedAtDefaultChoiceTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default(ifMissing: "some", forErrors: "another") + @CodedAt + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + do { + self.value = try String??(from: decoder) ?? "some" + } catch { + self.value = "another" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + try self.value.encode(to: encoder) + } + } + """ + ) } func testWithSinglePath() throws { @@ -188,6 +226,56 @@ final class CodedAtDefaultChoiceTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default(ifMissing: "some", forErrors: "another") + @CodedAt("key") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + if let container = container { + do { + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "another" + } + } else { + self.value = "another" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "key" + } + } + """ + ) } func testWithNestedPath() throws { @@ -360,6 +448,90 @@ final class CodedAtDefaultChoiceTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default(ifMissing: "some", forErrors: "another") + @CodedAt("deeply", "nested", "key") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + let deeply_container: KeyedDecodingContainer? + let deeply_containerMissing: Bool + if (try? container?.decodeNil(forKey: CodingKeys.deeply)) == false { + deeply_container = try? container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + deeply_containerMissing = false + } else { + deeply_container = nil + deeply_containerMissing = true + } + let nested_deeply_container: KeyedDecodingContainer? + let nested_deeply_containerMissing: Bool + if (try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false { + nested_deeply_container = try? deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + nested_deeply_containerMissing = false + } else { + nested_deeply_container = nil + nested_deeply_containerMissing = true + } + if let container = container { + if let deeply_container = deeply_container { + if let nested_deeply_container = nested_deeply_container { + do { + self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "another" + } + } else if nested_deeply_containerMissing { + self.value = "some" + } else { + self.value = "another" + } + } else if deeply_containerMissing { + self.value = "some" + } else { + self.value = "another" + } + } else { + self.value = "another" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "key" + case deeply = "deeply" + case nested = "nested" + } + } + """ + ) } func testWithNestedPathOnMultiOptionalTypes() throws { @@ -373,22 +545,26 @@ final class CodedAtDefaultChoiceTests: XCTestCase { let value1: String? @Default(ifMissing: "some", forErrors: "another") @CodedAt("deeply", "nested", "key2") - let value2: String? + let value2: String! @CodedAt("deeply", "nested1") let value3: String? + @CodedAt("deeply", "nested2") + let value4: String! } """, expandedSource: """ struct SomeCodable { let value1: String? - let value2: String? + let value2: String! let value3: String? + let value4: String! - init(value1: String? = "some", value2: String? = "some", value3: String? = nil) { + init(value1: String? = "some", value2: String! = "some", value3: String? = nil, value4: String! = nil) { self.value1 = value1 self.value2 = value2 self.value3 = value3 + self.value4 = value4 } } @@ -407,6 +583,7 @@ final class CodedAtDefaultChoiceTests: XCTestCase { } if let deeply_container = deeply_container { self.value3 = try deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) if let nested_deeply_container = nested_deeply_container { do { self.value1 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" @@ -429,6 +606,7 @@ final class CodedAtDefaultChoiceTests: XCTestCase { self.value1 = "some" self.value2 = "some" self.value3 = nil + self.value4 = nil } } } @@ -441,6 +619,7 @@ final class CodedAtDefaultChoiceTests: XCTestCase { try nested_deeply_container.encodeIfPresent(self.value1, forKey: CodingKeys.value1) try nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) try deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try deeply_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) } } @@ -451,6 +630,7 @@ final class CodedAtDefaultChoiceTests: XCTestCase { case nested = "nested" case value2 = "key2" case value3 = "nested1" + case value4 = "nested2" } } """ @@ -469,10 +649,15 @@ final class CodedAtDefaultChoiceTests: XCTestCase { @Default(ifMissing: "some", forErrors: "another") @CodedAt("deeply", "nested", "level", "key2") let value2: String? + @Default(ifMissing: "some", forErrors: "another") + @CodedAt("deeply", "nested", "level", "key3") + let value3: String! @CodedAt("deeply", "nested", "level1") - let value3: String? + let value4: String? + @CodedAt("deeply", "nested", "level2") + let value5: String! @CodedAt("deeply", "nested1") - let value4: String + let value6: String } """, expandedSource: @@ -480,14 +665,18 @@ final class CodedAtDefaultChoiceTests: XCTestCase { struct SomeCodable { let value1: String let value2: String? - let value3: String? - let value4: String + let value3: String! + let value4: String? + let value5: String! + let value6: String - init(value1: String = "some", value2: String? = "some", value3: String? = nil, value4: String) { + init(value1: String = "some", value2: String? = "some", value3: String! = "some", value4: String? = nil, value5: String! = nil, value6: String) { self.value1 = value1 self.value2 = value2 self.value3 = value3 self.value4 = value4 + self.value5 = value5 + self.value6 = value6 } } @@ -505,9 +694,10 @@ final class CodedAtDefaultChoiceTests: XCTestCase { level_nested_deeply_container = nil level_nested_deeply_containerMissing = true } - self.value4 = try deeply_container.decode(String.self, forKey: CodingKeys.value4) + self.value6 = try deeply_container.decode(String.self, forKey: CodingKeys.value6) if let nested_deeply_container = nested_deeply_container { - self.value3 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + self.value5 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value5) if let level_nested_deeply_container = level_nested_deeply_container { do { self.value1 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" @@ -519,17 +709,26 @@ final class CodedAtDefaultChoiceTests: XCTestCase { } catch { self.value2 = "another" } + do { + self.value3 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) ?? "some" + } catch { + self.value3 = "another" + } } else if level_nested_deeply_containerMissing { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } else { self.value1 = "another" self.value2 = "another" + self.value3 = "another" } } else { self.value1 = "some" self.value2 = "some" - self.value3 = nil + self.value3 = "some" + self.value4 = nil + self.value5 = nil } } } @@ -542,8 +741,10 @@ final class CodedAtDefaultChoiceTests: XCTestCase { var level_nested_deeply_container = nested_deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) try level_nested_deeply_container.encode(self.value1, forKey: CodingKeys.value1) try level_nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) - try nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) - try deeply_container.encode(self.value4, forKey: CodingKeys.value4) + try level_nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try nested_deeply_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) + try nested_deeply_container.encodeIfPresent(self.value5, forKey: CodingKeys.value5) + try deeply_container.encode(self.value6, forKey: CodingKeys.value6) } } @@ -554,8 +755,10 @@ final class CodedAtDefaultChoiceTests: XCTestCase { case nested = "nested" case level = "level" case value2 = "key2" - case value3 = "level1" - case value4 = "nested1" + case value3 = "key3" + case value4 = "level1" + case value5 = "level2" + case value6 = "nested1" } } """ @@ -573,10 +776,15 @@ final class CodedAtDefaultChoiceTests: XCTestCase { @Default(ifMissing: "some", forErrors: "another") @CodedAt("deeply", "nested", "level", "key2") let value2: String? + @Default(ifMissing: "some", forErrors: "another") + @CodedAt("deeply", "nested", "level", "key3") + let value3: String! @CodedAt("deeply", "nested", "level1") - let value3: String? + let value4: String? + @CodedAt("deeply", "nested", "level2") + let value5: String! @CodedAt("deeply", "nested1") - let value4: String + let value6: String } """, expandedSource: @@ -584,8 +792,10 @@ final class CodedAtDefaultChoiceTests: XCTestCase { class SomeCodable { let value1: String let value2: String? - let value3: String? - let value4: String + let value3: String! + let value4: String? + let value5: String! + let value6: String required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -600,9 +810,10 @@ final class CodedAtDefaultChoiceTests: XCTestCase { level_nested_deeply_container = nil level_nested_deeply_containerMissing = true } - self.value4 = try deeply_container.decode(String.self, forKey: CodingKeys.value4) + self.value6 = try deeply_container.decode(String.self, forKey: CodingKeys.value6) if let nested_deeply_container = nested_deeply_container { - self.value3 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + self.value5 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value5) if let level_nested_deeply_container = level_nested_deeply_container { do { self.value1 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" @@ -614,17 +825,26 @@ final class CodedAtDefaultChoiceTests: XCTestCase { } catch { self.value2 = "another" } + do { + self.value3 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) ?? "some" + } catch { + self.value3 = "another" + } } else if level_nested_deeply_containerMissing { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } else { self.value1 = "another" self.value2 = "another" + self.value3 = "another" } } else { self.value1 = "some" self.value2 = "some" - self.value3 = nil + self.value3 = "some" + self.value4 = nil + self.value5 = nil } } @@ -635,8 +855,10 @@ final class CodedAtDefaultChoiceTests: XCTestCase { var level_nested_deeply_container = nested_deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) try level_nested_deeply_container.encode(self.value1, forKey: CodingKeys.value1) try level_nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) - try nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) - try deeply_container.encode(self.value4, forKey: CodingKeys.value4) + try level_nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try nested_deeply_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) + try nested_deeply_container.encodeIfPresent(self.value5, forKey: CodingKeys.value5) + try deeply_container.encode(self.value6, forKey: CodingKeys.value6) } enum CodingKeys: String, CodingKey { @@ -645,8 +867,10 @@ final class CodedAtDefaultChoiceTests: XCTestCase { case nested = "nested" case level = "level" case value2 = "key2" - case value3 = "level1" - case value4 = "nested1" + case value3 = "key3" + case value4 = "level1" + case value5 = "level2" + case value6 = "nested1" } } diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultOnlyMissingTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultOnlyMissingTests.swift index 072c4939d..fc0f9bde1 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultOnlyMissingTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultOnlyMissingTests.swift @@ -76,6 +76,40 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default(ifMissing: "some") + @CodedAt + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + self.value = try String??(from: decoder) ?? "some" + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + try self.value.encode(to: encoder) + } + } + """ + ) } func testWithSinglePath() throws { @@ -164,6 +198,48 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default(ifMissing: "some") + @CodedAt("key") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "key" + } + } + """ + ) } func testWithNestedPath() throws { @@ -280,6 +356,62 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default(ifMissing: "some") + @CodedAt("deeply", "nested", "key") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil + let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil + if let deeply_container = deeply_container { + if let nested_deeply_container = nested_deeply_container { + self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } else { + self.value = "some" + } + } else { + self.value = "some" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "key" + case deeply = "deeply" + case nested = "nested" + } + } + """ + ) } func testWithNestedPathOnMultiOptionalTypes() throws { @@ -293,22 +425,26 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { let value1: String? @Default(ifMissing: "some") @CodedAt("deeply", "nested", "key2") - let value2: String? + let value2: String! @CodedAt(ifMissing: "deeply", "nested1") let value3: String? + @CodedAt(ifMissing: "deeply", "nested2") + let value4: String! } """, expandedSource: """ struct SomeCodable { let value1: String? - let value2: String? + let value2: String! let value3: String? + let value4: String! - init(value1: String? = "some", value2: String? = "some", value3: String? = nil) { + init(value1: String? = "some", value2: String! = "some", value3: String? = nil, value4: String! = nil) { self.value1 = value1 self.value2 = value2 self.value3 = value3 + self.value4 = value4 } } @@ -318,6 +454,7 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil self.value3 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value4) if let deeply_container = deeply_container { if let nested_deeply_container = nested_deeply_container { self.value1 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" @@ -341,6 +478,7 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { try nested_deeply_container.encodeIfPresent(self.value1, forKey: CodingKeys.value1) try nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) try container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) } } @@ -351,6 +489,7 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { case nested = "nested" case value2 = "key2" case value3 = "nested1" + case value4 = "nested2" } } """ @@ -369,10 +508,15 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { @Default(ifMissing: "some") @CodedAt("deeply", "nested", "level", "key2") let value2: String? + @Default(ifMissing: "some") + @CodedAt("deeply", "nested", "level", "key3") + let value3: String! @CodedAt("deeply", "nested", "level1") - let value3: String? + let value4: String? + @CodedAt("deeply", "nested", "level2") + let value5: String! @CodedAt("deeply", "nested1") - let value4: String + let value6: String } """, expandedSource: @@ -380,14 +524,18 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { struct SomeCodable { let value1: String let value2: String? - let value3: String? - let value4: String + let value3: String! + let value4: String? + let value5: String! + let value6: String - init(value1: String = "some", value2: String? = "some", value3: String? = nil, value4: String) { + init(value1: String = "some", value2: String? = "some", value3: String! = "some", value4: String? = nil, value5: String! = nil, value6: String) { self.value1 = value1 self.value2 = value2 self.value3 = value3 self.value4 = value4 + self.value5 = value5 + self.value6 = value6 } } @@ -397,20 +545,25 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) let nested_deeply_container = ((try? deeply_container.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil let level_nested_deeply_container = ((try? nested_deeply_container?.decodeNil(forKey: CodingKeys.level)) == false) ? try nested_deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) : nil - self.value4 = try deeply_container.decode(String.self, forKey: CodingKeys.value4) + self.value6 = try deeply_container.decode(String.self, forKey: CodingKeys.value6) if let nested_deeply_container = nested_deeply_container { - self.value3 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + self.value5 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value5) if let level_nested_deeply_container = level_nested_deeply_container { self.value1 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" self.value2 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) ?? "some" + self.value3 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) ?? "some" } else { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } } else { self.value1 = "some" self.value2 = "some" - self.value3 = nil + self.value3 = "some" + self.value4 = nil + self.value5 = nil } } } @@ -423,8 +576,10 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { var level_nested_deeply_container = nested_deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) try level_nested_deeply_container.encode(self.value1, forKey: CodingKeys.value1) try level_nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) - try nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) - try deeply_container.encode(self.value4, forKey: CodingKeys.value4) + try level_nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try nested_deeply_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) + try nested_deeply_container.encodeIfPresent(self.value5, forKey: CodingKeys.value5) + try deeply_container.encode(self.value6, forKey: CodingKeys.value6) } } @@ -435,8 +590,10 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { case nested = "nested" case level = "level" case value2 = "key2" - case value3 = "level1" - case value4 = "nested1" + case value3 = "key3" + case value4 = "level1" + case value5 = "level2" + case value6 = "nested1" } } """ @@ -454,10 +611,15 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { @Default(ifMissing: "some") @CodedAt("deeply", "nested", "level", "key2") let value2: String? + @Default(ifMissing: "some") + @CodedAt("deeply", "nested", "level", "key3") + let value3: String! @CodedAt("deeply", "nested", "level1") - let value3: String? + let value4: String? + @CodedAt("deeply", "nested", "level2") + let value5: String! @CodedAt("deeply", "nested1") - let value4: String + let value6: String } """, expandedSource: @@ -465,28 +627,35 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { class SomeCodable { let value1: String let value2: String? - let value3: String? - let value4: String + let value3: String! + let value4: String? + let value5: String! + let value6: String required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) let nested_deeply_container = ((try? deeply_container.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil let level_nested_deeply_container = ((try? nested_deeply_container?.decodeNil(forKey: CodingKeys.level)) == false) ? try nested_deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) : nil - self.value4 = try deeply_container.decode(String.self, forKey: CodingKeys.value4) + self.value6 = try deeply_container.decode(String.self, forKey: CodingKeys.value6) if let nested_deeply_container = nested_deeply_container { - self.value3 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + self.value5 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value5) if let level_nested_deeply_container = level_nested_deeply_container { self.value1 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" self.value2 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) ?? "some" + self.value3 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) ?? "some" } else { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } } else { self.value1 = "some" self.value2 = "some" - self.value3 = nil + self.value3 = "some" + self.value4 = nil + self.value5 = nil } } @@ -497,8 +666,10 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { var level_nested_deeply_container = nested_deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) try level_nested_deeply_container.encode(self.value1, forKey: CodingKeys.value1) try level_nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) - try nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) - try deeply_container.encode(self.value4, forKey: CodingKeys.value4) + try level_nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try nested_deeply_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) + try nested_deeply_container.encodeIfPresent(self.value5, forKey: CodingKeys.value5) + try deeply_container.encode(self.value6, forKey: CodingKeys.value6) } enum CodingKeys: String, CodingKey { @@ -507,8 +678,10 @@ final class CodedAtDefaultOnlyMissingTests: XCTestCase { case nested = "nested" case level = "level" case value2 = "key2" - case value3 = "level1" - case value4 = "nested1" + case value3 = "key3" + case value4 = "level1" + case value5 = "level2" + case value6 = "nested1" } } diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultTests.swift index 6159277c1..d24cdfb15 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtDefaultTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtDefaultTests.swift @@ -84,6 +84,44 @@ final class CodedAtDefaultTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default("some") + @CodedAt + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + do { + self.value = try String??(from: decoder) ?? "some" + } catch { + self.value = "some" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + try self.value.encode(to: encoder) + } + } + """ + ) } func testWithSinglePath() throws { @@ -188,6 +226,56 @@ final class CodedAtDefaultTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default("some") + @CodedAt("key") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + if let container = container { + do { + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "some" + } + } else { + self.value = "some" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "key" + } + } + """ + ) } func testWithNestedPath() throws { @@ -360,6 +448,90 @@ final class CodedAtDefaultTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default("some") + @CodedAt("deeply", "nested", "key") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + let deeply_container: KeyedDecodingContainer? + let deeply_containerMissing: Bool + if (try? container?.decodeNil(forKey: CodingKeys.deeply)) == false { + deeply_container = try? container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + deeply_containerMissing = false + } else { + deeply_container = nil + deeply_containerMissing = true + } + let nested_deeply_container: KeyedDecodingContainer? + let nested_deeply_containerMissing: Bool + if (try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false { + nested_deeply_container = try? deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + nested_deeply_containerMissing = false + } else { + nested_deeply_container = nil + nested_deeply_containerMissing = true + } + if let container = container { + if let deeply_container = deeply_container { + if let nested_deeply_container = nested_deeply_container { + do { + self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "some" + } + } else if nested_deeply_containerMissing { + self.value = "some" + } else { + self.value = "some" + } + } else if deeply_containerMissing { + self.value = "some" + } else { + self.value = "some" + } + } else { + self.value = "some" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "key" + case deeply = "deeply" + case nested = "nested" + } + } + """ + ) } func testWithNestedPathOnMultiOptionalTypes() throws { @@ -373,22 +545,26 @@ final class CodedAtDefaultTests: XCTestCase { let value1: String? @Default("some") @CodedAt("deeply", "nested", "key2") - let value2: String? + let value2: String! @CodedAt("deeply", "nested1") let value3: String? + @CodedAt("deeply", "nested2") + let value4: String! } """, expandedSource: """ struct SomeCodable { let value1: String? - let value2: String? + let value2: String! let value3: String? + let value4: String! - init(value1: String? = "some", value2: String? = "some", value3: String? = nil) { + init(value1: String? = "some", value2: String! = "some", value3: String? = nil, value4: String! = nil) { self.value1 = value1 self.value2 = value2 self.value3 = value3 + self.value4 = value4 } } @@ -407,6 +583,7 @@ final class CodedAtDefaultTests: XCTestCase { } if let deeply_container = deeply_container { self.value3 = try deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) if let nested_deeply_container = nested_deeply_container { do { self.value1 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" @@ -429,6 +606,7 @@ final class CodedAtDefaultTests: XCTestCase { self.value1 = "some" self.value2 = "some" self.value3 = nil + self.value4 = nil } } } @@ -441,6 +619,7 @@ final class CodedAtDefaultTests: XCTestCase { try nested_deeply_container.encodeIfPresent(self.value1, forKey: CodingKeys.value1) try nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) try deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try deeply_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) } } @@ -451,6 +630,7 @@ final class CodedAtDefaultTests: XCTestCase { case nested = "nested" case value2 = "key2" case value3 = "nested1" + case value4 = "nested2" } } """ @@ -469,10 +649,15 @@ final class CodedAtDefaultTests: XCTestCase { @Default("some") @CodedAt("deeply", "nested", "level", "key2") let value2: String? + @Default("some") + @CodedAt("deeply", "nested", "level", "key3") + let value3: String! @CodedAt("deeply", "nested", "level1") - let value3: String? + let value4: String? + @CodedAt("deeply", "nested", "level2") + let value5: String! @CodedAt("deeply", "nested1") - let value4: String + let value6: String } """, expandedSource: @@ -480,14 +665,18 @@ final class CodedAtDefaultTests: XCTestCase { struct SomeCodable { let value1: String let value2: String? - let value3: String? - let value4: String + let value3: String! + let value4: String? + let value5: String! + let value6: String - init(value1: String = "some", value2: String? = "some", value3: String? = nil, value4: String) { + init(value1: String = "some", value2: String? = "some", value3: String! = "some", value4: String? = nil, value5: String! = nil, value6: String) { self.value1 = value1 self.value2 = value2 self.value3 = value3 self.value4 = value4 + self.value5 = value5 + self.value6 = value6 } } @@ -505,9 +694,10 @@ final class CodedAtDefaultTests: XCTestCase { level_nested_deeply_container = nil level_nested_deeply_containerMissing = true } - self.value4 = try deeply_container.decode(String.self, forKey: CodingKeys.value4) + self.value6 = try deeply_container.decode(String.self, forKey: CodingKeys.value6) if let nested_deeply_container = nested_deeply_container { - self.value3 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + self.value5 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value5) if let level_nested_deeply_container = level_nested_deeply_container { do { self.value1 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" @@ -519,17 +709,26 @@ final class CodedAtDefaultTests: XCTestCase { } catch { self.value2 = "some" } + do { + self.value3 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) ?? "some" + } catch { + self.value3 = "some" + } } else if level_nested_deeply_containerMissing { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } else { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } } else { self.value1 = "some" self.value2 = "some" - self.value3 = nil + self.value3 = "some" + self.value4 = nil + self.value5 = nil } } } @@ -542,8 +741,10 @@ final class CodedAtDefaultTests: XCTestCase { var level_nested_deeply_container = nested_deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) try level_nested_deeply_container.encode(self.value1, forKey: CodingKeys.value1) try level_nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) - try nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) - try deeply_container.encode(self.value4, forKey: CodingKeys.value4) + try level_nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try nested_deeply_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) + try nested_deeply_container.encodeIfPresent(self.value5, forKey: CodingKeys.value5) + try deeply_container.encode(self.value6, forKey: CodingKeys.value6) } } @@ -554,8 +755,10 @@ final class CodedAtDefaultTests: XCTestCase { case nested = "nested" case level = "level" case value2 = "key2" - case value3 = "level1" - case value4 = "nested1" + case value3 = "key3" + case value4 = "level1" + case value5 = "level2" + case value6 = "nested1" } } """ @@ -573,10 +776,14 @@ final class CodedAtDefaultTests: XCTestCase { @Default("some") @CodedAt("deeply", "nested", "level", "key2") let value2: String? + @CodedAt("deeply", "nested", "level", "key3") + let value3: String! @CodedAt("deeply", "nested", "level1") - let value3: String? + let value4: String? + @CodedAt("deeply", "nested", "level2") + let value5: String! @CodedAt("deeply", "nested1") - let value4: String + let value6: String } """, expandedSource: @@ -584,25 +791,20 @@ final class CodedAtDefaultTests: XCTestCase { class SomeCodable { let value1: String let value2: String? - let value3: String? - let value4: String + let value3: String! + let value4: String? + let value5: String! + let value6: String required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) let nested_deeply_container = ((try? deeply_container.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil - let level_nested_deeply_container: KeyedDecodingContainer? - let level_nested_deeply_containerMissing: Bool - if (try? nested_deeply_container?.decodeNil(forKey: CodingKeys.level)) == false { - level_nested_deeply_container = try? nested_deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) - level_nested_deeply_containerMissing = false - } else { - level_nested_deeply_container = nil - level_nested_deeply_containerMissing = true - } - self.value4 = try deeply_container.decode(String.self, forKey: CodingKeys.value4) + let level_nested_deeply_container = ((try? nested_deeply_container?.decodeNil(forKey: CodingKeys.level)) == false) ? try nested_deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) : nil + self.value6 = try deeply_container.decode(String.self, forKey: CodingKeys.value6) if let nested_deeply_container = nested_deeply_container { - self.value3 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + self.value5 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value5) if let level_nested_deeply_container = level_nested_deeply_container { do { self.value1 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" @@ -614,17 +816,18 @@ final class CodedAtDefaultTests: XCTestCase { } catch { self.value2 = "some" } - } else if level_nested_deeply_containerMissing { - self.value1 = "some" - self.value2 = "some" + self.value3 = try level_nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) } else { self.value1 = "some" self.value2 = "some" + self.value3 = nil } } else { self.value1 = "some" self.value2 = "some" self.value3 = nil + self.value4 = nil + self.value5 = nil } } @@ -635,8 +838,10 @@ final class CodedAtDefaultTests: XCTestCase { var level_nested_deeply_container = nested_deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) try level_nested_deeply_container.encode(self.value1, forKey: CodingKeys.value1) try level_nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) - try nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) - try deeply_container.encode(self.value4, forKey: CodingKeys.value4) + try level_nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try nested_deeply_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) + try nested_deeply_container.encodeIfPresent(self.value5, forKey: CodingKeys.value5) + try deeply_container.encode(self.value6, forKey: CodingKeys.value6) } enum CodingKeys: String, CodingKey { @@ -645,8 +850,10 @@ final class CodedAtDefaultTests: XCTestCase { case nested = "nested" case level = "level" case value2 = "key2" - case value3 = "level1" - case value4 = "nested1" + case value3 = "key3" + case value4 = "level1" + case value5 = "level2" + case value6 = "nested1" } } diff --git a/Tests/MetaCodableTests/CodedAt/CodedAtTests.swift b/Tests/MetaCodableTests/CodedAt/CodedAtTests.swift index 0f85344f5..2b955f611 100644 --- a/Tests/MetaCodableTests/CodedAt/CodedAtTests.swift +++ b/Tests/MetaCodableTests/CodedAt/CodedAtTests.swift @@ -228,6 +228,39 @@ final class CodedAtTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @CodedAt + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = nil) { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + self.value = try String?(from: decoder) + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + try self.value.encode(to: encoder) + } + } + """ + ) } func testWithSinglePath() throws { @@ -314,6 +347,47 @@ final class CodedAtTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @CodedAt("key") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = nil) { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "key" + } + } + """ + ) } func testWithNestedPath() throws { @@ -420,6 +494,61 @@ final class CodedAtTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @CodedAt("deeply", "nested", "key") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = nil) { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil + let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil + if let deeply_container = deeply_container { + if let nested_deeply_container = nested_deeply_container { + self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) + } else { + self.value = nil + } + } else { + self.value = nil + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "key" + case deeply = "deeply" + case nested = "nested" + } + } + """ + ) } func testWithNestedPathOnMultiOptionalTypes() throws { @@ -431,22 +560,26 @@ final class CodedAtTests: XCTestCase { @CodedAt("deeply", "nested", "key1") let value1: String? @CodedAt("deeply", "nested", "key2") - let value2: String? + let value2: String! @CodedAt("deeply", "nested1") let value3: String? + @CodedAt("deeply", "nested2") + let value4: String! } """, expandedSource: """ struct SomeCodable { let value1: String? - let value2: String? + let value2: String! let value3: String? + let value4: String! - init(value1: String? = nil, value2: String? = nil, value3: String? = nil) { + init(value1: String? = nil, value2: String! = nil, value3: String? = nil, value4: String! = nil) { self.value1 = value1 self.value2 = value2 self.value3 = value3 + self.value4 = value4 } } @@ -457,6 +590,7 @@ final class CodedAtTests: XCTestCase { let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil if let deeply_container = deeply_container { self.value3 = try deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + self.value4 = try deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) if let nested_deeply_container = nested_deeply_container { self.value1 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) self.value2 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) @@ -468,6 +602,7 @@ final class CodedAtTests: XCTestCase { self.value1 = nil self.value2 = nil self.value3 = nil + self.value4 = nil } } } @@ -480,6 +615,7 @@ final class CodedAtTests: XCTestCase { try nested_deeply_container.encodeIfPresent(self.value1, forKey: CodingKeys.value1) try nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) try deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) + try deeply_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) } } @@ -490,6 +626,7 @@ final class CodedAtTests: XCTestCase { case nested = "nested" case value2 = "key2" case value3 = "nested1" + case value4 = "nested2" } } """ @@ -506,6 +643,8 @@ final class CodedAtTests: XCTestCase { let value1: String @CodedAt("deeply", "nested", "key2") let value2: String? + @CodedAt("deeply", "nested", "key3") + let value3: String! } """, expandedSource: @@ -513,10 +652,12 @@ final class CodedAtTests: XCTestCase { struct SomeCodable { let value1: String let value2: String? + let value3: String! - init(value1: String, value2: String? = nil) { + init(value1: String, value2: String? = nil, value3: String! = nil) { self.value1 = value1 self.value2 = value2 + self.value3 = value3 } } @@ -527,6 +668,7 @@ final class CodedAtTests: XCTestCase { let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) self.value1 = try nested_deeply_container.decode(String.self, forKey: CodingKeys.value1) self.value2 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) + self.value3 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) } } @@ -537,6 +679,7 @@ final class CodedAtTests: XCTestCase { var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) try nested_deeply_container.encode(self.value1, forKey: CodingKeys.value1) try nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + try nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) } } @@ -546,6 +689,7 @@ final class CodedAtTests: XCTestCase { case deeply = "deeply" case nested = "nested" case value2 = "key2" + case value3 = "key3" } } """ @@ -561,6 +705,8 @@ final class CodedAtTests: XCTestCase { let value1: String @CodedAt("deeply", "nested", "key2") let value2: String? + @CodedAt("deeply", "nested", "key3") + let value3: String! } """, expandedSource: @@ -568,6 +714,7 @@ final class CodedAtTests: XCTestCase { class SomeCodable { let value1: String let value2: String? + let value3: String! required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -575,6 +722,7 @@ final class CodedAtTests: XCTestCase { let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) self.value1 = try nested_deeply_container.decode(String.self, forKey: CodingKeys.value1) self.value2 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) + self.value3 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) } func encode(to encoder: any Encoder) throws { @@ -583,6 +731,7 @@ final class CodedAtTests: XCTestCase { var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) try nested_deeply_container.encode(self.value1, forKey: CodingKeys.value1) try nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + try nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) } enum CodingKeys: String, CodingKey { @@ -590,6 +739,7 @@ final class CodedAtTests: XCTestCase { case deeply = "deeply" case nested = "nested" case value2 = "key2" + case value3 = "key3" } } @@ -612,6 +762,8 @@ final class CodedAtTests: XCTestCase { let value1: String @CodedAt("deeply", "nested", "key2") var value2: String? + @CodedAt("deeply", "nested", "key3") + var value3: String! } """, expandedSource: @@ -619,10 +771,12 @@ final class CodedAtTests: XCTestCase { actor SomeCodable { let value1: String var value2: String? + var value3: String! - init(value1: String, value2: String? = nil) { + init(value1: String, value2: String? = nil, value3: String! = nil) { self.value1 = value1 self.value2 = value2 + self.value3 = value3 } init(from decoder: any Decoder) throws { @@ -631,6 +785,7 @@ final class CodedAtTests: XCTestCase { let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) self.value1 = try nested_deeply_container.decode(String.self, forKey: CodingKeys.value1) self.value2 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) + self.value3 = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) } func encode(to encoder: any Encoder) throws { @@ -639,6 +794,7 @@ final class CodedAtTests: XCTestCase { var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) try nested_deeply_container.encode(self.value1, forKey: CodingKeys.value1) try nested_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + try nested_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) } enum CodingKeys: String, CodingKey { @@ -646,6 +802,7 @@ final class CodedAtTests: XCTestCase { case deeply = "deeply" case nested = "nested" case value2 = "key2" + case value3 = "key3" } } diff --git a/Tests/MetaCodableTests/CodedIn/CodedInDefaultTests.swift b/Tests/MetaCodableTests/CodedIn/CodedInDefaultTests.swift index 3ade7a442..5ab8794ef 100644 --- a/Tests/MetaCodableTests/CodedIn/CodedInDefaultTests.swift +++ b/Tests/MetaCodableTests/CodedIn/CodedInDefaultTests.swift @@ -107,6 +107,56 @@ final class CodedInDefaultTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @CodedIn + @Default("some") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + if let container = container { + do { + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "some" + } + } else { + self.value = "some" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + } + } + """ + ) } func testWithSinglePath() throws { @@ -245,6 +295,73 @@ final class CodedInDefaultTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default("some") + @CodedIn("nested") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + let nested_container: KeyedDecodingContainer? + let nested_containerMissing: Bool + if (try? container?.decodeNil(forKey: CodingKeys.nested)) == false { + nested_container = try? container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + nested_containerMissing = false + } else { + nested_container = nil + nested_containerMissing = true + } + if let container = container { + if let nested_container = nested_container { + do { + self.value = try nested_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "some" + } + } else if nested_containerMissing { + self.value = "some" + } else { + self.value = "some" + } + } else { + self.value = "some" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var nested_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + case nested = "nested" + } + } + """ + ) } func testWithNestedPath() throws { @@ -417,6 +534,90 @@ final class CodedInDefaultTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @Default("some") + @CodedIn("deeply", "nested") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = "some") { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try? decoder.container(keyedBy: CodingKeys.self) + let deeply_container: KeyedDecodingContainer? + let deeply_containerMissing: Bool + if (try? container?.decodeNil(forKey: CodingKeys.deeply)) == false { + deeply_container = try? container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + deeply_containerMissing = false + } else { + deeply_container = nil + deeply_containerMissing = true + } + let nested_deeply_container: KeyedDecodingContainer? + let nested_deeply_containerMissing: Bool + if (try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false { + nested_deeply_container = try? deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + nested_deeply_containerMissing = false + } else { + nested_deeply_container = nil + nested_deeply_containerMissing = true + } + if let container = container { + if let deeply_container = deeply_container { + if let nested_deeply_container = nested_deeply_container { + do { + self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "some" + } + } else if nested_deeply_containerMissing { + self.value = "some" + } else { + self.value = "some" + } + } else if deeply_containerMissing { + self.value = "some" + } else { + self.value = "some" + } + } else { + self.value = "some" + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + case deeply = "deeply" + case nested = "nested" + } + } + """ + ) } func testWithNestedPathOnMultiOptionalTypes() throws { @@ -430,7 +631,7 @@ final class CodedInDefaultTests: XCTestCase { let value1: String? @Default("some") @CodedIn("deeply", "nested") - let value2: String? + let value2: String! @CodedIn("deeply") let value3: String? } @@ -439,10 +640,10 @@ final class CodedInDefaultTests: XCTestCase { """ struct SomeCodable { let value1: String? - let value2: String? + let value2: String! let value3: String? - init(value1: String? = "some", value2: String? = "some", value3: String? = nil) { + init(value1: String? = "some", value2: String! = "some", value3: String? = nil) { self.value1 = value1 self.value2 = value2 self.value3 = value3 @@ -526,83 +727,104 @@ final class CodedInDefaultTests: XCTestCase { @Default("some") @CodedIn("deeply", "nested", "level") let value2: String? + @Default("some") + @CodedIn("deeply", "nested", "level") + let value3: String! @CodedAt("deeply", "nested") - let value3: String? + let value4: String? + @CodedAt("deeply", "nested") + let value5: String! @CodedAt("deeply") - let value4: String + let value6: String } """, - expandedSource: + expandedSource: // FIXME: not compilable """ struct SomeCodable { let value1: String let value2: String? - let value3: String? - let value4: String + let value3: String! + let value4: String? + let value5: String! + let value6: String - init(value4: String, value3: String? = nil, value1: String = "some", value2: String? = "some") { + init(value6: String, value4: String? = nil, value5: String! = nil, value1: String = "some", value2: String? = "some", value3: String! = "some") { + self.value6 = value6 self.value4 = value4 - self.value3 = value3 + self.value5 = value5 self.value1 = value1 self.value2 = value2 + self.value3 = value3 } } extension SomeCodable: Decodable { init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let value4_container = ((try? container.decodeNil(forKey: CodingKeys.value4)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value4) : nil - let value3_value4_container: KeyedDecodingContainer? - let value3_value4_containerMissing: Bool - if (try? value4_container?.decodeNil(forKey: CodingKeys.value3)) == false { - value3_value4_container = try? value4_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value3) - value3_value4_containerMissing = false + let value6_container = ((try? container.decodeNil(forKey: CodingKeys.value6)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value6) : nil + let value4_value6_container: KeyedDecodingContainer? + let value4_value6_containerMissing: Bool + if (try? value6_container?.decodeNil(forKey: CodingKeys.value4)) == false { + value4_value6_container = try? value6_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value4) + value4_value6_containerMissing = false } else { - value3_value4_container = nil - value3_value4_containerMissing = true + value4_value6_container = nil + value4_value6_containerMissing = true } - let level_value3_value4_container: KeyedDecodingContainer? - let level_value3_value4_containerMissing: Bool - if (try? value3_value4_container?.decodeNil(forKey: CodingKeys.level)) == false { - level_value3_value4_container = try? value3_value4_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) - level_value3_value4_containerMissing = false + let level_value4_value6_container: KeyedDecodingContainer? + let level_value4_value6_containerMissing: Bool + if (try? value4_value6_container?.decodeNil(forKey: CodingKeys.level)) == false { + level_value4_value6_container = try? value4_value6_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) + level_value4_value6_containerMissing = false } else { - level_value3_value4_container = nil - level_value3_value4_containerMissing = true + level_value4_value6_container = nil + level_value4_value6_containerMissing = true } - self.value4 = try container.decode(String.self, forKey: CodingKeys.value4) - if let value4_container = value4_container { - self.value3 = try value4_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) - if let value3_value4_container = value3_value4_container { - if let level_value3_value4_container = level_value3_value4_container { + self.value6 = try container.decode(String.self, forKey: CodingKeys.value6) + if let value6_container = value6_container { + self.value4 = try value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + self.value5 = try value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + if let value4_value6_container = value4_value6_container { + if let level_value4_value6_container = level_value4_value6_container { do { - self.value1 = try level_value3_value4_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" + self.value1 = try level_value4_value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" } catch { self.value1 = "some" } do { - self.value2 = try level_value3_value4_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) ?? "some" + self.value2 = try level_value4_value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) ?? "some" } catch { self.value2 = "some" } - } else if level_value3_value4_containerMissing { + do { + self.value3 = try level_value4_value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) ?? "some" + } catch { + self.value3 = "some" + } + } else if level_value4_value6_containerMissing { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } else { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } - } else if value3_value4_containerMissing { + } else if value4_value6_containerMissing { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } else { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } } else { - self.value3 = nil + self.value4 = nil + self.value5 = nil self.value1 = "some" self.value2 = "some" + self.value3 = "some" } } } @@ -610,23 +832,26 @@ final class CodedInDefaultTests: XCTestCase { extension SomeCodable: Encodable { func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.value4, forKey: CodingKeys.value4) - var value4_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value4) - try value4_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) - var value3_value4_container = value4_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value3) - var level_value3_value4_container = value3_value4_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) - try level_value3_value4_container.encode(self.value1, forKey: CodingKeys.value1) - try level_value3_value4_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + try container.encode(self.value6, forKey: CodingKeys.value6) + var value6_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value6) + try value6_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) + try value6_container.encodeIfPresent(self.value5, forKey: CodingKeys.value4) + var value4_value6_container = value6_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value4) + var level_value4_value6_container = value4_value6_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) + try level_value4_value6_container.encode(self.value1, forKey: CodingKeys.value1) + try level_value4_value6_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + try level_value4_value6_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) } } extension SomeCodable { enum CodingKeys: String, CodingKey { case value1 = "value1" - case value4 = "deeply" - case value3 = "nested" + case value6 = "deeply" + case value4 = "nested" case level = "level" case value2 = "value2" + case value3 = "value3" } } """ @@ -644,94 +869,116 @@ final class CodedInDefaultTests: XCTestCase { @Default("some") @CodedIn("deeply", "nested", "level") let value2: String? + @Default("some") + @CodedIn("deeply", "nested", "level") + let value3: String! @CodedAt("deeply", "nested") - let value3: String? + let value4: String? + @CodedAt("deeply", "nested") + let value5: String! @CodedAt("deeply") - let value4: String + let value6: String } """, - expandedSource: + expandedSource: // FIXME: not compilable """ class SomeCodable { let value1: String let value2: String? - let value3: String? - let value4: String + let value3: String! + let value4: String? + let value5: String! + let value6: String required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let value4_container = ((try? container.decodeNil(forKey: CodingKeys.value4)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value4) : nil - let value3_value4_container: KeyedDecodingContainer? - let value3_value4_containerMissing: Bool - if (try? value4_container?.decodeNil(forKey: CodingKeys.value3)) == false { - value3_value4_container = try? value4_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value3) - value3_value4_containerMissing = false + let value6_container = ((try? container.decodeNil(forKey: CodingKeys.value6)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value6) : nil + let value4_value6_container: KeyedDecodingContainer? + let value4_value6_containerMissing: Bool + if (try? value6_container?.decodeNil(forKey: CodingKeys.value4)) == false { + value4_value6_container = try? value6_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value4) + value4_value6_containerMissing = false } else { - value3_value4_container = nil - value3_value4_containerMissing = true + value4_value6_container = nil + value4_value6_containerMissing = true } - let level_value3_value4_container: KeyedDecodingContainer? - let level_value3_value4_containerMissing: Bool - if (try? value3_value4_container?.decodeNil(forKey: CodingKeys.level)) == false { - level_value3_value4_container = try? value3_value4_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) - level_value3_value4_containerMissing = false + let level_value4_value6_container: KeyedDecodingContainer? + let level_value4_value6_containerMissing: Bool + if (try? value4_value6_container?.decodeNil(forKey: CodingKeys.level)) == false { + level_value4_value6_container = try? value4_value6_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) + level_value4_value6_containerMissing = false } else { - level_value3_value4_container = nil - level_value3_value4_containerMissing = true + level_value4_value6_container = nil + level_value4_value6_containerMissing = true } - self.value4 = try container.decode(String.self, forKey: CodingKeys.value4) - if let value4_container = value4_container { - self.value3 = try value4_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) - if let value3_value4_container = value3_value4_container { - if let level_value3_value4_container = level_value3_value4_container { + self.value6 = try container.decode(String.self, forKey: CodingKeys.value6) + if let value6_container = value6_container { + self.value4 = try value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + self.value5 = try value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value4) + if let value4_value6_container = value4_value6_container { + if let level_value4_value6_container = level_value4_value6_container { do { - self.value1 = try level_value3_value4_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" + self.value1 = try level_value4_value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" } catch { self.value1 = "some" } do { - self.value2 = try level_value3_value4_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) ?? "some" + self.value2 = try level_value4_value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) ?? "some" } catch { self.value2 = "some" } - } else if level_value3_value4_containerMissing { + do { + self.value3 = try level_value4_value6_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) ?? "some" + } catch { + self.value3 = "some" + } + } else if level_value4_value6_containerMissing { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } else { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } - } else if value3_value4_containerMissing { + } else if value4_value6_containerMissing { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } else { self.value1 = "some" self.value2 = "some" + self.value3 = "some" } } else { - self.value3 = nil + self.value4 = nil + self.value5 = nil self.value1 = "some" self.value2 = "some" + self.value3 = "some" } } func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.value4, forKey: CodingKeys.value4) - var value4_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value4) - try value4_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) - var value3_value4_container = value4_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value3) - var level_value3_value4_container = value3_value4_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) - try level_value3_value4_container.encode(self.value1, forKey: CodingKeys.value1) - try level_value3_value4_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + try container.encode(self.value6, forKey: CodingKeys.value6) + var value6_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value6) + try value6_container.encodeIfPresent(self.value4, forKey: CodingKeys.value4) + try value6_container.encodeIfPresent(self.value5, forKey: CodingKeys.value4) + var value4_value6_container = value6_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.value4) + var level_value4_value6_container = value4_value6_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.level) + try level_value4_value6_container.encode(self.value1, forKey: CodingKeys.value1) + try level_value4_value6_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + try level_value4_value6_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) } enum CodingKeys: String, CodingKey { case value1 = "value1" - case value4 = "deeply" - case value3 = "nested" + case value6 = "deeply" + case value4 = "nested" case level = "level" case value2 = "value2" + case value3 = "value3" } } diff --git a/Tests/MetaCodableTests/CodedIn/CodedInTests.swift b/Tests/MetaCodableTests/CodedIn/CodedInTests.swift index 991aff13a..6b717a0b7 100644 --- a/Tests/MetaCodableTests/CodedIn/CodedInTests.swift +++ b/Tests/MetaCodableTests/CodedIn/CodedInTests.swift @@ -185,6 +185,47 @@ final class CodedInTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @CodedIn + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = nil) { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + } + } + """ + ) } func testWithSinglePath() throws { @@ -281,6 +322,54 @@ final class CodedInTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @CodedIn("nested") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = nil) { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let nested_container = ((try? container.decodeNil(forKey: CodingKeys.nested)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil + if let nested_container = nested_container { + self.value = try nested_container.decodeIfPresent(String.self, forKey: CodingKeys.value) + } else { + self.value = nil + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var nested_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + case nested = "nested" + } + } + """ + ) } func testWithNestedPath() throws { @@ -387,6 +476,61 @@ final class CodedInTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + @CodedIn("deeply", "nested") + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = nil) { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let deeply_container = ((try? container.decodeNil(forKey: CodingKeys.deeply)) == false) ? try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) : nil + let nested_deeply_container = ((try? deeply_container?.decodeNil(forKey: CodingKeys.nested)) == false) ? try deeply_container?.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) : nil + if let deeply_container = deeply_container { + if let nested_deeply_container = nested_deeply_container { + self.value = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value) + } else { + self.value = nil + } + } else { + self.value = nil + } + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) + var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) + try nested_deeply_container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + case deeply = "deeply" + case nested = "nested" + } + } + """ + ) } func testWithNestedPathOnMultiOptionalTypes() throws { @@ -398,7 +542,7 @@ final class CodedInTests: XCTestCase { @CodedIn("deeply", "nested1") let value1: String? @CodedIn("deeply", "nested2") - let value2: String? + let value2: String! @CodedIn("deeply1") let value3: String? } @@ -407,10 +551,10 @@ final class CodedInTests: XCTestCase { """ struct SomeCodable { let value1: String? - let value2: String? + let value2: String! let value3: String? - init(value1: String? = nil, value2: String? = nil, value3: String? = nil) { + init(value1: String? = nil, value2: String! = nil, value3: String? = nil) { self.value1 = value1 self.value2 = value2 self.value3 = value3 @@ -485,6 +629,8 @@ final class CodedInTests: XCTestCase { let value1: String @CodedIn("deeply", "nested2") let value2: String? + @CodedIn("deeply", "nested3") + let value3: String! } """, expandedSource: @@ -492,10 +638,12 @@ final class CodedInTests: XCTestCase { struct SomeCodable { let value1: String let value2: String? + let value3: String! - init(value1: String, value2: String? = nil) { + init(value1: String, value2: String? = nil, value3: String! = nil) { self.value1 = value1 self.value2 = value2 + self.value3 = value3 } } @@ -505,12 +653,18 @@ final class CodedInTests: XCTestCase { let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) let nested1_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested1) let nested2_deeply_container = ((try? deeply_container.decodeNil(forKey: CodingKeys.nested2)) == false) ? try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested2) : nil + let nested3_deeply_container = ((try? deeply_container.decodeNil(forKey: CodingKeys.nested3)) == false) ? try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested3) : nil self.value1 = try nested1_deeply_container.decode(String.self, forKey: CodingKeys.value1) if let nested2_deeply_container = nested2_deeply_container { self.value2 = try nested2_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) } else { self.value2 = nil } + if let nested3_deeply_container = nested3_deeply_container { + self.value3 = try nested3_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + } else { + self.value3 = nil + } } } @@ -522,6 +676,8 @@ final class CodedInTests: XCTestCase { try nested1_deeply_container.encode(self.value1, forKey: CodingKeys.value1) var nested2_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested2) try nested2_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + var nested3_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested3) + try nested3_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) } } @@ -532,6 +688,8 @@ final class CodedInTests: XCTestCase { case nested1 = "nested1" case value2 = "value2" case nested2 = "nested2" + case value3 = "value3" + case nested3 = "nested3" } } """ @@ -547,6 +705,8 @@ final class CodedInTests: XCTestCase { let value1: String @CodedIn("deeply", "nested2") let value2: String? + @CodedIn("deeply", "nested3") + let value3: String! } """, expandedSource: @@ -554,18 +714,25 @@ final class CodedInTests: XCTestCase { class SomeCodable { let value1: String let value2: String? + let value3: String! required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) let nested1_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested1) let nested2_deeply_container = ((try? deeply_container.decodeNil(forKey: CodingKeys.nested2)) == false) ? try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested2) : nil + let nested3_deeply_container = ((try? deeply_container.decodeNil(forKey: CodingKeys.nested3)) == false) ? try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested3) : nil self.value1 = try nested1_deeply_container.decode(String.self, forKey: CodingKeys.value1) if let nested2_deeply_container = nested2_deeply_container { self.value2 = try nested2_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) } else { self.value2 = nil } + if let nested3_deeply_container = nested3_deeply_container { + self.value3 = try nested3_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + } else { + self.value3 = nil + } } func encode(to encoder: any Encoder) throws { @@ -575,6 +742,8 @@ final class CodedInTests: XCTestCase { try nested1_deeply_container.encode(self.value1, forKey: CodingKeys.value1) var nested2_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested2) try nested2_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + var nested3_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested3) + try nested3_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) } enum CodingKeys: String, CodingKey { @@ -583,6 +752,8 @@ final class CodedInTests: XCTestCase { case nested1 = "nested1" case value2 = "value2" case nested2 = "nested2" + case value3 = "value3" + case nested3 = "nested3" } } @@ -605,6 +776,8 @@ final class CodedInTests: XCTestCase { let value1: String @CodedIn("deeply", "nested2") var value2: String? + @CodedIn("deeply", "nested3") + var value3: String! } """, expandedSource: @@ -612,10 +785,12 @@ final class CodedInTests: XCTestCase { actor SomeCodable { let value1: String var value2: String? + var value3: String! - init(value1: String, value2: String? = nil) { + init(value1: String, value2: String? = nil, value3: String! = nil) { self.value1 = value1 self.value2 = value2 + self.value3 = value3 } init(from decoder: any Decoder) throws { @@ -623,12 +798,18 @@ final class CodedInTests: XCTestCase { let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) let nested1_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested1) let nested2_deeply_container = ((try? deeply_container.decodeNil(forKey: CodingKeys.nested2)) == false) ? try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested2) : nil + let nested3_deeply_container = ((try? deeply_container.decodeNil(forKey: CodingKeys.nested3)) == false) ? try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested3) : nil self.value1 = try nested1_deeply_container.decode(String.self, forKey: CodingKeys.value1) if let nested2_deeply_container = nested2_deeply_container { self.value2 = try nested2_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value2) } else { self.value2 = nil } + if let nested3_deeply_container = nested3_deeply_container { + self.value3 = try nested3_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.value3) + } else { + self.value3 = nil + } } func encode(to encoder: any Encoder) throws { @@ -638,6 +819,8 @@ final class CodedInTests: XCTestCase { try nested1_deeply_container.encode(self.value1, forKey: CodingKeys.value1) var nested2_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested2) try nested2_deeply_container.encodeIfPresent(self.value2, forKey: CodingKeys.value2) + var nested3_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested3) + try nested3_deeply_container.encodeIfPresent(self.value3, forKey: CodingKeys.value3) } enum CodingKeys: String, CodingKey { @@ -646,6 +829,8 @@ final class CodedInTests: XCTestCase { case nested1 = "nested1" case value2 = "value2" case nested2 = "nested2" + case value3 = "value3" + case nested3 = "nested3" } } diff --git a/Tests/MetaCodableTests/VariableDeclarationTests.swift b/Tests/MetaCodableTests/VariableDeclarationTests.swift index ca84ae78e..1e0ec1fb3 100644 --- a/Tests/MetaCodableTests/VariableDeclarationTests.swift +++ b/Tests/MetaCodableTests/VariableDeclarationTests.swift @@ -414,6 +414,46 @@ final class VariableDeclarationTests: XCTestCase { } """ ) + + assertMacroExpansion( + """ + @Codable + @MemberInit + struct SomeCodable { + let value: String! + } + """, + expandedSource: + """ + struct SomeCodable { + let value: String! + + init(value: String! = nil) { + self.value = value + } + } + + extension SomeCodable: Decodable { + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) + } + } + + extension SomeCodable: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(self.value, forKey: CodingKeys.value) + } + } + + extension SomeCodable { + enum CodingKeys: String, CodingKey { + case value = "value" + } + } + """ + ) } func testGenericSyntaxOptionalVariable() throws {