expo-modules-macros 0.24.0 → 0.25.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.
package/apple/ExpoModulesMacros
CHANGED
|
Binary file
|
|
@@ -152,9 +152,9 @@ internal struct JSFunction {
|
|
|
152
152
|
} else {
|
|
153
153
|
// One call shape per accepted arity, branching on `arguments.count`. A branch decodes a
|
|
154
154
|
// trailing slot before the call, so this is a `switch` statement (not an expression): a
|
|
155
|
-
// value-returning function declares `
|
|
155
|
+
// value-returning function declares `value` up front and each branch assigns it.
|
|
156
156
|
if let returnType {
|
|
157
|
-
lines.append("let
|
|
157
|
+
lines.append("let value: \(expressionType(returnType))")
|
|
158
158
|
}
|
|
159
159
|
lines.append("switch arguments.count {")
|
|
160
160
|
for arity in required...maximum {
|
|
@@ -163,7 +163,7 @@ internal struct JSFunction {
|
|
|
163
163
|
for index in required..<arity {
|
|
164
164
|
lines.append(contentsOf: decodeLines(at: index).map { " " + $0 })
|
|
165
165
|
}
|
|
166
|
-
let assignment = returnType != nil ? "
|
|
166
|
+
let assignment = returnType != nil ? "value = " : ""
|
|
167
167
|
lines.append(" \(assignment)\(callExpression(receiver: receiver, arity: arity))")
|
|
168
168
|
}
|
|
169
169
|
lines.append("}")
|
|
@@ -268,7 +268,7 @@ internal struct JSFunction {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
/// The flat (single-arity) call-and-encode lines used when no trailing parameter is omittable:
|
|
271
|
-
/// `let
|
|
271
|
+
/// `let value = <callee>.f(...)` then the encode (or just `<callee>.f(...)` for a function
|
|
272
272
|
/// that returns nothing).
|
|
273
273
|
private func callAndEncodeLines(receiver: Receiver, arity: Int, decodingFrom: Int) -> [String] {
|
|
274
274
|
var lines: [String] = []
|
|
@@ -276,7 +276,7 @@ internal struct JSFunction {
|
|
|
276
276
|
lines.append(contentsOf: decodeLines(at: index))
|
|
277
277
|
}
|
|
278
278
|
if returnType != nil {
|
|
279
|
-
lines.append("let
|
|
279
|
+
lines.append("let value = \(callExpression(receiver: receiver, arity: arity))")
|
|
280
280
|
lines.append(contentsOf: encodeResultLines())
|
|
281
281
|
} else {
|
|
282
282
|
lines.append(callExpression(receiver: receiver, arity: arity))
|
|
@@ -290,8 +290,9 @@ internal struct JSFunction {
|
|
|
290
290
|
/// `toJavaScriptValue(in:)` except for `Int`/`UInt`, where it range-checks
|
|
291
291
|
/// and throws instead of silently encoding an out-of-safe-range value as a lossy number — the
|
|
292
292
|
/// catchable error is the right behavior, and matches how non-primitive integers already encode. A
|
|
293
|
-
/// no-return function has nothing to encode
|
|
294
|
-
/// `decorateStatements(object:receiver:)`).
|
|
293
|
+
/// no-return function has nothing to encode, so its binding closure leaves the result slot empty (see
|
|
294
|
+
/// `decorateStatements(object:receiver:)`). A synchronous function encodes straight into the
|
|
295
|
+
/// engine's result slot, so a number, a boolean or a string never becomes a `JavaScriptValue`.
|
|
295
296
|
///
|
|
296
297
|
/// For an `async` function the encode must run on the JS thread. An `async` body may suspend and
|
|
297
298
|
/// resume on an arbitrary cooperative-pool thread, and encoding a heap-allocated JS value (a string,
|
|
@@ -303,15 +304,15 @@ internal struct JSFunction {
|
|
|
303
304
|
guard let returnType else {
|
|
304
305
|
return []
|
|
305
306
|
}
|
|
306
|
-
let encode = "try \(encodeCall(returnType, freeForm: returnFreeForm, of: "result"))"
|
|
307
307
|
if isAsync {
|
|
308
|
+
let encode = "try \(encodeCall(returnType, freeForm: returnFreeForm, of: "result"))"
|
|
308
309
|
return [
|
|
309
310
|
"return try await runtime.execute {",
|
|
310
311
|
" return \(encode)",
|
|
311
312
|
"}",
|
|
312
313
|
]
|
|
313
314
|
}
|
|
314
|
-
return ["
|
|
315
|
+
return [encodeIntoSlotStatement(returnType, freeForm: returnFreeForm, of: "value")]
|
|
315
316
|
}
|
|
316
317
|
|
|
317
318
|
/// The `setProperty` statement that installs this function on the JS object. The decode-call-encode
|
|
@@ -331,19 +332,18 @@ internal struct JSFunction {
|
|
|
331
332
|
// differ only in what they return. A module ignores `this`; a shared object unwraps it (still
|
|
332
333
|
// borrowed) — an async binding does so in its synchronous decode phase, before the borrow ends.
|
|
333
334
|
// The parameter list is parenthesized and fully typed, since Swift rejects a type annotation on
|
|
334
|
-
// a shorthand `{ [capture] name, name in }` parameter
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
// result as `undefined` without building and writing a value. The closure declares `-> Void`
|
|
339
|
-
// because a body of several statements without a `return` is otherwise ambiguous between the
|
|
340
|
-
// two unowned-`this` overloads.
|
|
335
|
+
// a shorthand `{ [capture] name, name in }` parameter.
|
|
336
|
+
// A sync function binds through the result-slot overload (`JavaScriptRuntime.HostFunctionClosure`):
|
|
337
|
+
// it encodes its result into the slot and gets the runtime as a parameter. A function that returns
|
|
338
|
+
// nothing writes nothing, so JS gets `undefined` without a value being built.
|
|
341
339
|
let captures = receiver.captureClause
|
|
342
|
-
let parameters =
|
|
343
|
-
|
|
340
|
+
let parameters =
|
|
341
|
+
isAsync
|
|
342
|
+
? "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"
|
|
343
|
+
: resultSlotClosureParameters
|
|
344
344
|
|
|
345
345
|
return """
|
|
346
|
-
\(object).setProperty("\(jsName)") { \(captures)\(parameters)
|
|
346
|
+
\(object).setProperty("\(jsName)") { \(captures)\(parameters) in
|
|
347
347
|
\(bodyStatements(receiver: receiver, indent: " "))
|
|
348
348
|
}
|
|
349
349
|
"""
|
|
@@ -419,8 +419,16 @@ internal struct JSProperty {
|
|
|
419
419
|
} else {
|
|
420
420
|
encode = "\(callee).\(swiftName).toJavaScriptValue(in: runtime)"
|
|
421
421
|
}
|
|
422
|
+
// A getter encodes straight into the engine's result slot. A lazy getter needs the encoded value as
|
|
423
|
+
// a `JavaScriptValue` to define it on `this` as well, and then moves it into the slot.
|
|
422
424
|
let getBody: String
|
|
423
|
-
if isLazy {
|
|
425
|
+
if !isLazy {
|
|
426
|
+
if let valueType {
|
|
427
|
+
getBody = encodeIntoSlotStatement(valueType, freeForm: freeForm, of: "\(callee).\(swiftName)")
|
|
428
|
+
} else {
|
|
429
|
+
getBody = "result.emplace(\(encode))"
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
424
432
|
let define = "this.getObject(in: runtime).defineProperty(\"\(jsName)\", value: value, options: [.enumerable])"
|
|
425
433
|
// A module getter ignores `this` otherwise, so it can be anything, and only an object can hold the
|
|
426
434
|
// value. A shared object's unwrap has already checked that `this` is an object.
|
|
@@ -428,9 +436,7 @@ internal struct JSProperty {
|
|
|
428
436
|
receiver.unwrapStatement == nil
|
|
429
437
|
? "if this.isObject() {\n \(define)\n}"
|
|
430
438
|
: define
|
|
431
|
-
getBody = "let value = \(encode)\n\(defineStatement)\
|
|
432
|
-
} else {
|
|
433
|
-
getBody = "return \(encode)"
|
|
439
|
+
getBody = "let value = \(encode)\n\(defineStatement)\nresult.emplace(value)"
|
|
434
440
|
}
|
|
435
441
|
lines.append(
|
|
436
442
|
accessorClosure(descriptorName, "get", receiver: receiver, body: "\(unwrap)\(getBody)")
|
|
@@ -444,13 +450,7 @@ internal struct JSProperty {
|
|
|
444
450
|
let decode = decodeCall(valueType, freeForm: freeForm, from: "arguments.unownedValue(at: 0)")
|
|
445
451
|
let setDecode = "\(callee).\(swiftName) = try \(decode)"
|
|
446
452
|
lines.append(
|
|
447
|
-
accessorClosure(
|
|
448
|
-
descriptorName,
|
|
449
|
-
"set",
|
|
450
|
-
receiver: receiver,
|
|
451
|
-
returnsVoid: true,
|
|
452
|
-
body: "\(unwrap)\(setDecode)"
|
|
453
|
-
)
|
|
453
|
+
accessorClosure(descriptorName, "set", receiver: receiver, body: "\(unwrap)\(setDecode)")
|
|
454
454
|
)
|
|
455
455
|
}
|
|
456
456
|
|
|
@@ -465,13 +465,10 @@ internal struct JSProperty {
|
|
|
465
465
|
|
|
466
466
|
/// One `descriptor.setProperty("get"/"set") { … }` accessor entry. The capture list follows the
|
|
467
467
|
/// receiver: a module captures `self` strong; a shared object captures nothing of the instance.
|
|
468
|
-
/// A setter passes `returnsVoid`, so its closure declares `-> Void` and binds through the `Void`
|
|
469
|
-
/// overload, like a function that returns nothing.
|
|
470
468
|
private func accessorClosure(
|
|
471
469
|
_ descriptorName: String,
|
|
472
470
|
_ key: String,
|
|
473
471
|
receiver: Receiver,
|
|
474
|
-
returnsVoid: Bool = false,
|
|
475
472
|
body: String
|
|
476
473
|
) -> String {
|
|
477
474
|
let captures = receiver.captureClause
|
|
@@ -482,21 +479,36 @@ internal struct JSProperty {
|
|
|
482
479
|
.split(separator: "\n", omittingEmptySubsequences: false)
|
|
483
480
|
.map { " \($0)" }
|
|
484
481
|
.joined(separator: "\n")
|
|
485
|
-
// Property `get`/`set` accessors are always synchronous, so they bind through the
|
|
482
|
+
// Property `get`/`set` accessors are always synchronous, so they bind through the result-slot
|
|
486
483
|
// `setProperty` overload like sync functions. The parameter list is parenthesized and fully typed
|
|
487
|
-
// because Swift rejects a type annotation on a shorthand closure parameter
|
|
488
|
-
//
|
|
489
|
-
// a shared object unwraps it in the body.
|
|
490
|
-
let parameters = "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"
|
|
491
|
-
let returnClause = returnsVoid ? " -> Void" : ""
|
|
484
|
+
// because Swift rejects a type annotation on a shorthand closure parameter. A module ignores `this`;
|
|
485
|
+
// a shared object unwraps it in the body. A setter writes nothing, so JS gets `undefined`.
|
|
492
486
|
return """
|
|
493
|
-
\(descriptorName).setProperty("\(key)") { \(captures)\(
|
|
487
|
+
\(descriptorName).setProperty("\(key)") { \(captures)\(resultSlotClosureParameters) in
|
|
494
488
|
\(indentedBody)
|
|
495
489
|
}
|
|
496
490
|
"""
|
|
497
491
|
}
|
|
498
492
|
}
|
|
499
493
|
|
|
494
|
+
/// The parameter list of a binding closure that writes its result into the engine's result slot
|
|
495
|
+
/// (`JavaScriptRuntime.HostFunctionClosure`). The closure gets the runtime as a parameter, which shadows
|
|
496
|
+
/// the decorator's `runtime`, so it doesn't capture it.
|
|
497
|
+
private let resultSlotClosureParameters =
|
|
498
|
+
"(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer, "
|
|
499
|
+
+ "result: borrowing JavaScriptValue.Slot, runtime: borrowing JavaScriptRuntime)"
|
|
500
|
+
|
|
501
|
+
/// The statement that encodes `valueExpression` of a boundary type into the result slot `result`. A type
|
|
502
|
+
/// that mentions `Any` has no `encode(_:into:in:)`, so its free-form encode result is moved into the slot.
|
|
503
|
+
private func encodeIntoSlotStatement(_ type: String, freeForm: FreeFormConversion?, of valueExpression: String)
|
|
504
|
+
-> String
|
|
505
|
+
{
|
|
506
|
+
if freeForm != nil {
|
|
507
|
+
return "result.emplace(try \(encodeCall(type, freeForm: freeForm, of: valueExpression)))"
|
|
508
|
+
}
|
|
509
|
+
return "try \(expressionType(type)).encode(\(valueExpression), into: result, in: runtime)"
|
|
510
|
+
}
|
|
511
|
+
|
|
500
512
|
/// The body shared by every decorator phase: every `@JS func` bound via an inlined `setProperty`
|
|
501
513
|
/// closure and every `@JS var` via a `defineProperty` accessor, joined for the function body. `object`
|
|
502
514
|
/// is the local the bindings decorate (matching the entry point's argument label); `receiver` selects
|
|
@@ -477,7 +477,9 @@ private func toObjectMethod() -> DeclSyntax {
|
|
|
477
477
|
/// property's declared default (inlined) when undefined; optional properties become `nil` when
|
|
478
478
|
/// undefined/null. It's the unowned overload: an owning value decodes through `JavaScriptDecodable`'s
|
|
479
479
|
/// default, which forwards to it. `encode` builds the JS object, writing each property. Its parameter
|
|
480
|
-
/// names the type, because a class can't take `Self` as a parameter type.
|
|
480
|
+
/// names the type, because a class can't take `Self` as a parameter type. `encode(_:into:in:)` builds
|
|
481
|
+
/// the same object and moves it into the slot (an array element, an argument or a result), with no
|
|
482
|
+
/// `JavaScriptValue` in between.
|
|
481
483
|
private func javaScriptCodableWitnesses(
|
|
482
484
|
properties: [RecordProperty],
|
|
483
485
|
typeName: String,
|
|
@@ -491,11 +493,12 @@ private func javaScriptCodableWitnesses(
|
|
|
491
493
|
let lookupLines = cachesPropNames ? propNamesLookupLines() : []
|
|
492
494
|
let readLines = lookupLines + jsObjectReadLines(properties: properties, cachesPropNames: cachesPropNames)
|
|
493
495
|
let decodeBody = objectLine + "\n" + factoryBody(properties: properties, readLines: readLines)
|
|
494
|
-
let
|
|
496
|
+
let objectLines =
|
|
495
497
|
[" let object = runtime.createObject()"]
|
|
496
498
|
+ lookupLines
|
|
497
499
|
+ objectWriteLines(properties: properties, of: "value", cachesPropNames: cachesPropNames)
|
|
498
|
-
|
|
500
|
+
let encodeLines = objectLines + [" return object.takeValue()"]
|
|
501
|
+
let encodeIntoSlotLines = objectLines + [" slot.emplace(object)"]
|
|
499
502
|
return [
|
|
500
503
|
"""
|
|
501
504
|
@JavaScriptActor
|
|
@@ -509,12 +512,23 @@ private func javaScriptCodableWitnesses(
|
|
|
509
512
|
\(raw: encodeLines.joined(separator: "\n"))
|
|
510
513
|
}
|
|
511
514
|
""",
|
|
515
|
+
"""
|
|
516
|
+
@JavaScriptActor
|
|
517
|
+
public static func encode(
|
|
518
|
+
_ value: \(raw: typeName),
|
|
519
|
+
into slot: borrowing JavaScriptValue.Slot,
|
|
520
|
+
in runtime: borrowing JavaScriptRuntime
|
|
521
|
+
) throws {
|
|
522
|
+
\(raw: encodeIntoSlotLines.joined(separator: "\n"))
|
|
523
|
+
}
|
|
524
|
+
""",
|
|
512
525
|
]
|
|
513
526
|
}
|
|
514
527
|
|
|
515
528
|
/// The statements that write each property of `record` (`self` or the `encode` parameter) into the
|
|
516
|
-
/// `object` local
|
|
517
|
-
///
|
|
529
|
+
/// `object` local. A property is encoded straight into the engine value that `setProperty` writes, through
|
|
530
|
+
/// `JavaScriptEncodable.encode(_:into:in:)`, so a number, a boolean or a string never becomes a
|
|
531
|
+
/// `JavaScriptValue`. A free-form property goes through the matching `JavaScriptValue` free-form method.
|
|
518
532
|
private func objectWriteLines(properties: [RecordProperty], of record: String, cachesPropNames: Bool) -> [String] {
|
|
519
533
|
return properties.enumerated().map { index, property in
|
|
520
534
|
let value = "\(record).\(property.name)"
|
|
@@ -529,7 +543,8 @@ private func objectWriteLines(properties: [RecordProperty], of record: String, c
|
|
|
529
543
|
// The explicit `as Any` keeps an optional property from warning about its implicit coercion.
|
|
530
544
|
encode = "try JavaScriptValue.encodeAny(\(value) as Any, in: runtime)"
|
|
531
545
|
case nil:
|
|
532
|
-
|
|
546
|
+
let propName = propNameArgument(for: property, at: index, cached: cachesPropNames)
|
|
547
|
+
return " try object.setProperty(\(propName), encoding: \(value), in: runtime)"
|
|
533
548
|
}
|
|
534
549
|
let propName = propNameArgument(for: property, at: index, cached: cachesPropNames)
|
|
535
550
|
return " object.setProperty(\(propName), value: \(encode))"
|
|
@@ -64,6 +64,7 @@ public struct UnionMacro: MemberMacro, ExtensionMacro {
|
|
|
64
64
|
members.append(decodableKindsProperty(union: union))
|
|
65
65
|
members.append(decodeMethod(union: union))
|
|
66
66
|
members.append(encodeMethod(union: union))
|
|
67
|
+
members.append(encodeIntoSlotMethod(union: union))
|
|
67
68
|
members.append(contentsOf: accessorMethods(union: union))
|
|
68
69
|
members.append(payloadTypeNameProperty(union: union))
|
|
69
70
|
return members
|
|
@@ -322,6 +323,30 @@ private func encodeMethod(union: UnionType) -> DeclSyntax {
|
|
|
322
323
|
"""
|
|
323
324
|
}
|
|
324
325
|
|
|
326
|
+
/// `encode(_:into:in:)`: the same `switch` as `encode(_:in:)`, with each payload encoded straight into the
|
|
327
|
+
/// slot through the payload type's own `encode(_:into:in:)`. A payload that is a number, a boolean or a
|
|
328
|
+
/// string is then constructed in the slot (an array element, an argument or a result), with no
|
|
329
|
+
/// `JavaScriptValue` in between.
|
|
330
|
+
private func encodeIntoSlotMethod(union: UnionType) -> DeclSyntax {
|
|
331
|
+
var lines: [String] = [" switch value {"]
|
|
332
|
+
for unionCase in union.cases {
|
|
333
|
+
lines.append(" case \(unionCase.pattern):")
|
|
334
|
+
lines.append(" try \(expressionType(unionCase.payloadType)).encode(payload, into: slot, in: runtime)")
|
|
335
|
+
}
|
|
336
|
+
lines.append(" }")
|
|
337
|
+
let body = lines.joined(separator: "\n")
|
|
338
|
+
return """
|
|
339
|
+
@JavaScriptActor
|
|
340
|
+
public static func encode(
|
|
341
|
+
_ value: Self,
|
|
342
|
+
into slot: borrowing JavaScriptValue.Slot,
|
|
343
|
+
in runtime: borrowing JavaScriptRuntime
|
|
344
|
+
) throws {
|
|
345
|
+
\(raw: body)
|
|
346
|
+
}
|
|
347
|
+
"""
|
|
348
|
+
}
|
|
349
|
+
|
|
325
350
|
/// `as(_:)`: a typed accessor per case, selected by the payload's metatype rather than the case name,
|
|
326
351
|
/// so a caller that only knows the type it wants writes `try value.as(String.self)` and gets a `String`.
|
|
327
352
|
/// When
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-macros",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
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.
|
|
30
|
-
"@expo/modules-macros-linux-x64": "0.
|
|
31
|
-
"@expo/modules-macros-win32-arm64": "0.
|
|
32
|
-
"@expo/modules-macros-win32-x64": "0.
|
|
29
|
+
"@expo/modules-macros-linux-arm64": "0.25.0",
|
|
30
|
+
"@expo/modules-macros-linux-x64": "0.25.0",
|
|
31
|
+
"@expo/modules-macros-win32-arm64": "0.25.0",
|
|
32
|
+
"@expo/modules-macros-win32-x64": "0.25.0"
|
|
33
33
|
}
|
|
34
34
|
}
|