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
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

@MainActor
public struct MainActorBox<Value> {
public var typeName: String { "\(Value.self)" }

public static func describeType() -> String {
"\(Value.self)"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

// Compiling the generated thunks exercises main actor isolation checking.
@MainActor
public final class MainActorCounter {
public var value: Int64

public init(value: Int64) {
self.value = value
}

public func increment() {
value += 1
}

public func label() -> String {
"Count: \(value)"
}

public func checkedValue() throws -> Int64 {
value
}

public nonisolated func constant() -> Int64 {
42
}

public nonisolated var constantValue: Int64 { 42 }

public subscript(offset: Int64) -> Int64 {
get { value + offset }
set { value = newValue - offset }
}

public static func initialValue() -> Int64 {
0
}
}

extension MainActorCounter {
public func reset() {
value = 0
}
}

@_Concurrency.MainActor
public func mainActorAnswer() -> Int64 {
42
}

@MainActor
public func mainActorThrowingAnswer() throws -> Int64 {
42
}
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ extension JNISwift2JavaGenerator {
}
translated.parentName = boxParentName

printCDecl(&printer, translated) { printer in
printCDecl(&printer, translated, mainActorIsolated: isSynchronousMainActorIsolated(method)) { printer in
self.printFunctionDowncall(&printer, method)
}
printer.println()
Expand Down Expand Up @@ -485,6 +485,7 @@ extension JNISwift2JavaGenerator {
printCDecl(
&printer,
translatedDecl,
mainActorIsolated: isSynchronousMainActorIsolated(decl),
) { printer in
if let parent = decl.parentType?.asNominalType, parent.hasGenericParameter {
self.printFunctionOpenerCall(&printer, decl)
Expand Down Expand Up @@ -600,6 +601,48 @@ extension JNISwift2JavaGenerator {
}
}

private func isSynchronousMainActorIsolated(_ decl: ExtractedFunc) -> Bool {
guard !decl.isAsync, !decl.isIsolated, !decl.isImplicitlyAsync,
!decl.functionSignature.isNonisolated
else {
return false
}

// Initializers and accessors also carry modifiers on their declarations.
if decl.swiftDecl.asProtocol(WithModifiersSyntax.self)?.modifiers.contains(where: {
$0.name.tokenKind == .keyword(.nonisolated)
}) == true {
return false
}

func hasMainActor(_ attributes: AttributeListSyntax?) -> Bool {
attributes?.contains { element in
guard let attribute = element.as(AttributeSyntax.self) else { return false }
let name = attribute.attributeName.trimmedDescription
return name == "MainActor" || name == "_Concurrency.MainActor"
} ?? false
}

if hasMainActor(decl.attributeList)
|| hasMainActor(decl.parentType?.asNominalType?.nominalTypeDecl.syntax.attributes)
{
return true
}

// An extension can isolate its members without isolating the nominal type.
var context = decl.swiftDecl.parent
while let node = context {
if let extensionDecl = node.as(ExtensionDeclSyntax.self) {
return hasMainActor(extensionDecl.attributes)
}
if node.isProtocol(DeclGroupSyntax.self) {
break
}
context = node.parent
}
return false
}

private func printFunctionDowncall(
_ printer: inout SwiftPrinter,
_ decl: ExtractedFunc,
Expand Down Expand Up @@ -846,6 +889,7 @@ extension JNISwift2JavaGenerator {
private func printCDecl(
_ printer: inout SwiftPrinter,
_ translatedDecl: TranslatedFunctionDecl,
mainActorIsolated: Bool = false,
_ body: (inout SwiftPrinter) -> Void,
) {
let nativeSignature = translatedDecl.nativeFunctionSignature
Expand All @@ -865,6 +909,7 @@ extension JNISwift2JavaGenerator {
parentName: translatedDecl.parentName,
parameters: parameters,
resultType: nativeSignature.result.javaType,
mainActorIsolated: mainActorIsolated,
) { printer in
body(&printer)
}
Expand All @@ -876,6 +921,7 @@ extension JNISwift2JavaGenerator {
parentName: SwiftQualifiedTypeName,
parameters: [JavaParameter],
resultType: JavaType,
mainActorIsolated: Bool = false,
_ body: (inout SwiftPrinter) -> Void,
) {
let jniSignature = parameters.reduce(into: "") { signature, parameter in
Expand Down Expand Up @@ -911,10 +957,15 @@ extension JNISwift2JavaGenerator {
#if compiler(>=6.4)
@diagnose(DeprecatedDeclaration, as: ignored)
#endif
@_cdecl("\(cName)")
\(mainActorIsolated ? "@MainActor\n" : "")@_cdecl("\(cName)")
public func \(cName)(\(thunkParameters.joined(separator: .comma)))\(thunkReturnType)
"""
) { printer in
if mainActorIsolated {
// JNI callers must already be on the main actor. Preserve synchronous
// calls and diagnose an incorrect executor before accessing Swift state.
printer.print("MainActor.preconditionIsolated()")
}
body(&printer)
}
}
Expand Down Expand Up @@ -1177,6 +1228,10 @@ extension JNISwift2JavaGenerator {
] + translatedParameters
let thunkReturnType = resultType != .void ? " -> \(resultType.jniTypeName)" : ""

if isSynchronousMainActorIsolated(decl) {
printer.print("@MainActor")
}

let signature = #"static func \#(decl.openerMethodName)(\#(thunkParameters.joined(separator: .comma)))\#(thunkReturnType)"#
if !skipMethodBody {
printer.printBraceBlock(signature) { printer in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,21 @@ suspend fun useAsyncSum() {

This relies on the Kotlin `kotlinx-coroutines-jdk8` library which adds the `await()` extension function on `java.util.concurrent.CompletableFuture`.

### Main actor isolation (JNI)

Synchronous functions explicitly isolated to `@MainActor` remain synchronous in
Java. This also applies to initializers, methods, and property or subscript
accessors isolated by an enclosing `@MainActor` type or extension.

The generated Swift thunk is annotated with `@MainActor` and calls
`MainActor.preconditionIsolated` to check that the caller is already executing on
the main actor. It does not dispatch the call to the main actor. Calling one of
these synchronous APIs from another executor terminates the process, so Java
callers must arrange to run on the main actor's executor before invoking it.

Explicitly `nonisolated` members do not perform this check. Explicitly `async`
functions continue to return futures and await the isolated Swift call instead.

### Collections

Arrays are copied across the boundary. Dictionaries and sets are handed to Java as
Expand Down
Loading