Bind @SharedObject members directly into the JS object via _decorateSharedObject#22
Open
tsapeta wants to merge 1 commit into
Open
Bind @SharedObject members directly into the JS object via _decorateSharedObject#22tsapeta wants to merge 1 commit into
@SharedObject members directly into the JS object via _decorateSharedObject#22tsapeta wants to merge 1 commit into
Conversation
63becea to
897715d
Compare
…teSharedObject`
Move `@SharedObject`'s `@JS func`s, `@JS var`s, and the `@JS init` off the DSL
`Class { Function/Property/Constructor }` path and bind them directly into the
shared object's JS object, the same direct-JSI strategy `@ExpoModule` already
uses for modules.
`@SharedObject` now synthesizes two static entry points alongside
`_synthesizedClassDefinition()`:
- `_decorateSharedObject(prototype:in:appContext:)` binds each `@JS func`
(inlined `setProperty` closure) and `@JS var` (a `defineProperty` get/set
accessor) onto the class prototype. The first parameter is named `prototype`
(not `object` as on `_decorateModule`) because core hands in the shared class
prototype, not an instance. Because a shared object has a distinct native
instance behind each JS object, the bindings are static and recover the typed
receiver from the JS `this` per call (`try SharedObject.native(from:
this.asObject(in: runtime), as: <Type>.self)`, bound to a `_self` local)
rather than capturing a singleton `self`.
- `_constructSharedObject(this:arguments:in:appContext:)` decodes the `@JS init`
arguments and returns a freshly built instance.
The decode/encode fast path, arity handling (range-based check throwing
`Exceptions.ArgumentsRangeMismatch`), and unowned-`this` closures are shared
with the module path. To express both receivers, `JSFunction`/`JSProperty` are
parameterized by a `Receiver` (module `self` vs. shared-object `_self`): the
module bindings keep capturing `self` strong and ignore `this`; the
shared-object bindings capture nothing of the instance and recover `this`. The
`_self` local is named with a leading underscore so it never collides with a
user member (e.g. a `var owner`).
`collectProperties` moves to `MacroHelpers` since both macros now use it. The
`Class` block keeps only non-`@JS` definitions (none today), so it's empty when
every member is `@JS`.
897715d to
62bce8f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
@SharedObjectstill routed its@JSmembers through the DSLClass { Function/Property/Constructor }path, while@ExpoModulealready binds module members directly into the JS object via_decorateModule. This brings shared objects onto the same direct-JSI strategy, so their methods, properties, and constructor are bound onto the class prototype without going through the dynamic definition path.How
@SharedObjectnow synthesizes two static entry points alongside_synthesizedClassDefinition():_decorateSharedObject(prototype:in:appContext:)binds each@JS func(an inlinedsetPropertyclosure) and@JS var(adefinePropertyget/set accessor) onto the class prototype. Because a shared object has a distinct native instance behind each JS object, the bindings are static and recover the typed receiver from the JSthisper call (try SharedObject.native(from: this.asObject(in: runtime), as: <Type>.self), bound to a_selflocal) rather than capturing a singletonself._constructSharedObject(this:arguments:in:appContext:)decodes the@JS initarguments and returns a freshly built instance.The decode/encode fast path, range-based arity handling, and unowned-
thisclosures are shared with the module path:JSFunction/JSPropertyare parameterized by aReceiver(moduleselfvs. shared-object_self).collectPropertiesmoves toMacroHelperssince both macros use it.The receiver recovery uses
expo-modules-core'sSharedObject.native(from:as:)andJavaScriptUnownedValue.asObject(in:), added in expo/expo#47054 (now merged).Test Plan
swift testinapple/: 111 tests across 9 suites pass, including the@SharedObject macroexpansion assertions, which pin the generated_decorateSharedObject/_constructSharedObjectoutput (now emittingSharedObject.native(from:as:)).Merge order
Land #23 (implicitly-unwrapped optional normalization) first. It adds
expressionType(_:)and fixes the existing module/record type splices onmain. This PR adds new shared-object property splices (_decorateSharedObject's get/set accessors) that have the same IUO bug, so when rebasing this branch onto amainthat includes #23, applyexpressionType(_:)to those shared-object splices too.The
expo-modules-coreAPI this generates against (SharedObject.native(from:as:)/JavaScriptUnownedValue.asObject(in:), expo/expo#47054) has merged, so that dependency is no longer a blocker.