@expo/expo-modules-macros-plugin 0.7.0 → 0.8.0
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.
|
Binary file
|
|
@@ -4,12 +4,15 @@ import SwiftSyntax
|
|
|
4
4
|
/// `Function(...)` / `AsyncFunction(...)` DSL entry that the runtime interprets per call, the enclosing
|
|
5
5
|
/// macro synthesizes a decorator (`_decorateModule(object:)` / `_decorateSharedObject(prototype:)`) that binds each such
|
|
6
6
|
/// function into the JS object via the closure-taking `JavaScriptObject.setProperty(_:)`, with the
|
|
7
|
-
/// decode-call-encode body inlined into the closure. This omits the
|
|
7
|
+
/// decode-call-encode body inlined into the closure. This omits the dynamic-call path entirely:
|
|
8
8
|
/// every argument is decoded individually by its static type.
|
|
9
9
|
///
|
|
10
10
|
/// The receiver (see `Receiver`) is the module's `self` for a module binding, or the per-call `_self`
|
|
11
|
-
/// unwrapped from the JS `this` for a shared-object binding. An `async` `@JS func`
|
|
12
|
-
///
|
|
11
|
+
/// unwrapped from the JS `this` for a shared-object binding. An `async` `@JS func` binds through the
|
|
12
|
+
/// two-phase async `setProperty(_:)` overload: the closure is the synchronous decode phase (receiver
|
|
13
|
+
/// unwrap, arity guard, argument decode, all while `this` and the arguments are still valid)
|
|
14
|
+
/// and returns the async body (call and result encode), so nothing JSI-owned crosses the asynchronous
|
|
15
|
+
/// boundary and JS gets a promise.
|
|
13
16
|
internal struct JSFunction {
|
|
14
17
|
let swiftName: String
|
|
15
18
|
let jsName: String
|
|
@@ -60,7 +63,7 @@ internal struct JSFunction {
|
|
|
60
63
|
let maximum = parameters.count
|
|
61
64
|
var lines: [String] = []
|
|
62
65
|
|
|
63
|
-
if let unwrap = receiver.unwrapStatement
|
|
66
|
+
if let unwrap = receiver.unwrapStatement {
|
|
64
67
|
lines.append(unwrap)
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -87,8 +90,31 @@ internal struct JSFunction {
|
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
if required == maximum {
|
|
90
|
-
// No omittable trailing run: a single flat call with every argument decoded.
|
|
91
|
-
|
|
93
|
+
// No omittable trailing run: a single flat call with every argument decoded. An async
|
|
94
|
+
// function decodes any remaining slots here (still the synchronous phase) and returns its
|
|
95
|
+
// body; a sync one calls and encodes directly.
|
|
96
|
+
if isAsync {
|
|
97
|
+
for index in required..<maximum {
|
|
98
|
+
lines.append(decodeStatement(at: index))
|
|
99
|
+
}
|
|
100
|
+
lines.append(contentsOf: asyncBodyLines(receiver: receiver, arity: maximum))
|
|
101
|
+
} else {
|
|
102
|
+
lines.append(contentsOf: callAndEncodeLines(receiver: receiver, arity: maximum, decodingFrom: required))
|
|
103
|
+
}
|
|
104
|
+
} else if isAsync {
|
|
105
|
+
// One body per accepted arity, branching on `arguments.count`. A branch decodes its trailing
|
|
106
|
+
// slots synchronously and returns the async body for that call shape, so each `return` ends
|
|
107
|
+
// the decode phase for its arity.
|
|
108
|
+
lines.append("switch arguments.count {")
|
|
109
|
+
for arity in required...maximum {
|
|
110
|
+
let label = arity == maximum ? "default:" : "case \(arity):"
|
|
111
|
+
lines.append(label)
|
|
112
|
+
for index in required..<arity {
|
|
113
|
+
lines.append(" " + decodeStatement(at: index))
|
|
114
|
+
}
|
|
115
|
+
lines.append(contentsOf: asyncBodyLines(receiver: receiver, arity: arity).map { " " + $0 })
|
|
116
|
+
}
|
|
117
|
+
lines.append("}")
|
|
92
118
|
} else {
|
|
93
119
|
// One call shape per accepted arity, branching on `arguments.count`. A branch decodes a
|
|
94
120
|
// trailing slot before the call, so this is a `switch` statement (not an expression): a
|
|
@@ -116,6 +142,24 @@ internal struct JSFunction {
|
|
|
116
142
|
.joined(separator: "\n")
|
|
117
143
|
}
|
|
118
144
|
|
|
145
|
+
/// The `return { … }` statement closing an async binding's decode phase: the returned closure is
|
|
146
|
+
/// the function's async body (`AsyncFunctionBody`), awaiting the call and encoding the result. It
|
|
147
|
+
/// captures only what the call needs — the decoded `arg<i>` locals, the receiver, and `runtime`
|
|
148
|
+
/// for the encode — never `this` or the arguments buffer, which are only valid for the duration
|
|
149
|
+
/// of the host call.
|
|
150
|
+
private func asyncBodyLines(receiver: Receiver, arity: Int) -> [String] {
|
|
151
|
+
var lines: [String] = ["return {"]
|
|
152
|
+
if returnType != nil {
|
|
153
|
+
lines.append(" let result = \(callExpression(receiver: receiver, arity: arity))")
|
|
154
|
+
lines.append(contentsOf: encodeResultLines().map { " " + $0 })
|
|
155
|
+
} else {
|
|
156
|
+
lines.append(" \(callExpression(receiver: receiver, arity: arity))")
|
|
157
|
+
lines.append(" return .undefined")
|
|
158
|
+
}
|
|
159
|
+
lines.append("}")
|
|
160
|
+
return lines
|
|
161
|
+
}
|
|
162
|
+
|
|
119
163
|
/// `let arg<index> = …` decoding the slot at `index` by its static type through
|
|
120
164
|
/// `JavaScriptDecodable.decode` on the borrowed `JavaScriptUnownedValue` — no owning value, no
|
|
121
165
|
/// `jsi::Value` copy, no `Any` boxing, no force-cast; it returns the concrete type directly. A
|
|
@@ -202,28 +246,25 @@ internal struct JSFunction {
|
|
|
202
246
|
/// The `setProperty` statement that installs this function on the JS object. The decode-call-encode
|
|
203
247
|
/// body is inlined directly into the closure passed to the closure-taking `setProperty` overload
|
|
204
248
|
/// (which creates the host function under the hood) — no separate named binding. For an `async`
|
|
205
|
-
/// function the
|
|
206
|
-
/// receives a promise).
|
|
249
|
+
/// function the closure is the synchronous decode phase and returns the async body, which selects
|
|
250
|
+
/// the async `setProperty` overload (so JS receives a promise).
|
|
207
251
|
///
|
|
208
|
-
///
|
|
252
|
+
/// A module captures its `self` **strong** —
|
|
209
253
|
/// the host-function closure is what keeps the native callable alive for as long as JS can invoke
|
|
210
254
|
/// it; its lifetime is bounded by the JS VM's garbage collection of the object. A shared object
|
|
211
255
|
/// captures nothing of the instance: it recovers the typed receiver from the JS `this` per call.
|
|
212
256
|
func decorateStatements(object: String, receiver: Receiver) -> String {
|
|
213
|
-
//
|
|
214
|
-
// `
|
|
215
|
-
//
|
|
216
|
-
// `this`; a shared object unwraps it (still
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
257
|
+
// Every `@JS` binding hands `this` in as a borrowed `JavaScriptUnownedValue` instead of
|
|
258
|
+
// allocating an owning `JavaScriptValue` and forming its `weak`-runtime reference on every call,
|
|
259
|
+
// and consumes the arguments buffer — sync and async closures share one parameter shape and
|
|
260
|
+
// differ only in what they return. A module ignores `this`; a shared object unwraps it (still
|
|
261
|
+
// borrowed) — an async binding does so in its synchronous decode phase, before the borrow ends.
|
|
262
|
+
// The parameter list is parenthesized and fully typed, since Swift rejects a type annotation on
|
|
263
|
+
// a shorthand `{ [capture] name, name in }` parameter; for a sync binding the explicit
|
|
264
|
+
// `JavaScriptUnownedValue` also selects the (otherwise `@_disfavoredOverload`) unowned-`this`
|
|
265
|
+
// overload.
|
|
222
266
|
let captures = receiver.captureClause
|
|
223
|
-
let parameters =
|
|
224
|
-
isAsync
|
|
225
|
-
? "this, arguments"
|
|
226
|
-
: "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"
|
|
267
|
+
let parameters = "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"
|
|
227
268
|
|
|
228
269
|
return """
|
|
229
270
|
\(object).setProperty("\(jsName)") { \(captures)\(parameters) in
|
|
@@ -237,7 +278,7 @@ internal struct JSFunction {
|
|
|
237
278
|
/// `Property(...)` DSL entry, the enclosing macro synthesizes a get/set accessor into the JS object
|
|
238
279
|
/// inside its decorator (`_decorateModule(object:)` / `_decorateSharedObject(prototype:)`): it builds a descriptor object
|
|
239
280
|
/// (`enumerable` + `get`, and `set` when the property is settable) and installs it with
|
|
240
|
-
/// `object.defineProperty(name, descriptor:)
|
|
281
|
+
/// `object.defineProperty(name, descriptor:)`.
|
|
241
282
|
/// The `get`/`set` host functions are installed the same way `@JS func`s are — the closure-taking
|
|
242
283
|
/// `setProperty(_:)` overload, with the read/write body inlined into the closure.
|
|
243
284
|
///
|
|
@@ -268,7 +309,7 @@ internal struct JSProperty {
|
|
|
268
309
|
// A shared object's accessors unwrap the JS `this` into `_self` before reading/writing; a module
|
|
269
310
|
// reads `self` directly. The unwrap leads each accessor body. Property accessors are synchronous, so
|
|
270
311
|
// they take the borrowed unowned `this`.
|
|
271
|
-
let unwrap = receiver.unwrapStatement
|
|
312
|
+
let unwrap = receiver.unwrapStatement.map { "\($0)\n" } ?? ""
|
|
272
313
|
var lines: [String] = []
|
|
273
314
|
|
|
274
315
|
lines.append("let \(descriptorName) = runtime.createObject()")
|
|
@@ -348,9 +389,9 @@ private func decorateBody(
|
|
|
348
389
|
/// The module decorator: a `_decorateModule(object:)` that binds every `@JS func` (via an inlined
|
|
349
390
|
/// `setProperty` closure) and every `@JS var` (via a `defineProperty` accessor) into the module's own
|
|
350
391
|
/// JS object. Core supplies the object; the bindings call into the module `self`. It's an *instance*
|
|
351
|
-
/// method (a module is a singleton, satisfying the `AnyModule` requirement of the same name),
|
|
352
|
-
///
|
|
353
|
-
///
|
|
392
|
+
/// method (a module is a singleton, satisfying the `AnyModule` requirement of the same name), with a
|
|
393
|
+
/// `borrowing` object parameter (it mutates through the reference without reassigning or taking
|
|
394
|
+
/// ownership). Only emitted when there's at least
|
|
354
395
|
/// one member to bind. Uses the shared body generation with the module `object:` phase and `self`
|
|
355
396
|
/// receiver; the shared-object counterpart is `buildDecorateSharedObjectPhase`.
|
|
356
397
|
internal func buildDecorateJavaScriptObject(functions: [JSFunction], properties: [JSProperty]) -> DeclSyntax {
|
|
@@ -39,16 +39,15 @@ internal enum Receiver {
|
|
|
39
39
|
/// `native(from:as:)` recovers the typed instance from `this`, throwing on a foreign object or a type
|
|
40
40
|
/// mismatch.
|
|
41
41
|
///
|
|
42
|
-
/// The `this` object comes from the borrowed `JavaScriptUnownedValue`
|
|
43
|
-
///
|
|
44
|
-
///
|
|
45
|
-
|
|
42
|
+
/// The `this` object always comes from the borrowed `JavaScriptUnownedValue` (`asObject(in:)`): an
|
|
43
|
+
/// async binding unwraps in its synchronous decode phase, before the borrowed value's lifetime ends,
|
|
44
|
+
/// and only the recovered native instance crosses into the async body.
|
|
45
|
+
var unwrapStatement: String? {
|
|
46
46
|
switch self {
|
|
47
47
|
case .module, .staticMember:
|
|
48
48
|
return nil
|
|
49
49
|
case .sharedObject(let typeName):
|
|
50
|
-
let
|
|
51
|
-
return "let _self = try SharedObject.native(from: \(thisObject), as: \(typeName).self)"
|
|
50
|
+
return "let _self = try SharedObject.native(from: this.asObject(in: runtime), as: \(typeName).self)"
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -67,7 +66,7 @@ internal enum Receiver {
|
|
|
67
66
|
/// Which JS object a set of bindings is installed on: the second orthogonal axis alongside `Receiver`.
|
|
68
67
|
/// It selects the decorator entry point's argument label and the local name the body binds members onto,
|
|
69
68
|
/// mirroring JS class semantics (a class has a constructor function whose `.prototype` carries instance
|
|
70
|
-
/// members)
|
|
69
|
+
/// members).
|
|
71
70
|
/// The raw value is the argument label of the decorator entry point, which is also the local name the
|
|
72
71
|
/// body binds members onto.
|
|
73
72
|
internal enum Phase: String {
|