expo-modules-macros 0.24.0 → 0.26.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/README.md +3 -1
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Sources/ExpoModulesMacros/ClosureArguments.swift +28 -4
- package/apple/Sources/ExpoModulesMacros/DecorateModuleBuilder.swift +53 -40
- package/apple/Sources/ExpoModulesMacros/EventMacro.swift +23 -10
- package/apple/Sources/ExpoModulesMacros/ExpoModuleMacro.swift +13 -1
- package/apple/Sources/ExpoModulesMacros/JSMacro.swift +71 -54
- package/apple/Sources/ExpoModulesMacros/MacroHelpers.swift +11 -4
- package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +36 -10
- package/apple/Sources/ExpoModulesMacros/SharedObjectMacro.swift +13 -1
- package/apple/Sources/ExpoModulesMacros/TupleTypes.swift +234 -0
- package/apple/Sources/ExpoModulesMacros/TypeAliases.swift +75 -0
- package/apple/Sources/ExpoModulesMacros/TypeConformanceAssertion.swift +104 -41
- package/apple/Sources/ExpoModulesMacros/UnionMacro.swift +40 -4
- package/apple/Sources/ExpoModulesScanner/Exports/ExportedSurface.swift +3 -2
- package/apple/Sources/ExpoModulesScanner/Exports/ResolveRefs.swift +9 -0
- package/apple/Sources/ExpoModulesScanner/Exports/SurfaceVisitor.swift +10 -3
- package/apple/Sources/ExpoModulesScanner/Exports/TupleTypeAliases.swift +126 -0
- package/apple/Sources/ExpoModulesScanner/Exports/TypeNode.swift +47 -4
- package/build/types.d.ts +32 -1
- package/build/types.js +1 -1
- package/package.json +5 -5
|
@@ -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
|
|
@@ -269,9 +270,9 @@ private func validatedUnion(of declaration: some DeclGroupSyntax) throws -> Unio
|
|
|
269
270
|
private func decodeMethod(union: UnionType) -> DeclSyntax {
|
|
270
271
|
var lines: [String] = [" let kind = JavaScriptValueKinds(of: value)"]
|
|
271
272
|
for unionCase in union.cases {
|
|
272
|
-
let
|
|
273
|
+
let conversion = payloadConversion(unionCase)
|
|
273
274
|
lines.append(
|
|
274
|
-
" if \(
|
|
275
|
+
" if \(conversion.decodableKindsExpression).contains(kind), let payload = try? \(conversion.decodeExpression(of: "value")) {"
|
|
275
276
|
)
|
|
276
277
|
lines.append(" return \(unionCase.construction)")
|
|
277
278
|
lines.append(" }")
|
|
@@ -292,7 +293,7 @@ private func decodeMethod(union: UnionType) -> DeclSyntax {
|
|
|
292
293
|
/// `decodableKinds`: the union of the payloads' kinds, so a union nested in another union, or wrapped
|
|
293
294
|
/// in an optional, is skipped as precisely as its payloads are.
|
|
294
295
|
private func decodableKindsProperty(union: UnionType) -> DeclSyntax {
|
|
295
|
-
let kinds = union.cases.map {
|
|
296
|
+
let kinds = union.cases.map { payloadConversion($0).decodableKindsExpression }
|
|
296
297
|
// An option set's array literal is the union of its elements, which reads like the case list. The
|
|
297
298
|
// optimizer folds it to a constant after specialization, the same as a chain of `union(_:)` calls.
|
|
298
299
|
let expression = kinds.count == 1 ? kinds[0] : "[\(kinds.joined(separator: ", "))]"
|
|
@@ -310,7 +311,7 @@ private func encodeMethod(union: UnionType) -> DeclSyntax {
|
|
|
310
311
|
var lines: [String] = [" switch value {"]
|
|
311
312
|
for unionCase in union.cases {
|
|
312
313
|
lines.append(" case \(unionCase.pattern):")
|
|
313
|
-
lines.append(" return try \(
|
|
314
|
+
lines.append(" return try \(payloadConversion(unionCase).encodeExpression(of: "payload"))")
|
|
314
315
|
}
|
|
315
316
|
lines.append(" }")
|
|
316
317
|
let body = lines.joined(separator: "\n")
|
|
@@ -322,6 +323,35 @@ 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
|
+
if let tuple = tupleConversion(of: unionCase.payloadType) {
|
|
335
|
+
// A payload that holds a tuple has no `encode(_:into:in:)`, so its encoded value is moved in.
|
|
336
|
+
lines.append(" slot.emplace(try \(tuple.encodeExpression(of: "payload")))")
|
|
337
|
+
} else {
|
|
338
|
+
lines.append(" try \(expressionType(unionCase.payloadType)).encode(payload, into: slot, in: runtime)")
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
lines.append(" }")
|
|
342
|
+
let body = lines.joined(separator: "\n")
|
|
343
|
+
return """
|
|
344
|
+
@JavaScriptActor
|
|
345
|
+
public static func encode(
|
|
346
|
+
_ value: Self,
|
|
347
|
+
into slot: borrowing JavaScriptValue.Slot,
|
|
348
|
+
in runtime: borrowing JavaScriptRuntime
|
|
349
|
+
) throws {
|
|
350
|
+
\(raw: body)
|
|
351
|
+
}
|
|
352
|
+
"""
|
|
353
|
+
}
|
|
354
|
+
|
|
325
355
|
/// `as(_:)`: a typed accessor per case, selected by the payload's metatype rather than the case name,
|
|
326
356
|
/// so a caller that only knows the type it wants writes `try value.as(String.self)` and gets a `String`.
|
|
327
357
|
/// When
|
|
@@ -414,3 +444,9 @@ private struct UnionFixItMessage: FixItMessage {
|
|
|
414
444
|
self.fixItID = MessageID(domain: "ExpoModulesMacros", id: id)
|
|
415
445
|
}
|
|
416
446
|
}
|
|
447
|
+
|
|
448
|
+
/// How a case's payload converts: through the tuple methods when it holds a tuple (see
|
|
449
|
+
/// `TupleConversion`), and through its own `decode` / `encode` otherwise.
|
|
450
|
+
private func payloadConversion(_ unionCase: UnionCase) -> TupleConversion {
|
|
451
|
+
return tupleConversion(of: unionCase.payloadType) ?? .conforming(type: unionCase.payloadType)
|
|
452
|
+
}
|
|
@@ -463,8 +463,9 @@ struct ExportedSurface: Encodable, Equatable {
|
|
|
463
463
|
/// qualified names and gives a resolved ref the name of its declaration; version 8 reports
|
|
464
464
|
/// declarations in `#if` blocks, adds `platforms` (with deprecations), `accessLevel`, and
|
|
465
465
|
/// `defaultValue`, and replaces `constructorParameters` with `initializer`; version 9 adds the
|
|
466
|
-
/// `sharedRef` type kind and the `sharedRef` of a shared object
|
|
467
|
-
|
|
466
|
+
/// `sharedRef` type kind and the `sharedRef` of a shared object; version 10 adds the `tuple` and `object` type kinds (named after a tuple alias)
|
|
467
|
+
/// and resolves the tuple type aliases declared in `@ExpoModule` and `@SharedObject` classes.
|
|
468
|
+
let scanExportsSchemaVersion = 10
|
|
468
469
|
|
|
469
470
|
/// The `scan-exports` result: the surface plus the run's stats. A distinct envelope from
|
|
470
471
|
/// `ScanModulesResult` (different consumer: TS generation vs. autolinking).
|
|
@@ -136,6 +136,15 @@ extension TypeNode {
|
|
|
136
136
|
)
|
|
137
137
|
case .promise(let value):
|
|
138
138
|
return .promise(value: value.resolvingRefs(using: index, in: scope))
|
|
139
|
+
case .tuple(let elements, let name):
|
|
140
|
+
return .tuple(elements: elements.map { $0.resolvingRefs(using: index, in: scope) }, name: name)
|
|
141
|
+
case .object(let properties, let name):
|
|
142
|
+
return .object(
|
|
143
|
+
properties: properties.map {
|
|
144
|
+
TypeNodeProperty(name: $0.name, type: $0.type.resolvingRefs(using: index, in: scope))
|
|
145
|
+
},
|
|
146
|
+
name: name
|
|
147
|
+
)
|
|
139
148
|
case .function(let parameters, let returns, let isAsync, let isThrowing):
|
|
140
149
|
return .function(
|
|
141
150
|
parameters: parameters.map { $0.resolvingRefs(using: index, in: scope) },
|
|
@@ -142,13 +142,14 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
142
142
|
guard scope.isEmpty else {
|
|
143
143
|
return
|
|
144
144
|
}
|
|
145
|
+
let aliases = TupleTypeAliases(in: members, scope: qualified)
|
|
145
146
|
let (functions, properties, events, _) = collectJSMembers(members)
|
|
146
147
|
modules.append(
|
|
147
148
|
ExportedModule(
|
|
148
149
|
name: qualified,
|
|
149
150
|
jsName: stringArgument(of: attribute) ?? name,
|
|
150
|
-
functions: functions,
|
|
151
|
-
properties: properties,
|
|
151
|
+
functions: functions.map(aliases.resolving),
|
|
152
|
+
properties: properties.map(aliases.resolving),
|
|
152
153
|
events: events,
|
|
153
154
|
// `jsName` is filled in by `ExportedSurface.resolvingRefs()`, since the `@SharedObject` can
|
|
154
155
|
// be declared in another file.
|
|
@@ -164,7 +165,13 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
if let attribute = attributes.firstAttribute(named: DetectedMacro.sharedObject.rawValue) {
|
|
167
|
-
let (
|
|
168
|
+
let aliases = TupleTypeAliases(in: members, scope: qualified)
|
|
169
|
+
var (functions, properties, events, initializer) = collectJSMembers(members)
|
|
170
|
+
functions = functions.map(aliases.resolving)
|
|
171
|
+
properties = properties.map(aliases.resolving)
|
|
172
|
+
if let parameters = initializer?.parameters {
|
|
173
|
+
initializer?.parameters = parameters.map(aliases.resolving)
|
|
174
|
+
}
|
|
168
175
|
sharedObjects.append(
|
|
169
176
|
ExportedSharedObject(
|
|
170
177
|
name: qualified,
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import SwiftSyntax
|
|
2
|
+
|
|
3
|
+
/// The type aliases declared in the body of an `@ExpoModule` or `@SharedObject` class whose aliased type
|
|
4
|
+
/// holds a tuple, resolved to that type. Mirrors `TupleTypeAliases` in the macros, which resolve these
|
|
5
|
+
/// aliases in the `@JS` members the class binds, so that a member typed with such an alias reports the
|
|
6
|
+
/// tuple it converts as instead of an unresolvable ref.
|
|
7
|
+
///
|
|
8
|
+
/// Like the macros, it reads only the class body: an alias declared anywhere else stays a ref. A generic
|
|
9
|
+
/// alias isn't resolved, and neither is an alias in an `@Event` payload, which `@Event` converts on its
|
|
10
|
+
/// own.
|
|
11
|
+
struct TupleTypeAliases {
|
|
12
|
+
/// The resolved type of each alias, by name.
|
|
13
|
+
private let resolved: [String: TypeNode]
|
|
14
|
+
|
|
15
|
+
/// Collects the aliases in `members`, the body of the type named `scope`. A tuple alias keeps its name
|
|
16
|
+
/// qualified with `scope`, like the other declarations in the scan, so a consumer can name the type it
|
|
17
|
+
/// stands for.
|
|
18
|
+
init(in members: MemberBlockItemListSyntax, scope: String) {
|
|
19
|
+
var declared: [String: TypeNode] = [:]
|
|
20
|
+
for member in members {
|
|
21
|
+
guard let alias = member.decl.as(TypeAliasDeclSyntax.self), alias.genericParameterClause == nil else {
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
let name = alias.name.text
|
|
25
|
+
declared[name] = typeNode(from: alias.initializer.value).named("\(scope).\(name)")
|
|
26
|
+
}
|
|
27
|
+
// Aliases may refer to each other, so each one is resolved through the others. An alias that refers
|
|
28
|
+
// back to itself is invalid Swift, and is left as written after `declared.count` rounds.
|
|
29
|
+
var resolved = declared
|
|
30
|
+
for _ in 0..<declared.count {
|
|
31
|
+
resolved = resolved.mapValues { $0.replacingAliases(resolved) }
|
|
32
|
+
}
|
|
33
|
+
self.resolved = resolved.filter { $0.value.holdsTuple }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// `node` with each ref to an alias that holds a tuple replaced by the aliased type.
|
|
37
|
+
func resolving(_ node: TypeNode) -> TypeNode {
|
|
38
|
+
guard !resolved.isEmpty else {
|
|
39
|
+
return node
|
|
40
|
+
}
|
|
41
|
+
return node.replacingAliases(resolved)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func resolving(_ function: ExportedFunction) -> ExportedFunction {
|
|
45
|
+
var resolvedFunction = function
|
|
46
|
+
resolvedFunction.parameters = function.parameters.map(resolving)
|
|
47
|
+
resolvedFunction.returns = function.returns.map(resolving)
|
|
48
|
+
return resolvedFunction
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func resolving(_ property: ExportedProperty) -> ExportedProperty {
|
|
52
|
+
var resolvedProperty = property
|
|
53
|
+
resolvedProperty.type = property.type.map(resolving)
|
|
54
|
+
return resolvedProperty
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func resolving(_ parameter: ExportedParameter) -> ExportedParameter {
|
|
58
|
+
var resolvedParameter = parameter
|
|
59
|
+
resolvedParameter.type = resolving(parameter.type)
|
|
60
|
+
return resolvedParameter
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
extension TypeNode {
|
|
65
|
+
/// This node with each ref named in `aliases`, bare (`Span`) or qualified with `Self` (`Self.Span`),
|
|
66
|
+
/// replaced by the aliased type.
|
|
67
|
+
fileprivate func replacingAliases(_ aliases: [String: TypeNode]) -> TypeNode {
|
|
68
|
+
switch self {
|
|
69
|
+
case .primitive, .unknown, .sharedRef:
|
|
70
|
+
return self
|
|
71
|
+
case .ref(let name, _, _):
|
|
72
|
+
let unqualified = name.hasPrefix("Self.") ? String(name.dropFirst("Self.".count)) : name
|
|
73
|
+
return aliases[unqualified] ?? self
|
|
74
|
+
case .optional(let wrapped):
|
|
75
|
+
return .optional(wrapped: wrapped.replacingAliases(aliases))
|
|
76
|
+
case .array(let element):
|
|
77
|
+
return .array(element: element.replacingAliases(aliases))
|
|
78
|
+
case .dictionary(let key, let value):
|
|
79
|
+
return .dictionary(key: key.replacingAliases(aliases), value: value.replacingAliases(aliases))
|
|
80
|
+
case .promise(let value):
|
|
81
|
+
return .promise(value: value.replacingAliases(aliases))
|
|
82
|
+
case .tuple(let elements, let name):
|
|
83
|
+
return .tuple(elements: elements.map { $0.replacingAliases(aliases) }, name: name)
|
|
84
|
+
case .object(let properties, let name):
|
|
85
|
+
return .object(
|
|
86
|
+
properties: properties.map { TypeNodeProperty(name: $0.name, type: $0.type.replacingAliases(aliases)) },
|
|
87
|
+
name: name
|
|
88
|
+
)
|
|
89
|
+
case .function(let parameters, let returns, let isAsync, let isThrowing):
|
|
90
|
+
return .function(
|
|
91
|
+
parameters: parameters.map { $0.replacingAliases(aliases) },
|
|
92
|
+
returns: returns?.replacingAliases(aliases),
|
|
93
|
+
isAsync: isAsync,
|
|
94
|
+
isThrowing: isThrowing
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// This node named `name` when it's a tuple or an object, which an alias names. Any other node, such as
|
|
100
|
+
/// an array of tuples, is returned as it is: its elements carry the names of their own aliases.
|
|
101
|
+
fileprivate func named(_ name: String) -> TypeNode {
|
|
102
|
+
switch self {
|
|
103
|
+
case .tuple(let elements, _):
|
|
104
|
+
return .tuple(elements: elements, name: name)
|
|
105
|
+
case .object(let properties, _):
|
|
106
|
+
return .object(properties: properties, name: name)
|
|
107
|
+
default:
|
|
108
|
+
return self
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// True for a type the macros convert through the tuple methods: a tuple (`.tuple`, or `.object` for a
|
|
113
|
+
/// labeled one), or an optional, array or `String`-keyed dictionary that holds one.
|
|
114
|
+
fileprivate var holdsTuple: Bool {
|
|
115
|
+
switch self {
|
|
116
|
+
case .tuple, .object:
|
|
117
|
+
return true
|
|
118
|
+
case .optional(let inner), .array(let inner):
|
|
119
|
+
return inner.holdsTuple
|
|
120
|
+
case .dictionary(.primitive("String", _), let value):
|
|
121
|
+
return value.holdsTuple
|
|
122
|
+
default:
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -72,12 +72,22 @@ indirect enum TypeNode: Equatable {
|
|
|
72
72
|
/// node parsed at a use site has never seen.
|
|
73
73
|
case ref(name: String, refKind: RefKind? = nil, jsTypeOverride: JSType? = nil)
|
|
74
74
|
|
|
75
|
+
/// An unlabeled tuple of two or more elements, `(A, B)`, which crosses as an array with one value per
|
|
76
|
+
/// element, like a TypeScript tuple. `name` is the qualified name of the type alias it was written
|
|
77
|
+
/// through, such as `MyModule.Pair`, or `nil` when it was spelled out.
|
|
78
|
+
case tuple(elements: [TypeNode], name: String? = nil)
|
|
79
|
+
|
|
80
|
+
/// An object with a fixed set of properties, which a labeled tuple of two or more elements,
|
|
81
|
+
/// `(x: A, y: B)`, crosses as: one property per label. `name` is the qualified name of the type alias it
|
|
82
|
+
/// was written through, such as `MyModule.Span`, or `nil` when it was spelled out.
|
|
83
|
+
case object(properties: [TypeNodeProperty], name: String? = nil)
|
|
84
|
+
|
|
75
85
|
/// `SharedRef<T>` (or `ExpoModulesCore.SharedRef<T>`): a shared object holding a native `T`, which
|
|
76
86
|
/// JS sees as an instance of `expo.SharedRef`. `native` is `T` parsed like any other type, but it
|
|
77
87
|
/// never crosses the boundary itself, so refs in it are not resolved.
|
|
78
88
|
case sharedRef(native: TypeNode)
|
|
79
89
|
|
|
80
|
-
/// A spelling the parser doesn't model (generic parameter,
|
|
90
|
+
/// A spelling the parser doesn't model (generic parameter, metatype, …), kept verbatim so
|
|
81
91
|
/// nothing is lost. Has no `typeof`.
|
|
82
92
|
case unknown(text: String)
|
|
83
93
|
|
|
@@ -87,7 +97,7 @@ indirect enum TypeNode: Equatable {
|
|
|
87
97
|
switch self {
|
|
88
98
|
case .primitive(_, let jsType):
|
|
89
99
|
return jsType
|
|
90
|
-
case .array, .dictionary, .promise, .sharedRef:
|
|
100
|
+
case .array, .dictionary, .promise, .tuple, .object, .sharedRef:
|
|
91
101
|
return .object
|
|
92
102
|
case .ref(_, _, let jsTypeOverride):
|
|
93
103
|
// An unresolved ref, and every resolved one but an enum, is an object.
|
|
@@ -102,6 +112,12 @@ indirect enum TypeNode: Equatable {
|
|
|
102
112
|
}
|
|
103
113
|
}
|
|
104
114
|
|
|
115
|
+
/// A property of a `.object` node: its name, and the type of its value.
|
|
116
|
+
struct TypeNodeProperty: Encodable, Equatable {
|
|
117
|
+
let name: String
|
|
118
|
+
let type: TypeNode
|
|
119
|
+
}
|
|
120
|
+
|
|
105
121
|
extension TypeNode: Encodable {
|
|
106
122
|
private enum CodingKeys: String, CodingKey {
|
|
107
123
|
case refKind
|
|
@@ -110,7 +126,7 @@ extension TypeNode: Encodable {
|
|
|
110
126
|
// The `typeof` category. Spelled `typeof` in JSON (what the consumer reads); the Swift property is
|
|
111
127
|
// `jsType` since it's the cleaner Swift name.
|
|
112
128
|
case jsType = "typeof"
|
|
113
|
-
case wrapped, element, key, value, parameters, returns, text, native
|
|
129
|
+
case wrapped, element, key, value, parameters, returns, text, native, elements, properties
|
|
114
130
|
// A closure type's effects, matching `ExportedFunction`'s TS-keyword spellings.
|
|
115
131
|
case isAsync = "async"
|
|
116
132
|
case isThrowing = "throws"
|
|
@@ -148,6 +164,14 @@ extension TypeNode: Encodable {
|
|
|
148
164
|
try container.encode("ref", forKey: .kind)
|
|
149
165
|
try container.encode(name, forKey: .name)
|
|
150
166
|
try container.encodeIfPresent(refKind, forKey: .refKind)
|
|
167
|
+
case .tuple(let elements, let name):
|
|
168
|
+
try container.encode("tuple", forKey: .kind)
|
|
169
|
+
try container.encode(elements, forKey: .elements)
|
|
170
|
+
try container.encodeIfPresent(name, forKey: .name)
|
|
171
|
+
case .object(let properties, let name):
|
|
172
|
+
try container.encode("object", forKey: .kind)
|
|
173
|
+
try container.encode(properties, forKey: .properties)
|
|
174
|
+
try container.encodeIfPresent(name, forKey: .name)
|
|
151
175
|
case .sharedRef(let native):
|
|
152
176
|
try container.encode("sharedRef", forKey: .kind)
|
|
153
177
|
try container.encode(native, forKey: .native)
|
|
@@ -228,7 +252,22 @@ func typeNode(from type: TypeSyntax) -> TypeNode {
|
|
|
228
252
|
}
|
|
229
253
|
return .ref(name: member.trimmedDescription)
|
|
230
254
|
}
|
|
231
|
-
//
|
|
255
|
+
// `(A, B)` / `(x: A, y: B)`, matching the tuples the macros convert: two or more elements, all
|
|
256
|
+
// labeled or none, without a variadic or `inout` element.
|
|
257
|
+
if let tuple = type.as(TupleTypeSyntax.self), tuple.elements.count >= 2,
|
|
258
|
+
tuple.elements.allSatisfy({ $0.ellipsis == nil && $0.inoutKeyword == nil })
|
|
259
|
+
{
|
|
260
|
+
let labels = tuple.elements.compactMap { $0.firstName?.text }
|
|
261
|
+
if labels.isEmpty {
|
|
262
|
+
return .tuple(elements: tuple.elements.map { typeNode(from: $0.type) })
|
|
263
|
+
}
|
|
264
|
+
if labels.count == tuple.elements.count {
|
|
265
|
+
return .object(
|
|
266
|
+
properties: zip(labels, tuple.elements).map { TypeNodeProperty(name: $0, type: typeNode(from: $1.type)) }
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Other tuples, metatypes, some/any types, etc.: not modeled, preserve the verbatim spelling.
|
|
232
271
|
return .unknown(text: type.trimmedDescription)
|
|
233
272
|
}
|
|
234
273
|
|
|
@@ -316,6 +355,10 @@ extension TypeNode {
|
|
|
316
355
|
return "Promise<\(value.spelling)>"
|
|
317
356
|
case .sharedRef(let native):
|
|
318
357
|
return "SharedRef<\(native.spelling)>"
|
|
358
|
+
case .tuple(let elements, _):
|
|
359
|
+
return "(\(elements.map(\.spelling).joined(separator: ", ")))"
|
|
360
|
+
case .object(let properties, _):
|
|
361
|
+
return "(\(properties.map { "\($0.name): \($0.type.spelling)" }.joined(separator: ", ")))"
|
|
319
362
|
case .function(let parameters, let returns, _, _):
|
|
320
363
|
return "(\(parameters.map { $0.spelling }.joined(separator: ", "))) -> \(returns?.spelling ?? "Void")"
|
|
321
364
|
case .unknown(let text):
|
package/build/types.d.ts
CHANGED
|
@@ -61,6 +61,37 @@ export type TypeNode = {
|
|
|
61
61
|
name: string;
|
|
62
62
|
refKind?: 'record' | 'sharedObject' | 'enum' | 'union';
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* An unlabeled Swift tuple of two or more elements, `(A, B)`. It crosses as an array with one value per
|
|
66
|
+
* element, like a TypeScript tuple.
|
|
67
|
+
*/
|
|
68
|
+
| {
|
|
69
|
+
kind: 'tuple';
|
|
70
|
+
typeof: JSType;
|
|
71
|
+
elements: TypeNode[];
|
|
72
|
+
/**
|
|
73
|
+
* The qualified name of the type alias the tuple was written through, such as `MyModule.Pair`.
|
|
74
|
+
* Absent when the tuple is spelled out.
|
|
75
|
+
*/
|
|
76
|
+
name?: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* An object with a fixed set of properties. A labeled Swift tuple of two or more elements,
|
|
80
|
+
* `(x: A, y: B)`, crosses as one, with one property per label.
|
|
81
|
+
*/
|
|
82
|
+
| {
|
|
83
|
+
kind: 'object';
|
|
84
|
+
typeof: JSType;
|
|
85
|
+
properties: {
|
|
86
|
+
name: string;
|
|
87
|
+
type: TypeNode;
|
|
88
|
+
}[];
|
|
89
|
+
/**
|
|
90
|
+
* The qualified name of the type alias the labeled tuple was written through, such as
|
|
91
|
+
* `MyModule.Span`. Absent when the tuple is spelled out.
|
|
92
|
+
*/
|
|
93
|
+
name?: string;
|
|
94
|
+
}
|
|
64
95
|
/**
|
|
65
96
|
* `SharedRef<T>`: a shared object that JS sees as an instance of `expo.SharedRef`. `native` is the
|
|
66
97
|
* `T` it holds on the native side. It never reaches JS, so refs in it are not resolved.
|
|
@@ -389,4 +420,4 @@ export interface ScanExportsResult {
|
|
|
389
420
|
* as a clear error instead of silently misread fields.
|
|
390
421
|
*/
|
|
391
422
|
export declare const SUPPORTED_SCAN_MODULES_SCHEMA_VERSION = 2;
|
|
392
|
-
export declare const SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION =
|
|
423
|
+
export declare const SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION = 10;
|
package/build/types.js
CHANGED
|
@@ -12,4 +12,4 @@ exports.SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION = exports.SUPPORTED_SCAN_MODULES_S
|
|
|
12
12
|
* as a clear error instead of silently misread fields.
|
|
13
13
|
*/
|
|
14
14
|
exports.SUPPORTED_SCAN_MODULES_SCHEMA_VERSION = 2;
|
|
15
|
-
exports.SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION =
|
|
15
|
+
exports.SUPPORTED_SCAN_EXPORTS_SCHEMA_VERSION = 10;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-macros",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.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.26.0",
|
|
30
|
+
"@expo/modules-macros-linux-x64": "0.26.0",
|
|
31
|
+
"@expo/modules-macros-win32-arm64": "0.26.0",
|
|
32
|
+
"@expo/modules-macros-win32-x64": "0.26.0"
|
|
33
33
|
}
|
|
34
34
|
}
|