diff --git a/Sources/PluginCore/Attributes/CodedBy.swift b/Sources/PluginCore/Attributes/CodedBy.swift index a6f8ae769..d95cccdb0 100644 --- a/Sources/PluginCore/Attributes/CodedBy.swift +++ b/Sources/PluginCore/Attributes/CodedBy.swift @@ -74,7 +74,7 @@ package struct CodedBy: PropertyAttribute { extension Registration where Decl: AttributableDeclSyntax, Var: DefaultPropertyVariable, - Var.Initialization == RequiredInitialization + Var.Initialization: RequiredVariableInitialization { /// The optional variable data with helper expression /// that output registration will have. diff --git a/Sources/PluginCore/Attributes/Default.swift b/Sources/PluginCore/Attributes/Default.swift index fcdabde7a..fe0c8a69f 100644 --- a/Sources/PluginCore/Attributes/Default.swift +++ b/Sources/PluginCore/Attributes/Default.swift @@ -58,7 +58,7 @@ package struct Default: PropertyAttribute { extension Registration where Decl: AttributableDeclSyntax, Var: PropertyVariable, - Var.Initialization == RequiredInitialization + Var.Initialization: RequiredVariableInitialization { /// The variable data with default expression /// that output registration will have. @@ -77,8 +77,31 @@ where } } +extension AnyPropertyVariable: DefaultPropertyVariable {} + +extension Registration +where + Decl: AttributableDeclSyntax, Var: PropertyVariable, + Var.Initialization: RequiredVariableInitialization +{ + /// Update registration with binding initializer value. If the ``Default`` attribute is applied, it takes precedence. + /// + /// New registration is updated with default expression data that will be + /// used for decoding failure and memberwise initializer(s), if provided. + /// + /// - Parameter decl: The declaration to check for attribute. + /// - Returns: Newly built registration with default expression data or self. + func addDefaultValueIfInitializerExists() -> Registration> { + guard Default(from: decl) == nil, let value = self.variable.value?.trimmed else { + return self.updating(with: variable.any) + } + let newVar = variable.with(default: value) + return self.updating(with: newVar.any) + } +} + fileprivate extension PropertyVariable -where Initialization == RequiredInitialization { +where Initialization: RequiredVariableInitialization { /// Update variable data with the default value expression provided. /// /// `DefaultValueVariable` is created with this variable as base diff --git a/Sources/PluginCore/Variables/Initialization/OptionalInitialization.swift b/Sources/PluginCore/Variables/Initialization/OptionalInitialization.swift deleted file mode 100644 index 3cbc574bc..000000000 --- a/Sources/PluginCore/Variables/Initialization/OptionalInitialization.swift +++ /dev/null @@ -1,24 +0,0 @@ -/// Represents initialization is optional for the variable. -/// -/// The variable must be mutable and initialized already. -struct OptionalInitialization: VariableInitialization -where Wrapped: RequiredVariableInitialization { - /// The value wrapped by this instance. - /// - /// Only function parameter and code block provided - /// with`RequiredVariableInitialization` - /// can be wrapped by this instance. - let base: Wrapped - - /// Adds current initialization type to memberwise initialization - /// generator. - /// - /// New memberwise initialization generator is created after adding this - /// initialization as optional and returned. - /// - /// - Parameter generator: The init-generator to add in. - /// - Returns: The modified generator containing this initialization. - func add(to generator: MemberwiseInitGenerator) -> MemberwiseInitGenerator { - generator.add(optional: .init(param: base.param, code: base.code)) - } -} diff --git a/Sources/PluginCore/Variables/Initialization/RequiredInitializationWithDefaultValue.swift b/Sources/PluginCore/Variables/Initialization/RequiredInitializationWithDefaultValue.swift index 63409a408..f5ecd25b9 100644 --- a/Sources/PluginCore/Variables/Initialization/RequiredInitializationWithDefaultValue.swift +++ b/Sources/PluginCore/Variables/Initialization/RequiredInitializationWithDefaultValue.swift @@ -12,7 +12,7 @@ struct RequiredInitializationWithDefaultValue: RequiredVariableInitialization { /// syntax is fetched from this value and /// updated when adding this initialization /// type to init-generator. - let base: RequiredInitialization + let base: RequiredVariableInitialization /// The default expression when /// no value provided explicitly. /// diff --git a/Sources/PluginCore/Variables/Initialization/RequiredVariableInitialization.swift b/Sources/PluginCore/Variables/Initialization/RequiredVariableInitialization.swift index 486f32d1a..5cd35c657 100644 --- a/Sources/PluginCore/Variables/Initialization/RequiredVariableInitialization.swift +++ b/Sources/PluginCore/Variables/Initialization/RequiredVariableInitialization.swift @@ -20,12 +20,3 @@ package protocol RequiredVariableInitialization: VariableInitialization { /// generating initializer. var code: CodeBlockItemSyntax { get } } - -extension RequiredVariableInitialization { - /// Converts initialization to optional from required initialization. - /// - /// Wraps current instance in `OptionalInitialization`. - var optionalize: OptionalInitialization { - return .init(base: self) - } -} diff --git a/Sources/PluginCore/Variables/Property/DefaultValueVariable.swift b/Sources/PluginCore/Variables/Property/DefaultValueVariable.swift index ead46173a..a646d2314 100644 --- a/Sources/PluginCore/Variables/Property/DefaultValueVariable.swift +++ b/Sources/PluginCore/Variables/Property/DefaultValueVariable.swift @@ -10,7 +10,7 @@ /// * For providing default value to variable in memberwise initializer(s). struct DefaultValueVariable: ComposedVariable, PropertyVariable where - Wrapped: PropertyVariable, Wrapped.Initialization == RequiredInitialization + Wrapped: PropertyVariable, Wrapped.Initialization: RequiredVariableInitialization { /// The customization options for `DefaultValueVariable`. /// diff --git a/Sources/PluginCore/Variables/Property/InitializationVariable.swift b/Sources/PluginCore/Variables/Property/InitializationVariable.swift index afcd323ce..096dc80d1 100644 --- a/Sources/PluginCore/Variables/Property/InitializationVariable.swift +++ b/Sources/PluginCore/Variables/Property/InitializationVariable.swift @@ -110,11 +110,7 @@ where in context: some MacroExpansionContext ) -> AnyInitialization { return if options.`init` { - if options.initialized { - base.initializing(in: context).optionalize.any - } else { - base.initializing(in: context).any - } + base.initializing(in: context).any } else { IgnoredInitialization().any } diff --git a/Sources/PluginCore/Variables/Type/MemberGroup.swift b/Sources/PluginCore/Variables/Type/MemberGroup.swift index aaa6a29b2..0441dfc73 100644 --- a/Sources/PluginCore/Variables/Type/MemberGroup.swift +++ b/Sources/PluginCore/Variables/Type/MemberGroup.swift @@ -184,6 +184,7 @@ where Decl.ChildSyntaxInput == Void, Decl.MemberSyntax == PropertyDeclSyntax { return input .transformKeysAccordingToStrategy(attachedTo: decl) + .addDefaultValueIfInitializerExists() .checkInitializedCodingIgnored(attachedAt: decl) .registerKeyPath( provider: CodedAt(from: input.decl) diff --git a/Tests/MetaCodableTests/CodableInheritanceTests.swift b/Tests/MetaCodableTests/CodableInheritanceTests.swift index a53acf8ca..d3e95bd92 100644 --- a/Tests/MetaCodableTests/CodableInheritanceTests.swift +++ b/Tests/MetaCodableTests/CodableInheritanceTests.swift @@ -79,7 +79,11 @@ final class CodableInheritanceTests: XCTestCase { required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.value = try container.decode(String.self, forKey: CodingKeys.value) + do { + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "" + } catch { + self.value = "" + } } func encode(to encoder: any Encoder) throws { @@ -122,7 +126,11 @@ final class CodableInheritanceTests: XCTestCase { required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.value = try container.decode(String.self, forKey: CodingKeys.value) + do { + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "" + } catch { + self.value = "" + } try super.init(from: decoder) } @@ -161,7 +169,11 @@ final class CodableInheritanceTests: XCTestCase { required init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.value = try container.decode(String.self, forKey: CodingKeys.value) + do { + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "" + } catch { + self.value = "" + } try super.init(from: decoder) } diff --git a/Tests/MetaCodableTests/GroupedMutableVariableTests.swift b/Tests/MetaCodableTests/GroupedMutableVariableTests.swift index f9254fa49..c58814357 100644 --- a/Tests/MetaCodableTests/GroupedMutableVariableTests.swift +++ b/Tests/MetaCodableTests/GroupedMutableVariableTests.swift @@ -68,12 +68,7 @@ final class GroupedMutableVariableTests: XCTestCase { struct SomeCodable { var one, two: String, three: String = "" - init(one: String, two: String) { - self.one = one - self.two = two - } - - init(one: String, two: String, three: String) { + init(one: String, two: String, three: String = "") { self.one = one self.two = two self.three = three @@ -85,7 +80,11 @@ final class GroupedMutableVariableTests: XCTestCase { let container = try decoder.container(keyedBy: CodingKeys.self) self.one = try container.decode(String.self, forKey: CodingKeys.one) self.two = try container.decode(String.self, forKey: CodingKeys.two) - self.three = try container.decode(String.self, forKey: CodingKeys.three) + do { + self.three = try container.decodeIfPresent(String.self, forKey: CodingKeys.three) ?? "" + } catch { + self.three = "" + } } } @@ -215,12 +214,7 @@ final class GroupedMutableVariableTests: XCTestCase { struct SomeCodable { var one: String, two: String = "", three: Int - init(one: String, three: Int) { - self.one = one - self.three = three - } - - init(one: String, two: String, three: Int) { + init(one: String, two: String = "", three: Int) { self.one = one self.two = two self.three = three @@ -231,7 +225,11 @@ final class GroupedMutableVariableTests: XCTestCase { init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.one = try container.decode(String.self, forKey: CodingKeys.one) - self.two = try container.decode(String.self, forKey: CodingKeys.two) + do { + self.two = try container.decodeIfPresent(String.self, forKey: CodingKeys.two) ?? "" + } catch { + self.two = "" + } self.three = try container.decode(Int.self, forKey: CodingKeys.three) } } @@ -270,12 +268,7 @@ final class GroupedMutableVariableTests: XCTestCase { struct SomeCodable { var one: String, two = "", three: Int - init(one: String, three: Int) { - self.one = one - self.three = three - } - - init(one: String, two: Int, three: Int) { + init(one: String, two: Int = "", three: Int) { self.one = one self.two = two self.three = three @@ -286,7 +279,11 @@ final class GroupedMutableVariableTests: XCTestCase { init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.one = try container.decode(String.self, forKey: CodingKeys.one) - self.two = try container.decode(Int.self, forKey: CodingKeys.two) + do { + self.two = try container.decodeIfPresent(Int.self, forKey: CodingKeys.two) ?? "" + } catch { + self.two = "" + } self.three = try container.decode(Int.self, forKey: CodingKeys.three) } } diff --git a/Tests/MetaCodableTests/IgnoreCodingTests.swift b/Tests/MetaCodableTests/IgnoreCodingTests.swift index 75705dc91..d98708e89 100644 --- a/Tests/MetaCodableTests/IgnoreCodingTests.swift +++ b/Tests/MetaCodableTests/IgnoreCodingTests.swift @@ -295,7 +295,11 @@ final class IgnoreCodingTests: XCTestCase { extension SomeCodable: Decodable { init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.one = try container.decode(String.self, forKey: CodingKeys.one) + do { + self.one = try container.decodeIfPresent(String.self, forKey: CodingKeys.one) ?? "some" + } catch { + self.one = "some" + } self.two = try container.decode(String.self, forKey: CodingKeys.two) } } @@ -398,10 +402,30 @@ final class IgnoreCodingTests: XCTestCase { extension SomeCodable: Decodable { 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.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) - self.four = try nested_deeply_container.decode(String.self, forKey: CodingKeys.two) - self.three = try nested_deeply_container.decode(String.self, forKey: CodingKeys.three) + if let deeply_container = try? container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) { + if let nested_deeply_container = try? deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) { + do { + self.four = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.two) ?? "some" + } catch { + self.four = "some" + } + do { + self.three = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.three) ?? "some" + } catch { + self.three = "some" + } + } else { + self.one = "some" + self.two = "some" + self.four = "some" + self.three = "some" + } + } else { + self.one = "some" + self.two = "some" + self.four = "some" + self.three = "some" + } } } @@ -457,10 +481,30 @@ final class IgnoreCodingTests: XCTestCase { 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.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) - self.four = try nested_deeply_container.decode(String.self, forKey: CodingKeys.two) - self.three = try nested_deeply_container.decode(String.self, forKey: CodingKeys.three) + if let deeply_container = try? container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply) { + if let nested_deeply_container = try? deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested) { + do { + self.four = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.two) ?? "some" + } catch { + self.four = "some" + } + do { + self.three = try nested_deeply_container.decodeIfPresent(String.self, forKey: CodingKeys.three) ?? "some" + } catch { + self.three = "some" + } + } else { + self.one = "some" + self.two = "some" + self.four = "some" + self.three = "some" + } + } else { + self.one = "some" + self.two = "some" + self.four = "some" + self.three = "some" + } } func encode(to encoder: any Encoder) throws { diff --git a/Tests/MetaCodableTests/IgnoreInitializedTests.swift b/Tests/MetaCodableTests/IgnoreInitializedTests.swift index 7671cf169..268469563 100644 --- a/Tests/MetaCodableTests/IgnoreInitializedTests.swift +++ b/Tests/MetaCodableTests/IgnoreInitializedTests.swift @@ -298,7 +298,11 @@ final class IgnoreInitializedTests: XCTestCase { extension SomeCodable: Decodable { init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.one = try container.decode(String.self, forKey: CodingKeys.one) + do { + self.one = try container.decodeIfPresent(String.self, forKey: CodingKeys.one) ?? "some" + } catch { + self.one = "some" + } } } @@ -376,7 +380,11 @@ final class IgnoreInitializedTests: XCTestCase { extension SomeCodable: Decodable { init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.one = try container.decode(String.self, forKey: CodingKeys.one) + do { + self.one = try container.decodeIfPresent(String.self, forKey: CodingKeys.one) ?? "some" + } catch { + self.one = "some" + } } } diff --git a/Tests/MetaCodableTests/VariableDeclarationTests.swift b/Tests/MetaCodableTests/VariableDeclarationTests.swift index ca84ae78e..7ced4d63b 100644 --- a/Tests/MetaCodableTests/VariableDeclarationTests.swift +++ b/Tests/MetaCodableTests/VariableDeclarationTests.swift @@ -58,10 +58,7 @@ final class VariableDeclarationTests: XCTestCase { struct SomeCodable { var value: String = "some" - init() { - } - - init(value: String) { + init(value: String = "some") { self.value = value } } @@ -69,7 +66,11 @@ final class VariableDeclarationTests: XCTestCase { extension SomeCodable: Decodable { init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.value = try container.decode(String.self, forKey: CodingKeys.value) + do { + self.value = try container.decodeIfPresent(String.self, forKey: CodingKeys.value) ?? "some" + } catch { + self.value = "some" + } } } @@ -288,18 +289,7 @@ final class VariableDeclarationTests: XCTestCase { } } - init() { - } - - init(value1: String) { - self.value1 = value1 - } - - init(value2: String) { - self.value2 = value2 - } - - init(value1: String, value2: String) { + init(value1: String = "some", value2: String = "some") { self.value1 = value1 self.value2 = value2 } @@ -308,8 +298,16 @@ final class VariableDeclarationTests: XCTestCase { extension SomeCodable: Decodable { init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.value1 = try container.decode(String.self, forKey: CodingKeys.value1) - self.value2 = try container.decode(String.self, forKey: CodingKeys.value2) + do { + self.value1 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value1) ?? "some" + } catch { + self.value1 = "some" + } + do { + self.value2 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value2) ?? "some" + } catch { + self.value2 = "some" + } } }