Skip to content

Emit the JavaScriptUnownedValue decode overload for @Union - #63

Closed
tsapeta wants to merge 3 commits into
mainfrom
tsapeta/union-unowned-decode
Closed

tsapeta wants to merge 3 commits into
mainfrom
tsapeta/union-unowned-decode

Conversation

@tsapeta

@tsapeta tsapeta commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Why

@Union only emitted decode(_: borrowing JavaScriptValue, in:). The JavaScriptUnownedValue overload fell back to the JavaScriptDecodable default in expo-modules-jsi, which calls value.copied(in: runtime) before decoding. Every union argument decoded through the argument fast path paid for that copy, even when the matching case was a primitive that can read the borrowed value.

How

The macro now emits both decode overloads, with the same case order.

In the unowned overload, a case whose payload has its own unowned decode reads the borrowed value directly. The macro recognizes those payloads by spelling: Bool, String, the numeric types, ArrayBuffer, and an optional of any of them. Every other case (a record, an array, a dictionary, or any type the macro doesn't recognize, like a typealias or a SharedObject subclass) would copy the value through the protocol default, so these cases share one owning copy instead. The copy is made the first time such a case runs, and the mismatch error reuses it.

Copies per call of the unowned overload:

  • 0 when a borrowing case matches before any other case runs.
  • At most 1 otherwise, including on a mismatch. That is the same as the default overload this replaces, so the count never goes up.

A type missing from the list only costs that one shared copy; the result is still correct.

No core API change is needed.

Test plan

  • Updated the expected expansions in UnionMacroTests. The Input fixture (Point, [Point], [String: Point], Int?) now shows the shared copy for the first three cases and the borrowed value for Int?. Unions with only builtin payloads emit no copy.
  • swift test: 105 + 181 tests pass.
  • Built the release plugin and swapped it into expo/expo's node_modules:
    • expo-modules-core's MacroUnionTests (a union with a record case, so it goes through the shared copy) compile and pass: 12 tests.
    • expo-sqlite's unions compile, the object file has SQLiteBindValue.decode(_: JavaScriptUnownedValue, in:), and expo-sqlite's 50 unit tests pass (checked on the first revision of this PR).

@tsapeta
tsapeta added this pull request to stack #65 October 1, 2026 13:42
@tsapeta
tsapeta marked this pull request as ready for review October 2, 2026 08:57
@tsapeta
tsapeta removed this pull request from stack #65 October 2, 2026 09:08
@tsapeta tsapeta closed this Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant