expo-modules-macros 0.23.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Sources/ExpoModulesMacros/EventMacro.swift +5 -4
- package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +17 -21
- package/apple/Sources/ExpoModulesMacros/TypeConformanceAssertion.swift +39 -63
- package/apple/Sources/ExpoModulesMacros/UnionMacro.swift +1 -1
- package/package.json +5 -5
package/apple/ExpoModulesMacros
CHANGED
|
Binary file
|
|
@@ -80,18 +80,19 @@ extension EventMacro: PeerMacro {
|
|
|
80
80
|
return []
|
|
81
81
|
}
|
|
82
82
|
// One nested helper asserts everything in a single call: the payload type is JS-convertible
|
|
83
|
-
// (the
|
|
84
|
-
// enclosing type can emit (the
|
|
83
|
+
// (the first parameter, dropped for no-payload events and known-conforming primitives) and the
|
|
84
|
+
// enclosing type can emit (the last parameter, always present since any `@Event` dispatches
|
|
85
85
|
// through `self.emit`). Named after the member so the member shows up in either diagnostic,
|
|
86
86
|
// and asserting the enclosing type by its spelled name so the conformance error names the
|
|
87
87
|
// user's type rather than 'Self'.
|
|
88
88
|
let name = event.swiftName
|
|
89
89
|
let payload = event.payloadType.flatMap { assertableBoundaryType($0, constraint: jsConvertibleProtocolName) }
|
|
90
90
|
let owner = enclosingTypeName(in: context) ?? "Self"
|
|
91
|
+
let emitterParameter = opaqueMetatypeParameter(constraint: eventEmitterProtocolName)
|
|
91
92
|
let helper =
|
|
92
93
|
payload != nil
|
|
93
|
-
? "func \(name)
|
|
94
|
-
: "func \(name)
|
|
94
|
+
? "func \(name)(\(opaqueMetatypeParameter(constraint: jsConvertibleProtocolName)), \(emitterParameter)) {}"
|
|
95
|
+
: "func \(name)(\(emitterParameter)) {}"
|
|
95
96
|
let call = payload.map { "\(name)(\($0).self, \(owner).self)" } ?? "\(name)(\(owner).self)"
|
|
96
97
|
let peerName = derivedIdentifier(from: name, prefix: "_assertTypesConformance_")
|
|
97
98
|
return [
|
|
@@ -43,14 +43,17 @@ import SwiftSyntaxMacros
|
|
|
43
43
|
/// (`from(dictionary:)` / `toDictionary(appContext:)`), since `JavaScriptCodable` only converts JS
|
|
44
44
|
/// values, not native `Any`. Both spell types as `public` symbols so the synthesized code compiles inside
|
|
45
45
|
/// user modules without any internal core symbols. Every property type must therefore conform to both
|
|
46
|
-
/// `AnyArgument` and `JavaScriptDecodable & JavaScriptEncodable`.
|
|
46
|
+
/// `AnyArgument` and `JavaScriptDecodable & JavaScriptEncodable`. The conformance assertion checks only
|
|
47
|
+
/// the JS codable protocols; the compiler reports a missing `AnyArgument` at the `getDynamicType()`
|
|
48
|
+
/// call.
|
|
47
49
|
///
|
|
48
50
|
/// A property whose type mentions `Any` (`Any?`, `[String: Any]?`, `[String: [Any]]`, …) can't conform to
|
|
49
|
-
/// the JS codable protocols. On the JS-value paths it converts through the
|
|
50
|
-
/// methods instead: `decodeAny`/`decodeAnyArray`/`decodeAnyDictionary` and
|
|
51
|
-
/// for `Any`, `[Any]` and `[String: Any]` (optional or not), and the generic
|
|
52
|
-
/// `encodeAny(_:in:)` for any other type. Such a property only needs
|
|
53
|
-
/// paths, and a bare `Any` needs nothing, since the dictionary paths
|
|
51
|
+
/// the JS codable protocols, so it isn't asserted. On the JS-value paths it converts through the
|
|
52
|
+
/// `JavaScriptValue` free-form methods instead: `decodeAny`/`decodeAnyArray`/`decodeAnyDictionary` and
|
|
53
|
+
/// the matching `encodeAny…` for `Any`, `[Any]` and `[String: Any]` (optional or not), and the generic
|
|
54
|
+
/// `decodeAny(_:as:in:)` / `encodeAny(_:in:)` for any other type. Such a property only needs
|
|
55
|
+
/// `AnyArgument` for the dictionary paths, and a bare `Any` needs nothing, since the dictionary paths
|
|
56
|
+
/// pass it through unchanged.
|
|
54
57
|
///
|
|
55
58
|
/// A record class must be `final` and have no inheritance clause. The synthesized factories construct
|
|
56
59
|
/// `Self` through the memberwise init, which only a final class can do without a `required` init, and
|
|
@@ -87,23 +90,16 @@ public struct RecordMacro: MemberMacro, ExtensionMacro {
|
|
|
87
90
|
|
|
88
91
|
var members: [DeclSyntax] = []
|
|
89
92
|
|
|
90
|
-
// A single never-called member that makes the compiler verify each property type
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
// conversion code below.
|
|
93
|
+
// A single never-called member that makes the compiler verify each property type converts both
|
|
94
|
+
// ways (the JS-value paths call `decode`/`encode`). Each property keeps its own named assertion
|
|
95
|
+
// inside, so the compiler's conformance diagnostic names the offending property (see
|
|
96
|
+
// `typeConformanceAssertions`). Emitted first so that, for a non-conforming type, this clear
|
|
97
|
+
// "requires that '…' conform to '…'" error is reported ahead of the noisier "no member 'decode'"
|
|
98
|
+
// errors from the conversion code below.
|
|
97
99
|
let assertions = properties.map { property in
|
|
98
|
-
|
|
99
|
-
// without one.
|
|
100
|
-
ConformanceAssertion(
|
|
101
|
-
name: property.name,
|
|
102
|
-
types: property.freeForm?.isBareAny == true ? [] : [property.type],
|
|
103
|
-
constraint: property.freeForm != nil ? jsConvertibleProtocolName : nil
|
|
104
|
-
)
|
|
100
|
+
ConformanceAssertion(name: property.name, types: property.freeForm == nil ? [property.type] : [])
|
|
105
101
|
}
|
|
106
|
-
if let assertionMember = typeConformanceAssertions(for: assertions, constraint:
|
|
102
|
+
if let assertionMember = typeConformanceAssertions(for: assertions, constraint: javaScriptCodableProtocolName) {
|
|
107
103
|
members.append(assertionMember)
|
|
108
104
|
}
|
|
109
105
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import SwiftSyntax
|
|
2
2
|
import SwiftSyntaxBuilder
|
|
3
3
|
|
|
4
|
-
/// The protocol
|
|
5
|
-
///
|
|
6
|
-
/// generated conversions go through the dynamic-type API (`@Record`, `@Event`).
|
|
4
|
+
/// The protocol of the 1.0 dynamic-type API (`getDynamicType()`). `@Event` asserts payload types
|
|
5
|
+
/// against it.
|
|
7
6
|
internal let jsConvertibleProtocolName = "AnyArgument"
|
|
8
7
|
|
|
9
8
|
/// The constraint a `@JS` boundary type must satisfy, by direction: an argument (and a settable
|
|
@@ -15,23 +14,10 @@ internal let jsConvertibleProtocolName = "AnyArgument"
|
|
|
15
14
|
internal let javaScriptDecodableProtocolName = "JavaScriptDecodable"
|
|
16
15
|
internal let javaScriptEncodableProtocolName = "JavaScriptEncodable"
|
|
17
16
|
|
|
18
|
-
/// The constraint a `@Record` property type
|
|
19
|
-
///
|
|
20
|
-
///
|
|
21
|
-
|
|
22
|
-
/// support both directions, so the assertion requires the intersection.
|
|
23
|
-
///
|
|
24
|
-
/// The exception is a property whose type mentions `Any` (`[String: Any]?`, `[Any]`, …): it can't
|
|
25
|
-
/// conform to the JS codable protocols, so the JS-value paths convert it through the `JavaScriptValue`
|
|
26
|
-
/// free-form methods, and it's asserted against `jsConvertibleProtocolName` alone for the dictionary
|
|
27
|
-
/// paths.
|
|
28
|
-
internal let recordFieldProtocolName = "AnyArgument & JavaScriptDecodable & JavaScriptEncodable"
|
|
29
|
-
|
|
30
|
-
/// The constraint a `@Union` case payload type must satisfy: the union decodes by trying each payload's
|
|
31
|
-
/// `decode` and encodes through the matching payload's `encode`, so every alternative must convert in
|
|
32
|
-
/// both directions. Unlike a `@Record` field, a union never crosses the native-`Any` dictionary path, so
|
|
33
|
-
/// `AnyArgument` is not required.
|
|
34
|
-
internal let unionPayloadProtocolName = "JavaScriptDecodable & JavaScriptEncodable"
|
|
17
|
+
/// The constraint a `@Record` property type and a `@Union` case payload type must satisfy. A record
|
|
18
|
+
/// decodes and encodes each property, and a union decodes by trying each payload's `decode` and
|
|
19
|
+
/// encodes through the matching payload's `encode`, so both convert in both directions.
|
|
20
|
+
internal let javaScriptCodableProtocolName = "JavaScriptDecodable & JavaScriptEncodable"
|
|
35
21
|
|
|
36
22
|
/// The protocol a type must conform to for `self.emit(event:…)` to resolve; core conforms
|
|
37
23
|
/// `BaseModule` and `SharedObject` to it. Asserted by `@Event` so attaching it to a type that can't
|
|
@@ -52,20 +38,15 @@ internal struct ConformanceAssertion {
|
|
|
52
38
|
/// Declared types as written. Composed types (`[Int]`, `String?`, …) are kept verbatim — their
|
|
53
39
|
/// conditional conformances transitively constrain the elements.
|
|
54
40
|
let types: [String]
|
|
55
|
-
/// The constraint for this member's types, overriding the one passed to `typeConformanceAssertions`.
|
|
56
|
-
/// `@Record` uses it to assert only `AnyArgument` for a property that mentions `Any`, which can't be
|
|
57
|
-
/// `JavaScriptDecodable & JavaScriptEncodable`.
|
|
58
|
-
var constraint: String? = nil
|
|
59
41
|
}
|
|
60
42
|
|
|
61
43
|
/// A directional conformance-assertion peer for a `@JS` member: a never-called `private func` whose
|
|
62
44
|
/// body asserts each argument type is `JavaScriptDecodable` and the return/getter type is
|
|
63
45
|
/// `JavaScriptEncodable`, matching how the generated binding converts each (decode on the way in,
|
|
64
|
-
/// encode on the way out). The check is a single member-named nested helper with one
|
|
65
|
-
/// parameter per asserted
|
|
66
|
-
///
|
|
67
|
-
///
|
|
68
|
-
/// direction.
|
|
46
|
+
/// encode on the way out). The check is a single member-named nested helper with one opaque metatype
|
|
47
|
+
/// parameter per asserted type, called once with every type's metatype; naming the helper after the
|
|
48
|
+
/// member puts the member in the compiler's diagnostic, and the per-parameter constraint reports the
|
|
49
|
+
/// offending type against the protocol for *its* direction.
|
|
69
50
|
///
|
|
70
51
|
/// `decodableTypes` are the argument types (and a settable property's value type); `encodableType` is
|
|
71
52
|
/// the single return type (or a property's value type on read), or `nil` when there's none.
|
|
@@ -87,31 +68,18 @@ internal func directionalConformanceAssertion(
|
|
|
87
68
|
return nil
|
|
88
69
|
}
|
|
89
70
|
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
for (index, type) in callbackArguments.enumerated() {
|
|
104
|
-
parameters.append("E\(index): \(javaScriptEncodableProtocolName) & ~Copyable")
|
|
105
|
-
typeParameters.append("_: E\(index).Type")
|
|
106
|
-
arguments.append("\(type).self")
|
|
107
|
-
}
|
|
108
|
-
if let encodable {
|
|
109
|
-
parameters.append("Return: \(javaScriptEncodableProtocolName) & ~Copyable")
|
|
110
|
-
typeParameters.append("_: Return.Type")
|
|
111
|
-
arguments.append("\(encodable).self")
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let helper = "func \(name)<\(parameters.joined(separator: ", "))>(\(typeParameters.joined(separator: ", "))) {}"
|
|
71
|
+
// Arguments are constrained `JavaScriptDecodable`, and closure-argument parameters and the
|
|
72
|
+
// return/getter value `JavaScriptEncodable`. Every parameter suppresses `Copyable`, so a
|
|
73
|
+
// non-copyable boundary type such as `JavaScriptValue` can fill it.
|
|
74
|
+
let encodables = callbackArguments + [encodable].compactMap { $0 }
|
|
75
|
+
let decodableParameter = opaqueMetatypeParameter(constraint: "\(javaScriptDecodableProtocolName) & ~Copyable")
|
|
76
|
+
let encodableParameter = opaqueMetatypeParameter(constraint: "\(javaScriptEncodableProtocolName) & ~Copyable")
|
|
77
|
+
let parameters =
|
|
78
|
+
Array(repeating: decodableParameter, count: decodables.count)
|
|
79
|
+
+ Array(repeating: encodableParameter, count: encodables.count)
|
|
80
|
+
let arguments = (decodables + encodables).map { "\($0).self" }
|
|
81
|
+
|
|
82
|
+
let helper = "func \(name)(\(parameters.joined(separator: ", "))) {}"
|
|
115
83
|
let call = "\(name)(\(arguments.joined(separator: ", ")))"
|
|
116
84
|
let staticKeyword = isStatic ? "static " : ""
|
|
117
85
|
let peerName = derivedIdentifier(from: name, prefix: "_assertTypesConformance_")
|
|
@@ -132,9 +100,9 @@ internal func directionalConformanceAssertion(
|
|
|
132
100
|
/// caller emits nothing.
|
|
133
101
|
internal func typeConformanceAssertions(
|
|
134
102
|
for assertions: [ConformanceAssertion],
|
|
135
|
-
constraint: String
|
|
103
|
+
constraint: String
|
|
136
104
|
) -> DeclSyntax? {
|
|
137
|
-
let bodies = assertions.compactMap { conformanceAssertionBody($0, constraint:
|
|
105
|
+
let bodies = assertions.compactMap { conformanceAssertionBody($0, constraint: constraint) }
|
|
138
106
|
guard !bodies.isEmpty else {
|
|
139
107
|
return nil
|
|
140
108
|
}
|
|
@@ -186,25 +154,33 @@ private func setsConform(to constraint: String) -> Bool {
|
|
|
186
154
|
}
|
|
187
155
|
}
|
|
188
156
|
|
|
189
|
-
/// The assertion's body fragment: a nested
|
|
190
|
-
///
|
|
191
|
-
///
|
|
192
|
-
///
|
|
193
|
-
///
|
|
157
|
+
/// The assertion's body fragment: a nested helper named after the member, plus one call per distinct
|
|
158
|
+
/// type that needs an assertion. Nesting the helper keeps the constraint entirely local — no shared
|
|
159
|
+
/// symbol, nothing to collide, nothing left in the type's namespace — and naming it after the member
|
|
160
|
+
/// puts the member's name in the compiler's conformance diagnostic. Returns `nil` when every type was
|
|
161
|
+
/// known to conform or the list was empty.
|
|
194
162
|
private func conformanceAssertionBody(
|
|
195
163
|
_ assertion: ConformanceAssertion,
|
|
196
|
-
constraint: String
|
|
164
|
+
constraint: String
|
|
197
165
|
) -> String? {
|
|
198
166
|
let distinct = distinctAssertableTypes(assertion.types, constraint: constraint)
|
|
199
167
|
guard !distinct.isEmpty else {
|
|
200
168
|
return nil
|
|
201
169
|
}
|
|
202
170
|
|
|
203
|
-
var lines = ["func \(assertion.name)
|
|
171
|
+
var lines = ["func \(assertion.name)(\(opaqueMetatypeParameter(constraint: constraint))) {}"]
|
|
204
172
|
lines.append(contentsOf: distinct.map { "\(assertion.name)(\($0).self)" })
|
|
205
173
|
return lines.joined(separator: "\n")
|
|
206
174
|
}
|
|
207
175
|
|
|
176
|
+
/// An unlabeled parameter that takes the metatype of any type satisfying `constraint`, for an
|
|
177
|
+
/// assertion helper.
|
|
178
|
+
internal func opaqueMetatypeParameter(constraint: String) -> String {
|
|
179
|
+
// A named generic parameter would shadow a generic parameter of the same name on an enclosing
|
|
180
|
+
// type, which is an error. An opaque parameter declares no name.
|
|
181
|
+
return "_: (some \(constraint)).Type"
|
|
182
|
+
}
|
|
183
|
+
|
|
208
184
|
/// The distinct types to assert against `constraint` from a list, mapping each through
|
|
209
185
|
/// `assertableBoundaryType` (which drops what needs no assertion) and deduping in first-seen order.
|
|
210
186
|
private func distinctAssertableTypes(_ types: [String], constraint: String) -> [String] {
|
|
@@ -57,7 +57,7 @@ public struct UnionMacro: MemberMacro, ExtensionMacro {
|
|
|
57
57
|
// reports the clear "requires that '…' conform to …" error ahead of the noisier "no member
|
|
58
58
|
// 'decode'"/"'encode'" errors from the conversion code below.
|
|
59
59
|
let assertions = union.cases.map { ConformanceAssertion(name: $0.name, types: [$0.payloadType]) }
|
|
60
|
-
if let assertionMember = typeConformanceAssertions(for: assertions, constraint:
|
|
60
|
+
if let assertionMember = typeConformanceAssertions(for: assertions, constraint: javaScriptCodableProtocolName) {
|
|
61
61
|
members.append(assertionMember)
|
|
62
62
|
}
|
|
63
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-macros",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"description": "Swift macro plugin for Expo modules",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"typescript": "^5.4.0"
|
|
27
27
|
},
|
|
28
28
|
"optionalDependencies": {
|
|
29
|
-
"@expo/modules-macros-linux-arm64": "0.23.
|
|
30
|
-
"@expo/modules-macros-linux-x64": "0.23.
|
|
31
|
-
"@expo/modules-macros-win32-arm64": "0.23.
|
|
32
|
-
"@expo/modules-macros-win32-x64": "0.23.
|
|
29
|
+
"@expo/modules-macros-linux-arm64": "0.23.1",
|
|
30
|
+
"@expo/modules-macros-linux-x64": "0.23.1",
|
|
31
|
+
"@expo/modules-macros-win32-arm64": "0.23.1",
|
|
32
|
+
"@expo/modules-macros-win32-x64": "0.23.1"
|
|
33
33
|
}
|
|
34
34
|
}
|