Skip to content

Prepare accessor function descriptors for properties: get set #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 8, 2024
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
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "main"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
.package(url: "https://github.com/apple/swift-system", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-system", from: "1.0.0"), // TODO: remove, we should not need 'nm' or process callouts
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.1.0")),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OrderedSet to avoid sourcegen order breaking some tests

],
targets: [
.macro(
Expand Down Expand Up @@ -282,6 +283,7 @@ let package = Package(
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Collections", package: "swift-collections"),
"_Subprocess",
"JavaTypes",
],
Expand Down
6 changes: 5 additions & 1 deletion Samples/SwiftKitSampleApp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ application {
task jextract(type: Exec) {
description = "Extracts Java accessor sources using jextract"
outputs.dir(layout.buildDirectory.dir("generated"))
inputs.dir("$rootDir/Sources/ExampleSwiftLibrary")
inputs.dir("$rootDir/Sources/ExampleSwiftLibrary") // monitored library

// any changes in the source generator sources also mean the resulting output might change
inputs.dir("$rootDir/Sources/JExtractSwift")
inputs.dir("$rootDir/Sources/JExtractSwiftTool")

workingDir = rootDir
commandLine "make"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
package com.example.swift.generated;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand All @@ -31,6 +34,27 @@ static void beforeAll() {
System.setProperty("jextract.trace.downcalls", "true");
}

// TODO: test member methods on MySwiftClass
@Test
@DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces
void test_MySwiftClass_voidMethod() {
MySwiftClass o = new MySwiftClass(12, 42);
o.voidMethod();
}

@Test
@DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces
void test_MySwiftClass_makeIntMethod() {
MySwiftClass o = new MySwiftClass(12, 42);
var got = o.makeIntMethod();
assertEquals(12, got);
}

@Test
@DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of tests waiting on #27

void test_MySwiftClass_property_len() {
MySwiftClass o = new MySwiftClass(12, 42);
var got = o.makeIntMethod();
assertEquals(12, got);
}

}
2 changes: 2 additions & 0 deletions Sources/ExampleSwiftLibrary/MySwiftLibrary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public class MySwiftClass {
p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))")
}

public var counter: Int32 = 0

public func voidMethod() {
p("")
}
Expand Down
25 changes: 25 additions & 0 deletions Sources/JExtractSwift/Convenience/String+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 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
//
//===----------------------------------------------------------------------===//

extension String {

// TODO: naive implementation good enough for our simple case `methodMethodSomething` -> `MethodSomething`
var toCamelCase: String {
guard let f = first else {
return self
}

return "\(f.uppercased())\(String(dropFirst()))"
}
}
102 changes: 102 additions & 0 deletions Sources/JExtractSwift/ImportedDecls+Printing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 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 Foundation
import JavaTypes
import SwiftSyntax
import OrderedCollections

extension ImportedFunc {
/// Render a `@{@snippet ... }` comment section that can be put inside a JavaDoc comment
/// when referring to the original declaration a printed method refers to.
var renderCommentSnippet: String? {
if let syntax {
"""
* {@snippet lang=swift :
* \(syntax)
* }
"""
} else {
nil
}
}
}

extension VariableAccessorKind {

public var fieldSuffix: String {
switch self {
case .get: "_GET"
case .set: "_SET"
}
}

public var renderDescFieldName: String {
switch self {
case .get: "DESC_GET"
case .set: "DESC_SET"
}
}

public var renderAddrFieldName: String {
switch self {
case .get: "ADDR_GET"
case .set: "ADDR_SET"
}
}

public var renderHandleFieldName: String {
switch self {
case .get: "HANDLE_GET"
case .set: "HANDLE_SET"
}
}

/// Renders a "$get" part that can be used in a method signature representing this accessor.
public var renderMethodNameSegment: String {
switch self {
case .get: "$get"
case .set: "$set"
}
}

func renderMethodName(_ decl: ImportedFunc) -> String? {
switch self {
case .get: "get\(decl.identifier.toCamelCase)"
case .set: "set\(decl.identifier.toCamelCase)"
}
}
}

extension Optional where Wrapped == VariableAccessorKind {
public var renderDescFieldName: String {
self?.renderDescFieldName ?? "DESC"
}

public var renderAddrFieldName: String {
self?.renderAddrFieldName ?? "ADDR"
}

public var renderHandleFieldName: String {
self?.renderHandleFieldName ?? "HANDLE"
}

public var renderMethodNameSegment: String {
self?.renderMethodNameSegment ?? ""
}

func renderMethodName(_ decl: ImportedFunc) -> String {
self?.renderMethodName(decl) ?? decl.baseIdentifier
}
}
153 changes: 151 additions & 2 deletions Sources/JExtractSwift/ImportedDecls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
import Foundation
import JavaTypes
import SwiftSyntax
import OrderedCollections

/// Any imported (Swift) declaration
protocol ImportedDecl {

}
Expand All @@ -32,6 +34,7 @@ public struct ImportedNominalType: ImportedDecl {

public var initializers: [ImportedFunc] = []
public var methods: [ImportedFunc] = []
public var variables: [ImportedVariable] = []

public init(swiftTypeName: String, javaType: JavaType, swiftMangledName: String? = nil, kind: NominalTypeKind) {
self.swiftTypeName = swiftTypeName
Expand Down Expand Up @@ -195,7 +198,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {

public var swiftMangledName: String = ""

public var swiftDeclRaw: String? = nil
public var syntax: String? = nil

public var isInit: Bool = false

Expand All @@ -221,7 +224,153 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {

Swift mangled name:
Imported from:
\(swiftDeclRaw ?? "<no swift source>")
\(syntax?.description ?? "<no swift source>")
}
"""
}
}

public enum VariableAccessorKind {
case get
case set
}

public struct ImportedVariable: ImportedDecl, CustomStringConvertible {
/// If this function/method is member of a class/struct/protocol,
/// this will contain that declaration's imported name.
///
/// This is necessary when rendering accessor Java code we need the type that "self" is expecting to have.
public var parentName: TranslatedType?
public var hasParent: Bool { parentName != nil }

/// This is a full name such as "counter".
public var identifier: String

/// Which accessors are we able to expose.
///
/// Usually this will be all the accessors the variable declares,
/// however if the getter is async or throwing we may not be able to import it
/// (yet), and therefore would skip it from the supported set.
public var supportedAccessorKinds: OrderedSet<VariableAccessorKind> = [.get, .set]

/// This is the base identifier for the function, e.g., "init" for an
/// initializer or "f" for "f(a:b:)".
public var baseIdentifier: String {
guard let idx = identifier.firstIndex(of: "(") else {
return identifier
}
return String(identifier[..<idx])
}

/// A display name to use to refer to the Swift declaration with its
/// enclosing type, if there is one.
public var displayName: String {
if let parentName {
return "\(parentName.swiftTypeName).\(identifier)"
}

return identifier
}

public var returnType: TranslatedType

/// Synthetic signature of an accessor function of the given kind of this property
public func accessorFunc(kind: VariableAccessorKind) -> ImportedFunc? {
guard self.supportedAccessorKinds.contains(kind) else {
return nil
}

switch kind {
case .set:
let newValueParam: FunctionParameterSyntax = "_ newValue: \(self.returnType.cCompatibleSwiftType)"
var funcDecl = ImportedFunc(
parentName: self.parentName,
identifier: self.identifier,
returnType: TranslatedType.void,
parameters: [.init(param: newValueParam, type: self.returnType)])
funcDecl.swiftMangledName = self.swiftMangledName + "s" // form mangled name of the getter by adding the suffix
return funcDecl

case .get:
var funcDecl = ImportedFunc(
parentName: self.parentName,
identifier: self.identifier,
returnType: self.returnType,
parameters: [])
funcDecl.swiftMangledName = self.swiftMangledName + "g" // form mangled name of the getter by adding the suffix
return funcDecl
}
}

public func effectiveAccessorParameters(_ kind: VariableAccessorKind, selfVariant: SelfParameterVariant?) -> [ImportedParam] {
var params: [ImportedParam] = []

if kind == .set {
let newValueParam: FunctionParameterSyntax = "_ newValue: \(raw: self.returnType.swiftTypeName)"
params.append(
ImportedParam(
param: newValueParam,
type: self.returnType)
)
}

if let parentName {
// Add `self: Self` for method calls on a member
//
// allocating initializer takes a Self.Type instead, but it's also a pointer
switch selfVariant {
case nil, .wrapper:
break

case .pointer:
let selfParam: FunctionParameterSyntax = "self$: $swift_pointer"
params.append(
ImportedParam(
param: selfParam,
type: parentName
)
)

case .memorySegment:
let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment"
var parentForSelf = parentName
parentForSelf.javaType = .javaForeignMemorySegment
params.append(
ImportedParam(
param: selfParam,
type: parentForSelf
)
)
}
}

return params
}

public var swiftMangledName: String = ""

public var syntax: VariableDeclSyntax? = nil

public init(
parentName: TranslatedType?,
identifier: String,
returnType: TranslatedType
) {
self.parentName = parentName
self.identifier = identifier
self.returnType = returnType
}

public var description: String {
"""
ImportedFunc {
mangledName: \(swiftMangledName)
identifier: \(identifier)
returnType: \(returnType)

Swift mangled name:
Imported from:
\(syntax?.description ?? "<no swift source>")
}
"""
}
Expand Down
Loading