Skip to content

Commit 97a6057

Browse files
committed
feat: added actor support
1 parent a22e9d1 commit 97a6057

File tree

13 files changed

+270
-39
lines changed

13 files changed

+270
-39
lines changed

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,16 @@ extension Codable: MemberMacro, ExtensionMacro {
7070
in context: some MacroExpansionContext
7171
) throws -> [DeclSyntax] {
7272
guard
73-
let exp = AttributeExpander(for: declaration, in: context),
74-
let decl = declaration.as(ClassDeclSyntax.self),
75-
case let type = IdentifierTypeSyntax(name: decl.name)
73+
let exp = AttributeExpander(for: declaration, in: context)
7674
else { return [] }
75+
let type: IdentifierTypeSyntax
76+
if let decl = declaration.as(ClassDeclSyntax.self) {
77+
type = .init(name: decl.name)
78+
} else if let decl = declaration.as(ActorDeclSyntax.self) {
79+
type = .init(name: decl.name)
80+
} else {
81+
return []
82+
}
7783
let exts = exp.codableExpansion(for: type, to: protocols, in: context)
7884
return exts.flatMap { `extension` in
7985
`extension`.memberBlock.members.map { DeclSyntax($0.decl) }
@@ -120,7 +126,9 @@ extension Codable: MemberMacro, ExtensionMacro {
120126
let exp = AttributeExpander(for: declaration, in: context)
121127
else { return [] }
122128
var exts = exp.codableExpansion(for: type, to: protocols, in: context)
123-
if declaration.is(ClassDeclSyntax.self) {
129+
if declaration.is(ClassDeclSyntax.self)
130+
|| declaration.is(ActorDeclSyntax.self)
131+
{
124132
for (index, var `extension`) in exts.enumerated() {
125133
`extension`.memberBlock = .init(members: [])
126134
exts[index] = `extension`

Sources/CodableMacroPlugin/Attributes/Codable/Codable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ struct Codable: Attribute {
4848
return AggregatedDiagnosticProducer {
4949
expect(
5050
syntaxes: StructDeclSyntax.self, ClassDeclSyntax.self,
51-
EnumDeclSyntax.self
51+
EnumDeclSyntax.self, ActorDeclSyntax.self
5252
)
5353
cantDuplicate()
5454
}

Sources/CodableMacroPlugin/Attributes/MemberInit/MemberInit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct MemberInit: Attribute {
3939
/// - Returns: The built diagnoser instance.
4040
func diagnoser() -> DiagnosticProducer {
4141
return AggregatedDiagnosticProducer {
42-
expect(syntaxes: StructDeclSyntax.self)
42+
expect(syntaxes: StructDeclSyntax.self, ActorDeclSyntax.self)
4343
cantDuplicate()
4444
}
4545
}

Sources/CodableMacroPlugin/Variables/ComposedVariable.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,21 @@ where Self: EnumCaseVariable, Wrapped: EnumCaseVariable {
137137
/// Provides associated variables of the underlying variable value.
138138
var variables: [any AssociatedVariable] { base.variables }
139139
}
140+
141+
extension ComposedVariable where Self: TypeVariable, Wrapped: TypeVariable {
142+
/// Provides the syntax for `CodingKeys` declarations.
143+
///
144+
/// Provides members generated by the underlying variable value.
145+
///
146+
/// - Parameters:
147+
/// - protocols: The protocols for which conformance generated.
148+
/// - context: The context in which to perform the macro expansion.
149+
///
150+
/// - Returns: The `CodingKeys` declarations.
151+
func codingKeys(
152+
confirmingTo protocols: [TypeSyntax],
153+
in context: some MacroExpansionContext
154+
) -> MemberBlockItemListSyntax {
155+
return base.codingKeys(confirmingTo: protocols, in: context)
156+
}
157+
}

Sources/CodableMacroPlugin/Variables/Syntax/MemberGroupSyntax.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ extension ClassDeclSyntax: MemberGroupSyntax, VariableSyntax {
9292
typealias Variable = ClassVariable
9393
}
9494

95+
extension ActorDeclSyntax: MemberGroupSyntax, VariableSyntax {
96+
/// The `Variable` type this syntax represents.
97+
///
98+
/// The actor variable type used with current declaration.
99+
typealias Variable = ActorVariable
100+
}
101+
95102
extension EnumDeclSyntax: MemberGroupSyntax, VariableSyntax {
96103
/// The `Variable` type this syntax represents.
97104
///
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
@_implementationOnly import SwiftSyntax
2+
@_implementationOnly import SwiftSyntaxMacros
3+
4+
/// A `TypeVariable` that provides `Codable` conformance
5+
/// for an `actor` type.
6+
///
7+
/// This type can be used for `actor`s for `Decodable` conformance and
8+
/// `Encodable` implementation without conformance.
9+
struct ActorVariable: TypeVariable, DeclaredVariable, ComposedVariable,
10+
InitializableVariable
11+
{
12+
/// The initialization type of this variable.
13+
///
14+
/// Initialization type is the same as underlying member group variable.
15+
typealias Initialization = MemberGroup<ActorDeclSyntax>.Initialization
16+
/// The member group used to generate conformance implementations.
17+
let base: MemberGroup<ActorDeclSyntax>
18+
19+
/// Creates a new variable from declaration and expansion context.
20+
///
21+
/// Uses the actor declaration with member group to generate conformances.
22+
///
23+
/// - Parameters:
24+
/// - decl: The declaration to read from.
25+
/// - context: The context in which the macro expansion performed.
26+
init(from decl: ActorDeclSyntax, in context: some MacroExpansionContext) {
27+
self.base = .init(from: decl, in: context)
28+
}
29+
30+
/// Provides the syntax for encoding at the provided location.
31+
///
32+
/// Uses member group to generate syntax, the implementation is added
33+
/// while not conforming to `Encodable` protocol.
34+
///
35+
/// - Parameters:
36+
/// - context: The context in which to perform the macro expansion.
37+
/// - location: The encoding location.
38+
///
39+
/// - Returns: The generated encoding syntax.
40+
func encoding(
41+
in context: some MacroExpansionContext,
42+
to location: TypeCodingLocation
43+
) -> TypeGenerated? {
44+
guard
45+
let generated = base.encoding(in: context, to: location)
46+
else { return nil }
47+
return .init(
48+
code: generated.code, modifiers: generated.modifiers,
49+
whereClause: generated.whereClause,
50+
inheritanceClause: nil
51+
)
52+
}
53+
}

Sources/CodableMacroPlugin/Variables/Type/Data/ConstraintGenerator.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,4 @@ protocol GenericTypeDeclSyntax {
122122
extension StructDeclSyntax: GenericTypeDeclSyntax {}
123123
extension ClassDeclSyntax: GenericTypeDeclSyntax {}
124124
extension EnumDeclSyntax: GenericTypeDeclSyntax {}
125+
extension ActorDeclSyntax: GenericTypeDeclSyntax {}

Sources/MetaCodable/Codable/Codable.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
/// Generate `Codable` implementation of `struct`, `class`, `enum` types
2-
/// by leveraging custom attributes provided on variable declarations.
1+
/// Generate `Codable` implementation of `struct`, `class`, `enum`, `actor`
2+
/// types by leveraging custom attributes provided on variable declarations.
33
///
44
/// # Usage
55
/// By default the field name is used as `CodingKey` for the field value during
@@ -38,8 +38,8 @@
3838
/// * If attached declaration already conforms to `Codable` this macro expansion
3939
/// is skipped.
4040
///
41-
/// - Important: The attached declaration must be of a `struct`, `class`
42-
/// or `enum` type. [See the limitations for this macro](<doc:Limitations>).
41+
/// - Important: The attached declaration must be of a `struct`, `class`, `enum`
42+
/// or `actor` type. [See the limitations for this macro](<doc:Limitations>).
4343
@attached(
4444
extension, conformances: Decodable, Encodable,
4545
names: named(CodingKeys), named(DecodingKeys),

Sources/MetaCodable/CodedAt.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,35 @@
7676
/// }
7777
/// ```
7878
///
79+
/// * For enums, this attribute can be used along with ``TaggedAt(_:_:)``
80+
/// to support adjacently tagged enums. The path provided represents the path
81+
/// where associated values of each case is decoded/encoded.
82+
/// i.e. for JSON with following format:
83+
/// ```json
84+
/// {"t": "para", "c": [{...}, {...}]}
85+
/// ```
86+
/// ```json
87+
/// {"t": "str", "c": "the string"}
88+
/// ```
89+
/// enum representation can be created:
90+
/// ```swift
91+
/// @Codable
92+
/// @TaggedAt("t")
93+
/// @CodedAt("c")
94+
/// enum Block {
95+
/// case para([Inline]),
96+
/// case str(String),
97+
/// }
98+
/// ```
99+
///
79100
/// - Parameter path: The `CodingKey` path value located at.
80101
///
81102
/// - Note: This macro on its own only validates if attached declaration
82103
/// is a variable declaration. ``Codable()`` macro uses this macro
83104
/// when generating final implementations.
84105
///
85-
/// - Important: The field type must confirm to `Codable`.
106+
/// - Important: When applied to fields, the field type must confirm to
107+
/// `Codable`.
86108
@attached(peer)
87109
@available(swift 5.9)
88110
public macro CodedAt(_ path: StaticString...) =

Sources/MetaCodable/MetaCodable.docc/Limitations.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,40 @@ struct Model {
3131
The ability to pass conformance data to macro for classes when performing member attribute expansion was introduced in [`Swift 5.9.2`](https://github.com/apple/swift-evolution/blob/main/proposals/0407-member-macro-conformances.md). Please make sure to upgrade to this version to have this working.
3232

3333
Even with this it is unable for ``Codable()`` to get clear indication where conformance to `Codable` is implemented by current class or the super class. ``Codable()`` checks current class for the conformance implementation by checking implementation functions and the check will not work if some `typealias` used for `Decoder`/`Encoder` in implementation function definition.
34+
35+
### Why enum-case associated values decoding/encoding are not customizable?
36+
37+
The goal of ``MetaCodable`` is to allow same level of customization for enum-case associated values as it is allowed for `struct`/`class`/`actor` member properties. Unfortunately, as of now, `Swift` doesn't allow macro attributes (or any attributes) to be attached per enum-case arguments.
38+
39+
[A pitch has been created to allow this support in `Swift`](https://forums.swift.org/t/attached-macro-support-for-enum-case-arguments/67952), you can support this pitch on `Swift` forum if this feature will benefit you.
40+
41+
The current workaround is to extract enum-case arguments to separate `struct` and have the customization options in the `struct` itself. i.e. since following isn't possible:
42+
43+
```swift
44+
@Codable
45+
enum SomeEnum {
46+
case string(@CodedAt("data") String)
47+
}
48+
```
49+
50+
you can convert it to:
51+
52+
```swift
53+
@Codable
54+
enum SomeEnum {
55+
case string(StringData)
56+
57+
@Codable
58+
struct StringData {
59+
let data: String
60+
}
61+
}
62+
```
63+
64+
### Why `actor` conformance to `Encodable` not generated?
65+
66+
For `actor`s ``Codable()`` generates `Decodable` conformance, while `Encodable` conformance isn't generated, only `encode(to:)` method implementation is generated which is isolated to `actor`.
67+
68+
To generate `Encodable` conformance, the `encode(to:)` method must be `nonisolated` to `actor`, and since `encode(to:)` method must be synchronous making it `nonisolated` will prevent accessing mutable properties.
69+
70+
Due to these limitations, `Encodable` conformance isn't generated, users has to implement the conformance manually.

0 commit comments

Comments
 (0)