Skip to content
Closed
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,23 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

package com.example.swift;

/** Permitted subclass of {@link Op}; carries an add-flavored value. */
public final class Add implements Op {
private final int value;
public Add(int value) { this.value = value; }
@Override public int eval() { return value; }
@Override public Op combine(Op other) { return new Add(value + other.eval()); }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

package com.example.swift;

/** Permitted subclass of {@link Op}; carries a mul-flavored value. */
public final class Mul implements Op {
private final int value;
public Mul(int value) { this.value = value; }
@Override public int eval() { return value; }
@Override public Op combine(Op other) { return new Mul(value * other.eval()); }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

package com.example.swift;

/**
* Java sealed interface used to smoke-test wrap-java's enum-based
* translation of {@code sealed interface}. Verifies that:
* <ol>
* <li>the parent interface is wrapped as a Swift {@code enum} with
* a case per permitted subclass,</li>
* <li>methods declared on the parent interface remain callable on
* the enum itself (JNI virtual-dispatches to the underlying
* concrete Java class),</li>
* <li>returning the parent interface type from Java produces a
* Swift enum value whose case matches the actual runtime class.</li>
* </ol>
*/
public sealed interface Op permits Add, Mul {
int eval();
Op combine(Op other);

static Op makeAdd(int x) { return new Add(x); }
static Op makeMul(int x) { return new Mul(x); }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"com.example.swift.HelloJavaKitArrays" : "HelloJavaKitArrays",
"com.example.swift.JavaKitSampleMain" : "JavaKitSampleMain",
"com.example.swift.Point" : "Point",
"com.example.swift.ThreadSafeHelperClass" : "ThreadSafeHelperClass"
"com.example.swift.ThreadSafeHelperClass" : "ThreadSafeHelperClass",
"com.example.swift.Op" : "Op",
"com.example.swift.Add" : "Add",
"com.example.swift.Mul" : "Mul"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

import JavaKitExample
import SwiftJava
import Testing

/// Runtime tests for wrap-java's enum-based translation of Java
/// `sealed interface`. These exercise the code paths that only run
/// when a real JVM is present: JNI method lookup on the underlying
/// concrete class, and the enum's `init(javaHolder:)` dispatch that
/// wraps a returned parent-typed object back into the correct case.
@Suite
struct JavaSealedInterfaceRuntimeTests {

let jvm = try JavaKitSampleJVM.shared

/// Constructing an `Add` and wrapping it into the sealed enum via
/// `init(javaHolder:)` picks the `.add` case (not `.unknown`).
@Test
func initJavaHolderDispatchesToAddCase() throws {
let env = try jvm.environment()

let add = Add(3, environment: env)
let op = Op(javaHolder: add.javaHolder)

switch op {
case .add: break // expected
case .mul(let v): Issue.record("expected .add, got .mul(\(v.eval()))")
case .unknown(let v):
Issue.record("expected .add, got .unknown(\(v.javaClass.getName()))")
}

// The enum's own `eval()` method (declared on the sealed interface)
// must virtual-dispatch to Add.eval via JNI.
#expect(op.eval() == 3)
}

/// Same as above but for `Mul`; guards against a broken always-first-case
/// dispatch in the enum's `init(javaHolder:)`.
@Test
func initJavaHolderDispatchesToMulCase() throws {
let env = try jvm.environment()

let mul = Mul(7, environment: env)
let op = Op(javaHolder: mul.javaHolder)

switch op {
case .mul: break // expected
case .add(let v): Issue.record("expected .mul, got .add(\(v.eval()))")
case .unknown(let v):
Issue.record("expected .mul, got .unknown(\(v.javaClass.getName()))")
}

#expect(op.eval() == 7)
}

/// The static Java factories return the sealed interface type `Op`;
/// wrap-java exposes them on `JavaClass<Op>`. The returned Swift value
/// is an `Op!` enum whose case reflects the actual runtime class.
@Test
func staticFactoryReturnsCorrectEnumCase() throws {
let env = try jvm.environment()

let opClass = try JavaClass<Op>(environment: env)

guard let add = opClass.makeAdd(10) else {
Issue.record("makeAdd returned nil")
return
}
switch add {
case .add: break
default: Issue.record("expected .add from makeAdd, got \(add)")
}
#expect(add.eval() == 10)

guard let mul = opClass.makeMul(5) else {
Issue.record("makeMul returned nil")
return
}
switch mul {
case .mul: break
default: Issue.record("expected .mul from makeMul, got \(mul)")
}
#expect(mul.eval() == 5)
}

/// `combine(Op) -> Op` is declared on the sealed interface, so wrap-java
/// emits it directly on the enum. Verifies that:
/// 1. calling the method on the enum reaches the concrete Java subclass
/// (Add.combine adds, Mul.combine multiplies),
/// 2. the returned enum value's case matches the concrete Java subclass
/// that Java produced (Add returns an Add, Mul returns a Mul).
@Test
func combineOnEnumDispatchesToConcreteSubclass() throws {
let env = try jvm.environment()

let opClass = try JavaClass<Op>(environment: env)
let three = opClass.makeAdd(3)!
let two = opClass.makeMul(2)!

// 3 + 2 = 5, and the result should be an Add (Add.combine returns Add)
let sum = three.combine(two)!
#expect(sum.eval() == 5)
switch sum {
case .add: break
default: Issue.record("expected .add from Add.combine, got \(sum)")
}

// 2 * 3 = 6, and the result should be a Mul (Mul.combine returns Mul)
let product = two.combine(three)!
#expect(product.eval() == 6)
switch product {
case .mul: break
default: Issue.record("expected .mul from Mul.combine, got \(product)")
}
}

/// The macro-generated `@JavaMethod` body on the enum uses
/// `self.javaThis` from `self.javaHolder`, then `GetObjectClass` to
/// resolve the method — so `eval()` on `.add(v)` must return the
/// same value as `v.eval()` called on the concrete wrapper directly.
@Test
func methodOnEnumMatchesMethodOnConcreteSubclass() throws {
let env = try jvm.environment()

let add = Add(42, environment: env)
let asOp: Op = .add(add)

#expect(asOp.eval() == add.eval())
#expect(asOp.eval() == 42)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
import SwiftJava
import SwiftJavaJNICore

@JavaClass("java.lang.reflect.Executable", implements: GenericDeclaration.self)
/// Java `sealed class`, permits:
/// - ``Constructor`` (`java.lang.reflect.Constructor`)
/// - ``Method`` (`java.lang.reflect.Method`)
@JavaClass(.sealed, "java.lang.reflect.Executable", implements: GenericDeclaration.self)
open class Executable: AccessibleObject {
/// Java method `getAnnotatedExceptionTypes`.
///
Expand Down
43 changes: 43 additions & 0 deletions Sources/SwiftJava/JavaClassModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

/// Java class-level modifiers surfaced as an option set for the ``JavaClass``,
/// ``JavaRecord``, and ``JavaInterface`` attached macros.
///
/// ```swift
/// @JavaClass(.sealed, "com.example.Shape")
/// @JavaClass(.final, "com.example.Leaf")
/// ```
///
/// Java sealed types don't carry an explicit permitted-subclass list in Swift:
/// the generator models the hierarchy directly in the type system. Sealed
/// interfaces become Swift enums with one case per permitted subclass; sealed
/// classes remain Swift classes and the permitted subclasses are just the
/// declared subclasses reachable via the enclosing type.
public struct JavaClassModifier: OptionSet, Sendable, Hashable {
public let rawValue: Int

public init(rawValue: Int) {
self.rawValue = rawValue
}

/// Java `sealed` type. Has a specific set of subclasses in Java.
public static let sealed = JavaClassModifier(rawValue: 1 << 0)

/// Java `final` type. Cannot be extended in Java.
public static let `final` = JavaClassModifier(rawValue: 1 << 1)

/// Java `abstract` type. Cannot be instantiated directly.
public static let abstract = JavaClassModifier(rawValue: 1 << 2)
}
Loading