Skip to content
Merged
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,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
//
//===----------------------------------------------------------------------===//

infix operator +-==*

public struct OperatorScore {
public var value: Int64

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

public static func + (left: OperatorScore, right: OperatorScore) -> OperatorScore {
OperatorScore(value: left.value + right.value)
}

public static func - (left: OperatorScore, right: OperatorScore) -> OperatorScore {
OperatorScore(value: left.value - right.value)
}

public static func * (left: OperatorScore, right: OperatorScore) -> OperatorScore {
OperatorScore(value: left.value * right.value)
}

public static func / (left: OperatorScore, right: OperatorScore) -> OperatorScore {
OperatorScore(value: left.value / right.value)
}

public static func +-==* (left: OperatorScore, right: OperatorScore) -> String {
"Called +-==* in Java successfully with left: \(left.value) and right: \(right.value)"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//===----------------------------------------------------------------------===//
//
// 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;

import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.SwiftArena;

import static org.junit.jupiter.api.Assertions.*;

public class OperatorsTest {
@Test
void plus() {
try (var arena = SwiftArena.ofConfined()) {
var left = OperatorScore.init(40, arena);
var right = OperatorScore.init(2, arena);

var result = OperatorScore.plus(left, right, arena);

assertEquals(42, result.getValue());
}
}

@Test
void randomOperator() {
try (var arena = SwiftArena.ofConfined()) {
var left = OperatorScore.init(40, arena);
var right = OperatorScore.init(2, arena);

var result = OperatorScore.plusMinusIsEqualTimes(left, right);

assertEquals("Called +-==* in Java successfully with left: 40 and right: 2", result);
}
}
}
4 changes: 2 additions & 2 deletions Sources/JExtractSwiftLib/ExtractedDecls+JavaNaming.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ extension ExtractedFunc {
package var javaGetterName: String? {
switch apiKind {
case .getter, .subscriptGetter: break
case .setter, .subscriptSetter, .function, .initializer, .enumCase: return nil
case .setter, .subscriptSetter, .function, .initializer, .enumCase, .binaryOperator, .prefixOperator, .postfixOperator: return nil
}

let returnsBoolean = self.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool
Expand All @@ -81,7 +81,7 @@ extension ExtractedFunc {
package var javaSetterName: String? {
switch apiKind {
case .setter, .subscriptSetter: break
case .getter, .subscriptGetter, .function, .initializer, .enumCase: return nil
case .getter, .subscriptGetter, .function, .initializer, .enumCase, .binaryOperator, .prefixOperator, .postfixOperator: return nil
}

let isBooleanSetter = self.functionSignature.parameters.first?.type.asNominalTypeDeclaration?.knownTypeKind == .bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1020,8 +1020,16 @@ extension LoweredFunctionSignature {
if let selfExpr {
switch apiKind {
// Don't bother to create explicit ${Self}.init expression.
case .initializer, .subscriptGetter, .subscriptSetter: selfExpr
default: ExprSyntax(MemberAccessExprSyntax(base: selfExpr, name: .identifier(swiftAPIName)))
case .initializer, .subscriptGetter, .subscriptSetter:
selfExpr
case .prefixOperator:
ExprSyntax(DeclReferenceExprSyntax(baseName: .prefixOperator(swiftAPIName)))
case .binaryOperator:
ExprSyntax(DeclReferenceExprSyntax(baseName: .binaryOperator(swiftAPIName)))
case .postfixOperator:
ExprSyntax(DeclReferenceExprSyntax(baseName: .postfixOperator(swiftAPIName)))
default:
ExprSyntax(MemberAccessExprSyntax(base: selfExpr, name: .identifier(swiftAPIName)))
}
} else {
ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(swiftAPIName)))
Expand All @@ -1041,6 +1049,18 @@ extension LoweredFunctionSignature {
.joined(separator: .comma)
resultExpr = "\(callee)(\(raw: arguments))"

case .binaryOperator:
assert(paramExprs.count == 2)
resultExpr = "(\(paramExprs[0]) \(callee) \(paramExprs[1]))"

case .prefixOperator:
assert(paramExprs.count == 1)
resultExpr = "(\(callee)\(paramExprs[0]))"

case .postfixOperator:
assert(paramExprs.count == 1)
resultExpr = "(\(paramExprs[0])\(callee))"

case .getter:
assert(paramExprs.isEmpty)
resultExpr = callee
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,15 @@ extension JNISwift2JavaGenerator {
}
.joined(separator: .comma)
result = "\(tryClause)\(callee).\(decl.name)(\(downcallArguments))"

case .binaryOperator:
precondition(arguments.count == 2, "Binary operator must have exactly 2 arguments: \(decl)")
result = "(\(tryClause)((\(arguments.first!)) \(decl.name) (\(arguments.last!))))"
case .prefixOperator:
precondition(arguments.count == 1, "Prefix operator must have exactly 1 argument: \(decl)")
result = "(\(tryClause)(\(decl.name) (\(arguments.first!))))"
case .postfixOperator:
precondition(arguments.count == 1, "Postfix operator must have exactly 1 argument: \(decl)")
result = "(\(tryClause)((\(arguments.first!)) \(decl.name)))"
case .enumCase:
let downcallArguments = zip(
decl.functionSignature.parameters,
Expand Down
2 changes: 1 addition & 1 deletion Sources/JExtractSwiftLib/JavaExtractDecider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public struct JavaExtractDecider: ExtractDecider {
// Swift operators have no Java mapping
if let fn = decl.as(FunctionDeclSyntax.self) {
switch fn.name.tokenKind {
case .binaryOperator, .prefixOperator, .postfixOperator:
case .prefixOperator, .postfixOperator:
log.trace("Skip '\(decl.qualifiedNameForDebug)': operators are not supported on Java")
return false
default:
Expand Down
58 changes: 57 additions & 1 deletion Sources/JExtractSwiftLib/JavaIdentifierFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ package struct JavaIdentifierFactory {
switch method.apiKind {
case .getter, .subscriptGetter: method.javaGetterName!
case .setter, .subscriptSetter: method.javaSetterName!
case .function, .initializer, .enumCase: method.name
case .function, .initializer, .enumCase, .binaryOperator, .prefixOperator, .postfixOperator: method.name
}
methodsByBaseName[baseName, default: []].append(method)
}
Expand Down Expand Up @@ -69,6 +69,8 @@ package struct JavaIdentifierFactory {
case .getter, .subscriptGetter: decl.javaGetterName!
case .setter, .subscriptSetter: decl.javaSetterName!
case .function, .initializer, .enumCase: decl.name
case .binaryOperator, .prefixOperator, .postfixOperator:
Self.javaOperatorName(decl.name)
Comment thread
AbdAlRahmanGad marked this conversation as resolved.
}
var methodName = baseName + paramsSuffix(decl, baseName: baseName)
if Self.javaKeywords.contains(methodName) {
Expand All @@ -94,6 +96,60 @@ package struct JavaIdentifierFactory {
}
}

private static func javaOperatorName(_ swiftName: String) -> String {
var index = swiftName.startIndex
var javaName = ""

while index < swiftName.endIndex {
let isFirstToken = index == swiftName.startIndex
let oneCharacterEndIndex = swiftName.index(after: index)
let oneCharacterToken = String(swiftName[index..<oneCharacterEndIndex])
var tokenEndIndex: String.Index = oneCharacterEndIndex
var tokenName = knownOperatorNames[oneCharacterToken] ?? "operator"

// Prefer a known two-character token if it exists, e.g. `==` instead of `=` + `=`.
if oneCharacterEndIndex < swiftName.endIndex {
let twoCharacterEndIndex = swiftName.index(after: oneCharacterEndIndex)
let twoCharacterToken = String(swiftName[index..<twoCharacterEndIndex])
if let knownName = knownOperatorNames[twoCharacterToken] {
tokenEndIndex = twoCharacterEndIndex
tokenName = knownName
}
}

javaName += isFirstToken ? tokenName : tokenName.firstCharacterUppercased
index = tokenEndIndex
}

return javaName
}
Comment thread
ktoso marked this conversation as resolved.

private static let knownOperatorNames: [String: String] = [
"+": "plus",
"-": "minus",
"*": "times",
"/": "dividedBy",
"%": "remainder",
"<<": "shiftedLeft",
">>": "shiftedRight",
"|": "bitwiseOr",
"~": "bitwiseNot",
"==": "isEqual",
"!=": "isNotEqual",
"<": "lessThan",
"<=": "lessThanOrEqual",
">": "greaterThan",
">=": "greaterThanOrEqual",
"&": "bitwiseAnd",
"^": "bitwiseXor",
Comment thread
AbdAlRahmanGad marked this conversation as resolved.
"??": "coalescingNil",
"!": "logicalNot",
"=": "equal",
"&&": "and",
"||": "or",
".": "dot",
]

static let javaKeywords: Set<String> = [
/// https://docs.oracle.com/javase/specs/jls/se25/html/jls-3.html#jls-3.9
"abstract", "continue", "for", "new", "switch",
Expand Down
12 changes: 10 additions & 2 deletions Sources/JExtractSwiftLib/ThunkNameRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,20 @@ package struct ThunkNameRegistry {
.joined()
}

let baseName =
switch decl.apiKind {
case .binaryOperator, .prefixOperator, .postfixOperator:
JavaIdentifierFactory().makeJavaMethodName(decl)
default:
decl.name
}

let name: String
if let parent = decl.parentType, let nominalDecl = parent.asNominalTypeDeclaration {
let parentName = nominalDecl.flatName
name = "swiftjava_\(decl.module)_\(parentName)_\(decl.name)\(suffix)"
name = "swiftjava_\(decl.module)_\(parentName)_\(baseName)\(suffix)"
} else {
name = "swiftjava_\(decl.module)_\(decl.name)\(suffix)"
name = "swiftjava_\(decl.module)_\(baseName)\(suffix)"
}

let emittedCount = self.duplicateNames[name, default: 0]
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftExtract/ExtractedDecls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public enum SwiftAPIKind: Equatable {
case enumCase
case subscriptGetter
case subscriptSetter
case prefixOperator
case binaryOperator
case postfixOperator
}

/// Describes a Swift nominal type (e.g., a class, struct, enum) that has been
Expand Down Expand Up @@ -343,6 +346,9 @@ public final class ExtractedFunc: ExtractedSwiftDecl, CustomStringConvertible {
case .function, .initializer: ""
case .subscriptGetter: "subscriptGetter:"
case .subscriptSetter: "subscriptSetter:"
case .prefixOperator: "prefixOperator:"
case .binaryOperator: "binaryOperator:"
case .postfixOperator: "postfixOperator:"
}

let context =
Expand Down
22 changes: 21 additions & 1 deletion Sources/SwiftExtract/SwiftAnalysisVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ final class SwiftAnalysisVisitor {
}
private var deferredConstrainedExtensions: [DeferredConstrainedExtension] = []

private static func operatorKind(_ node: FunctionDeclSyntax) -> SwiftAPIKind? {
switch node.name.tokenKind {
case .binaryOperator:
return .binaryOperator
case .prefixOperator:
return .prefixOperator
case .postfixOperator:
return .postfixOperator
default:
return nil
}
}
Comment thread
ktoso marked this conversation as resolved.

func visit(inputFile: SwiftInputFile) {
let node = inputFile.syntax
for codeItem in node.statements {
Expand Down Expand Up @@ -201,11 +214,18 @@ final class SwiftAnalysisVisitor {
return
}

let apiKind: SwiftAPIKind =
if typeContext != nil && Self.operatorKind(node) != nil {
Self.operatorKind(node)!
} else {
.function
}

let extracted = ExtractedFunc(
module: analyzer.swiftModuleName,
swiftDecl: node,
name: node.name.text.unescapedSwiftName,
apiKind: .function,
apiKind: apiKind,
functionSignature: signature,
)

Expand Down
Loading
Loading