Skip to content

Commit 665306f

Browse files
committed
feat: added CodingKey alias support
1 parent 2670fde commit 665306f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+970
-205
lines changed

Sources/CodableMacroPlugin/Attributes/CodedAs.swift

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@_implementationOnly import SwiftSyntax
2+
@_implementationOnly import SwiftSyntaxMacros
23

34
/// Attribute type for `CodedAs` macro-attribute.
45
///
@@ -11,9 +12,9 @@ struct CodedAs: PropertyAttribute {
1112
let node: AttributeSyntax
1213

1314
/// The alternate value expression provided.
14-
var expr: ExprSyntax? {
15+
var exprs: [ExprSyntax] {
1516
return node.arguments?
16-
.as(LabeledExprListSyntax.self)?.first?.expression
17+
.as(LabeledExprListSyntax.self)?.map(\.expression) ?? []
1718
}
1819

1920
/// The type to which to be decoded/encoded.
@@ -53,32 +54,62 @@ struct CodedAs: PropertyAttribute {
5354
/// * This attribute mustn't be combined with `CodedBy`
5455
/// attribute.
5556
/// * If macro has one argument provided:
56-
/// * Attached declaration is an enum-case declaration.
57+
/// * Attached declaration is an enum-case or variable declaration.
5758
/// * This attribute isn't used combined with `IgnoreCoding`
5859
/// attribute.
60+
/// * If macro attached declaration is variable declaration:
61+
/// * Attached declaration is not a grouped variable declaration.
62+
/// * Attached declaration is not a static variable declaration.
5963
///
6064
/// - Returns: The built diagnoser instance.
6165
func diagnoser() -> DiagnosticProducer {
6266
return AggregatedDiagnosticProducer {
6367
cantDuplicate()
6468
`if`(
65-
has(arguments: 1),
69+
has(arguments: 0),
6670
AggregatedDiagnosticProducer {
67-
expect(syntaxes: EnumCaseDeclSyntax.self)
68-
cantBeCombined(with: IgnoreCoding.self)
69-
},
70-
else: AggregatedDiagnosticProducer {
7171
expect(syntaxes: EnumDeclSyntax.self)
7272
mustBeCombined(with: Codable.self)
7373
mustBeCombined(with: CodedAt.self)
7474
cantBeCombined(with: CodedBy.self)
75-
}
75+
},
76+
else: `if`(
77+
isVariable,
78+
AggregatedDiagnosticProducer {
79+
attachedToUngroupedVariable()
80+
attachedToNonStaticVariable()
81+
cantBeCombined(with: IgnoreCoding.self)
82+
},
83+
else: AggregatedDiagnosticProducer {
84+
expect(
85+
syntaxes: EnumCaseDeclSyntax.self,
86+
VariableDeclSyntax.self
87+
)
88+
cantBeCombined(with: IgnoreCoding.self)
89+
}
90+
)
7691
)
7792
}
7893
}
7994
}
8095

81-
extension Registration where Key == ExprSyntax?, Decl: AttributableDeclSyntax {
96+
extension CodedAs: KeyPathProvider {
97+
/// Indicates whether `CodingKey` path
98+
/// data is provided to this instance.
99+
///
100+
/// Always `true` for this type.
101+
var provided: Bool { true }
102+
103+
/// Updates `CodingKey` path using the provided path.
104+
///
105+
/// The `CodingKey` path overrides current `CodingKey` path data.
106+
///
107+
/// - Parameter path: Current `CodingKey` path.
108+
/// - Returns: Updated `CodingKey` path.
109+
func keyPath(withExisting path: [String]) -> [String] { providedPath }
110+
}
111+
112+
extension Registration where Key == [ExprSyntax], Decl: AttributableDeclSyntax {
82113
/// Update registration with alternate value expression data.
83114
///
84115
/// New registration is updated with value expression data that will be
@@ -88,9 +119,37 @@ extension Registration where Key == ExprSyntax?, Decl: AttributableDeclSyntax {
88119
/// - Returns: Newly built registration with value expression data.
89120
func checkForAlternateValue() -> Self {
90121
guard
91-
self.key == nil,
122+
self.key.isEmpty,
92123
let attr = CodedAs(from: self.decl)
93124
else { return self }
94-
return self.updating(with: attr.expr)
125+
return self.updating(with: attr.exprs)
126+
}
127+
}
128+
129+
extension Registration
130+
where Key == [String], Decl: AttributableDeclSyntax, Var: PropertyVariable {
131+
/// Update registration with alternate `CodingKey`s data.
132+
///
133+
/// New registration is updated with `CodingKey`s data that will be
134+
/// used for decoding/encoding, if provided.
135+
///
136+
/// - Parameters:
137+
/// - codingKeys: The `CodingKeys` map new data will be added.
138+
/// - context: The context in which to perform the macro expansion.
139+
///
140+
/// - Returns: Newly built registration with additional `CodingKey`s data.
141+
func checkForAlternateKeyValues(
142+
addTo codingKeys: CodingKeysMap,
143+
context: some MacroExpansionContext
144+
) -> Registration<Decl, Key, AnyPropertyVariable<Var.Initialization>> {
145+
guard
146+
let attr = CodedAs(from: self.decl),
147+
case let path = attr.providedPath,
148+
!path.isEmpty
149+
else { return self.updating(with: self.variable.any) }
150+
let keys = codingKeys.add(keys: path, context: context)
151+
let oldVar = self.variable
152+
let newVar = AliasedPropertyVariable(base: oldVar, additionalKeys: keys)
153+
return self.updating(with: newVar.any)
95154
}
96155
}

Sources/CodableMacroPlugin/Attributes/IgnoreCoding/IgnoreCoding.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ struct IgnoreCoding: PropertyAttribute {
3434
/// * Attached declaration is a variable or enum case declaration.
3535
/// * Attached variable declaration has default initialization or
3636
/// variable is a computed property.
37-
/// * This attribute isn't used combined with `CodedIn` and
38-
/// `CodedAt` attribute.
37+
/// * This attribute isn't used combined with `CodedIn`,
38+
/// `CodedAt` and `CodedAs` attribute.
3939
/// * Additionally, warning generated if macro usage is duplicated
4040
/// for the same declaration.
4141
///
@@ -44,6 +44,7 @@ struct IgnoreCoding: PropertyAttribute {
4444
return AggregatedDiagnosticProducer {
4545
cantBeCombined(with: CodedIn.self)
4646
cantBeCombined(with: CodedAt.self)
47+
cantBeCombined(with: CodedAs.self)
4748
shouldNotDuplicate()
4849
`if`(
4950
isVariable, attachedToInitializedVariable(),

Sources/CodableMacroPlugin/Attributes/KeyPath/CodedAt.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ struct CodedAt: PropertyAttribute {
4141
/// * Attached declaration is not a grouped variable
4242
/// declaration.
4343
/// * Attached declaration is not a static variable
44-
/// declaration
44+
/// declaration.
4545
/// * This attribute isn't used combined with `CodedIn`
4646
/// and `IgnoreCoding` attribute.
4747
///

Sources/CodableMacroPlugin/Diagnostics/Condition/ArgumentCountCondition.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,8 @@ struct ArgumentCountCondition<Attr>: DiagnosticCondition where Attr: Attribute {
2424
/// - Parameter syntax: The syntax to validate.
2525
/// - Returns: Whether syntax passes validation.
2626
func satisfied(by syntax: some SyntaxProtocol) -> Bool {
27-
guard
28-
let args = attr.node.arguments?.as(LabeledExprListSyntax.self),
29-
args.count == expected
30-
else { return false }
31-
return true
27+
return expected == attr.node.arguments?
28+
.as(LabeledExprListSyntax.self)?.count ?? 0
3229
}
3330
}
3431

Sources/CodableMacroPlugin/Variables/ComposedVariable.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,34 @@ extension ComposedVariable where Self: ValuedVariable, Wrapped: ValuedVariable {
7979
var value: ExprSyntax? { base.value }
8080
}
8181

82+
extension ComposedVariable
83+
where Self: ConditionalVariable, Wrapped: ConditionalVariable {
84+
/// Whether the variable is to be decoded.
85+
///
86+
/// Whether underlying wrapped variable is to be decoded.
87+
var decode: Bool? { base.decode }
88+
/// Whether the variable is to be encoded.
89+
///
90+
/// Whether underlying wrapped variable is to be encoded.
91+
var encode: Bool? { base.encode }
92+
}
93+
8294
extension ComposedVariable
8395
where Self: PropertyVariable, Wrapped: PropertyVariable {
8496
/// The type of the variable.
8597
///
8698
/// Provides type of the underlying variable value.
8799
var type: TypeSyntax { base.type }
88100

101+
/// Whether the variable type requires `Decodable` conformance.
102+
///
103+
/// Whether underlying wrapped variable requires `Decodable` conformance.
104+
var requireDecodable: Bool? { base.requireDecodable }
105+
/// Whether the variable type requires `Encodable` conformance.
106+
///
107+
/// Whether underlying wrapped variable requires `Encodable` conformance.
108+
var requireEncodable: Bool? { base.requireEncodable }
109+
89110
/// The prefix token to use along with `name` when decoding.
90111
///
91112
/// Provides decode prefix of the underlying variable value.

Sources/CodableMacroPlugin/Variables/Enum/AdjacentlyTaggedEnumSwitcher.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,23 @@ where Wrapped: AdjacentlyTaggableSwitcher {
4040
self.base = base.registering(variable: variable, keyPath: keys)
4141
}
4242

43-
/// Creates value expression for provided enum-case variable.
43+
/// Creates value expressions for provided enum-case variable.
4444
///
4545
/// Provides value generated by the underlying variable value.
4646
///
4747
/// - Parameters:
4848
/// - variable: The variable for which generated.
49-
/// - value: The optional value present in syntax.
49+
/// - values: The values present in syntax.
5050
/// - codingKeys: The map where `CodingKeys` maintained.
5151
/// - context: The context in which to perform the macro expansion.
5252
///
5353
/// - Returns: The generated value.
5454
func keyExpression<Var: EnumCaseVariable>(
55-
for variable: Var, value: ExprSyntax?,
55+
for variable: Var, values: [ExprSyntax],
5656
codingKeys: CodingKeysMap, context: some MacroExpansionContext
5757
) -> EnumVariable.CaseValue {
5858
return base.keyExpression(
59-
for: variable, value: value,
59+
for: variable, values: values,
6060
codingKeys: codingKeys, context: context
6161
)
6262
}

Sources/CodableMacroPlugin/Variables/Enum/AnyEnumSwitcher.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ struct AnyEnumSwitcher: EnumSwitcherVariable {
1414
/// operators (`as?`, `as!`, or `as`).
1515
let base: any EnumSwitcherVariable
1616

17-
/// Creates value expression for provided enum-case variable.
17+
/// Creates value expressions for provided enum-case variable.
1818
///
1919
/// Provides value generated by the underlying variable value.
2020
///
2121
/// - Parameters:
2222
/// - variable: The variable for which generated.
23-
/// - value: The optional value present in syntax.
23+
/// - values: The values present in syntax.
2424
/// - codingKeys: The map where `CodingKeys` maintained.
2525
/// - context: The context in which to perform the macro expansion.
2626
///
2727
/// - Returns: The generated value.
2828
func keyExpression<Var: EnumCaseVariable>(
29-
for variable: Var, value: SwiftSyntax.ExprSyntax?,
29+
for variable: Var, values: [ExprSyntax],
3030
codingKeys: CodingKeysMap, context: some MacroExpansionContext
3131
) -> EnumVariable.CaseValue {
3232
return base.keyExpression(
33-
for: variable, value: value,
33+
for: variable, values: values,
3434
codingKeys: codingKeys, context: context
3535
)
3636
}

Sources/CodableMacroPlugin/Variables/Enum/BasicAssociatedVariable.swift

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,6 @@ struct BasicAssociatedVariable: AssociatedVariable, ComposedVariable,
1919
/// initialization of this variable.
2020
let label: TokenSyntax?
2121

22-
/// Whether the variable is to be decoded.
23-
///
24-
/// Whether underlying wrapped variable is to be decoded.
25-
var decode: Bool? { base.decode }
26-
/// Whether the variable is to be encoded.
27-
///
28-
/// Whether underlying wrapped variable is to be encoded.
29-
var encode: Bool? { base.encode }
30-
31-
/// Whether the variable type requires `Decodable` conformance.
32-
///
33-
/// Whether underlying wrapped variable requires `Decodable` conformance.
34-
var requireDecodable: Bool? { base.requireDecodable }
35-
/// Whether the variable type requires `Encodable` conformance.
36-
///
37-
/// Whether underlying wrapped variable requires `Encodable` conformance.
38-
var requireEncodable: Bool? { base.requireEncodable }
39-
4022
/// Creates a new variable from declaration and expansion context.
4123
///
4224
/// Uses the declaration to read variable specific data,

Sources/CodableMacroPlugin/Variables/Enum/BasicEnumCaseVariable.swift

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,15 @@ struct BasicEnumCaseVariable: EnumCaseVariable {
102102
in context: some MacroExpansionContext,
103103
from location: EnumCaseCodingLocation
104104
) -> SwitchCaseSyntax {
105-
let value = location.value
106-
let pattern = ExpressionPatternSyntax(expression: value)
107-
let label = SwitchCaseLabelSyntax { .init(pattern: pattern) }
105+
let firstKey = location.values.first!
106+
let label = SwitchCaseLabelSyntax(
107+
caseItems: .init {
108+
for value in location.values {
109+
let pattern = ExpressionPatternSyntax(expression: value)
110+
SwitchCaseItemSyntax.init(pattern: pattern)
111+
}
112+
}
113+
)
108114
let caseArgs = variables.map { variable in
109115
let decode = (variable.decode ?? true)
110116
let expr: ExprSyntax = decode ? "\(variable.name)" : variable.value!
@@ -118,12 +124,25 @@ struct BasicEnumCaseVariable: EnumCaseVariable {
118124
switch location.data {
119125
case .container(let container):
120126
contentCoder = "contentDecoder"
121-
prefix = """
122-
let \(contentCoder) = try \(container).superDecoder(forKey: \(value))
123-
"""
127+
prefix = CodeBlockItemListSyntax {
128+
if location.values.count > 1 {
129+
let keyName: TokenSyntax = "identifierKey"
130+
let keyList = ArrayExprSyntax(expressions: location.values)
131+
"""
132+
let \(keyName) = \(keyList).first { \(container).allKeys.contains($0) } ?? \(firstKey)
133+
"""
134+
"""
135+
let \(contentCoder) = try \(container).superDecoder(forKey: \(keyName))
136+
"""
137+
} else {
138+
"""
139+
let \(contentCoder) = try \(container).superDecoder(forKey: \(firstKey))
140+
"""
141+
}
142+
}
124143
case .coder(let coder, let callback):
125144
contentCoder = coder
126-
prefix = callback("\(value)")
145+
prefix = callback("\(firstKey)")
127146
}
128147
return SwitchCaseSyntax(label: .case(label)) {
129148
prefix
@@ -160,7 +179,7 @@ struct BasicEnumCaseVariable: EnumCaseVariable {
160179
let expr: ExprSyntax = ".\(name)\(argExpr)"
161180
let pattern = ExpressionPatternSyntax(expression: expr)
162181
let label = SwitchCaseLabelSyntax { .init(pattern: pattern) }
163-
let value = location.value
182+
let value = location.values.first!
164183
let contentCoder: TokenSyntax
165184
let prefix: CodeBlockItemListSyntax
166185
switch location.data {

Sources/CodableMacroPlugin/Variables/Enum/EnumCaseVariable.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ struct EnumCaseCodingLocation {
2424
/// Represents the data related to switch statement
2525
/// of enum passed to each case.
2626
let data: EnumSwitcherGenerated.CaseData
27-
/// The value of the variable.
27+
/// The values of the variable.
2828
///
29-
/// Represents the actual value that will be decoded/encoded.
30-
let value: ExprSyntax
29+
/// Represents the actual values that will be decoded.
30+
/// Only the first value will be encoded.
31+
let values: [ExprSyntax]
3132
}

0 commit comments

Comments
 (0)