-
-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathCodedAs.swift
More file actions
158 lines (149 loc) · 5.81 KB
/
Copy pathCodedAs.swift
File metadata and controls
158 lines (149 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import OrderedCollections
import SwiftSyntax
import SwiftSyntaxMacros
/// Attribute type for `CodedAs` macro-attribute.
///
/// This type can validate`CodedAs` macro-attribute
/// usage and extract data for `Codable` macro to
/// generate implementation.
package struct CodedAs: PropertyAttribute {
/// The node syntax provided
/// during initialization.
let node: AttributeSyntax
/// The alternate value expression provided.
var exprs: [ExprSyntax] {
return node.arguments?
.as(LabeledExprListSyntax.self)?.map(\.expression) ?? []
}
/// The type to which to be decoded/encoded.
///
/// Used for enums with internal/adjacent tagging to decode
/// the identifier to this type.
var type: TypeSyntax? {
return node.attributeName.as(IdentifierTypeSyntax.self)?
.genericArgumentClause?.arguments
.first?.argument.as(TypeSyntax.self)
}
/// Creates a new instance with the provided node.
///
/// The initializer fails to create new instance if the name
/// of the provided node is different than this attribute.
///
/// - Parameter node: The attribute syntax to create with.
/// - Returns: Newly created attribute instance.
init?(from node: AttributeSyntax) {
guard
node.attributeName.as(IdentifierTypeSyntax.self)!
.name.text == Self.name
else { return nil }
self.node = node
}
/// Builds diagnoser that can validate this macro
/// attached declaration.
///
/// The following conditions are checked by the
/// built diagnoser:
/// * Macro usage is not duplicated for the same declaration.
/// * If macro has zero arguments provided:
/// * Attached declaration is an enum/protocol declaration.
/// * This attribute must be combined with `Codable`
/// and `CodedAt` attribute.
/// * If macro has one argument provided:
/// * Attached declaration is an enum-case or variable declaration.
/// * This attribute isn't used combined with `IgnoreCoding`
/// attribute.
/// * If macro attached declaration is variable declaration:
/// * Attached declaration is not a grouped variable declaration.
/// * Attached declaration is not a static variable declaration.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
cantDuplicate()
`if`(
has(arguments: 0),
AggregatedDiagnosticProducer {
expect(
syntaxes: EnumDeclSyntax.self, ProtocolDeclSyntax.self
)
mustBeCombined(with: Codable.self)
mustBeCombined(
with: CodedAt.self, or: DecodedAt.self, EncodedAt.self
)
},
else: `if`(
isVariable,
AggregatedDiagnosticProducer {
attachedToUngroupedVariable()
attachedToNonStaticVariable()
cantBeCombined(with: IgnoreCoding.self)
},
else: AggregatedDiagnosticProducer {
expect(
syntaxes: EnumCaseDeclSyntax.self,
VariableDeclSyntax.self
)
cantBeCombined(with: IgnoreCoding.self)
}
)
)
}
}
}
extension CodedAs: KeyPathProvider {
/// Indicates whether `CodingKey` path
/// data is provided to this instance.
///
/// Always `true` for this type.
var provided: Bool { true }
/// Updates `CodingKey` path using the provided path.
///
/// The `CodingKey` path overrides current `CodingKey` path data.
///
/// - Parameter path: Current `CodingKey` path.
/// - Returns: Updated `CodingKey` path.
func keyPath(withExisting path: [String]) -> [String] { providedPath }
}
extension Registration where Key == [ExprSyntax], Decl: AttributableDeclSyntax {
/// Update registration with alternate value expression data.
///
/// New registration is updated with value expression data that will be
/// used for decoding/encoding, if provided and registration doesn't
/// already have a value.
///
/// - Returns: Newly built registration with value expression data.
func checkForAlternateValue() -> Self {
guard
self.key.isEmpty,
let attr = CodedAs(from: self.decl)
else { return self }
return self.updating(with: attr.exprs)
}
}
extension Registration
where Decl: AttributableDeclSyntax, Var: PropertyVariable {
/// Update registration with alternate `CodingKey`s data.
///
/// New registration is updated with `CodingKey`s data that will be
/// used for decoding/encoding, if provided.
///
/// - Parameters:
/// - codingKeys: The `CodingKeys` map new data will be added.
/// - context: The context in which to perform the macro expansion.
///
/// - Returns: Newly built registration with additional `CodingKey`s data.
func checkForAlternateKeyValues(
addTo codingKeys: CodingKeysMap,
context: some MacroExpansionContext
) -> Registration<Decl, Key, AnyPropertyVariable<Var.Initialization>> {
guard
let attr = CodedAs(from: self.decl),
case let path = attr.providedPath,
!path.isEmpty
else { return self.updating(with: self.variable.any) }
let keys = OrderedSet(codingKeys.add(keys: path, context: context))
let oldVar = self.variable
let newVar = AliasedPropertyVariable(base: oldVar, additionalKeys: keys)
return self.updating(with: newVar.any)
}
}