-
-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathPropertyVariable.swift
More file actions
252 lines (238 loc) · 8.84 KB
/
PropertyVariable.swift
File metadata and controls
252 lines (238 loc) · 8.84 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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import SwiftSyntax
import SwiftSyntaxMacros
/// A type representing data associated with a property variable
/// inside types/enum-case declarations.
///
/// This type informs how this variable needs to be initialized,
/// decoded/encoded in the macro expansion phase.
package protocol PropertyVariable<Initialization>: NamedVariable,
ValuedVariable, ConditionalVariable, InitializableVariable
where
CodingLocation == PropertyCodingLocation,
Generated == CodeBlockItemListSyntax, Initialization: VariableInitialization
{
/// The type of the variable.
///
/// - For a declaration:
/// ```swift
/// let variable: String = "data"
/// ```
/// the `type` will be `String`.
///
/// - For a declaration:
/// ```swift
/// (_ variable: String = "data")
/// ```
/// the `type` will be `String`.
var type: TypeSyntax { get }
/// Whether the variable type requires
/// `Decodable` conformance.
///
/// Used for generic where clause, for
/// `Decodable` conformance generation.
///
/// If `nil` is returned, variable is used in
/// generic where clause by default.
var requireDecodable: Bool? { get }
/// Whether the variable type requires
/// `Encodable` conformance.
///
/// Used for generic where clause, for
/// `Encodable` conformance generation.
///
/// If `nil` is returned, variable is used in
/// generic where clause by default.
var requireEncodable: Bool? { get }
/// The prefix token to use along with `name` when decoding.
///
/// When generating decode implementation the prefix
/// is used before `name` during assignment.
var decodePrefix: TokenSyntax { get }
/// The prefix token to use along with `name` when encoding.
///
/// When generating encode implementation the prefix
/// is used before `name` during method invocation.
var encodePrefix: TokenSyntax { get }
/// The fallback behavior when decoding fails.
///
/// In the event this decoding this variable is failed,
/// appropriate fallback would be applied.
var decodingFallback: DecodingFallback { get }
/// The number of variables this variable depends on.
///
/// The number of variables that this variable depends
/// on to be decoded first, before decoding this variable.
var dependenciesCount: UInt { get }
/// Checks whether this variable is dependent on the provided variable.
///
/// Whether provided variable needs to be decoded first,
/// before decoding this variable.
///
/// - Parameter variable: The variable to check for.
/// - Returns: Whether this variable is dependent on the provided variable.
func depends<Variable: PropertyVariable>(on variable: Variable) -> Bool
}
/// Represents the location for decoding/encoding for `Variable`s.
///
/// Represents whether `Variable`s need to decode/encode directly
/// from/to the decoder/encoder respectively or at path of a container.
package enum PropertyCodingLocation {
/// Represents a top-level decoding/encoding location.
///
/// The variable needs to be decoded/encoded directly to the
/// decoder/encoder provided, not nested at a `CodingKey`.
///
/// - Parameters:
/// - coder: The decoder/encoder for decoding/encoding.
/// - method: The method to use for decoding/encoding.
case coder(_ coder: TokenSyntax, method: ExprSyntax?)
/// Represents decoding/encoding location at a `CodingKey`
/// for a container.
///
/// The variable needs to be decoded/encoded at the
/// `CodingKey` inside the container provided.
///
/// - Parameters:
/// - container: The container for decoding/encoding.
/// - key: The `CodingKey` inside the container.
/// - method: The method to use for decoding/encoding.
case container(
_ container: TokenSyntax, key: ExprSyntax,
method: ExprSyntax?
)
}
extension PropertyVariable
where Self: ComposedVariable, Self.Wrapped: ConditionalVariable {
/// The arguments passed to encoding condition.
///
/// Provides arguments of underlying variable value.
var conditionArguments: LabeledExprListSyntax {
base.conditionArguments
}
}
extension PropertyVariable {
/// The arguments passed to encoding condition.
///
/// Passes current variable as single argument.
var conditionArguments: LabeledExprListSyntax {
[
.init(expression: "\(self.encodePrefix)\(self.name)" as ExprSyntax)
]
}
/// Check whether current type syntax
/// represents an optional type.
///
/// 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<Type>`).
var hasOptionalType: Bool { type.isOptionalTypeSyntax }
/// Provides type and method expression to use
/// with container expression for decoding/encoding.
///
/// For optional types `IfPresent` is added to
/// the `method` name passed and wrapped
/// type is passed as type, otherwise `method`
/// name and type are used as is.
///
/// - Parameter method: The default method name.
/// - Returns: The type and method expression
/// for decoding/encoding.
func codingTypeMethod(
forMethod method: ExprSyntax
) -> (TypeSyntax, ExprSyntax) {
let (dType, dMethod): (TypeSyntax, ExprSyntax)
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,
gArgs.count == 1,
let type = gArgs.first?.argument.as(TypeSyntax.self)
{
dType = type
dMethod = "\(method)IfPresent"
} else {
dType = type
dMethod = method
}
return (dType, dMethod)
}
}
extension CodeBlockItemListSyntax: ConditionalVariableSyntax {
/// Generates new syntax with provided condition.
///
/// Wraps existing syntax with an if expression based on provided condition.
///
/// - Parameter condition: The condition for the existing syntax.
/// - Returns: The new syntax.
func adding(condition: LabeledExprListSyntax) -> CodeBlockItemListSyntax {
let condition = ConditionElementListSyntax {
.init(condition: .expression("(\(condition))"))
}
return CodeBlockItemListSyntax {
IfExprSyntax(conditions: condition) {
self
}
}
}
}
extension TypeSyntax {
/// Extract actual type of an optional type.
///
/// Extracts type based on 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<Type>`).
/// Otherwise, returns the type syntax as is.
var wrappedType: TypeSyntax {
if let type = self.as(OptionalTypeSyntax.self) {
return type.wrappedType
} else if let type = self.as(ImplicitlyUnwrappedOptionalTypeSyntax.self)
{
return type.wrappedType
} else if let type = self.as(IdentifierTypeSyntax.self),
type.name.text == "Optional",
let gArgs = type.genericArgumentClause?.arguments,
gArgs.count == 1,
let wrappedType = gArgs.first?.argument.as(TypeSyntax.self)
{
return wrappedType
} else {
return self
}
}
/// Check whether current type syntax represents an optional type.
///
/// 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<Type>`).
var isOptionalTypeSyntax: Bool {
wrappedType != self
}
}
#if swift(<6.0)
extension Collection {
/// Returns the number of elements in the sequence that satisfy
/// the given predicate.
///
/// This method can be used to count the number of elements
/// that pass a test.
///
/// - Parameter predicate: A closure that takes each element
/// of the sequence as its argument and returns a Boolean
/// value indicating whether the element should be included
/// in the count.
/// - Returns: The number of elements in the sequence that satisfy
/// the given predicate.
func count(where predicate: (Element) -> Bool) -> Int {
self.filter(predicate).count
}
}
#endif