@expo/expo-modules-macros-plugin 0.6.1 → 0.7.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
@@ -2,7 +2,7 @@ import SwiftSyntax
2
2
 
3
3
  /// A `@JS func` collected for **direct JSI binding**. Instead of describing the function with a
4
4
  /// `Function(...)` / `AsyncFunction(...)` DSL entry that the runtime interprets per call, the enclosing
5
- /// macro synthesizes a decorator (`_decorateModule` / `_decorateSharedObject`) that binds each such
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
7
  /// decode-call-encode body inlined into the closure. This omits the `[Any]`/`toTuple` dynamic-call path:
8
8
  /// every argument is decoded individually by its static type.
@@ -60,7 +60,7 @@ internal struct JSFunction {
60
60
  let maximum = parameters.count
61
61
  var lines: [String] = []
62
62
 
63
- if let unwrap = receiver.unwrapStatement {
63
+ if let unwrap = receiver.unwrapStatement(isAsync: isAsync) {
64
64
  lines.append(unwrap)
65
65
  }
66
66
 
@@ -177,11 +177,26 @@ internal struct JSFunction {
177
177
  /// and throws instead of silently encoding an out-of-safe-range value as a lossy number — the
178
178
  /// catchable error is the right behavior, and matches how non-primitive integers already encode. A
179
179
  /// no-return function returns `.undefined` instead.
180
+ ///
181
+ /// For an `async` function the encode must run on the JS thread. An `async` body may suspend and
182
+ /// resume on an arbitrary cooperative-pool thread, and encoding a heap-allocated JS value (a string,
183
+ /// object, array, or typed array) off the JS thread races the engine's garbage collector and
184
+ /// corrupts the heap. `runtime.execute` hops the encode onto the JS thread, running inline when it is
185
+ /// already there (the common case, where the body never truly suspended), so synchronous functions —
186
+ /// which always run on the JS thread — keep encoding directly without the wrapper.
180
187
  private func encodeResultLines() -> [String] {
181
188
  guard let returnType else {
182
189
  return ["return .undefined"]
183
190
  }
184
- return ["return try \(expressionType(returnType)).encode(result, in: runtime)"]
191
+ let encode = "try \(expressionType(returnType)).encode(result, in: runtime)"
192
+ if isAsync {
193
+ return [
194
+ "return try await runtime.execute {",
195
+ " return \(encode)",
196
+ "}",
197
+ ]
198
+ }
199
+ return ["return \(encode)"]
185
200
  }
186
201
 
187
202
  /// The `setProperty` statement that installs this function on the JS object. The decode-call-encode
@@ -194,7 +209,7 @@ internal struct JSFunction {
194
209
  /// the host-function closure is what keeps the native callable alive for as long as JS can invoke
195
210
  /// it; its lifetime is bounded by the JS VM's garbage collection of the object. A shared object
196
211
  /// captures nothing of the instance: it recovers the typed receiver from the JS `this` per call.
197
- func decorateStatements(receiver: Receiver) -> String {
212
+ func decorateStatements(object: String, receiver: Receiver) -> String {
198
213
  // Synchronous `@JS` bindings bind through the unowned-`this` `setProperty` overload, which hands
199
214
  // `this` in as a borrowed `JavaScriptUnownedValue` instead of allocating an owning
200
215
  // `JavaScriptValue` and forming its `weak`-runtime reference on every call. A module ignores
@@ -210,7 +225,6 @@ internal struct JSFunction {
210
225
  ? "this, arguments"
211
226
  : "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"
212
227
 
213
- let object = receiver.decoratedObject
214
228
  return """
215
229
  \(object).setProperty("\(jsName)") { \(captures)\(parameters) in
216
230
  \(bodyStatements(receiver: receiver, indent: " "))
@@ -221,7 +235,7 @@ internal struct JSFunction {
221
235
 
222
236
  /// A `@JS var` collected for **direct JSI binding**. Instead of describing the property with a
223
237
  /// `Property(...)` DSL entry, the enclosing macro synthesizes a get/set accessor into the JS object
224
- /// inside its decorator (`_decorateModule` / `_decorateSharedObject`): it builds a descriptor object
238
+ /// inside its decorator (`_decorateModule(object:)` / `_decorateSharedObject(prototype:)`): it builds a descriptor object
225
239
  /// (`enumerable` + `get`, and `set` when the property is settable) and installs it with
226
240
  /// `object.defineProperty(name, descriptor:)`, mirroring core's `PropertyDefinition.buildDescriptor`.
227
241
  /// The `get`/`set` host functions are installed the same way `@JS func`s are — the closure-taking
@@ -248,13 +262,13 @@ internal struct JSProperty {
248
262
  /// closure — and installs it with `object.defineProperty(name, descriptor:)`. Capture matches the
249
263
  /// function bindings: a module captures `self` strong, a shared object captures nothing of the
250
264
  /// instance. Getter and setter are gated independently.
251
- func decorateStatements(receiver: Receiver) -> String {
265
+ func decorateStatements(object: String, receiver: Receiver) -> String {
252
266
  let descriptorName = "\(swiftName)Descriptor"
253
267
  let callee = receiver.callee
254
- let object = receiver.decoratedObject
255
268
  // A shared object's accessors unwrap the JS `this` into `_self` before reading/writing; a module
256
- // reads `self` directly. The unwrap leads each accessor body.
257
- let unwrap = receiver.unwrapStatement.map { "\($0)\n" } ?? ""
269
+ // reads `self` directly. The unwrap leads each accessor body. Property accessors are synchronous, so
270
+ // they take the borrowed unowned `this`.
271
+ let unwrap = receiver.unwrapStatement(isAsync: false).map { "\($0)\n" } ?? ""
258
272
  var lines: [String] = []
259
273
 
260
274
  lines.append("let \(descriptorName) = runtime.createObject()")
@@ -318,24 +332,30 @@ internal struct JSProperty {
318
332
  }
319
333
  }
320
334
 
321
- /// The body shared by both decorators: every `@JS func` bound via an inlined `setProperty` closure
322
- /// and every `@JS var` via a `defineProperty` accessor, joined for the function body. The `receiver`
323
- /// selects how each binding reaches its Swift value (module `self` vs. shared-object `_self`).
324
- private func decorateBody(functions: [JSFunction], properties: [JSProperty], receiver: Receiver) -> String {
325
- let functionBody = functions.map { $0.decorateStatements(receiver: receiver) }
326
- let propertyBody = properties.map { $0.decorateStatements(receiver: receiver) }
335
+ /// The body shared by every decorator phase: every `@JS func` bound via an inlined `setProperty`
336
+ /// closure and every `@JS var` via a `defineProperty` accessor, joined for the function body. `object`
337
+ /// is the local the bindings decorate (matching the entry point's argument label); `receiver` selects
338
+ /// how each binding reaches its Swift value (module `self`, shared-object instance `_self`, or the
339
+ /// metatype for a static member).
340
+ private func decorateBody(
341
+ functions: [JSFunction], properties: [JSProperty], object: String, receiver: Receiver
342
+ ) -> String {
343
+ let functionBody = functions.map { $0.decorateStatements(object: object, receiver: receiver) }
344
+ let propertyBody = properties.map { $0.decorateStatements(object: object, receiver: receiver) }
327
345
  return (functionBody + propertyBody).joined(separator: "\n")
328
346
  }
329
347
 
330
- /// The single generated function that decorates the module's JS object. Core supplies the object;
331
- /// this binds every `@JS func` (via an inlined `setProperty` closure) and every `@JS var` (via a
332
- /// `defineProperty` accessor) into it. Mirrors core's `ObjectDefinition.decorate(object:)`, including
333
- /// its `borrowing` object parameter (it mutates through the reference without reassigning or taking
334
- /// ownership). Named `_decorateModule` with the leading-underscore convention for synthesized members
335
- /// the **runtime calls by name**; the `ExpoModule` suffix names the `@ExpoModule` macro it came from (a
336
- /// shared object's counterpart is `_decorateSharedObject`). The bindings call into the module `self`.
348
+ /// The module decorator: a `_decorateModule(object:)` that binds every `@JS func` (via an inlined
349
+ /// `setProperty` closure) and every `@JS var` (via a `defineProperty` accessor) into the module's own
350
+ /// 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), mirroring
352
+ /// core's `ObjectDefinition.decorate(object:)` including the `borrowing` object parameter (it mutates
353
+ /// through the reference without reassigning or taking ownership). Only emitted when there's at least
354
+ /// one member to bind. Uses the shared body generation with the module `object:` phase and `self`
355
+ /// receiver; the shared-object counterpart is `buildDecorateSharedObjectPhase`.
337
356
  internal func buildDecorateJavaScriptObject(functions: [JSFunction], properties: [JSProperty]) -> DeclSyntax {
338
- let body = decorateBody(functions: functions, properties: properties, receiver: .module)
357
+ let body = decorateBody(
358
+ functions: functions, properties: properties, object: Phase.object.rawValue, receiver: .module)
339
359
  return """
340
360
  @JavaScriptActor
341
361
  public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime) throws {
@@ -344,22 +364,28 @@ internal func buildDecorateJavaScriptObject(functions: [JSFunction], properties:
344
364
  """
345
365
  }
346
366
 
347
- /// The shared-object counterpart of `_decorateModule`. Core supplies the class `prototype`; this binds
348
- /// every `@JS func` and `@JS var` of the given shared-object type onto it. Because a shared object has a
349
- /// distinct native instance behind each JS object, the bindings recover the typed receiver from the JS
350
- /// `this` per call (`try SharedObject.native(from: this.asObject(in: runtime), as: <Type>.self)`)
351
- /// rather than capturing a singleton `self`. Overrides the base `SharedObject` class method so core can
352
- /// dispatch to it through the concrete type's metatype. The first parameter is `prototype` (not `object`
353
- /// as on `_decorateModule`) because it's the shared class prototype, not an instance. The constructor is
354
- /// bound separately (see `JSConstructor.buildConstructor`). Only emitted when the type has at least one
355
- /// `@JS func`/`var`.
356
- internal func buildDecorateSharedObject(
357
- functions: [JSFunction], properties: [JSProperty], typeName: String
367
+ /// A shared-object decorator phase, a label overload of `_decorateSharedObject` overriding the matching
368
+ /// `open class func` on the `SharedObject` base so core can dispatch through the concrete type's
369
+ /// metatype. The `prototype:` overload binds the type's instance `@JS func`/`var` members onto the class
370
+ /// prototype (the original hook, unchanged); the `constructor:` overload binds the `static`/`class` ones
371
+ /// onto the constructor function itself (a new, additive overload, so introducing it isn't a breaking
372
+ /// core change). Both are *static* (a shared object has no singleton `self`). An instance binding
373
+ /// recovers its typed receiver from the JS `this` per call (`SharedObject.native(from:as:)`); a static
374
+ /// binding calls the Swift member on the type and ignores `this` (which, on the static side, is the
375
+ /// constructor). The constructor *object* the static members decorate is distinct from the `@JS init`,
376
+ /// which builds an instance and is emitted separately (see `JSConstructor.buildConstructor`). Each phase
377
+ /// is emitted only when the type has a member for it.
378
+ internal func buildDecorateSharedObjectPhase(
379
+ phase: Phase, functions: [JSFunction], properties: [JSProperty], typeName: String
358
380
  ) -> DeclSyntax {
359
- let body = decorateBody(functions: functions, properties: properties, receiver: .sharedObject(typeName: typeName))
381
+ let receiver: Receiver = phase == .constructor
382
+ ? .staticMember(typeName: typeName)
383
+ : .sharedObject(typeName: typeName)
384
+ let body = decorateBody(
385
+ functions: functions, properties: properties, object: phase.rawValue, receiver: receiver)
360
386
  return """
361
387
  @JavaScriptActor
362
- public override class func _decorateSharedObject(prototype: borrowing JavaScriptObject, in runtime: JavaScriptRuntime) throws {
388
+ public override class func _decorateSharedObject(\(raw: phase.rawValue): borrowing JavaScriptObject, in runtime: JavaScriptRuntime) throws {
363
389
  \(raw: body)
364
390
  }
365
391
  """
@@ -98,14 +98,6 @@ private func boundaryMember(of declaration: some DeclSyntaxProtocol) -> Boundary
98
98
  return nil
99
99
  }
100
100
 
101
- /// True when the modifiers make the member type-level (`static` or `class`), so its assertion peer
102
- /// must be emitted in the same metatype context rather than as an instance member.
103
- private func isTypeLevel(_ modifiers: DeclModifierListSyntax) -> Bool {
104
- return modifiers.contains {
105
- $0.name.tokenKind == .keyword(.static) || $0.name.tokenKind == .keyword(.class)
106
- }
107
- }
108
-
109
101
  /// True when a return clause is written as `Void` / `()` — nothing crosses the boundary, so it needs
110
102
  /// no conformance assertion. (A missing return clause never reaches here: `returnClause` is `nil`.)
111
103
  private func isVoidType(_ type: TypeSyntax) -> Bool {
@@ -43,6 +43,15 @@ internal func isOptionalType(_ type: TypeSyntax) -> Bool {
43
43
  return false
44
44
  }
45
45
 
46
+ /// True when the modifiers make the member type-level (`static` or `class`). A shared object routes
47
+ /// such members to its constructor (JS-static) and instance members to its prototype; the `@JS`
48
+ /// conformance assertion also uses this to emit its peer in the matching metatype context.
49
+ internal func isTypeLevel(_ modifiers: DeclModifierListSyntax) -> Bool {
50
+ return modifiers.contains {
51
+ $0.name.tokenKind == .keyword(.static) || $0.name.tokenKind == .keyword(.class)
52
+ }
53
+ }
54
+
46
55
  /// True if a trailing occurrence of this parameter may be omitted by the JS caller: it either has a
47
56
  /// default value (Swift applies it) or is an optional type (an absent slot becomes `nil`). The arity
48
57
  /// range and the per-arity call branches are derived from this.
@@ -145,14 +154,13 @@ internal func memberHasJSAttribute(_ decl: DeclSyntaxProtocol) -> Bool {
145
154
  return false
146
155
  }
147
156
 
148
- /**
149
- Decides whether the macro should stamp `@JavaScriptActor` on a `@JS`-marked member.
150
- The macro defers to the user when they've already chosen an isolation:
151
- - the `nonisolated` modifier is present on the member
152
- - any attribute whose name matches a known global actor (`@MainActor`, `@JavaScriptActor`)
153
- or follows the `*Actor` naming convention is present on the member or its enclosing type
154
- Async members never get the stamp because `AsyncFunction` controls their dispatch separately.
155
- */
157
+ /// Decides whether the macro should stamp `@JavaScriptActor` on a `@JS`-marked member.
158
+ /// The macro defers to the user when they've already chosen an isolation:
159
+ /// - the `nonisolated` modifier is present on the member
160
+ /// - any attribute whose name matches a known global actor (`@MainActor`, `@JavaScriptActor`)
161
+ /// or follows the `*Actor` naming convention is present on the member or its enclosing type
162
+ /// `async` members are stamped too: an `async` function starts its execution on the JS thread and
163
+ /// stays there until the first suspension point, where it may hop to another executor.
156
164
  internal func shouldStampJavaScriptActor(
157
165
  on member: DeclSyntaxProtocol,
158
166
  enclosedBy enclosing: some DeclGroupSyntax
@@ -162,11 +170,6 @@ internal func shouldStampJavaScriptActor(
162
170
  return false
163
171
  }
164
172
 
165
- if let funcDecl = member.as(FunctionDeclSyntax.self),
166
- funcDecl.signature.effectSpecifiers?.asyncSpecifier != nil {
167
- return false
168
- }
169
-
170
173
  let memberAttributes = memberAttributes(of: member)
171
174
  if memberAttributes.contains(where: hasGlobalActorShape) {
172
175
  return false
@@ -1,57 +1,80 @@
1
1
  import SwiftSyntax
2
2
 
3
- /// Where a directly-bound closure gets the Swift value it calls into. A module is a singleton, so its
4
- /// bindings call `self` and ignore the JS `this`; a shared object has a distinct native instance per JS
5
- /// object, so its bindings recover the typed receiver from `this`.
3
+ /// Where a directly-bound closure gets the Swift value it calls into. This is one of the two
4
+ /// orthogonal axes of a binding; the other is the `Phase` (which JS object the member is installed
5
+ /// on). The two are independent: choosing the prototype phase does not by itself decide whether the
6
+ /// receiver is `self`, `_self`, or the metatype.
7
+ ///
8
+ /// - A module is a singleton, so its bindings call `self` and ignore the JS `this`.
9
+ /// - A shared-object *instance* member has a distinct native instance per JS object, so it recovers
10
+ /// the typed receiver from `this`.
11
+ /// - A `static`/`class` member has no instance at all; it calls the Swift member on the metatype
12
+ /// (`Cache.open(…)`) and ignores `this` (which, on the static side, is the constructor).
6
13
  internal enum Receiver {
7
14
  /// The module singleton; the closure captures `self` strong.
8
15
  case module
9
16
  /// A shared object of the given concrete type; the closure captures nothing and recovers the receiver
10
17
  /// from `this` per call.
11
18
  case sharedObject(typeName: String)
19
+ /// A `static`/`class` member of the given concrete type; the closure captures nothing and calls the
20
+ /// Swift member on the type itself, ignoring `this`.
21
+ case staticMember(typeName: String)
12
22
 
13
23
  /// The expression the body calls members on: `self` for a module, `_self` (bound by `unwrapStatement`)
14
- /// for a shared object. The leading underscore avoids colliding with a user member like `var owner`.
24
+ /// for a shared-object instance, the type name for a static member. The leading underscore on `_self`
25
+ /// avoids colliding with a user member like `var owner`.
15
26
  var callee: String {
16
27
  switch self {
17
28
  case .module:
18
29
  return "self"
19
30
  case .sharedObject:
20
31
  return "_self"
32
+ case .staticMember(let typeName):
33
+ return typeName
21
34
  }
22
35
  }
23
36
 
24
- /// The JS object the decorator binds members onto, matching its first parameter: `object` for a
25
- /// module (its own JS object), `prototype` for a shared object (the shared class prototype).
26
- var decoratedObject: String {
37
+ /// The leading body line binding the receiver, or `nil` when nothing needs to be unwrapped (a module
38
+ /// reads `self` directly; a static member calls the type directly). For a shared-object instance,
39
+ /// `native(from:as:)` recovers the typed instance from `this`, throwing on a foreign object or a type
40
+ /// mismatch.
41
+ ///
42
+ /// The `this` object comes from the borrowed `JavaScriptUnownedValue` in a sync binding (`asObject(in:)`)
43
+ /// and from the owning `JavaScriptValue` in an async one (`asObject()`). An async binding must take an
44
+ /// owning `this` because a borrowed unowned value can't survive the closure's suspension points.
45
+ func unwrapStatement(isAsync: Bool) -> String? {
27
46
  switch self {
28
- case .module:
29
- return "object"
30
- case .sharedObject:
31
- return "prototype"
32
- }
33
- }
34
-
35
- /// The leading body line binding the receiver, or `nil` for a module (it reads `self` directly). For a
36
- /// shared object, `native(from:as:)` recovers the typed instance from the borrowed `this`, throwing on
37
- /// a foreign object or a type mismatch.
38
- var unwrapStatement: String? {
39
- switch self {
40
- case .module:
47
+ case .module, .staticMember:
41
48
  return nil
42
49
  case .sharedObject(let typeName):
43
- return "let _self = try SharedObject.native(from: this.asObject(in: runtime), as: \(typeName).self)"
50
+ let thisObject = isAsync ? "this.asObject()" : "this.asObject(in: runtime)"
51
+ return "let _self = try SharedObject.native(from: \(thisObject), as: \(typeName).self)"
44
52
  }
45
53
  }
46
54
 
47
55
  /// The capture-clause fragment (with a trailing space, or empty when nothing is captured). A module
48
- /// captures `self` strong; a shared object captures nothing of the instance.
56
+ /// captures `self` strong; a shared-object instance and a static member capture nothing.
49
57
  var captureClause: String {
50
58
  switch self {
51
59
  case .module:
52
60
  return "[self] "
53
- case .sharedObject:
61
+ case .sharedObject, .staticMember:
54
62
  return ""
55
63
  }
56
64
  }
57
65
  }
66
+
67
+ /// Which JS object a set of bindings is installed on: the second orthogonal axis alongside `Receiver`.
68
+ /// It selects the decorator entry point's argument label and the local name the body binds members onto,
69
+ /// mirroring JS class semantics (a class has a constructor function whose `.prototype` carries instance
70
+ /// members) and core's `ClassDefinition.decorate`.
71
+ /// The raw value is the argument label of the decorator entry point, which is also the local name the
72
+ /// body binds members onto.
73
+ internal enum Phase: String {
74
+ /// A concrete JS object: a singleton's own object (the module). Instance method; receiver `self`.
75
+ case object
76
+ /// The class constructor's `prototype`, carrying per-instance members. Static; receiver `_self`.
77
+ case prototype
78
+ /// The class constructor function itself, carrying `static`/`class` members. Static; receiver the type.
79
+ case constructor
80
+ }
@@ -42,13 +42,20 @@ public struct SharedObjectMacro: MemberMacro {
42
42
  let jsName = jsNameArgument(of: node) ?? typeName
43
43
 
44
44
  // `@JS func`s/`var`s and the `@JS init` are bound directly into the shared object's JS object by
45
- // the synthesized `_decorateSharedObject` / `_constructSharedObject` rather than described with a
46
- // `Function(...)` / `Property(...)` / `Constructor { … }` DSL entry, so they're collected here
47
- // instead of appended to the `Class` block. The block keeps only non-`@JS` definitions (none are
48
- // collected today), so it's empty when every member is `@JS`.
45
+ // the synthesized `_decorateSharedObject(prototype:)` / `_decorateSharedObject(constructor:)` / `_constructSharedObject`
46
+ // rather than described with a `Function(...)` / `Property(...)` / `Constructor { … }` DSL entry, so
47
+ // they're collected here instead of appended to the `Class` block. The block keeps only non-`@JS`
48
+ // definitions (none are collected today), so it's empty when every member is `@JS`.
49
+ //
50
+ // Members split by the `static`/`class` modifier onto two different JS objects: instance members
51
+ // decorate the prototype (receiver recovered from JS `this`), static members decorate the
52
+ // constructor (called on the metatype). A JS instance and static member may share a name without
53
+ // colliding: they live on different objects.
49
54
  let entries: [String] = []
50
- var functions: [JSFunction] = []
51
- var properties: [JSProperty] = []
55
+ var instanceFunctions: [JSFunction] = []
56
+ var instanceProperties: [JSProperty] = []
57
+ var staticFunctions: [JSFunction] = []
58
+ var staticProperties: [JSProperty] = []
52
59
  var constructor: JSConstructor?
53
60
 
54
61
  for member in classDecl.memberBlock.members {
@@ -66,13 +73,23 @@ public struct SharedObjectMacro: MemberMacro {
66
73
 
67
74
  if let funcDecl = decl.as(FunctionDeclSyntax.self),
68
75
  let attribute = funcDecl.attributes.firstAttribute(named: "JS") {
69
- functions.append(JSFunction(funcDecl: funcDecl, attribute: attribute))
76
+ let function = JSFunction(funcDecl: funcDecl, attribute: attribute)
77
+ if isTypeLevel(funcDecl.modifiers) {
78
+ staticFunctions.append(function)
79
+ } else {
80
+ instanceFunctions.append(function)
81
+ }
70
82
  continue
71
83
  }
72
84
 
73
85
  if let varDecl = decl.as(VariableDeclSyntax.self),
74
86
  let attribute = varDecl.attributes.firstAttribute(named: "JS") {
75
- properties.append(contentsOf: collectProperties(varDecl: varDecl, attribute: attribute))
87
+ let collected = collectProperties(varDecl: varDecl, attribute: attribute)
88
+ if isTypeLevel(varDecl.modifiers) {
89
+ staticProperties.append(contentsOf: collected)
90
+ } else {
91
+ instanceProperties.append(contentsOf: collected)
92
+ }
76
93
  }
77
94
  }
78
95
 
@@ -89,13 +106,22 @@ public struct SharedObjectMacro: MemberMacro {
89
106
  """
90
107
  ]
91
108
 
92
- // Direct JSI binding: one `_decorateSharedObject` that binds each `@JS func`/`var` onto the JS
93
- // object (unwrapping the per-call receiver from `this`), and a `_constructSharedObject` that
94
- // builds an instance from the `@JS init` arguments. Each is emitted only when it has something
95
- // to do.
96
- if !functions.isEmpty || !properties.isEmpty {
109
+ // Direct JSI binding, split by which JS object each member decorates:
110
+ // `_decorateSharedObject(prototype:)` binds instance `@JS func`/`var`s (unwrapping the per-call receiver from
111
+ // `this`); `_decorateSharedObject(constructor:)` binds `static`/`class` ones (called on the metatype); and
112
+ // `_constructSharedObject` builds an instance from the `@JS init` arguments. Each is emitted only
113
+ // when it has something to bind.
114
+ if !instanceFunctions.isEmpty || !instanceProperties.isEmpty {
115
+ emitted.append(
116
+ buildDecorateSharedObjectPhase(
117
+ phase: .prototype, functions: instanceFunctions, properties: instanceProperties,
118
+ typeName: typeName))
119
+ }
120
+ if !staticFunctions.isEmpty || !staticProperties.isEmpty {
97
121
  emitted.append(
98
- buildDecorateSharedObject(functions: functions, properties: properties, typeName: typeName))
122
+ buildDecorateSharedObjectPhase(
123
+ phase: .constructor, functions: staticFunctions, properties: staticProperties,
124
+ typeName: typeName))
99
125
  }
100
126
  if let constructor {
101
127
  emitted.append(constructor.buildConstructor(typeName: typeName))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/expo-modules-macros-plugin",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Swift macro plugin for Expo modules",
5
5
  "license": "MIT",
6
6
  "author": "650 Industries, Inc.",