-
-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathCodedAt.swift
More file actions
181 lines (174 loc) · 8.28 KB
/
CodedAt.swift
File metadata and controls
181 lines (174 loc) · 8.28 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
import SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxMacros
/// Attribute type for `CodedAt` macro-attribute.
///
/// This type can validate`CodedAt` macro-attribute
/// usage and extract data for `Codable` macro to
/// generate implementation.
package struct CodedAt: PropertyAttribute {
/// The node syntax provided
/// during initialization.
let node: AttributeSyntax
/// 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 is attached to enum/protocol declaration:
/// * This attribute must be combined with `Codable`
/// attribute.
/// * This attribute isn't used combined with `UnTagged`
/// attribute.
/// * else:
/// * Attached declaration is a variable declaration.
/// * Attached declaration is not a grouped variable
/// declaration.
/// * Attached declaration is not a static variable
/// declaration.
/// * This attribute isn't used combined with `CodedIn`
/// and `IgnoreCoding` attribute.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
AggregatedDiagnosticProducer {
cantDuplicate()
`if`(
isEnum || isProtocol,
AggregatedDiagnosticProducer {
mustBeCombined(with: Codable.self)
cantBeCombined(with: UnTagged.self)
cantBeCombined(with: DecodedAt.self)
cantBeCombined(with: EncodedAt.self)
},
else: AggregatedDiagnosticProducer {
attachedToUngroupedVariable()
attachedToNonStaticVariable()
cantBeCombined(with: DecodedAt.self)
cantBeCombined(with: EncodedAt.self)
cantBeCombined(with: CodedIn.self)
cantBeCombined(with: IgnoreCoding.self)
}
)
}
}
}
extension Registration
where Var == ExternallyTaggedEnumSwitcher, Decl == EnumDeclSyntax {
/// Checks if enum declares internal tagging and creates appropriate switcher.
///
/// Examines the enum declaration for `CodedAt`, `DecodedAt`, and `EncodedAt`
/// attributes to determine if internal tagging should be used. Internal tagging
/// occurs when these attributes specify non-empty key paths that indicate where
/// the type identifier should be located within the encoded structure.
///
/// If valid key paths are found, creates an `InternallyTaggedEnumSwitcher` with
/// the specified configuration. The identifier type is determined from the `CodedAs`
/// attribute if present, otherwise defaults to `String`. The `topDecode` and `topEncode`
/// flags are determined based on whether the respective key paths are empty.
///
/// - Parameters:
/// - decl: The enum declaration syntax for which code is being generated.
/// - coderPrefix: The prefix for coder variable names that will be used
/// to generate decoder and encoder variable names.
/// - identifier: The identifier token name to use for tagging.
/// - codingKeys: The coding keys map for key path resolution.
/// - forceDecodingReturn: Whether to force explicit `return` statements in
/// generated decoding switch cases. When `true`, each case will include a
/// `return` statement after assignment for early exit.
/// - context: The macro expansion context for diagnostics and code generation.
/// - variableBuilder: Builder function for creating the identifier variable
/// from the basic property variable registration.
/// - switcherBuilder: Builder function for creating the final switcher from
/// the internally tagged enum switcher registration.
///
/// - Returns: A type-erased enum switcher registration. If internal tagging
/// is detected (non-empty decode and encode paths), returns the result of
/// applying both builder functions. Otherwise, returns the current registration
/// with a type-erased variable, indicating external tagging should be used.
func checkForInternalTagging<Variable, Switcher>(
decl: EnumDeclSyntax,
coderPrefix: TokenSyntax, identifier: TokenSyntax,
codingKeys: CodingKeysMap, forceDecodingReturn: Bool,
context: some MacroExpansionContext,
variableBuilder: @escaping (
PathRegistration<EnumDeclSyntax, BasicPropertyVariable>
) -> PathRegistration<EnumDeclSyntax, Variable>,
switcherBuilder: @escaping (
Registration<Decl, Key, InternallyTaggedEnumSwitcher<Variable>>
) -> Registration<Decl, Key, Switcher>
) -> Registration<Decl, Key, AnyEnumSwitcher>
where Variable: PropertyVariable, Switcher: EnumSwitcherVariable {
let tagAttr = CodedAt(from: decl)
let decodeTagAttr = DecodedAt(from: decl)
let encodeTagAttr = EncodedAt(from: decl)
let path = tagAttr?.keyPath(withExisting: []) ?? []
let decodedPath = decodeTagAttr?.keyPath(withExisting: path) ?? path
let encodedPath = encodeTagAttr?.keyPath(withExisting: path) ?? path
let rawRepresentable =
decl.inheritanceClause?
.inheritedTypes.contains { $0.type.isRawValueType } ?? false
&& decl.codableMembers(input: codingKeys).allSatisfy { member in
member.element.parameterClause == nil
}
guard
(!decodedPath.isEmpty && !encodedPath.isEmpty) || rawRepresentable
else { return self.updating(with: variable.any) }
let typeAttr = CodedAs(from: decl)
let keyPath = PathKey(decoding: decodedPath, encoding: encodedPath)
let variable = InternallyTaggedEnumSwitcher(
coderPrefix: coderPrefix, topDecode: keyPath.decoding.isEmpty,
topEncode: keyPath.encoding.isEmpty,
identifier: identifier, identifierType: typeAttr?.type,
keyPath: keyPath, codingKeys: codingKeys,
decl: decl, context: context,
forceDecodingReturn: forceDecodingReturn,
rawRepresentable: rawRepresentable,
variableBuilder: variableBuilder
)
let newRegistration = switcherBuilder(self.updating(with: variable))
return newRegistration.updating(with: newRegistration.variable.any)
}
}
extension TypeSyntax {
/// Determines if this type syntax represents a raw value type suitable for enum raw values.
///
/// Raw value types are fundamental Swift types that can be used as the underlying
/// raw value type for enums. This includes all integer types, floating-point types,
/// boolean, string, and character types that Swift supports natively.
///
/// The supported types include:
/// - **Boolean**: `Bool`
/// - **String types**: `String`, `Character`
/// - **Signed integers**: `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Int128`
/// - **Unsigned integers**: `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`, `UInt128`
/// - **Floating-point**: `Float`, `Float16`, `Float80`, `Double`
///
/// This property is used to determine if an enum can be treated as RawRepresentable
/// and whether it should use raw value-based decoding/encoding strategies.
///
/// - Returns: `true` if the type is a valid raw value type, `false` otherwise.
var isRawValueType: Bool {
[
"Bool", "String", "Character",
"Int", "Int8", "Int16", "Int32", "Int64", "Int128",
"UInt", "UInt8", "UInt16", "UInt32", "UInt64", "UInt128",
"Float", "Float16", "Float80", "Double",
].contains(trimmedDescription)
}
}