Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions apple/Sources/ExpoModulesMacros/DecorateModuleBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,24 @@ internal struct JSFunction {
/// body never references `appContext`, so the capture and guard are omitted to avoid the
/// unused-capture warning.
var decorateStatements: String {
// Synchronous `@JS` functions never decode `this` (the receiver is the module's real `self`), so
// they bind through the unowned-`this` `setProperty` overload, which hands `this` in as a borrowed
// `JavaScriptUnownedValue` instead of allocating an owning `JavaScriptValue` and forming its
// `weak`-runtime reference on every call. The first parameter is typed `borrowing
// JavaScriptUnownedValue` to select that (otherwise `@_disfavoredOverload`) overload — which
// requires the *parenthesized, fully typed* parameter list, since Swift rejects a type annotation
// on a shorthand `{ [capture] name, name in }` parameter. Async functions keep the untyped
// shorthand and the owning-`this` overload: there is no unowned-`this` async variant and the buffer
// escapes into the task anyway.
let captures = usesAppContext ? "[weak appContext, self]" : "[self]"
let parameters =
isAsync
? "this, arguments"
: "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"

if usesAppContext {
return """
object.setProperty("\(jsName)") { [weak appContext, self] this, arguments in
object.setProperty("\(jsName)") { \(captures) \(parameters) in
guard let appContext else {
throw Exceptions.AppContextLost()
}
Expand All @@ -212,7 +227,7 @@ internal struct JSFunction {
"""
}
return """
object.setProperty("\(jsName)") { [self] this, arguments in
object.setProperty("\(jsName)") { \(captures) \(parameters) in
\(bodyStatements(indent: " "))
}
"""
Expand Down Expand Up @@ -323,9 +338,14 @@ internal struct JSProperty {
.split(separator: "\n", omittingEmptySubsequences: false)
.map { " \($0)" }
.joined(separator: "\n")
// Property `get`/`set` accessors are always synchronous and never decode `this`, so they bind
// through the unowned-`this` `setProperty` overload like sync functions. The parameter list is
// parenthesized and fully typed because Swift rejects a type annotation on a shorthand closure
// parameter; the explicit `borrowing JavaScriptUnownedValue` selects the unowned-`this` overload.
let parameters = "(this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer)"
if usesAppContext {
return """
\(descriptorName).setProperty("\(key)") { [weak appContext, self] this, arguments in
\(descriptorName).setProperty("\(key)") { [weak appContext, self] \(parameters) in
guard let appContext else {
throw Exceptions.AppContextLost()
}
Expand All @@ -334,7 +354,7 @@ internal struct JSProperty {
"""
}
return """
\(descriptorName).setProperty("\(key)") { [self] this, arguments in
\(descriptorName).setProperty("\(key)") { [self] \(parameters) in
\(indentedBody)
}
"""
Expand Down
32 changes: 16 additions & 16 deletions apple/Tests/ExpoModulesMacrosTests/ExpoModuleMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("greet") { [self] this, arguments in
object.setProperty("greet") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard arguments.count == 1 else {
throw Exceptions.ArgumentsRangeMismatch((functionName: "greet", received: arguments.count, required: 1, maximum: 1))
}
Expand Down Expand Up @@ -141,7 +141,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("add") { [self] this, arguments in
object.setProperty("add") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard arguments.count == 2 else {
throw Exceptions.ArgumentsRangeMismatch((functionName: "add", received: arguments.count, required: 2, maximum: 2))
}
Expand Down Expand Up @@ -179,7 +179,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("resize") { [self] this, arguments in
object.setProperty("resize") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard arguments.count >= 1 && arguments.count <= 2 else {
throw Exceptions.ArgumentsRangeMismatch((functionName: "resize", received: arguments.count, required: 1, maximum: 2))
}
Expand Down Expand Up @@ -222,7 +222,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("tag") { [weak appContext, self] this, arguments in
object.setProperty("tag") { [weak appContext, self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard let appContext else {
throw Exceptions.AppContextLost()
}
Expand Down Expand Up @@ -268,7 +268,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("ping") { [self] this, arguments in
object.setProperty("ping") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard arguments.count >= 0 && arguments.count <= 1 else {
throw Exceptions.ArgumentsRangeMismatch((functionName: "ping", received: arguments.count, required: 0, maximum: 1))
}
Expand Down Expand Up @@ -317,7 +317,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("transform") { [weak appContext, self] this, arguments in
object.setProperty("transform") { [weak appContext, self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard let appContext else {
throw Exceptions.AppContextLost()
}
Expand Down Expand Up @@ -364,7 +364,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("describe") { [weak appContext, self] this, arguments in
object.setProperty("describe") { [weak appContext, self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard let appContext else {
throw Exceptions.AppContextLost()
}
Expand Down Expand Up @@ -404,7 +404,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("doReset") { [self] this, arguments in
object.setProperty("doReset") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard arguments.count == 0 else {
throw Exceptions.ArgumentsRangeMismatch((functionName: "doReset", received: arguments.count, required: 0, maximum: 0))
}
Expand Down Expand Up @@ -513,7 +513,7 @@ struct ExpoModuleMacroTests {
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
let statusDescriptor = runtime.createObject()
statusDescriptor.setProperty("enumerable", value: true)
statusDescriptor.setProperty("get") { [self] this, arguments in
statusDescriptor.setProperty("get") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
return self.status.toJavaScriptValue(in: runtime)
}
object.defineProperty("status", descriptor: statusDescriptor)
Expand Down Expand Up @@ -548,10 +548,10 @@ struct ExpoModuleMacroTests {
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
let readyDescriptor = runtime.createObject()
readyDescriptor.setProperty("enumerable", value: true)
readyDescriptor.setProperty("get") { [self] this, arguments in
readyDescriptor.setProperty("get") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
return self.ready.toJavaScriptValue(in: runtime)
}
readyDescriptor.setProperty("set") { [self] this, arguments in
readyDescriptor.setProperty("set") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
self.ready = try arguments.unownedValue(at: 0).asBool()
return .undefined
}
Expand Down Expand Up @@ -593,13 +593,13 @@ struct ExpoModuleMacroTests {
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
let configDescriptor = runtime.createObject()
configDescriptor.setProperty("enumerable", value: true)
configDescriptor.setProperty("get") { [weak appContext, self] this, arguments in
configDescriptor.setProperty("get") { [weak appContext, self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard let appContext else {
throw Exceptions.AppContextLost()
}
return try MyRecord.getDynamicType().castToJS(self.config, appContext: appContext, in: runtime)
}
configDescriptor.setProperty("set") { [weak appContext, self] this, arguments in
configDescriptor.setProperty("set") { [weak appContext, self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard let appContext else {
throw Exceptions.AppContextLost()
}
Expand Down Expand Up @@ -645,7 +645,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("greet") { [self] this, arguments in
object.setProperty("greet") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard arguments.count == 1 else {
throw Exceptions.ArgumentsRangeMismatch((functionName: "greet", received: arguments.count, required: 1, maximum: 1))
}
Expand All @@ -655,7 +655,7 @@ struct ExpoModuleMacroTests {
}
let statusDescriptor = runtime.createObject()
statusDescriptor.setProperty("enumerable", value: true)
statusDescriptor.setProperty("get") { [self] this, arguments in
statusDescriptor.setProperty("get") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
return self.status.toJavaScriptValue(in: runtime)
}
object.defineProperty("status", descriptor: statusDescriptor)
Expand Down Expand Up @@ -687,7 +687,7 @@ struct ExpoModuleMacroTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("compute") { [self] this, arguments in
object.setProperty("compute") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard arguments.count == 0 else {
throw Exceptions.ArgumentsRangeMismatch((functionName: "compute", received: arguments.count, required: 0, maximum: 0))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ struct ExpoModuleClassesTests {

@JavaScriptActor
public func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime, appContext: AppContext) throws {
object.setProperty("ping") { [self] this, arguments in
object.setProperty("ping") { [self] (this: borrowing JavaScriptUnownedValue, arguments: consuming JavaScriptValuesBuffer) in
guard arguments.count == 0 else {
throw Exceptions.ArgumentsRangeMismatch((functionName: "ping", received: arguments.count, required: 0, maximum: 0))
}
Expand Down
Loading