Skip to content

Commit 7fffe0c

Browse files
committed
fix: emit diagnostic for @js mutating struct methods instead of generating broken code
Per reviewer feedback (wfltaylor): mutations to `_self` are not propagated back to JavaScript, so the mutable-self binding approach silently discards all mutations. Replace it with a clear compile-time diagnostic pointing users to a value-returning redesign.
1 parent 360e459 commit 7fffe0c

2 files changed

Lines changed: 20 additions & 23 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -228,24 +228,14 @@ public class ExportSwift {
228228
}
229229
}
230230

231-
func callMethod(methodName: String, returnType: BridgeType, isMutating: Bool = false) {
232-
let (selfParam, selfExpr) = removeFirstLiftedParameter()
233-
if isMutating, case .swiftStruct = selfParam.type {
234-
append("var _self = \(selfExpr)")
235-
generateParameterLifting()
236-
let item = renderCallStatement(
237-
callee: "_self.\(raw: methodName)",
238-
returnType: returnType
239-
)
240-
append(item)
241-
} else {
242-
generateParameterLifting()
243-
let item = renderCallStatement(
244-
callee: "\(raw: selfExpr).\(raw: methodName)",
245-
returnType: returnType
246-
)
247-
append(item)
248-
}
231+
func callMethod(methodName: String, returnType: BridgeType) {
232+
let (_, selfExpr) = removeFirstLiftedParameter()
233+
generateParameterLifting()
234+
let item = renderCallStatement(
235+
callee: "\(raw: selfExpr).\(raw: methodName)",
236+
returnType: returnType
237+
)
238+
append(item)
249239
}
250240

251241
/// Generates intermediate variables for stack-using parameters if needed for LIFO compatibility
@@ -571,7 +561,7 @@ public class ExportSwift {
571561
if method.effects.isStatic {
572562
builder.call(name: "\(ownerTypeName).\(method.name)", returnType: method.returnType)
573563
} else {
574-
builder.callMethod(methodName: method.name, returnType: method.returnType, isMutating: method.effects.isMutating)
564+
builder.callMethod(methodName: method.name, returnType: method.returnType)
575565
}
576566
try builder.lowerReturnValue(returnType: method.returnType)
577567
return builder.render(abiName: method.abiName)

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,8 +1198,15 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
11981198
className: classNameForABI
11991199
)
12001200

1201-
let isMutating = node.modifiers.contains { $0.name.tokenKind == .keyword(.mutating) }
1202-
guard let effects = collectEffects(signature: node.signature, isStatic: isStatic, isMutating: isMutating) else {
1201+
if let mutatingModifier = node.modifiers.first(where: { $0.name.tokenKind == .keyword(.mutating) }) {
1202+
diagnose(
1203+
node: mutatingModifier,
1204+
message: "@JS does not support mutating struct methods: mutations to 'self' cannot be propagated back to JavaScript",
1205+
hint: "Remove the mutating keyword or redesign the API to return the updated value instead"
1206+
)
1207+
return nil
1208+
}
1209+
guard let effects = collectEffects(signature: node.signature, isStatic: isStatic) else {
12031210
return nil
12041211
}
12051212

@@ -1214,7 +1221,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
12141221
)
12151222
}
12161223

1217-
private func collectEffects(signature: FunctionSignatureSyntax, isStatic: Bool = false, isMutating: Bool = false) -> Effects? {
1224+
private func collectEffects(signature: FunctionSignatureSyntax, isStatic: Bool = false) -> Effects? {
12181225
let isAsync = signature.effectSpecifiers?.asyncSpecifier != nil
12191226
var isThrows = false
12201227
if let throwsClause: ThrowsClauseSyntax = signature.effectSpecifiers?.throwsClause {
@@ -1235,7 +1242,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
12351242
}
12361243
isThrows = true
12371244
}
1238-
return Effects(isAsync: isAsync, isThrows: isThrows, isStatic: isStatic, isMutating: isMutating)
1245+
return Effects(isAsync: isAsync, isThrows: isThrows, isStatic: isStatic)
12391246
}
12401247

12411248
private func extractNamespace(

0 commit comments

Comments
 (0)