-
-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathConditionalCodingVariable.swift
More file actions
155 lines (142 loc) · 5.97 KB
/
Copy pathConditionalCodingVariable.swift
File metadata and controls
155 lines (142 loc) · 5.97 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
import SwiftSyntax
import SwiftSyntaxMacros
/// A variable value containing data whether to perform decoding/encoding.
///
/// The `ConditionalCodingVariable` type forwards `Variable`
/// decoding/encoding, initialization implementations and only
/// decoding/encoding condition are customized.
struct ConditionalCodingVariable<Var>: ComposedVariable, Variable
where Var: ConditionalVariable, Var.Generated: ConditionalVariableSyntax {
/// The customization options for `ConditionalCodingVariable`.
///
/// `ConditionalCodingVariable` uses the instance of this type,
/// provided during initialization, for customizing code generation.
struct Options {
/// Whether variable needs to be decoded.
///
/// `True` for non-initialized stored variables.
/// `False` for variables with `@IgnoreCoding`
/// and `@IgnoreDecoding` attributes.
let decode: Bool?
/// Whether variable should to be encoded.
///
/// False for variables with `@IgnoreCoding`
/// and `@IgnoreEncoding` attributes.
let encode: Bool?
/// The condition based on which encoding is decided.
///
/// This condition accepts arguments from this variable and resolves
/// to either `true` or `false` based on which encoding is ignored.
let encodingCondition: Condition?
/// Condition for determining when encoding should not be performed.
enum Condition {
/// Encoding is performed only if the provided expression evaluates
/// to false.
///
/// The expression accepts arguments from this variable and resolves
/// to either `true` or `false` based on which encoding is decided.
case `if`(ExprSyntax)
/// Encoding is performed only if the provided expression evaluates
/// to false.
///
/// The expression accepts argument as the value that contains
/// this property.
case basedOn(ExprSyntax)
}
}
/// The value wrapped by this instance.
///
/// The wrapped variable's type data is
/// preserved and provided during initialization.
let base: Var
/// The options for customizations.
///
/// Options is provided during initialization.
let options: Options
/// Provides the code syntax for encoding this variable
/// at the provided location.
///
/// If any encoding condition expression is provided then based on the
/// result of the expression encoding is performed by generated syntax.
/// Otherwise, provides code syntax for encoding of the underlying
/// variable value.
///
/// - Parameters:
/// - context: The context in which to perform the macro expansion.
/// - location: The encoding location for the variable.
///
/// - Returns: The generated variable encoding code.
func encoding(
in context: some MacroExpansionContext,
to location: Var.CodingLocation
) -> Var.Generated {
let syntax = base.encoding(in: context, to: location)
guard let condition = options.encodingCondition else { return syntax }
let conditionExpr: ExprSyntax
let args: LabeledExprListSyntax
switch condition {
case .if(let expr):
conditionExpr = expr
args = self.conditionArguments
case .basedOn(let expr):
conditionExpr = expr
args = LabeledExprListSyntax {
LabeledExprSyntax(expression: "self" as ExprSyntax)
}
}
let returnList = TupleTypeElementListSyntax {
for _ in args {
TupleTypeElementSyntax(type: "_" as TypeSyntax)
}
}
let expr: ExprSyntax = "!{ () -> (\(returnList)) -> Bool in \(conditionExpr) }()(\(args))"
return syntax.adding(condition: [.init(expression: expr)])
}
}
extension ConditionalCodingVariable: ConditionalVariable
where Wrapped: ConditionalVariable {
/// Whether the variable is to be decoded.
///
/// Provides whether underlying variable value is to be decoded,
/// if provided decode option is set as `true` otherwise `false`.
var decode: Bool? { (options.decode ?? true) ? base.decode : false }
/// Whether the variable is to be encoded.
///
/// Provides whether underlying variable value is to be encoded,
/// if provided encode option is set as `true` otherwise `false`.
var encode: Bool? { (options.encode ?? true) ? base.encode : false }
}
extension ConditionalCodingVariable: PropertyVariable
where Var: PropertyVariable {
/// Whether the variable type requires `Decodable` conformance.
///
/// Provides whether underlying variable type requires
/// `Decodable` conformance, if provided decode
/// option is set as `true` otherwise `false`.
var requireDecodable: Bool? {
return (options.decode ?? true) ? base.requireDecodable : false
}
/// Whether the variable type requires `Encodable` conformance.
///
/// Provides whether underlying variable type requires
/// `Encodable` conformance, if provided encode
/// option is set as `true` otherwise `false`.
var requireEncodable: Bool? {
return (options.encode ?? true) ? base.requireEncodable : false
}
}
extension ConditionalCodingVariable: InitializableVariable
where Var: InitializableVariable {
/// The initialization type of this variable.
///
/// Initialization type is the same as underlying wrapped variable.
typealias Initialization = Var.Initialization
}
extension ConditionalCodingVariable: NamedVariable where Var: NamedVariable {}
extension ConditionalCodingVariable: ValuedVariable where Var: ValuedVariable {}
extension ConditionalCodingVariable: DefaultPropertyVariable
where Var: DefaultPropertyVariable {}
extension ConditionalCodingVariable: AssociatedVariable
where Var: AssociatedVariable {}
extension ConditionalCodingVariable: EnumCaseVariable
where Var: EnumCaseVariable {}