Skip to content
28 changes: 19 additions & 9 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,24 @@ public class ExportSwift {
}
}

func callMethod(methodName: String, returnType: BridgeType) {
let (_, selfExpr) = removeFirstLiftedParameter()
generateParameterLifting()
let item = renderCallStatement(
callee: "\(raw: selfExpr).\(raw: methodName)",
returnType: returnType
)
append(item)
func callMethod(methodName: String, returnType: BridgeType, isMutating: Bool = false) {
let (selfParam, selfExpr) = removeFirstLiftedParameter()
if isMutating, case .swiftStruct = selfParam.type {
append("var _self = \(selfExpr)")
generateParameterLifting()
let item = renderCallStatement(
callee: "_self.\(raw: methodName)",
returnType: returnType
)
append(item)
} else {
generateParameterLifting()
let item = renderCallStatement(
callee: "\(raw: selfExpr).\(raw: methodName)",
returnType: returnType
)
append(item)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won’t work, since _self is not propagated back to JavaScript, hence the mutations are discarded.

}

/// Generates intermediate variables for stack-using parameters if needed for LIFO compatibility
Expand Down Expand Up @@ -561,7 +571,7 @@ public class ExportSwift {
if method.effects.isStatic {
builder.call(name: "\(ownerTypeName).\(method.name)", returnType: method.returnType)
} else {
builder.callMethod(methodName: method.name, returnType: method.returnType)
builder.callMethod(methodName: method.name, returnType: method.returnType, isMutating: method.effects.isMutating)
}
try builder.lowerReturnValue(returnType: method.returnType)
return builder.render(abiName: method.abiName)
Expand Down
9 changes: 5 additions & 4 deletions Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
className: classNameForABI
)

guard let effects = collectEffects(signature: node.signature, isStatic: isStatic) else {
let isMutating = node.modifiers.contains { $0.name.tokenKind == .keyword(.mutating) }
guard let effects = collectEffects(signature: node.signature, isStatic: isStatic, isMutating: isMutating) else {
return nil
}

Expand All @@ -1213,7 +1214,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
)
}

private func collectEffects(signature: FunctionSignatureSyntax, isStatic: Bool = false) -> Effects? {
private func collectEffects(signature: FunctionSignatureSyntax, isStatic: Bool = false, isMutating: Bool = false) -> Effects? {
let isAsync = signature.effectSpecifiers?.asyncSpecifier != nil
var isThrows = false
if let throwsClause: ThrowsClauseSyntax = signature.effectSpecifiers?.throwsClause {
Expand All @@ -1234,7 +1235,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
}
isThrows = true
}
return Effects(isAsync: isAsync, isThrows: isThrows, isStatic: isStatic)
return Effects(isAsync: isAsync, isThrows: isThrows, isStatic: isStatic, isMutating: isMutating)
}

private func extractNamespace(
Expand Down Expand Up @@ -1522,7 +1523,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
}
}

/// Walks extension members under the matching types state, returning whether the type was found.
/// Walks extension members under the matching type's state, returning whether the type was found.
///
/// Note: The lookup scans dictionaries keyed by `makeKey(name:namespace:)`, matching only by
/// plain name. If two types share a name but differ by namespace, `.first(where:)` picks
Expand Down
26 changes: 25 additions & 1 deletion Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -635,11 +635,35 @@ public struct Effects: Codable, Equatable, Sendable {
public var isAsync: Bool
public var isThrows: Bool
public var isStatic: Bool
public var isMutating: Bool

public init(isAsync: Bool, isThrows: Bool, isStatic: Bool = false) {
public init(isAsync: Bool, isThrows: Bool, isStatic: Bool = false, isMutating: Bool = false) {
self.isAsync = isAsync
self.isThrows = isThrows
self.isStatic = isStatic
self.isMutating = isMutating
}

private enum CodingKeys: String, CodingKey {
case isAsync, isThrows, isStatic, isMutating
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.isAsync = try container.decode(Bool.self, forKey: .isAsync)
self.isThrows = try container.decode(Bool.self, forKey: .isThrows)
self.isStatic = try container.decode(Bool.self, forKey: .isStatic)
self.isMutating = try container.decodeIfPresent(Bool.self, forKey: .isMutating) ?? false
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(isAsync, forKey: .isAsync)
try container.encode(isThrows, forKey: .isThrows)
try container.encode(isStatic, forKey: .isStatic)
if isMutating {
try container.encode(isMutating, forKey: .isMutating)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@JS struct Counter {
var number: Int
}

extension Counter {
@JS public mutating func increment() {
number += 1
}
@JS public mutating func add(_ value: Int) {
number += value
}
}