Skip to content

Commit 4bfeac3

Browse files
committed
feat: added class support
1 parent 495cea4 commit 4bfeac3

File tree

90 files changed

+3654
-2288
lines changed

Some content is hidden

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

90 files changed

+3654
-2288
lines changed

.github/config/spellcheck-wordlist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ HelperCoder
88
HelperCoders
99
JSON
1010
LossySequenceCoder
11+
Codable
1112
MetaCodable
1213
Midbin
1314
README
@@ -48,3 +49,4 @@ variadic
4849
vscode
4950
watchOS
5051
www
52+
typealias

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ You can even create your own by conforming to `HelperCoder`.
236236
</details>
237237

238238
See the full documentation for [`MetaCodable`](https://swiftpackageindex.com/SwiftyLab/MetaCodable/main/documentation/metacodable) and [`HelperCoders`](https://swiftpackageindex.com/SwiftyLab/MetaCodable/main/documentation/helpercoders), for API details and advanced use cases.
239+
Also, [see the limitations](Sources/MetaCodable/MetaCodable.docc/Limitations.md).
239240

240241
## Contributing
241242

Sources/CodableMacroPlugin/Attributes/AttributableDeclSyntax.swift

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,10 @@
11
@_implementationOnly import SwiftSyntax
22

3-
/// An extension that manages fetching attributes
4-
/// attached to declarations.
5-
extension SyntaxProtocol {
6-
/// Provides all the attributes attached to this declaration of
7-
/// the provided type.
8-
///
9-
/// All the attribute syntaxes are checked and those matching
10-
/// the provided type are returned.
11-
///
12-
/// - Parameter type: The macro-attribute type to search.
13-
/// - Returns: All the attributes of provided type.
14-
func attributes<A: Attribute>(for type: A.Type) -> [A] {
15-
guard
16-
case .choices(let choices) = DeclSyntax.structure
17-
else { return [] }
18-
19-
let declSyntaxChoice = choices.first { choice in
20-
if case .node(let type) = choice {
21-
return type is AttributableDeclSyntax.Type
22-
&& self.is(type)
23-
} else {
24-
return false
25-
}
26-
}
27-
28-
guard
29-
let declSyntaxChoice,
30-
case .node(let declSyntaxType) = declSyntaxChoice,
31-
let declaration = self.as(declSyntaxType),
32-
let declaration = declaration as? AttributableDeclSyntax
33-
else { return [] }
34-
35-
return declaration.attributes.compactMap { attribute in
36-
guard case .attribute(let attribute) = attribute else { return nil }
37-
return type.init(from: attribute)
38-
}
39-
}
40-
}
41-
423
/// A declaration syntax type that supports macro-attribute.
434
///
445
/// This type can check whether an `AttributeSyntax`
456
/// is for this attribute and perform validation of this attribute usage.
46-
protocol AttributableDeclSyntax: DeclSyntaxProtocol {
7+
protocol AttributableDeclSyntax {
478
/// The list of attributes attached to this declaration.
489
var attributes: AttributeListSyntax { get }
4910
}

Sources/CodableMacroPlugin/Attributes/Attribute.swift

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,58 @@ extension Attribute {
5151
///
5252
/// This attribute can must be removed or its usage condition
5353
/// must be satisfied.
54-
var misuseMessageID: MessageID { .messageID("\(id)-misuse") }
54+
var misuseMessageID: MessageID { messageID("\(id)-misuse") }
5555
/// Message id for unnecessary usage of this attribute.
5656
///
5757
/// This attribute can be omitted in such scenario and the
5858
/// final result will still be the same.
59-
var unusedMessageID: MessageID { .messageID("\(id)-unused") }
59+
var unusedMessageID: MessageID { messageID("\(id)-unused") }
60+
61+
/// Creates a new message id in current package domain.
62+
///
63+
/// - Parameters id: The message id.
64+
/// - Returns: Created message id.
65+
func messageID(_ id: String) -> MessageID {
66+
return .init(
67+
domain: "com.SwiftyLab.MetaCodable",
68+
id: id
69+
)
70+
}
71+
72+
/// Provides all the attributes of current type attached to
73+
/// the provided declaration.
74+
///
75+
/// All the attribute syntaxes are checked and those matching
76+
/// the current type are returned.
77+
///
78+
/// - Parameter syntax: The declaration to search in.
79+
/// - Returns: All the attributes of current type.
80+
static func attributes(attachedTo syntax: some SyntaxProtocol) -> [Self] {
81+
guard
82+
case .choices(let choices) = DeclSyntax.structure
83+
else { return [] }
84+
85+
let declSyntaxChoice = choices.first { choice in
86+
if case .node(let type) = choice {
87+
return type is AttributableDeclSyntax.Type
88+
&& syntax.is(type)
89+
} else {
90+
return false
91+
}
92+
}
93+
94+
guard
95+
let declSyntaxChoice,
96+
case .node(let declSyntaxType) = declSyntaxChoice,
97+
let declaration = syntax.as(declSyntaxType),
98+
let declaration = declaration as? AttributableDeclSyntax
99+
else { return [] }
100+
101+
return declaration.attributes.compactMap { attribute in
102+
guard case let .attribute(attribute) = attribute else { return nil }
103+
return Self.init(from: attribute)
104+
}
105+
}
60106

61107
/// Checks whether this attribute is applied more than once to
62108
/// provided declaration.
@@ -65,6 +111,27 @@ extension Attribute {
65111
/// is attached to.
66112
/// - Returns: Whether this attribute is applied more than once.
67113
func isDuplicated(in declaration: some SyntaxProtocol) -> Bool {
68-
return declaration.attributes(for: Self.self).count > 1
114+
return Self.attributes(attachedTo: declaration).count > 1
115+
}
116+
117+
/// Creates a new diagnostic message instance at current attribute node
118+
/// with provided message, id and severity.
119+
///
120+
/// - Parameters:
121+
/// - message: The message to be shown.
122+
/// - messageID: The id associated with diagnostic.
123+
/// - severity: The severity of diagnostic.
124+
///
125+
/// - Returns: The newly created diagnostic message instance.
126+
func diagnostic(
127+
message: String, id: MessageID,
128+
severity: DiagnosticSeverity
129+
) -> MetaCodableMessage {
130+
return .init(
131+
macro: self.node,
132+
message: message,
133+
messageID: id,
134+
severity: severity
135+
)
69136
}
70137
}

Sources/CodableMacroPlugin/Attributes/Codable/Codable+Expansion.swift

Lines changed: 102 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,95 @@
1-
@_implementationOnly import Foundation
21
@_implementationOnly import SwiftSyntax
32
@_implementationOnly import SwiftSyntaxMacros
43

5-
extension Codable: ExtensionMacro {
4+
extension Codable: MemberMacro, ExtensionMacro {
65
/// Expand to produce extensions with `Codable` implementation
7-
/// members for attached struct.
6+
/// members for attached `class`.
7+
///
8+
/// Conformance for both `Decodable` and `Encodable` is generated regardless
9+
/// of whether class already conforms to any. Class or its super class
10+
/// shouldn't conform to `Decodable` or `Encodable`
11+
///
12+
/// The `AttributeExpander` instance provides declarations based on
13+
/// whether declaration is supported.
14+
///
15+
/// - Parameters:
16+
/// - node: The custom attribute describing this attached macro.
17+
/// - declaration: The declaration this macro attribute is attached to.
18+
/// - context: The context in which to perform the macro expansion.
19+
///
20+
/// - Returns: Declarations of `CodingKeys` type, `Decodable`
21+
/// conformance with `init(from:)` implementation and `Encodable`
22+
/// conformance with `encode(to:)` implementation depending on already
23+
/// declared conformances of type.
24+
///
25+
/// - Note: For types other than `class` types no declarations generated.
26+
static func expansion(
27+
of node: AttributeSyntax,
28+
providingMembersOf declaration: some DeclGroupSyntax,
29+
in context: some MacroExpansionContext
30+
) throws -> [DeclSyntax] {
31+
let defaultProtocols: [TypeSyntax] = [
32+
.init(stringLiteral: TypeCodingLocation.Method.decode.protocol),
33+
.init(stringLiteral: TypeCodingLocation.Method.encode.protocol),
34+
]
35+
return try Self.expansion(
36+
of: node, providingMembersOf: declaration,
37+
conformingTo: defaultProtocols, in: context
38+
)
39+
}
40+
41+
/// Expand to produce extensions with `Codable` implementation
42+
/// members for attached `class`.
43+
///
44+
/// Depending on whether attached type already conforms to `Decodable`
45+
/// or `Encodable`, `Decodable` or `Encodable` conformance
46+
/// implementation is skipped. Entire macro expansion is skipped if attached
47+
/// type already conforms to both `Decodable` and`Encodable`.
48+
///
49+
/// The `AttributeExpander` instance provides declarations based on
50+
/// whether declaration is supported.
51+
///
52+
/// - Parameters:
53+
/// - node: The custom attribute describing this attached macro.
54+
/// - declaration: The declaration this macro attribute is attached to.
55+
/// - protocols: The list of protocols to add conformances to. These will
56+
/// always be protocols that `type` does not already state a conformance
57+
/// to.
58+
/// - context: The context in which to perform the macro expansion.
59+
///
60+
/// - Returns: Declarations of `CodingKeys` type, `Decodable`
61+
/// conformance with `init(from:)` implementation and `Encodable`
62+
/// conformance with `encode(to:)` implementation depending on already
63+
/// declared conformances of type.
64+
///
65+
/// - Note: For types other than `class` types no declarations generated.
66+
static func expansion(
67+
of node: AttributeSyntax,
68+
providingMembersOf declaration: some DeclGroupSyntax,
69+
conformingTo protocols: [TypeSyntax],
70+
in context: some MacroExpansionContext
71+
) throws -> [DeclSyntax] {
72+
guard
73+
let exp = AttributeExpander(for: declaration, in: context),
74+
let decl = declaration.as(ClassDeclSyntax.self),
75+
case let type = IdentifierTypeSyntax(name: decl.name)
76+
else { return [] }
77+
let exts = exp.codableExpansion(for: type, to: protocols, in: context)
78+
return exts.flatMap { `extension` in
79+
`extension`.memberBlock.members.map { DeclSyntax($0.decl) }
80+
}
81+
}
82+
83+
/// Expand to produce extensions with `Codable` implementation
84+
/// members for attached `struct` or `class`.
885
///
986
/// Depending on whether attached type already conforms to `Decodable`
1087
/// or `Encodable` extension for `Decodable` or `Encodable` conformance
11-
/// implementation is skipped.Entire macro expansion is skipped if attached type
12-
/// already conforms to both `Decodable` and`Encodable`.
88+
/// implementation is skipped. Entire macro expansion is skipped if attached
89+
/// type already conforms to both `Decodable` and`Encodable`.
1390
///
14-
/// For all the variable declarations in the attached type registration is
15-
/// done via `Registrar` instance with optional `PeerAttribute`
16-
/// metadata. The `Registrar` instance provides declarations based on
17-
/// all the registrations.
91+
/// The `AttributeExpander` instance provides declarations based on
92+
/// whether declaration is supported.
1893
///
1994
/// - Parameters:
2095
/// - node: The custom attribute describing this attached macro.
@@ -25,44 +100,33 @@ extension Codable: ExtensionMacro {
25100
/// to.
26101
/// - context: The context in which to perform the macro expansion.
27102
///
28-
/// - Returns: Extensions with `CodingKeys` type, `Decodable`
103+
/// - Returns: Extensions with `CodingKeys` type, `Decodable`
29104
/// conformance with `init(from:)` implementation and `Encodable`
30105
/// conformance with `encode(to:)` implementation depending on already
31106
/// declared conformances of type.
107+
///
108+
/// - Note: For `class` types only conformance is generated,
109+
/// member expansion generates the actual implementation.
32110
static func expansion(
33111
of node: AttributeSyntax,
34112
attachedTo declaration: some DeclGroupSyntax,
35113
providingExtensionsOf type: some TypeSyntaxProtocol,
36114
conformingTo protocols: [TypeSyntax],
37115
in context: some MacroExpansionContext
38116
) throws -> [ExtensionDeclSyntax] {
39-
let registrar = registrar(for: declaration, node: node, in: context)
40-
guard let registrar else { return [] }
41-
return registrar.codableExpansion(for: type, to: protocols, in: context)
117+
guard
118+
let self = Self(from: node),
119+
!self.diagnoser().produce(for: declaration, in: context),
120+
let exp = AttributeExpander(for: declaration, in: context)
121+
else { return [] }
122+
var exts = exp.codableExpansion(for: type, to: protocols, in: context)
123+
if declaration.is(ClassDeclSyntax.self) {
124+
for (index, var `extension`) in exts.enumerated() {
125+
`extension`.memberBlock = .init(members: [])
126+
exts[index] = `extension`
127+
}
128+
exts.removeAll { $0.inheritanceClause == nil }
129+
}
130+
return exts
42131
}
43132
}
44-
45-
/// An extension that converts field token syntax
46-
/// to equivalent key token.
47-
extension TokenSyntax {
48-
/// Convert field token syntax
49-
/// to equivalent key token
50-
/// string by trimming \`s`.
51-
var asKey: String {
52-
self.text.trimmingCharacters(in: .swiftVariableExtra)
53-
}
54-
55-
/// Convert field token syntax
56-
/// to equivalent key token
57-
/// by trimming \`s`.
58-
var raw: TokenSyntax { .identifier(self.asKey) }
59-
}
60-
61-
/// An extension that manages
62-
/// custom character sets
63-
/// for macro expansion.
64-
extension CharacterSet {
65-
/// Character set that contains extra characters in swift variable names
66-
/// not applicable for key construction.
67-
static let swiftVariableExtra: Self = .init(arrayLiteral: "`")
68-
}

Sources/CodableMacroPlugin/Attributes/Codable/Codable.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
/// methods.
1717
/// * If attached declaration already conforms to `Codable` this macro expansion
1818
/// is skipped.
19-
struct Codable: RegistrationAttribute {
19+
struct Codable: Attribute {
2020
/// The node syntax provided
2121
/// during initialization.
2222
let node: AttributeSyntax
@@ -40,13 +40,13 @@ struct Codable: RegistrationAttribute {
4040
/// attached declaration.
4141
///
4242
/// Builds diagnoser that validates attached declaration
43-
/// is `struct` declaration and macro usage is not
44-
/// duplicated for the same declaration.
43+
/// is `struct` or `class` declaration and macro
44+
/// usage is not duplicated for the same declaration.
4545
///
4646
/// - Returns: The built diagnoser instance.
4747
func diagnoser() -> DiagnosticProducer {
4848
return AggregatedDiagnosticProducer {
49-
expect(syntax: StructDeclSyntax.self)
49+
expect(syntaxes: StructDeclSyntax.self, ClassDeclSyntax.self)
5050
cantDuplicate()
5151
}
5252
}

0 commit comments

Comments
 (0)