Skip to content

Commit 29b9f00

Browse files
authored
feat: added support for implicitly unwrapped optional type (#87)
* feat: support implicitly unwrapped optional type * feat: support implicitly unwrapped optional with @default * feat: treat implicitly unwrapped optional as optional * feat: add necessary tests * feat: add tests with customization * refactor: changed to if-let expression
1 parent 99e8c33 commit 29b9f00

10 files changed

Lines changed: 1476 additions & 189 deletions

Sources/PluginCore/Variables/Property/BasicPropertyVariable.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,13 @@ struct BasicPropertyVariable: DefaultPropertyVariable, DeclaredVariable {
145145
) -> CodeBlockItemListSyntax {
146146
switch location {
147147
case .coder(let decoder, let passedMethod):
148-
let optionalToken: TokenSyntax =
149-
if passedMethod?.trimmedDescription == "decodeIfPresent" {
150-
"?"
151-
} else {
152-
""
153-
}
148+
let optionalToken: TokenSyntax = passedMethod?.trimmedDescription == "decodeIfPresent" ? "?" : ""
149+
150+
var type = type
151+
if let implicitlyUnwrappedType = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
152+
type = TypeSyntax(OptionalTypeSyntax(wrappedType: implicitlyUnwrappedType.wrappedType))
153+
}
154+
154155
return CodeBlockItemListSyntax {
155156
"""
156157
\(decodePrefix)\(name) = try \(type)\(optionalToken)(from: \(decoder))

Sources/PluginCore/Variables/Property/PropertyVariable.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,14 @@ extension PropertyVariable {
119119
///
120120
/// Checks whether the type syntax uses
121121
/// `?` optional type syntax (i.e. `Type?`) or
122+
/// `!` implicitly unwrapped optional type syntax (i.e. `Type!`) or
122123
/// generic optional syntax (i.e. `Optional<Type>`).
123124
var hasOptionalType: Bool {
124125
if type.is(OptionalTypeSyntax.self) {
125126
return true
126-
} else if let type = type.as(IdentifierTypeSyntax.self),
127+
} else if type.is(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
128+
return true
129+
} else if let type = type.as(IdentifierTypeSyntax.self),
127130
type.name.text == "Optional",
128131
let gArgs = type.genericArgumentClause?.arguments,
129132
gArgs.count == 1
@@ -152,6 +155,9 @@ extension PropertyVariable {
152155
if let type = type.as(OptionalTypeSyntax.self) {
153156
dType = type.wrappedType
154157
dMethod = "\(method)IfPresent"
158+
} else if let type = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
159+
dType = type.wrappedType
160+
dMethod = "\(method)IfPresent"
155161
} else if let type = type.as(IdentifierTypeSyntax.self),
156162
type.name.text == "Optional",
157163
let gArgs = type.genericArgumentClause?.arguments,

Tests/MetaCodableTests/CodableTests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,53 @@ final class CodableTests: XCTestCase {
4747
)
4848
}
4949

50+
func testOptionalWithoutAnyCustomization() throws {
51+
assertMacroExpansion(
52+
"""
53+
@Codable
54+
struct SomeCodable {
55+
let value1: String?
56+
let value2: String!
57+
let value3: Optional<String>
58+
}
59+
""",
60+
expandedSource:
61+
"""
62+
struct SomeCodable {
63+
let value1: String?
64+
let value2: String!
65+
let value3: Optional<String>
66+
}
67+
68+
extension SomeCodable: Decodable {
69+
init(from decoder: any Decoder) throws {
70+
let container = try decoder.container(keyedBy: CodingKeys.self)
71+
self.value1 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value1)
72+
self.value2 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value2)
73+
self.value3 = try container.decodeIfPresent(String.self, forKey: CodingKeys.value3)
74+
}
75+
}
76+
77+
extension SomeCodable: Encodable {
78+
func encode(to encoder: any Encoder) throws {
79+
var container = encoder.container(keyedBy: CodingKeys.self)
80+
try container.encodeIfPresent(self.value1, forKey: CodingKeys.value1)
81+
try container.encodeIfPresent(self.value2, forKey: CodingKeys.value2)
82+
try container.encodeIfPresent(self.value3, forKey: CodingKeys.value3)
83+
}
84+
}
85+
86+
extension SomeCodable {
87+
enum CodingKeys: String, CodingKey {
88+
case value1 = "value1"
89+
case value2 = "value2"
90+
case value3 = "value3"
91+
}
92+
}
93+
"""
94+
)
95+
}
96+
5097
func testWithoutAnyCustomizationWithStaticVar() throws {
5198
assertMacroExpansion(
5299
"""

0 commit comments

Comments
 (0)