Skip to content
Open
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: 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)
}
}

/// 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
}
}