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
10 changes: 9 additions & 1 deletion Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,14 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
className: classNameForABI
)

if let mutatingModifier = node.modifiers.first(where: { $0.name.tokenKind == .keyword(.mutating) }) {
diagnose(
node: mutatingModifier,
message: "@JS does not support mutating struct methods: mutations to 'self' cannot be propagated back to JavaScript",
hint: "Remove the mutating keyword or redesign the API to return the updated value instead"
)
return nil
}
guard let effects = collectEffects(signature: node.signature, isStatic: isStatic) else {
return nil
}
Expand Down Expand Up @@ -1522,7 +1530,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
}
}