expo-modules-macros 0.33.0 → 0.35.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/.github/workflows/macos.yml +16 -3
- package/AGENTS.md +5 -2
- package/README.md +2 -2
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/SurfaceVisitor.swift +32 -1
- package/apple/Sources/ExpoModulesMacrosCore/Macros/ExpoModuleMacro.swift +6 -3
- package/apple/Sources/ExpoModulesMacrosCore/Macros/JSMacro.swift +174 -2
- package/apple/Sources/ExpoModulesMacrosCore/Macros/ViewPropsMacro.swift +176 -35
- package/apple/Sources/ExpoModulesMacrosCore/Support/DecorateModuleBuilder.swift +17 -3
- package/apple/Sources/ExpoModulesMacrosCore/Support/JSConstructor.swift +11 -2
- package/apple/Sources/ExpoModulesMacrosCore/Support/MacroHelpers.swift +21 -3
- package/package.json +5 -5
|
@@ -40,6 +40,19 @@ jobs:
|
|
|
40
40
|
- name: Resolve dependencies
|
|
41
41
|
run: swift package resolve
|
|
42
42
|
|
|
43
|
+
- name: Test
|
|
44
|
+
# Also builds the debug tool, which the next step uses.
|
|
45
|
+
run: swift test -v
|
|
46
|
+
|
|
47
|
+
- name: Compile the macro expansions
|
|
48
|
+
# The expansion tests compare text. This compiles and runs the expansions of the fixtures in
|
|
49
|
+
# `scripts/check-compile`, also with library evolution and with `-default-isolation MainActor`.
|
|
50
|
+
# It runs in the background during the release build, with the debug tool, which the release
|
|
51
|
+
# build doesn't change. `Wait for the compile check` fails the job when it fails.
|
|
52
|
+
id: check-compile
|
|
53
|
+
background: true
|
|
54
|
+
run: node scripts/check-compile.js .build/debug/ExpoModulesMacrosTool
|
|
55
|
+
|
|
43
56
|
- name: Build release tool
|
|
44
57
|
# Runs `pnpm run build` (scripts/build.js): builds the universal
|
|
45
58
|
# (arm64 + x86_64) release plugin binary, copies it to
|
|
@@ -58,9 +71,6 @@ jobs:
|
|
|
58
71
|
arch -arm64 apple/ExpoModulesMacros --help > /dev/null
|
|
59
72
|
arch -x86_64 apple/ExpoModulesMacros --help > /dev/null
|
|
60
73
|
|
|
61
|
-
- name: Test
|
|
62
|
-
run: swift test -v
|
|
63
|
-
|
|
64
74
|
- name: Typecheck the TypeScript wrapper
|
|
65
75
|
run: |
|
|
66
76
|
pnpm install
|
|
@@ -72,3 +82,6 @@ jobs:
|
|
|
72
82
|
run: |
|
|
73
83
|
pnpm run build:ts
|
|
74
84
|
node scripts/check-scan.js .
|
|
85
|
+
|
|
86
|
+
- name: Wait for the compile check
|
|
87
|
+
wait: check-compile
|
package/AGENTS.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- `src/`: the TypeScript wrapper around the scanner CLI. `types.ts` mirrors the Swift `Codable` output types by hand.
|
|
18
18
|
- `scripts/build.js` (`pnpm run build`): builds the release binaries: the universal macOS `apple/ExpoModulesMacros` (committed and published), and on Windows and Linux `platforms/win32-<arch>/ExpoModulesMacros.exe` or `platforms/linux-<arch>/ExpoModulesMacros` for the host architecture (not committed; published in that package).
|
|
19
19
|
- `scripts/swift-format.js` (`pnpm run swift:format`, `pnpm run swift:lint`): runs swift-format on the `.swift` files that Git tracks or doesn't ignore, with the configuration in `.swift-format`.
|
|
20
|
+
- `scripts/check-compile.js` (macOS only): compiles the `@ViewProps` expansions of the fixtures in `scripts/check-compile` with `swiftc` and a given plugin binary, and runs them, without a default isolation, with library evolution (also building a client from the `.swiftinterface`), and with `-default-isolation MainActor`. Warnings fail it. `Core.swift` holds stand-ins for the core and JSI symbols that the expansions use, `Props.swift` the props structs, and `main.swift` checks the decoded and reset values at run time.
|
|
20
21
|
|
|
21
22
|
## Commands
|
|
22
23
|
|
|
@@ -30,9 +31,10 @@ pnpm run typecheck # TypeScript wrapper
|
|
|
30
31
|
pnpm run build # release binary (slow; on macOS uses Rosetta for x86_64)
|
|
31
32
|
pnpm run swift:format # format the Swift sources in place
|
|
32
33
|
pnpm run swift:lint # check the formatting, as CI does
|
|
34
|
+
node scripts/check-compile.js .build/debug/ExpoModulesMacrosTool # compile and run expansions (macOS, after swift build)
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
CI runs on macOS (`.github/workflows/macos.yml`), on Windows x64 and arm64 (`.github/workflows/windows.yml`), and on Linux x64 and arm64 (`.github/workflows/linux.yml`). On macOS it runs the release build, checks both binary slices, runs `
|
|
37
|
+
CI runs on macOS (`.github/workflows/macos.yml`), on Windows x64 and arm64 (`.github/workflows/windows.yml`), and on Linux x64 and arm64 (`.github/workflows/linux.yml`). On macOS it runs `swift test`, starts `scripts/check-compile.js` with the debug tool as a background step, which compiles and runs the expansions of the fixtures, then runs the release build, checks both binary slices, runs `pnpm run typecheck`, scans through the TypeScript wrapper with `scripts/check-scan.js`, and waits for the compile check. On Windows it builds and tests the package, builds the release binary, checks that it starts without the Swift runtime, packs it into its `platforms/` package (checking the files and the `os`/`cpu` fields), installs that package, and scans through the TypeScript wrapper with `scripts/check-scan.js`, which must find the executable there. On Linux it does the same in the `swift:6.4.0-rhel-ubi9` image, and also checks that no Swift library is linked dynamically, the newest glibc symbol version that the binary needs, and that the package keeps the executable bit. On every platform, `check-scan.js` also requires `scanExports` to report the surface in `scripts/check-scan-exports.json` for its fixture, so the generated types are the same on each OS. The Swift Format workflow (`.github/workflows/swift-format.yml`) builds the pinned swift-format version and runs `pnpm run swift:lint`. Code in `Sources` and `Tests` must not assume Apple platforms or POSIX paths.
|
|
36
38
|
|
|
37
39
|
## Windows
|
|
38
40
|
|
|
@@ -58,13 +60,14 @@ CI runs on macOS (`.github/workflows/macos.yml`), on Windows x64 and arm64 (`.gi
|
|
|
58
60
|
## Changing macros
|
|
59
61
|
|
|
60
62
|
- The scanner re-implements the macros' naming and member rules on the syntax tree; it does not call the macro code. When a macro changes which members it binds or how it names them (the `@JS` name override, the `on` prefix `@Event` strips, which properties `@Record` treats as fields), change `scan-exports` to match, or generated TypeScript types drift from the runtime.
|
|
63
|
+
- The expansion tests compare text, so they don't show whether the generated code compiles. After a change to the code that `@ViewProps` generates, run `swift build` and `node scripts/check-compile.js .build/debug/ExpoModulesMacrosTool`. A new case that the generated code must handle gets a props struct in `scripts/check-compile/Props.swift`, and a check in `main.swift` when its values matter.
|
|
61
64
|
- When the `scan-exports` output changes on purpose, update `scripts/check-scan-exports.json`: run `swift build`, then `node scripts/check-scan.js . --update .build/debug/ExpoModulesMacrosTool`, and review the diff. CI compares it with the output on macOS, Windows and Linux.
|
|
62
65
|
- The scanner only parses files that pass the pre-filter, `MacroPrefilter` in `ExpoModulesCLI/Core/SourceScan.swift`. Each command builds it from the `macros` and `conformances` arguments of its `scanFiles` call. A new attribute or conformance the scanner must detect has to be added to those arguments (an attribute also needs a `DetectedMacro` case), or files that use it are skipped without an error.
|
|
63
66
|
- A new macro needs: the implementation in `apple/Sources/ExpoModulesMacrosCore/Macros`, an entry in `ProvidedMacro.all` in `ProvidedMacros.swift` (with the attribute name and the extension conformances of its declaration), an expansion test file, the `#externalMacro` declaration in `expo-modules-core`, and an entry in the macro list in `README.md`.
|
|
64
67
|
|
|
65
68
|
## Contracts with other packages
|
|
66
69
|
|
|
67
|
-
- **Macro declarations live in `expo-modules-core`** (`ios/Core/ExpoModulesMacros.swift` in `expo/expo`), as `#externalMacro(module: "ExpoModulesMacros", type: ...)`. Adding a macro, renaming a macro type or changing a macro's signature needs a matching edit there, and the type must be listed in `ProvidedMacro.all` in `ProvidedMacros.swift`, with the same attribute name and extension conformances as the declaration.
|
|
70
|
+
- **Macro declarations live in `expo-modules-core`** (`ios/Core/ExpoModulesMacros.swift` in `expo/expo`), as `#externalMacro(module: "ExpoModulesMacros", type: ...)`. Adding a macro, renaming a macro type or changing a macro's signature needs a matching edit there, and the type must be listed in `ProvidedMacro.all` in `ProvidedMacros.swift`, with the same attribute name and extension conformances as the declaration. `scripts/check-compile/Core.swift` copies the `@ViewProps` declaration and `AnyViewProps` (`ios/Core/Views/v2/AnyViewProps.swift`), so a change to them in core needs the same change there.
|
|
68
71
|
- **Generated code calls `expo-modules-core` API.** An expansion that uses a new core symbol only works with a core version that has it.
|
|
69
72
|
- **Scanner output is versioned.** `scanModulesSchemaVersion` (`Commands/ScanModules/ScanModules.swift`), `scanExportsSchemaVersion` (`Commands/ScanExports/ExportedSurface.swift`) and `expandSchemaVersion` (`Commands/Expand/ExpandReport.swift`) change independently. When the output shape of a command changes in a way that breaks a consumer, bump its version and update the matching `SUPPORTED_*_SCHEMA_VERSION` constant. A new field that older consumers can ignore doesn't need a bump, but still update the mirror types in `src/types.ts`. The TypeScript wrapper throws when a report's version differs from its constant. `expo-modules-autolinking` gets the `scan-modules` report through the wrapper, and when the call throws, it links only the modules declared in `expo-module.config.json`.
|
|
70
73
|
- **The binary path is part of the contract.** `expo-modules-autolinking` resolves this package from `expo-modules-core` and passes `-load-plugin-executable <package>/apple/<binary>#ExpoModulesMacros` to the compiler. The scanner wrapper in autolinking uses the same path, and `expo-modules-cli` uses `getScannerBinaryPath()`. On Windows the binary is `ExpoModulesMacros.exe` in `@expo/modules-macros-win32-<arch>`, and on Linux `ExpoModulesMacros` in `@expo/modules-macros-linux-<arch>`. Renaming or moving a binary needs a matching change in `expo/expo`.
|
package/README.md
CHANGED
|
@@ -25,13 +25,13 @@ This package lists them as optional dependencies, and their `os`, `cpu` and (on
|
|
|
25
25
|
# Macros
|
|
26
26
|
|
|
27
27
|
- **`@ExpoModule(_ name: String? = nil, classes: [Any.Type] = [])`** on a class. Turns the class into a module: binds its `@JS` members into the module's JavaScript object and resolves the module name from the argument, falling back to the class name. It also synthesizes everything inheriting from `Module` used to provide, so a module class can carry any superclass, or none. It adds an `@unchecked Sendable` conformance when the class isn't `Sendable` yet (see [Sendable conformance](#sendable-conformance)).
|
|
28
|
-
- **`@JS(_ jsName: String? = nil, _ options: JSOptions...)`** on a member of a module or shared object. Marks a function, property or initializer for export. The class macro reads only the class body, so `@JS` on a member of an extension, or of a type that is neither a module nor a shared object, is an error. So is `@JS` on an initializer or a `static` member of a module, since JavaScript doesn't construct a module and gets only its instance. `@JS` on any other declaration, such as a subscript, is an error too. A `@JS` function or initializer can't be generic, since the binding converts each value by its static type. `@ExpoModule` and `@SharedObject` bind each marked member straight into the JavaScript object, with the argument decoding, the call and the result encoding inlined, so there is no dynamic per-call path. The JS name defaults to the Swift name; pass a string to override it. Two `@JS` members with the same JS name on one JavaScript object are a compile-time error, since JavaScript could reach only one of them. A shared object's static and instance members bind onto different objects, so they can share a name. A name that JavaScript already gives a meaning on the object is an error too: `__proto__` on any object, `constructor` on a shared object's prototype, and `prototype` on its constructor. The macro also checks that every type crossing the boundary is convertible in the direction it travels. For a top-level declaration, a bad type is reported on the author's own declaration. In an `@ExpoModule` or `@SharedObject` class, the class macro makes the check, since only it sees the type aliases in the class body, so the error is reported on the class and names the member and its argument labels, such as `local function 'move(_:by:returning:)' requires that 'Point' conform to 'JavaScriptEncodable'`. A `lazy var` is read-only from JavaScript. On the first read, its getter stores the value as a data property on the JavaScript object, so later reads don't call into native code. On a frozen object the value isn't stored, and each read calls the getter. The `.concurrent` option moves an `async` body off the JavaScript thread while still decoding and encoding on it. The `.withAsync("name")` option binds a synchronous function a second time under that name, as a function that returns a promise and runs the body on the concurrent thread pool. That function isn't isolated to the JavaScript thread, so its body must be safe to run on any thread. Because of that, a `.concurrent` function and a function with `.withAsync` can't take a JavaScript value such as `JavaScriptValue` or `JavaScriptObject`, which is valid only on the JavaScript thread; the macro reports such a parameter. The macro reports a function isolated to a global actor through its own attribute or its type's. When the isolation comes from elsewhere, such as a protocol, the error points into the generated code, so mark the function `nonisolated`. A function or initializer can take a closure argument, such as `(Point) throws -> Size` or `(String) async throws -> Data`. JavaScript passes a function for it, and native code can store the closure and call it from any thread. A closure that throws or returns a value blocks the caller until JavaScript returns, and an `async` closure suspends instead and awaits a promise that the function returns. A closure that returns a value must be `throws`, so that an error from JavaScript can reach the caller. A non-throwing `Void` closure returns before JavaScript reads its arguments, so don't change a class instance after passing it to one.
|
|
28
|
+
- **`@JS(_ jsName: String? = nil, _ options: JSOptions...)`** on a member of a module or shared object. Marks a function, property or initializer for export. The class macro reads only the class body, so `@JS` on a member of an extension, or of a type that is neither a module nor a shared object, is an error. So is `@JS` on an initializer or a `static` member of a module, since JavaScript doesn't construct a module and gets only its instance. `@JS` on any other declaration, such as a subscript, is an error too. A `@JS` function or initializer can't be generic, since the binding converts each value by its static type. `@ExpoModule` and `@SharedObject` bind each marked member straight into the JavaScript object, with the argument decoding, the call and the result encoding inlined, so there is no dynamic per-call path. The JS name defaults to the Swift name; pass a string to override it. Two `@JS` members with the same JS name on one JavaScript object are a compile-time error, since JavaScript could reach only one of them. A shared object's static and instance members bind onto different objects, so they can share a name. A name that JavaScript already gives a meaning on the object is an error too: `__proto__` on any object, `constructor` on a shared object's prototype, and `prototype` on its constructor. The macro also checks that every type crossing the boundary is convertible in the direction it travels. For a top-level declaration, a bad type is reported on the author's own declaration. In an `@ExpoModule` or `@SharedObject` class, the class macro makes the check, since only it sees the type aliases in the class body, so the error is reported on the class and names the member and its argument labels, such as `local function 'move(_:by:returning:)' requires that 'Point' conform to 'JavaScriptEncodable'`. A `lazy var` is read-only from JavaScript. On the first read, its getter stores the value as a data property on the JavaScript object, so later reads don't call into native code. On a frozen object the value isn't stored, and each read calls the getter. The `.concurrent` option moves an `async` body off the JavaScript thread while still decoding and encoding on it. The `.withAsync("name")` option binds a synchronous function a second time under that name, as a function that returns a promise and runs the body on the concurrent thread pool. That function isn't isolated to the JavaScript thread, so its body must be safe to run on any thread. Because of that, a `.concurrent` function and a function with `.withAsync` can't take a JavaScript value such as `JavaScriptValue` or `JavaScriptObject`, which is valid only on the JavaScript thread; the macro reports such a parameter. The macro reports a function isolated to a global actor through its own attribute or its type's. When the isolation comes from elsewhere, such as a protocol, the error points into the generated code, so mark the function `nonisolated`. A function or initializer can take a closure argument, such as `(Point) throws -> Size` or `(String) async throws -> Data`. JavaScript passes a function for it, and native code can store the closure and call it from any thread. A closure that throws or returns a value blocks the caller until JavaScript returns, and an `async` closure suspends instead and awaits a promise that the function returns. A closure that returns a value must be `throws`, so that an error from JavaScript can reach the caller. A non-throwing `Void` closure returns before JavaScript reads its arguments, so don't change a class instance after passing it to one. A synchronous function or an initializer can take the JavaScript runtime as its last parameter, `in runtime: borrowing JavaScriptRuntime`, for example to create a JavaScript value. The binding passes the runtime of the call, and JavaScript doesn't see the parameter. The runtime can be used only on the JavaScript thread, and an `async` function can resume on another thread after an `await`, so the macro reports the parameter on an `async` function or a function with `.withAsync`, and when it isn't the last parameter, isn't labeled `in`, isn't `borrowing` or is optional. The macro finds the parameter by its type as written, so write it as `JavaScriptRuntime`: a typealias or a subclass is decoded as a JavaScript argument instead.
|
|
29
29
|
- **`@Event(_ name: String? = nil, _ options: EventOptions...)`** on a function-typed `var`. Expands the property into a closure that emits the event, so calling the property sends it to JavaScript with the closure's parameter as the payload. The JS name defaults to the property name with a leading `on` stripped. `@ExpoModule` and `@SharedObject` warn when two events in the class body have the same JS name. By default the event is scheduled on the JavaScript thread. The `.inline` option runs the listeners before the call returns when it is made on the JavaScript thread, and schedules the event otherwise; it never blocks. The `.blocking` option runs the listeners before the call returns on any thread, so a call from another thread blocks until the JavaScript thread runs them. An event property is callable from any thread with each option.
|
|
30
30
|
- **`@SharedObject(_ name: String? = nil)`** on a subclass of `SharedObject`, direct or not, such as a `SharedRef<T>` subclass. Binds the class's `@JS` members through the `SharedObject` hooks that core calls when it builds the JavaScript class, and emits the JS class name, which defaults to the class name. A class without an inheritance clause is an error, with a Fix-It that adds `: SharedObject`. A module exposes the class through `@ExpoModule(classes:)`, which only accepts `SharedObject` subclasses. A subclass of another `@SharedObject` class also gets the `@JS` members of that class, and its own members replace inherited members with the same JS name. A subclass that isn't `final` can't be constructed from JavaScript through the `@JS init` of its superclass, so give each subclass that JavaScript constructs its own `@JS init`. Like `@ExpoModule`, it adds an `@unchecked Sendable` conformance when the class isn't `Sendable` yet.
|
|
31
31
|
- **`@Record()`** on a struct, or on a final class with no inheritance clause (declare a class's protocol conformances in an extension). Treats every non-static, non-private, non-computed stored property as a field, with no per-field wrapper, and synthesizes the memberwise initializer, the conversions in both directions, and the `Record` conformance. A record converts only from and to JavaScript values: its `from(dictionary:appContext:)` throws `RecordDictionaryConversionException`, so a record can't be created from a native `[String: Any]`. A `@Union` can be a record property. Requiredness is inferred: a default value makes a field optional, an optional type makes it nullable and optional. The memberwise initializer sets every field, so a field that is a `let` with a default value is an error. It doesn't set a `private` or `fileprivate` stored property, so such a property needs a default value, unless it's an optional `var` or the type declares its own memberwise initializer.
|
|
32
32
|
- **`@Union()`** on an enum whose cases each carry one associated value. Models a TypeScript union. Synthesizes the conversions in both directions, a typed accessor per payload type, and the `JavaScriptDecodable` and `JavaScriptEncodable` conformances. Decoding is ordered: the first case whose payload decodes wins, so the more specific case goes first. The macro warns about a case that an earlier case always decodes first: any case after a `JavaScriptValue` case, and a case with a payload that decodes only numbers, such as `Float` or `Int32`, after a `Double`, `Float` or `CGFloat` case.
|
|
33
33
|
|
|
34
|
-
The members and conformances that `@Record` and `@
|
|
34
|
+
The members and conformances that `@Record`, `@Union` and `@ViewProps` generate are `nonisolated`, so they also work in a module that builds with a default isolation, such as `-default-isolation MainActor`. The stored properties of a record still take that isolation. A class record, or a struct record with a property whose type isn't `Sendable`, then needs `nonisolated` on its declaration, for example `@Record nonisolated final class Options`. A `@ViewProps` struct needs it when it has an event prop (a function type isn't `Sendable`), a value prop whose type isn't `Sendable`, or a computed prop, for example `@ViewProps nonisolated struct CardProps`, and its own `init()` must be `nonisolated` too. A conformance that the type declares itself also needs `nonisolated`, for example `struct Options: nonisolated Record`.
|
|
35
35
|
|
|
36
36
|
A tuple can cross the boundary as a `@JS` argument, result or property, a `@Record` property, a `@Union` payload, an `@Event` payload, or a parameter or result of a closure argument that isn't `async`. An unlabeled tuple such as `(Int, String)` maps to an array with one value per element, and a labeled tuple such as `(x: Double, y: Double)` to an object with one property per label. A tuple must have two or more elements, all labeled or none, and each element must be convertible. Tuples nest in other tuples, optionals, arrays and `String`-keyed dictionaries. `@ExpoModule` and `@SharedObject` also resolve a type alias of a tuple, such as `typealias Span = (start: Int, end: Int)`, when the class body declares it. A macro sees only the declaration it is attached to, so an alias declared anywhere else doesn't resolve.
|
|
37
37
|
|
package/apple/ExpoModulesMacros
CHANGED
|
Binary file
|
|
@@ -639,8 +639,10 @@ final class SurfaceVisitor: SyntaxVisitor {
|
|
|
639
639
|
|
|
640
640
|
/// Projects a parameter clause into `ExportedParameter`s: label = first name, name = second (else
|
|
641
641
|
/// first), `optional` when it has a default value or an optional type, and the default as written.
|
|
642
|
+
/// A parameter that takes the JavaScript runtime (its type is written as `JavaScriptRuntime`) is left
|
|
643
|
+
/// out: the binding passes the runtime itself, and JavaScript doesn't see the parameter.
|
|
642
644
|
private func parameters(of clause: FunctionParameterClauseSyntax) -> [ExportedParameter] {
|
|
643
|
-
clause.parameters.map { parameter in
|
|
645
|
+
clause.parameters.filter { !takesRuntime($0) }.map { parameter in
|
|
644
646
|
let label = parameter.firstName.identifierName
|
|
645
647
|
let name = parameter.secondName?.identifierName ?? label
|
|
646
648
|
return ExportedParameter(
|
|
@@ -800,6 +802,35 @@ private func underlyingFunctionType(of type: TypeSyntax?) -> FunctionTypeSyntax?
|
|
|
800
802
|
return type.as(FunctionTypeSyntax.self)
|
|
801
803
|
}
|
|
802
804
|
|
|
805
|
+
/// True when a parameter takes the JavaScript runtime: its type, without specifiers such as `borrowing`,
|
|
806
|
+
/// is written as `JavaScriptRuntime` or `….JavaScriptRuntime`, or as an optional of it. This matches
|
|
807
|
+
/// `isRuntimeParameter` in the macros: a type that also carries attributes (`@Sendable`) isn't one.
|
|
808
|
+
private func takesRuntime(_ parameter: FunctionParameterSyntax) -> Bool {
|
|
809
|
+
var type = parameter.type
|
|
810
|
+
if let attributed = type.as(AttributedTypeSyntax.self) {
|
|
811
|
+
guard attributed.attributes.isEmpty else {
|
|
812
|
+
return false
|
|
813
|
+
}
|
|
814
|
+
type = attributed.baseType
|
|
815
|
+
}
|
|
816
|
+
if let optional = type.as(OptionalTypeSyntax.self) {
|
|
817
|
+
type = optional.wrappedType
|
|
818
|
+
} else if let implicitlyUnwrapped = type.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) {
|
|
819
|
+
type = implicitlyUnwrapped.wrappedType
|
|
820
|
+
} else if let identifier = type.as(IdentifierTypeSyntax.self), identifier.name.text == "Optional",
|
|
821
|
+
let argument = identifier.genericArgumentClause?.arguments.first, case .type(let wrapped) = argument.argument
|
|
822
|
+
{
|
|
823
|
+
type = wrapped
|
|
824
|
+
}
|
|
825
|
+
if let identifier = type.as(IdentifierTypeSyntax.self) {
|
|
826
|
+
return identifier.name.text == "JavaScriptRuntime"
|
|
827
|
+
}
|
|
828
|
+
if let member = type.as(MemberTypeSyntax.self) {
|
|
829
|
+
return member.name.text == "JavaScriptRuntime"
|
|
830
|
+
}
|
|
831
|
+
return false
|
|
832
|
+
}
|
|
833
|
+
|
|
803
834
|
/// True when an event's function type returns `Void`. The shared `isVoidType` is not reused: it
|
|
804
835
|
/// accepts neither `Swift.Void` nor a parenthesized `(Void)`, so it would drop valid events.
|
|
805
836
|
private func isVoidEventReturn(_ type: TypeSyntax) -> Bool {
|
|
@@ -119,11 +119,14 @@ public struct ExpoModuleMacro: MemberMacro {
|
|
|
119
119
|
// View classes are registered by type, not spliced as definitions: an `@ExpoView` emits no
|
|
120
120
|
// definition function, so there is nothing to call on each entry. Core reads a view's props
|
|
121
121
|
// type from its `Props` typealias and its event names from that type's `_eventNames`, both
|
|
122
|
-
// static.
|
|
122
|
+
// static. The element type makes the compiler reject a class that isn't an `@ExpoView`, whose
|
|
123
|
+
// macro adds the `AnyNativeView` conformance. Each entry is coerced on its own, because for an array
|
|
124
|
+
// literal of existential metatypes the compiler fails to produce a diagnostic. Emitted only when
|
|
125
|
+
// the argument is present, so a module without views gains nothing.
|
|
123
126
|
let viewTypes = classListArgument(of: node, label: "views")
|
|
124
127
|
if !viewTypes.isEmpty {
|
|
125
|
-
let entries = viewTypes.map { "\($0).self" }.joined(separator: ", ")
|
|
126
|
-
emitted.append("public static let _viewTypes: [
|
|
128
|
+
let entries = viewTypes.map { "\($0).self as any AnyNativeView.Type" }.joined(separator: ", ")
|
|
129
|
+
emitted.append("public static let _viewTypes: [any AnyNativeView.Type] = [\(raw: entries)]")
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
// `Module`/`BaseModule` already provide `appContext` storage and the
|
|
@@ -51,6 +51,7 @@ public struct JSMacro: PeerMacro {
|
|
|
51
51
|
diagnoseConcurrentOption(of: node, on: declaration, in: context)
|
|
52
52
|
diagnoseAsyncVariantOption(of: node, on: declaration, in: context)
|
|
53
53
|
diagnoseJavaScriptValuesOffThread(of: node, on: declaration, in: context)
|
|
54
|
+
diagnoseRuntimeParameter(of: node, on: declaration, in: context)
|
|
54
55
|
|
|
55
56
|
// In an `@ExpoModule` or `@SharedObject` class, the class macro emits the assertion instead: it sees
|
|
56
57
|
// the type aliases declared in the class body, which this macro can't (see `TupleTypeAliases`).
|
|
@@ -425,6 +426,175 @@ private func diagnoseJavaScriptValuesOffThread(
|
|
|
425
426
|
}
|
|
426
427
|
}
|
|
427
428
|
|
|
429
|
+
/// Emits the diagnostics for a parameter that takes the JavaScript runtime (see `isRuntimeParameter`).
|
|
430
|
+
/// The binding passes the runtime of the call, which can be used only on the JavaScript thread. So
|
|
431
|
+
/// the parameter must be:
|
|
432
|
+
/// - in a synchronous function or an initializer. An `async` function can resume on another thread
|
|
433
|
+
/// after an `await`: `@JavaScriptActor` runs a job on the thread that enqueues it, and doesn't move
|
|
434
|
+
/// it to the JavaScript thread. A `.concurrent` function and the variant that `.withAsync` adds run
|
|
435
|
+
/// on the concurrent thread pool;
|
|
436
|
+
/// - `borrowing`, so the body can't keep the runtime for later without an explicit `copy`;
|
|
437
|
+
/// - not optional, since the binding always has a runtime to pass;
|
|
438
|
+
/// - the last parameter, labeled `in` (or `runtime`), so every declaration reads the same and the JavaScript
|
|
439
|
+
/// arguments keep their order.
|
|
440
|
+
private func diagnoseRuntimeParameter(
|
|
441
|
+
of node: AttributeSyntax,
|
|
442
|
+
on declaration: some DeclSyntaxProtocol,
|
|
443
|
+
in context: some MacroExpansionContext
|
|
444
|
+
) {
|
|
445
|
+
let parameters: FunctionParameterListSyntax
|
|
446
|
+
if let funcDecl = declaration.as(FunctionDeclSyntax.self) {
|
|
447
|
+
parameters = funcDecl.signature.parameterClause.parameters
|
|
448
|
+
// `.concurrent` is checked first: its body never runs on the JavaScript thread, so making the
|
|
449
|
+
// function synchronous doesn't help (a synchronous `.concurrent` function is an error too).
|
|
450
|
+
let offThread: (reason: String, remedy: String)?
|
|
451
|
+
if hasOption(node, named: "concurrent") {
|
|
452
|
+
offThread = ("'.concurrent' runs the body on the concurrent thread pool", "Remove the parameter.")
|
|
453
|
+
} else if funcDecl.signature.effectSpecifiers?.asyncSpecifier != nil {
|
|
454
|
+
offThread = (
|
|
455
|
+
"an 'async' function can resume on another thread after an 'await'",
|
|
456
|
+
"Remove the parameter, or make the function synchronous."
|
|
457
|
+
)
|
|
458
|
+
} else if asyncVariantOption(of: node) != nil {
|
|
459
|
+
offThread = (
|
|
460
|
+
"the variant that '.withAsync' adds runs the function on the concurrent thread pool",
|
|
461
|
+
"Remove the parameter, or remove '.withAsync'."
|
|
462
|
+
)
|
|
463
|
+
} else {
|
|
464
|
+
offThread = nil
|
|
465
|
+
}
|
|
466
|
+
if let offThread {
|
|
467
|
+
for parameter in parameters where isRuntimeParameter(parameter) {
|
|
468
|
+
context.diagnose(
|
|
469
|
+
Diagnostic(
|
|
470
|
+
node: parameter.type,
|
|
471
|
+
message: JSDiagnosticMessage(
|
|
472
|
+
"A @JS function can take the JavaScript runtime only when it's synchronous: \(offThread.reason), and the runtime can be used only on the JavaScript thread. \(offThread.remedy)",
|
|
473
|
+
id: "js-runtime-parameter-async",
|
|
474
|
+
severity: .error
|
|
475
|
+
)
|
|
476
|
+
)
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
} else if let initDecl = declaration.as(InitializerDeclSyntax.self) {
|
|
482
|
+
parameters = initDecl.signature.parameterClause.parameters
|
|
483
|
+
} else {
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
for (index, parameter) in parameters.enumerated() where isRuntimeParameter(parameter) {
|
|
487
|
+
guard index == parameters.count - 1 else {
|
|
488
|
+
context.diagnose(
|
|
489
|
+
Diagnostic(
|
|
490
|
+
node: parameter,
|
|
491
|
+
message: JSDiagnosticMessage(
|
|
492
|
+
"The JavaScript runtime must be the last parameter of a @JS function or initializer, and only one parameter can take it. Move it to the end as 'in runtime: borrowing JavaScriptRuntime'.",
|
|
493
|
+
id: "js-runtime-parameter-position",
|
|
494
|
+
severity: .error
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
continue
|
|
499
|
+
}
|
|
500
|
+
// The errors and the docs name only `in`, which matches the JSI API, such as `encode(_:into:in:)`.
|
|
501
|
+
// An escaped label (`` `in` ``) is the same label.
|
|
502
|
+
if !runtimeParameterLabels.contains(withoutBackticks(parameter.firstName.text)) {
|
|
503
|
+
context.diagnose(
|
|
504
|
+
Diagnostic(
|
|
505
|
+
node: parameter.firstName,
|
|
506
|
+
message: JSDiagnosticMessage(
|
|
507
|
+
"The parameter that takes the JavaScript runtime must be labeled 'in', as in 'in runtime: borrowing JavaScriptRuntime'.",
|
|
508
|
+
id: "js-runtime-parameter-label",
|
|
509
|
+
severity: .error
|
|
510
|
+
),
|
|
511
|
+
fixIts: [runtimeLabelFixIt(for: parameter)]
|
|
512
|
+
)
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
let specified = SpecifiedType(parameter.type)
|
|
516
|
+
let wrapped = optionalWrappedType(specified.type)
|
|
517
|
+
if let wrapped {
|
|
518
|
+
context.diagnose(
|
|
519
|
+
Diagnostic(
|
|
520
|
+
node: specified.type,
|
|
521
|
+
message: JSDiagnosticMessage(
|
|
522
|
+
"The parameter that takes the JavaScript runtime can't be optional: the binding always passes the runtime of the call.",
|
|
523
|
+
id: "js-runtime-parameter-optional",
|
|
524
|
+
severity: .error
|
|
525
|
+
),
|
|
526
|
+
fixIts: [
|
|
527
|
+
FixIt(
|
|
528
|
+
message: JSFixItMessage("Remove the optional", id: "js-runtime-parameter-remove-optional"),
|
|
529
|
+
changes: [
|
|
530
|
+
.replace(
|
|
531
|
+
oldNode: Syntax(specified.type),
|
|
532
|
+
newNode: Syntax(
|
|
533
|
+
wrapped.trimmed
|
|
534
|
+
.with(\.leadingTrivia, specified.type.leadingTrivia)
|
|
535
|
+
.with(\.trailingTrivia, specified.type.trailingTrivia)
|
|
536
|
+
)
|
|
537
|
+
)
|
|
538
|
+
]
|
|
539
|
+
)
|
|
540
|
+
]
|
|
541
|
+
)
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
if specified.specifiers != ["borrowing"] {
|
|
545
|
+
context.diagnose(
|
|
546
|
+
Diagnostic(
|
|
547
|
+
node: parameter.type,
|
|
548
|
+
message: JSDiagnosticMessage(
|
|
549
|
+
"The parameter that takes the JavaScript runtime must be 'borrowing': the runtime can be used only on the JavaScript thread, and a borrowed parameter can't be kept for later without an explicit 'copy'.",
|
|
550
|
+
id: "js-runtime-parameter-borrowing",
|
|
551
|
+
severity: .error
|
|
552
|
+
),
|
|
553
|
+
fixIts: [
|
|
554
|
+
FixIt(
|
|
555
|
+
message: JSFixItMessage("Mark the parameter 'borrowing'", id: "js-runtime-parameter-insert-borrowing"),
|
|
556
|
+
changes: [
|
|
557
|
+
.replace(
|
|
558
|
+
oldNode: Syntax(parameter.type),
|
|
559
|
+
newNode: Syntax(borrowingType(wrapped ?? specified.type, like: parameter.type))
|
|
560
|
+
)
|
|
561
|
+
]
|
|
562
|
+
)
|
|
563
|
+
]
|
|
564
|
+
)
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/// The labels that the runtime parameter can have.
|
|
571
|
+
private let runtimeParameterLabels: Set<String> = ["in", "runtime"]
|
|
572
|
+
|
|
573
|
+
/// The fix-it that labels the runtime parameter `in`. It keeps the parameter name that the body uses, or
|
|
574
|
+
/// names the parameter `runtime` when it has no name.
|
|
575
|
+
private func runtimeLabelFixIt(for parameter: FunctionParameterSyntax) -> FixIt {
|
|
576
|
+
var newParameter = parameter
|
|
577
|
+
let name = parameter.secondName ?? (parameter.firstName.tokenKind == .wildcard ? nil : parameter.firstName)
|
|
578
|
+
newParameter.firstName = .keyword(.in, leadingTrivia: parameter.firstName.leadingTrivia, trailingTrivia: .space)
|
|
579
|
+
newParameter.secondName = name?.trimmed ?? .identifier("runtime")
|
|
580
|
+
return FixIt(
|
|
581
|
+
message: JSFixItMessage("Label the parameter 'in'", id: "js-runtime-parameter-label"),
|
|
582
|
+
changes: [.replace(oldNode: Syntax(parameter), newNode: Syntax(newParameter))]
|
|
583
|
+
)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/// The type `type` with the single specifier `borrowing`, with the trivia of `original`.
|
|
587
|
+
private func borrowingType(_ type: TypeSyntax, like original: TypeSyntax) -> TypeSyntax {
|
|
588
|
+
let specifier = SimpleTypeSpecifierSyntax(specifier: .keyword(.borrowing, trailingTrivia: .space))
|
|
589
|
+
let attributed = AttributedTypeSyntax(
|
|
590
|
+
specifiers: [.simpleTypeSpecifier(specifier)],
|
|
591
|
+
baseType: type.trimmed
|
|
592
|
+
)
|
|
593
|
+
return TypeSyntax(attributed)
|
|
594
|
+
.with(\.leadingTrivia, original.leadingTrivia)
|
|
595
|
+
.with(\.trailingTrivia, original.trailingTrivia)
|
|
596
|
+
}
|
|
597
|
+
|
|
428
598
|
private func diagnoseParameterSpecifiers(
|
|
429
599
|
in declaration: some DeclSyntaxProtocol,
|
|
430
600
|
in context: some MacroExpansionContext
|
|
@@ -437,7 +607,8 @@ private func diagnoseParameterSpecifiers(
|
|
|
437
607
|
} else {
|
|
438
608
|
return
|
|
439
609
|
}
|
|
440
|
-
|
|
610
|
+
// The runtime parameter isn't decoded, and `diagnoseRuntimeParameter` requires it to be `borrowing`.
|
|
611
|
+
for parameter in parameters where !isRuntimeParameter(parameter) {
|
|
441
612
|
guard let specifier = SpecifiedType(parameter.type).unsupportedSpecifier else {
|
|
442
613
|
continue
|
|
443
614
|
}
|
|
@@ -608,7 +779,8 @@ private func argumentBoundaryTypes(of parameters: FunctionParameterListSyntax) -
|
|
|
608
779
|
// A closure in any other position is diagnosed by `diagnoseClosureTypes` and left out here, since
|
|
609
780
|
// its metatype isn't a valid expression in the assertion.
|
|
610
781
|
var types: [AssertedType] = []
|
|
611
|
-
|
|
782
|
+
// The runtime parameter takes no JavaScript argument, so nothing decodes into it.
|
|
783
|
+
for parameter in parameters where !isRuntimeParameter(parameter) {
|
|
612
784
|
let label = parameter.firstName.tokenKind == .wildcard ? nil : parameter.firstName.text
|
|
613
785
|
// The specifier isn't part of the asserted type, and a parameter with one the binding can't honor
|
|
614
786
|
// is reported by `diagnoseParameterSpecifiers` and not asserted.
|
|
@@ -39,6 +39,10 @@ import SwiftSyntaxMacros
|
|
|
39
39
|
/// event without an initial value an empty closure (`{}`, `{ _ in }`, ...) and leaves the value
|
|
40
40
|
/// props at their initial values. It is never generated when the type declares its own `init()`.
|
|
41
41
|
/// Like any initializer in the struct body, the generated one removes the memberwise initializer.
|
|
42
|
+
/// - `_resetViewProp(_:in:)`: sets one value prop back to its default, by assigning the property's
|
|
43
|
+
/// initial value or `nil`, so a reset evaluates only that prop's default. It is a static member of
|
|
44
|
+
/// the struct body, because an initial value can name a static member without `Self.` or a member of
|
|
45
|
+
/// an enclosing type, which an instance method or the extension can't.
|
|
42
46
|
///
|
|
43
47
|
/// An event's function type must return `Void`, because JavaScript can't return a value to it.
|
|
44
48
|
///
|
|
@@ -47,7 +51,16 @@ import SwiftSyntaxMacros
|
|
|
47
51
|
/// - `allProps`: every bit set, the changed set on the first application.
|
|
48
52
|
/// - `propSet(for:)`: one name's bit, for folding raw changed keys into the mask.
|
|
49
53
|
/// - `_decode(_:from:in:)`: assigns one value prop from a JavaScript value. `null` and `undefined`
|
|
50
|
-
/// reset it
|
|
54
|
+
/// reset it with `_resetViewProp(_:in:)`, and any other value decodes through
|
|
55
|
+
/// `JavaScriptDecodable`.
|
|
56
|
+
///
|
|
57
|
+
/// `_decode` and `_resetViewProp` run on the JavaScript actor. The other generated types and members
|
|
58
|
+
/// and the conformance are `nonisolated`, so core can use them on any thread also when the module
|
|
59
|
+
/// builds with a default isolation, such as `-default-isolation MainActor`. The props' own
|
|
60
|
+
/// declarations still take that isolation, so a struct with an event prop, a value prop whose type
|
|
61
|
+
/// isn't `Sendable`, or a computed prop needs `nonisolated` on its declaration, and the author's
|
|
62
|
+
/// `init()` must be `nonisolated` too. So must a conformance that the author declares
|
|
63
|
+
/// (`: nonisolated AnyViewProps`).
|
|
51
64
|
///
|
|
52
65
|
/// Author-facing shape — no conformance to spell out:
|
|
53
66
|
///
|
|
@@ -83,6 +96,9 @@ public struct ViewPropsMacro: MemberMacro, ExtensionMacro {
|
|
|
83
96
|
if let initializer = defaultInitializer(props: props) {
|
|
84
97
|
members.append(initializer)
|
|
85
98
|
}
|
|
99
|
+
if let reset = resetFunction(valueProps: props.valueProps) {
|
|
100
|
+
members.append(reset)
|
|
101
|
+
}
|
|
86
102
|
return members
|
|
87
103
|
}
|
|
88
104
|
|
|
@@ -120,29 +136,33 @@ public struct ViewPropsMacro: MemberMacro, ExtensionMacro {
|
|
|
120
136
|
? "0"
|
|
121
137
|
: "0b" + String(repeating: "1", count: props.valueProps.count)
|
|
122
138
|
|
|
123
|
-
// With no value prop
|
|
139
|
+
// With no value prop, `PropName` has no case, so the function can't be called and its body is
|
|
140
|
+
// empty, like the one of `_decode`. Swift reports any statement in it, also an empty `switch`, as
|
|
141
|
+
// "will never be executed". The body starts with a line break, so the empty one closes on the
|
|
142
|
+
// next line.
|
|
124
143
|
let propSetBody: String
|
|
125
144
|
if props.valueProps.isEmpty {
|
|
126
|
-
propSetBody = "
|
|
145
|
+
propSetBody = ""
|
|
127
146
|
} else {
|
|
128
147
|
let cases = props.valueProps
|
|
129
148
|
.map { " case .\($0.name):\n return .\($0.name)" }
|
|
130
149
|
.joined(separator: "\n")
|
|
131
|
-
propSetBody = " switch name {\n\(cases)\n }"
|
|
150
|
+
propSetBody = "\n switch name {\n\(cases)\n }"
|
|
132
151
|
}
|
|
133
152
|
|
|
134
153
|
let decodeBody = decodeFunctionBody(valueProps: props.valueProps)
|
|
135
154
|
|
|
136
|
-
|
|
155
|
+
// `nonisolated` keeps the conformance and its members usable on the JavaScript thread when the
|
|
156
|
+
// module builds with a default isolation, which would otherwise isolate them to the main actor.
|
|
157
|
+
let conformanceClause = alreadyConforms ? "" : ": nonisolated \(viewPropsProtocolName)"
|
|
137
158
|
let ext: DeclSyntax = """
|
|
138
159
|
extension \(type.trimmed)\(raw: conformanceClause) {
|
|
139
|
-
public static let allProps = PropSet(rawValue: \(raw: allPropsMask))
|
|
160
|
+
nonisolated public static let allProps = PropSet(rawValue: \(raw: allPropsMask))
|
|
140
161
|
|
|
141
162
|
/// `@inlinable` so core's raw-key fold can inline the lookup across the module boundary:
|
|
142
163
|
/// it runs once per changed key per props batch.
|
|
143
164
|
@inlinable
|
|
144
|
-
public static func propSet(for name: PropName) -> PropSet {
|
|
145
|
-
\(raw: propSetBody)
|
|
165
|
+
nonisolated public static func propSet(for name: PropName) -> PropSet {\(raw: propSetBody)
|
|
146
166
|
}
|
|
147
167
|
|
|
148
168
|
@JavaScriptActor
|
|
@@ -488,9 +508,11 @@ private struct ViewProp {
|
|
|
488
508
|
/// The property's declared type, verbatim, or the type of its literal initial value. `_decode`
|
|
489
509
|
/// doesn't spell it: the decoded type is inferred from the assignment.
|
|
490
510
|
let type: String
|
|
491
|
-
/// Whether the prop is computed with a `mutating get`, which `
|
|
511
|
+
/// Whether the prop is computed with a `mutating get`, which `_resetViewProp` can only read from a
|
|
492
512
|
/// mutable value.
|
|
493
513
|
let hasMutatingGetter: Bool
|
|
514
|
+
/// What `_resetViewProp` assigns when React removes the prop. Events aren't reset.
|
|
515
|
+
let defaultValue: PropDefault
|
|
494
516
|
|
|
495
517
|
/// The name with any escaping backticks removed: the JS-visible key, so the enum's raw value and
|
|
496
518
|
/// `_eventNames` both read `"default"`, never `` "`default`" ``.
|
|
@@ -499,6 +521,49 @@ private struct ViewProp {
|
|
|
499
521
|
}
|
|
500
522
|
}
|
|
501
523
|
|
|
524
|
+
/// The value that a value prop goes back to when React removes it. Each one equals the prop's value
|
|
525
|
+
/// in `Self()`.
|
|
526
|
+
private enum PropDefault {
|
|
527
|
+
/// The initial value of a stored property, as written. Evaluating it again gives the value that
|
|
528
|
+
/// `Self()` gives the property.
|
|
529
|
+
case initialValue(ExprSyntax)
|
|
530
|
+
/// The implicit `nil` of a `var` whose type is written as `T?` or `T!`.
|
|
531
|
+
case implicitNil
|
|
532
|
+
/// The prop's value in a new `Self()`, when the macro can't tell it from the declaration: the
|
|
533
|
+
/// struct declares its own `init()`, which can set any value, the prop is computed, the property
|
|
534
|
+
/// has an attribute, such as a property wrapper, whose value after `init(wrappedValue:)` can differ
|
|
535
|
+
/// from its initial value, or the initial value has a `#` expression, such as `#function` or
|
|
536
|
+
/// `#line`, whose value depends on where it's evaluated.
|
|
537
|
+
case newInstance
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/// The default of a property with the given binding, for a struct that does or doesn't declare its own
|
|
541
|
+
/// `init()`.
|
|
542
|
+
private func propDefault(
|
|
543
|
+
of binding: PatternBindingSyntax,
|
|
544
|
+
in varDecl: VariableDeclSyntax,
|
|
545
|
+
declaredType: TypeSyntax?,
|
|
546
|
+
declaresUsableInitializer: Bool
|
|
547
|
+
) -> PropDefault {
|
|
548
|
+
if declaresUsableInitializer || !isStoredBinding(binding) || !varDecl.attributes.isEmpty {
|
|
549
|
+
return .newInstance
|
|
550
|
+
}
|
|
551
|
+
if let initializer = binding.initializer {
|
|
552
|
+
// A macro expansion, also a literal such as `#function`, can depend on where it's evaluated, so
|
|
553
|
+
// a copy in `_resetViewProp` could give another value.
|
|
554
|
+
let hasPoundExpression = initializer.value.tokens(viewMode: .sourceAccurate).contains {
|
|
555
|
+
$0.tokenKind == .pound
|
|
556
|
+
}
|
|
557
|
+
return hasPoundExpression ? .newInstance : .initialValue(initializer.value)
|
|
558
|
+
}
|
|
559
|
+
if varDecl.bindingSpecifier.tokenKind == .keyword(.var), let declaredType, hasImplicitNilDefault(declaredType) {
|
|
560
|
+
return .implicitNil
|
|
561
|
+
}
|
|
562
|
+
// A value prop without a default is reported, so this is only reached for an event, which isn't
|
|
563
|
+
// reset.
|
|
564
|
+
return .newInstance
|
|
565
|
+
}
|
|
566
|
+
|
|
502
567
|
/// An event prop declared without an initial value, which the generated `init()` assigns.
|
|
503
568
|
private struct EventDefault {
|
|
504
569
|
/// The property name as written, backticks included.
|
|
@@ -664,7 +729,17 @@ private func validatedViewProps(
|
|
|
664
729
|
// Only stored storage takes an initial value; a computed prop with a setter needs none.
|
|
665
730
|
let needsInitialValue =
|
|
666
731
|
binding.initializer == nil && isStoredBinding(binding) && !declaresUsableInitializer
|
|
667
|
-
let prop = ViewProp(
|
|
732
|
+
let prop = ViewProp(
|
|
733
|
+
name: name,
|
|
734
|
+
type: resolvedType,
|
|
735
|
+
hasMutatingGetter: hasMutatingGetter(binding),
|
|
736
|
+
defaultValue: propDefault(
|
|
737
|
+
of: binding,
|
|
738
|
+
in: varDecl,
|
|
739
|
+
declaredType: declaredType,
|
|
740
|
+
declaresUsableInitializer: declaresUsableInitializer
|
|
741
|
+
)
|
|
742
|
+
)
|
|
668
743
|
if let eventFunctionType {
|
|
669
744
|
// An event is dispatched to JavaScript, which can't hand a value back to the caller.
|
|
670
745
|
if !returnsVoid(eventFunctionType) {
|
|
@@ -926,6 +1001,10 @@ private func underlyingFunctionType(of type: TypeSyntax) -> FunctionTypeSyntax?
|
|
|
926
1001
|
/// Emitted even when empty, so a props type with only events still conforms. An enum with no cases
|
|
927
1002
|
/// can't declare a raw type, so the empty one conforms to `RawRepresentable` by hand, with `String`
|
|
928
1003
|
/// as its `RawValue` like the others.
|
|
1004
|
+
///
|
|
1005
|
+
/// The enum is `@frozen`, so the `switch` in the `@inlinable` `propSet(for:)` compiles in a module
|
|
1006
|
+
/// built with library evolution, where it would otherwise need a case for unknown values. The cases
|
|
1007
|
+
/// and their order are already part of the ABI, as the bits of `PropSet`.
|
|
929
1008
|
private func propNameEnum(valueProps: [ViewProp]) -> DeclSyntax {
|
|
930
1009
|
// An escaped case takes its raw value from the bare identifier already, but spelling it out keeps
|
|
931
1010
|
// the wire key visible in the generated source and independent of that implicit rule.
|
|
@@ -939,7 +1018,7 @@ private func propNameEnum(valueProps: [ViewProp]) -> DeclSyntax {
|
|
|
939
1018
|
.joined(separator: "\n")
|
|
940
1019
|
if valueProps.isEmpty {
|
|
941
1020
|
return """
|
|
942
|
-
public enum PropName: RawRepresentable, CaseIterable {
|
|
1021
|
+
@frozen nonisolated public enum PropName: RawRepresentable, CaseIterable {
|
|
943
1022
|
public init?(rawValue: String) {
|
|
944
1023
|
return nil
|
|
945
1024
|
}
|
|
@@ -952,7 +1031,7 @@ private func propNameEnum(valueProps: [ViewProp]) -> DeclSyntax {
|
|
|
952
1031
|
"""
|
|
953
1032
|
}
|
|
954
1033
|
return """
|
|
955
|
-
public enum PropName: String, CaseIterable {
|
|
1034
|
+
@frozen nonisolated public enum PropName: String, CaseIterable {
|
|
956
1035
|
\(raw: cases)
|
|
957
1036
|
}
|
|
958
1037
|
"""
|
|
@@ -970,7 +1049,7 @@ private func propSetStruct(valueProps: [ViewProp]) -> DeclSyntax {
|
|
|
970
1049
|
|
|
971
1050
|
if valueProps.isEmpty {
|
|
972
1051
|
return """
|
|
973
|
-
public struct PropSet: OptionSet, Sendable {
|
|
1052
|
+
nonisolated public struct PropSet: OptionSet, Sendable {
|
|
974
1053
|
public let rawValue: UInt64
|
|
975
1054
|
|
|
976
1055
|
public init(rawValue: UInt64) {
|
|
@@ -980,7 +1059,7 @@ private func propSetStruct(valueProps: [ViewProp]) -> DeclSyntax {
|
|
|
980
1059
|
"""
|
|
981
1060
|
}
|
|
982
1061
|
return """
|
|
983
|
-
public struct PropSet: OptionSet, Sendable {
|
|
1062
|
+
nonisolated public struct PropSet: OptionSet, Sendable {
|
|
984
1063
|
public let rawValue: UInt64
|
|
985
1064
|
|
|
986
1065
|
public init(rawValue: UInt64) {
|
|
@@ -998,31 +1077,23 @@ private func propSetStruct(valueProps: [ViewProp]) -> DeclSyntax {
|
|
|
998
1077
|
private func eventNamesConstant(eventProps: [ViewProp]) -> DeclSyntax {
|
|
999
1078
|
let names = eventProps.map { "\"\($0.wireName)\"" }.joined(separator: ", ")
|
|
1000
1079
|
return """
|
|
1001
|
-
public static let _eventNames: [String] = [\(raw: names)]
|
|
1080
|
+
nonisolated public static let _eventNames: [String] = [\(raw: names)]
|
|
1002
1081
|
"""
|
|
1003
1082
|
}
|
|
1004
1083
|
|
|
1005
1084
|
/// The body of `_decode`, which core calls once per changed prop. React sends a removed prop as
|
|
1006
|
-
/// `null`, so `null` and `undefined` reset the prop
|
|
1007
|
-
///
|
|
1008
|
-
/// static member of `AnyViewProps`, reached through `Self.`, so a prop
|
|
1009
|
-
///
|
|
1010
|
-
///
|
|
1011
|
-
///
|
|
1085
|
+
/// `null`, so `null` and `undefined` reset the prop with `_resetViewProp`; any other value decodes
|
|
1086
|
+
/// through `JavaScriptDecodable`, with the type inferred from the assignment. `_decodeViewProp` is a
|
|
1087
|
+
/// static member of `AnyViewProps`, reached through `Self.`, so a prop with the same name doesn't
|
|
1088
|
+
/// shadow it. The checks run once, before the switch, so each case is a single assignment. Members
|
|
1089
|
+
/// are reached through `self.` so that a prop named like a parameter (`value`, `runtime`) isn't
|
|
1090
|
+
/// shadowed by it.
|
|
1012
1091
|
///
|
|
1013
1092
|
/// With no value prop the body is empty: `PropName` is uninhabited, so the function is never called.
|
|
1014
|
-
///
|
|
1015
|
-
/// `defaults` is a `var` only when a prop has a `mutating get`, which can't be read from a constant.
|
|
1016
|
-
/// Otherwise it's a `let`, so the common case gets no "never mutated" warning.
|
|
1017
1093
|
private func decodeFunctionBody(valueProps: [ViewProp]) -> String {
|
|
1018
1094
|
if valueProps.isEmpty {
|
|
1019
1095
|
return ""
|
|
1020
1096
|
}
|
|
1021
|
-
let defaultsSpecifier = valueProps.contains(where: \.hasMutatingGetter) ? "var" : "let"
|
|
1022
|
-
let resetCases =
|
|
1023
|
-
valueProps
|
|
1024
|
-
.map { " case .\($0.name):\n self.\($0.name) = defaults.\($0.name)" }
|
|
1025
|
-
.joined(separator: "\n")
|
|
1026
1097
|
let decodeCases =
|
|
1027
1098
|
valueProps
|
|
1028
1099
|
.map { " case .\($0.name):\n self.\($0.name) = try Self._decodeViewProp(value, in: runtime)" }
|
|
@@ -1032,10 +1103,7 @@ private func decodeFunctionBody(valueProps: [ViewProp]) -> String {
|
|
|
1032
1103
|
return """
|
|
1033
1104
|
|
|
1034
1105
|
if value.isNull() || value.isUndefined() {
|
|
1035
|
-
|
|
1036
|
-
switch name {
|
|
1037
|
-
\(resetCases)
|
|
1038
|
-
}
|
|
1106
|
+
Self._resetViewProp(name, in: &self)
|
|
1039
1107
|
return
|
|
1040
1108
|
}
|
|
1041
1109
|
switch name {
|
|
@@ -1044,6 +1112,79 @@ private func decodeFunctionBody(valueProps: [ViewProp]) -> String {
|
|
|
1044
1112
|
"""
|
|
1045
1113
|
}
|
|
1046
1114
|
|
|
1115
|
+
/// `_resetViewProp(_:in:)`, which sets one value prop back to its default when React removes it. Each
|
|
1116
|
+
/// case assigns the prop's initial value or `nil`, so a reset evaluates only that prop's default. A
|
|
1117
|
+
/// prop whose default the macro can't tell from the declaration reads it from a new `Self()`.
|
|
1118
|
+
///
|
|
1119
|
+
/// A static function of the struct body resolves names as a property initializer does: a static
|
|
1120
|
+
/// member without `Self.` and a member of an enclosing type, which `_decode` in the extension can't
|
|
1121
|
+
/// name. The parameters are `_name` and `_props`, with a number after the name when an initial value
|
|
1122
|
+
/// uses it, so they don't hide a declaration that an initial value names.
|
|
1123
|
+
///
|
|
1124
|
+
/// A default argument such as `#function` or `#line` of a function that an initial value calls gets
|
|
1125
|
+
/// the location of `_resetViewProp`, which the macro can't see.
|
|
1126
|
+
///
|
|
1127
|
+
/// `nil` with no value prop: `PropName` is uninhabited, so `_decode` never resets.
|
|
1128
|
+
private func resetFunction(valueProps: [ViewProp]) -> DeclSyntax? {
|
|
1129
|
+
if valueProps.isEmpty {
|
|
1130
|
+
return nil
|
|
1131
|
+
}
|
|
1132
|
+
let usedNames = Set(
|
|
1133
|
+
valueProps.flatMap { prop -> [String] in
|
|
1134
|
+
guard case .initialValue(let expression) = prop.defaultValue else {
|
|
1135
|
+
return []
|
|
1136
|
+
}
|
|
1137
|
+
return expression.tokens(viewMode: .sourceAccurate).compactMap { token in
|
|
1138
|
+
guard case .identifier(let text) = token.tokenKind else {
|
|
1139
|
+
return nil
|
|
1140
|
+
}
|
|
1141
|
+
return withoutBackticks(text)
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
)
|
|
1145
|
+
let nameParameter = unusedName("_name", in: usedNames)
|
|
1146
|
+
let propsParameter = unusedName("_props", in: usedNames)
|
|
1147
|
+
let cases =
|
|
1148
|
+
valueProps
|
|
1149
|
+
.map { prop -> String in
|
|
1150
|
+
let assignment: String
|
|
1151
|
+
switch prop.defaultValue {
|
|
1152
|
+
case .initialValue(let expression):
|
|
1153
|
+
assignment = "\(propsParameter).\(prop.name) = \(expression.trimmedDescription)"
|
|
1154
|
+
case .implicitNil:
|
|
1155
|
+
assignment = "\(propsParameter).\(prop.name) = nil"
|
|
1156
|
+
case .newInstance where prop.hasMutatingGetter:
|
|
1157
|
+
// A mutating getter can't be read from a constant. The local is scoped to its case, where
|
|
1158
|
+
// no initial value is copied.
|
|
1159
|
+
assignment = "var _defaults = Self()\n \(propsParameter).\(prop.name) = _defaults.\(prop.name)"
|
|
1160
|
+
case .newInstance:
|
|
1161
|
+
assignment = "\(propsParameter).\(prop.name) = Self().\(prop.name)"
|
|
1162
|
+
}
|
|
1163
|
+
return " case .\(prop.name):\n \(assignment)"
|
|
1164
|
+
}
|
|
1165
|
+
.joined(separator: "\n")
|
|
1166
|
+
return """
|
|
1167
|
+
@JavaScriptActor
|
|
1168
|
+
static func _resetViewProp(_ \(raw: nameParameter): PropName, in \(raw: propsParameter): inout Self) {
|
|
1169
|
+
switch \(raw: nameParameter) {
|
|
1170
|
+
\(raw: cases)
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
"""
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/// `base`, or `base` with the lowest number after it that gives a name not in `names`.
|
|
1177
|
+
private func unusedName(_ base: String, in names: Set<String>) -> String {
|
|
1178
|
+
if !names.contains(base) {
|
|
1179
|
+
return base
|
|
1180
|
+
}
|
|
1181
|
+
var number = 1
|
|
1182
|
+
while names.contains("\(base)\(number)") {
|
|
1183
|
+
number += 1
|
|
1184
|
+
}
|
|
1185
|
+
return "\(base)\(number)"
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1047
1188
|
/// The public `init()` that `AnyViewProps` requires, when Swift's own can't satisfy it: it gives each
|
|
1048
1189
|
/// event prop without an initial value an empty closure, and leaves the value props at their initial
|
|
1049
1190
|
/// values. `nil` when the type declares a usable `init()`, or when Swift's `init()` is enough (see
|
|
@@ -1054,7 +1195,7 @@ private func defaultInitializer(props: ViewPropsModel) -> DeclSyntax? {
|
|
|
1054
1195
|
}
|
|
1055
1196
|
if props.eventDefaults.isEmpty {
|
|
1056
1197
|
return """
|
|
1057
|
-
public init() {
|
|
1198
|
+
nonisolated public init() {
|
|
1058
1199
|
}
|
|
1059
1200
|
"""
|
|
1060
1201
|
}
|
|
@@ -1062,7 +1203,7 @@ private func defaultInitializer(props: ViewPropsModel) -> DeclSyntax? {
|
|
|
1062
1203
|
.map { " self.\($0.name) = \($0.emptyClosure)" }
|
|
1063
1204
|
.joined(separator: "\n")
|
|
1064
1205
|
return """
|
|
1065
|
-
public init() {
|
|
1206
|
+
nonisolated public init() {
|
|
1066
1207
|
\(raw: assignments)
|
|
1067
1208
|
}
|
|
1068
1209
|
"""
|
|
@@ -17,10 +17,17 @@ import SwiftSyntax
|
|
|
17
17
|
/// A synchronous function marked `@JS(.withAsync("name"))` also binds a promise-returning variant
|
|
18
18
|
/// under that name (see `asyncVariant`). It decodes and encodes like an `async` function, and calls
|
|
19
19
|
/// the function on the concurrent pool.
|
|
20
|
+
///
|
|
21
|
+
/// A synchronous function can take the JavaScript runtime as its last parameter,
|
|
22
|
+
/// `in runtime: borrowing JavaScriptRuntime`. JavaScript doesn't see it: the binding passes the runtime
|
|
23
|
+
/// of the call.
|
|
20
24
|
internal struct JSFunction {
|
|
21
25
|
let swiftName: String
|
|
22
26
|
private(set) var jsName: String
|
|
27
|
+
/// The parameters that take JavaScript arguments, without the runtime parameter.
|
|
23
28
|
let parameters: [FunctionParameterSyntax]
|
|
29
|
+
/// The label of the parameter that takes the JavaScript runtime, or `nil` when the function has none.
|
|
30
|
+
private let runtimeLabel: String?
|
|
24
31
|
/// The declared return type as written, or `nil` when the function returns `Void`/nothing.
|
|
25
32
|
let returnType: String?
|
|
26
33
|
/// How the return value encodes when its type mentions `Any`, or `nil` for any other type.
|
|
@@ -39,7 +46,9 @@ internal struct JSFunction {
|
|
|
39
46
|
self.swiftName = funcDecl.name.text
|
|
40
47
|
self.jsName = jsNameArgument(of: attribute) ?? withoutBackticks(funcDecl.name.text)
|
|
41
48
|
self.nameToken = funcDecl.name
|
|
42
|
-
|
|
49
|
+
let declaredParameters = funcDecl.signature.parameterClause.parameters
|
|
50
|
+
self.parameters = declaredParameters.filter { !isRuntimeParameter($0) }
|
|
51
|
+
self.runtimeLabel = declaredParameters.first(where: isRuntimeParameter)?.firstName.text
|
|
43
52
|
|
|
44
53
|
let declaredReturnType = funcDecl.signature.returnClause?.type
|
|
45
54
|
self.returnType = isVoidType(declaredReturnType) ? nil : declaredReturnType?.trimmedDescription
|
|
@@ -264,6 +273,9 @@ internal struct JSFunction {
|
|
|
264
273
|
}
|
|
265
274
|
callArguments.append(label == "_" ? value : "\(label): \(value)")
|
|
266
275
|
}
|
|
276
|
+
if let runtimeLabel {
|
|
277
|
+
callArguments.append(runtimeLabel == "_" ? "runtime" : "\(runtimeLabel): runtime")
|
|
278
|
+
}
|
|
267
279
|
return callArguments.joined(separator: ", ")
|
|
268
280
|
}
|
|
269
281
|
|
|
@@ -338,12 +350,14 @@ internal struct JSFunction {
|
|
|
338
350
|
// returns nothing binds through the `Void` overload (`JavaScriptRuntime.VoidFunctionClosure`)
|
|
339
351
|
// instead, which leaves JS's result as `undefined`. A call through it is faster than through a
|
|
340
352
|
// result-slot closure that writes nothing. The closure declares `-> Void` because a body of several
|
|
341
|
-
// statements without a `return` is otherwise ambiguous between the overloads.
|
|
353
|
+
// statements without a `return` is otherwise ambiguous between the overloads. A function that takes
|
|
354
|
+
// the runtime binds through the result-slot overload even when it returns nothing, so it gets the
|
|
355
|
+
// runtime as the closure's parameter instead of capturing the decorator's.
|
|
342
356
|
let captures = receiver.captureClause
|
|
343
357
|
let parameters =
|
|
344
358
|
isAsync
|
|
345
359
|
? "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"
|
|
346
|
-
: returnType == nil
|
|
360
|
+
: returnType == nil && runtimeLabel == nil
|
|
347
361
|
? "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) -> Void"
|
|
348
362
|
: resultSlotClosureParameters
|
|
349
363
|
|
|
@@ -4,12 +4,18 @@ import SwiftSyntax
|
|
|
4
4
|
/// have a single constructor). Instead of a `Constructor { … }` DSL entry, the macro synthesizes an
|
|
5
5
|
/// override of `SharedObject._constructSharedObject(...)` that decodes the JS arguments and returns a
|
|
6
6
|
/// fresh instance; unlike the method/property bindings it produces the native instance rather than
|
|
7
|
-
/// recovering one.
|
|
7
|
+
/// recovering one. Like a function, it can take the JavaScript runtime as its last parameter.
|
|
8
8
|
internal struct JSConstructor {
|
|
9
|
+
/// The parameters that take JavaScript arguments, without the runtime parameter.
|
|
9
10
|
let parameters: [FunctionParameterSyntax]
|
|
11
|
+
/// The label of the parameter that takes the JavaScript runtime, or `nil` when the initializer has
|
|
12
|
+
/// none.
|
|
13
|
+
private let runtimeLabel: String?
|
|
10
14
|
|
|
11
15
|
init(initDecl: InitializerDeclSyntax) {
|
|
12
|
-
|
|
16
|
+
let declaredParameters = initDecl.signature.parameterClause.parameters
|
|
17
|
+
self.parameters = declaredParameters.filter { !isRuntimeParameter($0) }
|
|
18
|
+
self.runtimeLabel = declaredParameters.first(where: isRuntimeParameter)?.firstName.text
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
/// The body statements, indented with `indent`: arity guard, per-argument decode through
|
|
@@ -39,6 +45,9 @@ internal struct JSConstructor {
|
|
|
39
45
|
let label = parameter.firstName.text
|
|
40
46
|
callArguments.append(label == "_" ? "arg\(index)" : "\(label): arg\(index)")
|
|
41
47
|
}
|
|
48
|
+
if let runtimeLabel {
|
|
49
|
+
callArguments.append(runtimeLabel == "_" ? "runtime" : "\(runtimeLabel): runtime")
|
|
50
|
+
}
|
|
42
51
|
|
|
43
52
|
lines.append("return \(typeName)(\(callArguments.joined(separator: ", ")))")
|
|
44
53
|
|
|
@@ -128,6 +128,17 @@ internal func isOptionalType(_ type: TypeSyntax) -> Bool {
|
|
|
128
128
|
return false
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/// True when the parameter takes the JavaScript runtime instead of a JavaScript argument: its type,
|
|
132
|
+
/// without specifiers, is written as `JavaScriptRuntime` (or `….JavaScriptRuntime`), or as an optional
|
|
133
|
+
/// of it. The binding passes the runtime of the call, and JavaScript doesn't see the parameter. `@JS`
|
|
134
|
+
/// requires it to be the last parameter, labeled `in` or `runtime`, `borrowing` and not optional (see
|
|
135
|
+
/// `diagnoseRuntimeParameter`). A macro can't resolve types, so a typealias or a subclass of
|
|
136
|
+
/// `JavaScriptRuntime` isn't detected.
|
|
137
|
+
internal func isRuntimeParameter(_ parameter: FunctionParameterSyntax) -> Bool {
|
|
138
|
+
let type = SpecifiedType(parameter.type).type
|
|
139
|
+
return baseIdentifier(of: optionalWrappedType(type) ?? type) == "JavaScriptRuntime"
|
|
140
|
+
}
|
|
141
|
+
|
|
131
142
|
/// A parameter's type split from the specifiers written in front of it (`borrowing`, `consuming`,
|
|
132
143
|
/// `sending`, `inout`, `isolated`, ...). The specifiers aren't part of the type: the decode call and
|
|
133
144
|
/// the conformance assertion need the bare type (`borrowing Point.decode(...)` isn't valid Swift), and
|
|
@@ -620,8 +631,9 @@ internal func isExcludedByModifier(_ modifiers: DeclModifierListSyntax) -> Bool
|
|
|
620
631
|
|
|
621
632
|
/// True if the type's inheritance clause already lists a protocol with the given name. Matches
|
|
622
633
|
/// either the bare identifier (`Record`) or a qualified member access ending in the name
|
|
623
|
-
/// (`ExpoModulesCore.Record`)
|
|
624
|
-
///
|
|
634
|
+
/// (`ExpoModulesCore.Record`), also with `nonisolated` or an attribute such as `@preconcurrency`
|
|
635
|
+
/// before it. Used by the extension macros to skip a conformance the author already spelled out.
|
|
636
|
+
/// Works for `struct`, `class`, and `enum` declarations; any other declaration kind has no
|
|
625
637
|
/// inheritance clause to read and reports `false`.
|
|
626
638
|
internal func inheritsProtocol(named name: String, in declaration: some DeclGroupSyntax) -> Bool {
|
|
627
639
|
let inheritanceClause: InheritanceClauseSyntax?
|
|
@@ -637,5 +649,11 @@ internal func inheritsProtocol(named name: String, in declaration: some DeclGrou
|
|
|
637
649
|
guard let inherited = inheritanceClause?.inheritedTypes else {
|
|
638
650
|
return false
|
|
639
651
|
}
|
|
640
|
-
return inherited.contains {
|
|
652
|
+
return inherited.contains { entry in
|
|
653
|
+
var type = entry.type
|
|
654
|
+
while let attributed = type.as(AttributedTypeSyntax.self) {
|
|
655
|
+
type = attributed.baseType
|
|
656
|
+
}
|
|
657
|
+
return baseIdentifier(of: type) == name
|
|
658
|
+
}
|
|
641
659
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-macros",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.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.35.0",
|
|
30
|
+
"@expo/modules-macros-linux-x64": "0.35.0",
|
|
31
|
+
"@expo/modules-macros-win32-arm64": "0.35.0",
|
|
32
|
+
"@expo/modules-macros-win32-x64": "0.35.0"
|
|
33
33
|
}
|
|
34
34
|
}
|