expo-modules-macros 0.14.0 → 0.15.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
@@ -8,9 +8,13 @@ import SwiftSyntaxMacros
8
8
  /// level, so the author switches over it exhaustively with each payload keeping its static type, and
9
9
  /// the macro synthesizes the conversion surface that makes it a JS boundary type:
10
10
  ///
11
+ /// - `decodableKinds`: the union of the payloads' `decodableKinds`, the JS kinds the union can decode.
11
12
  /// - `decode(_:in:)`: an ordered decode that tries each case's payload converter in declaration order
12
13
  /// and returns the first case that decodes; when none does, it throws
13
14
  /// `Exceptions.UnionCaseMismatch` naming the union, the JS kind received, and the alternatives.
15
+ /// Only the overload taking a borrowed `JavaScriptUnownedValue` is emitted. The overload taking an
16
+ /// owning `JavaScriptValue` comes from the `JavaScriptDecodable` default, which borrows the value
17
+ /// and calls this one.
14
18
  /// - `encode(_:in:)`: a `switch` over the cases, encoding the payload through its own type.
15
19
  /// - `as(_:)`: one throwing overload per case, keyed by the payload's metatype, returning that payload
16
20
  /// (`try source.as(String.self)` is `String`; `try? source.as(String.self)` is `String?`). It unwraps
@@ -57,6 +61,7 @@ public struct UnionMacro: MemberMacro, ExtensionMacro {
57
61
  members.append(assertionMember)
58
62
  }
59
63
 
64
+ members.append(decodableKindsProperty(union: union))
60
65
  members.append(decodeMethod(union: union))
61
66
  members.append(encodeMethod(union: union))
62
67
  members.append(contentsOf: accessorMethods(union: union))
@@ -229,31 +234,50 @@ private func validatedUnion(of declaration: some DeclGroupSyntax) throws -> Unio
229
234
  // MARK: - Synthesized members
230
235
 
231
236
  /// `decode(_:in:)`: tries each case's payload converter in declaration order and returns the first
232
- /// case that decodes. `try?` turns a candidate's failure into "try the next one" without erasing the
237
+ /// case that decodes. Only the overload taking a borrowed `JavaScriptUnownedValue` is emitted; the
238
+ /// overload taking an owning `JavaScriptValue` comes from the `JavaScriptDecodable` default, which
239
+ /// borrows the value and calls this one. Each payload decode receives the same borrowed value. The
240
+ /// value's kind is read once, and each candidate is gated on its payload's `decodableKinds`, so a case
241
+ /// that can't accept that kind is skipped with a mask test instead of a thrown and discarded error. `try?` turns a candidate's failure into "try the next one" without erasing the
233
242
  /// payload (each `payload` local keeps its concrete type); the candidate's own error is discarded, since
234
243
  /// with several alternatives there is no single failure to surface. When no alternative accepts the
235
244
  /// value the factory throws `Exceptions.UnionCaseMismatch`, naming the union, the JS kind of the value
236
- /// received, and every payload type the union accepts.
245
+ /// received, and every payload type the union accepts. `JavaScriptUnownedValue` has no `kind`, so the
246
+ /// error reads it from an owning copy; the copy is made only on this failure path.
237
247
  private func decodeMethod(union: UnionType) -> DeclSyntax {
238
- var lines: [String] = []
248
+ var lines: [String] = [" let kind = JavaScriptValueKinds(of: value)"]
239
249
  for unionCase in union.cases {
240
250
  let payloadType = expressionType(unionCase.payloadType)
241
- lines.append(" if let payload = try? \(payloadType).decode(value, in: runtime) {")
251
+ lines.append(" if \(payloadType).decodableKinds.contains(kind), let payload = try? \(payloadType).decode(value, in: runtime) {")
242
252
  lines.append(" return \(unionCase.construction)")
243
253
  lines.append(" }")
244
254
  }
245
255
  let expected = union.cases.map { "\"\($0.payloadType)\"" }.joined(separator: ", ")
246
- let mismatch = "(unionName: \"\(union.name)\", received: value.kind.rawValue, expected: [\(expected)])"
256
+ let mismatch = "(unionName: \"\(union.name)\", received: value.copied(in: runtime).kind.rawValue, expected: [\(expected)])"
247
257
  lines.append(" throw Exceptions.UnionCaseMismatch(\(mismatch))")
248
258
  let body = lines.joined(separator: "\n")
249
259
  return """
250
260
  @JavaScriptActor
251
- public static func decode(_ value: borrowing JavaScriptValue, in runtime: borrowing JavaScriptRuntime) throws -> Self {
261
+ public static func decode(_ value: borrowing JavaScriptUnownedValue, in runtime: borrowing JavaScriptRuntime) throws -> Self {
252
262
  \(raw: body)
253
263
  }
254
264
  """
255
265
  }
256
266
 
267
+ /// `decodableKinds`: the union of the payloads' kinds, so a union nested in another union, or wrapped
268
+ /// in an optional, is skipped as precisely as its payloads are.
269
+ private func decodableKindsProperty(union: UnionType) -> DeclSyntax {
270
+ let kinds = union.cases.map { "\(expressionType($0.payloadType)).decodableKinds" }
271
+ // An option set's array literal is the union of its elements, which reads like the case list. The
272
+ // optimizer folds it to a constant after specialization, the same as a chain of `union(_:)` calls.
273
+ let expression = kinds.count == 1 ? kinds[0] : "[\(kinds.joined(separator: ", "))]"
274
+ return """
275
+ public static var decodableKinds: JavaScriptValueKinds {
276
+ return \(raw: expression)
277
+ }
278
+ """
279
+ }
280
+
257
281
  /// `encode(_:in:)`: a `switch` over the cases, each encoding its payload through the payload type's
258
282
  /// own `encode`. Exhaustive by construction, so a case added later without re-expansion can't slip
259
283
  /// through silently.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-macros",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Swift macro plugin for Expo modules",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",