Skip to content

Commit e6fc6a8

Browse files
committed
Allow jextract to optionally import package/internal decls as well
1 parent f7a5082 commit e6fc6a8

File tree

8 files changed

+171
-19
lines changed

8 files changed

+171
-19
lines changed

Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,76 @@ extension DeclModifierSyntax {
5151
extension DeclModifierSyntax {
5252
var isPublic: Bool {
5353
switch self.name.tokenKind {
54-
case .keyword(.private): return false
55-
case .keyword(.fileprivate): return false
56-
case .keyword(.internal): return false
57-
case .keyword(.package): return false
58-
case .keyword(.public): return true
59-
case .keyword(.open): return true
60-
default: return false
54+
case .keyword(.private): false
55+
case .keyword(.fileprivate): false
56+
case .keyword(.internal): false
57+
case .keyword(.package): false
58+
case .keyword(.public): true
59+
case .keyword(.open): true
60+
default: false
6161
}
6262
}
63+
64+
var isPackage: Bool {
65+
switch self.name.tokenKind {
66+
case .keyword(.private): false
67+
case .keyword(.fileprivate): false
68+
case .keyword(.internal): false
69+
case .keyword(.package): true
70+
case .keyword(.public): false
71+
case .keyword(.open): false
72+
default: false
73+
}
74+
}
75+
76+
var isAtLeastPackage: Bool {
77+
isPackage || isPublic
78+
}
79+
80+
var isInternal: Bool {
81+
return switch self.name.tokenKind {
82+
case .keyword(.private): false
83+
case .keyword(.fileprivate): false
84+
case .keyword(.internal): true
85+
case .keyword(.package): false
86+
case .keyword(.public): false
87+
case .keyword(.open): false
88+
default: false
89+
}
90+
}
91+
92+
var isAtLeastInternal: Bool {
93+
isInternal || isPackage || isPublic
94+
}
6395
}
6496

6597
extension WithModifiersSyntax {
6698
var isPublic: Bool {
67-
self.modifiers.contains { modifier in
99+
return self.modifiers.contains { modifier in
68100
modifier.isPublic
69101
}
70102
}
103+
104+
var isAtLeastPackage: Bool {
105+
if self.modifiers.isEmpty {
106+
return false
107+
}
108+
109+
return self.modifiers.contains { modifier in
110+
modifier.isAtLeastInternal
111+
}
112+
}
113+
114+
var isAtLeastInternal: Bool {
115+
if self.modifiers.isEmpty {
116+
// we assume that default access level is internal
117+
return true
118+
}
119+
120+
return self.modifiers.contains { modifier in
121+
modifier.isAtLeastInternal
122+
}
123+
}
71124
}
72125

73126
extension AttributeListSyntax.Element {

Sources/JExtractSwiftLib/Logger.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@ public struct Logger {
2727
self.logLevel = logLevel
2828
}
2929

30+
public func error(
31+
_ message: @autoclosure () -> String,
32+
metadata: [String: Any] = [:],
33+
file: String = #fileID,
34+
line: UInt = #line,
35+
function: String = #function
36+
) {
37+
guard logLevel <= .error else {
38+
return
39+
}
40+
41+
let metadataString: String =
42+
if metadata.isEmpty { "" } else { "\(metadata)" }
43+
44+
print("[error][\(file):\(line)](\(function)) \(message()) \(metadataString)")
45+
}
46+
3047
public func warning(
3148
_ message: @autoclosure () -> String,
3249
metadata: [String: Any] = [:],

Sources/JExtractSwiftLib/Swift2JavaTranslator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ extension Swift2JavaTranslator {
200200
_ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax,
201201
parent: ImportedNominalType?
202202
) -> ImportedNominalType? {
203-
if !nominalNode.shouldImport(log: log) {
203+
if !nominalNode.shouldExtract(config: config, log: log) {
204204
return nil
205205
}
206206

@@ -225,7 +225,7 @@ extension Swift2JavaTranslator {
225225
guard swiftNominalDecl.moduleName == self.swiftModuleName else {
226226
return nil
227227
}
228-
guard swiftNominalDecl.syntax!.shouldImport(log: log) else {
228+
guard swiftNominalDecl.syntax!.shouldExtract(config: config, log: log) else {
229229
return nil
230230
}
231231

Sources/JExtractSwiftLib/Swift2JavaVisitor.swift

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
import Foundation
1616
import SwiftParser
1717
import SwiftSyntax
18+
import JavaKitConfigurationShared
1819

1920
final class Swift2JavaVisitor {
2021
let translator: Swift2JavaTranslator
22+
var config: Configuration {
23+
self.translator.config
24+
}
2125

2226
init(translator: Swift2JavaTranslator) {
2327
self.translator = translator
@@ -48,7 +52,7 @@ final class Swift2JavaVisitor {
4852
case .extensionDecl(let node):
4953
self.visit(extensionDecl: node, in: parent)
5054
case .typeAliasDecl:
51-
break // TODO: Implement
55+
break // TODO: Implement; https://github.com/swiftlang/swift-java/issues/338
5256
case .associatedTypeDecl:
5357
break // TODO: Implement
5458

@@ -93,7 +97,7 @@ final class Swift2JavaVisitor {
9397
}
9498

9599
func visit(functionDecl node: FunctionDeclSyntax, in typeContext: ImportedNominalType?) {
96-
guard node.shouldImport(log: log) else {
100+
guard node.shouldExtract(config: config, log: log) else {
97101
return
98102
}
99103

@@ -128,7 +132,7 @@ final class Swift2JavaVisitor {
128132
}
129133

130134
func visit(variableDecl node: VariableDeclSyntax, in typeContext: ImportedNominalType?) {
131-
guard node.shouldImport(log: log) else {
135+
guard node.shouldExtract(config: config, log: log) else {
132136
return
133137
}
134138

@@ -182,7 +186,7 @@ final class Swift2JavaVisitor {
182186
self.log.info("Initializer must be within a current type; \(node)")
183187
return
184188
}
185-
guard node.shouldImport(log: log) else {
189+
guard node.shouldExtract(config: config, log: log) else {
186190
return
187191
}
188192

@@ -212,13 +216,20 @@ final class Swift2JavaVisitor {
212216
}
213217

214218
extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax {
215-
func shouldImport(log: Logger) -> Bool {
216-
guard accessControlModifiers.contains(where: { $0.isPublic }) else {
217-
log.trace("Skip import '\(self.qualifiedNameForDebug)': not public")
219+
func shouldExtract(config: Configuration, log: Logger) -> Bool {
220+
let meetsRequiredAccessLevel: Bool =
221+
switch config.effectiveMinimumInputAccessLevelMode {
222+
case .public: self.isPublic
223+
case .package: self.isAtLeastPackage
224+
case .internal: self.isAtLeastInternal
225+
}
226+
227+
guard meetsRequiredAccessLevel else {
228+
log.debug("Skip import '\(self.qualifiedNameForDebug)': not at least \(config.effectiveMinimumInputAccessLevelMode)")
218229
return false
219230
}
220231
guard !attributes.contains(where: { $0.isJava }) else {
221-
log.trace("Skip import '\(self.qualifiedNameForDebug)': is Java")
232+
log.debug("Skip import '\(self.qualifiedNameForDebug)': is Java")
222233
return false
223234
}
224235

Sources/JavaKitConfigurationShared/Configuration.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public struct Configuration: Codable {
4646
public var effectiveUnsignedNumbersMode: JExtractUnsignedIntegerMode {
4747
unsignedNumbersMode ?? .default
4848
}
49+
public var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode?
50+
public var effectiveMinimumInputAccessLevelMode: JExtractMinimumAccessLevelMode {
51+
minimumInputAccessLevelMode ?? .default
52+
}
4953

5054
// ==== java 2 swift ---------------------------------------------------------
5155

Sources/JavaKitConfigurationShared/GenerationMode.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,20 @@ extension JExtractUnsignedIntegerMode {
5959
}
6060
}
6161

62-
public static var `default`: JExtractUnsignedIntegerMode {
62+
public static var `default`: Self {
6363
.annotate
6464
}
6565
}
66+
67+
/// The minimum access level which
68+
public enum JExtractMinimumAccessLevelMode: String, Codable {
69+
case `public`
70+
case `package`
71+
case `internal`
72+
}
73+
74+
extension JExtractMinimumAccessLevelMode {
75+
public static var `default`: Self {
76+
.public
77+
}
78+
}

Sources/SwiftJavaTool/Commands/JExtractCommand.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ extension SwiftJava {
6464
@Option(help: "The mode of generation to use for the output files. Used with jextract mode. By default, unsigned Swift types are imported as their bit-width compatible signed Java counterparts, and annotated using the '@Unsigned' annotation. You may choose the 'wrap-guava' mode in order to import types as class wrapper types (`UnsignedInteger` et al) defined by the Google Guava library's `com.google.common.primitives' package. that ensure complete type-safety with regards to unsigned values, however they incur an allocation and performance overhead.")
6565
var unsignedNumbers: JExtractUnsignedIntegerMode = .default
6666

67+
@Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.")
68+
var minimumInputAccessLevel: JExtractMinimumAccessLevelMode = .default
69+
6770
@Option(
6871
help: """
6972
A swift-java configuration file for a given Swift module name on which this module depends,
@@ -85,6 +88,7 @@ extension SwiftJava.JExtractCommand {
8588
config.outputSwiftDirectory = outputSwift
8689
config.writeEmptyFiles = writeEmptyFiles
8790
config.unsignedNumbersMode = unsignedNumbers
91+
config.minimumInputAccessLevelMode = minimumInputAccessLevel
8892

8993
try checkModeCompatibility()
9094

@@ -143,3 +147,4 @@ struct IllegalModeCombinationError: Error {
143147

144148
extension JExtractGenerationMode: ExpressibleByArgument {}
145149
extension JExtractUnsignedIntegerMode: ExpressibleByArgument {}
150+
extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import JExtractSwiftLib
16+
import JavaKitConfigurationShared
17+
import Testing
18+
19+
final class InternalExtractTests {
20+
let text =
21+
"""
22+
internal func catchMeIfYouCan()
23+
"""
24+
25+
@Test("Import: internal decl if configured")
26+
func data_swiftThunk() throws {
27+
var config = Configuration()
28+
config.minimumInputAccessLevelMode = .internal
29+
30+
try assertOutput(
31+
input: text,
32+
config: config,
33+
.ffm, .java,
34+
expectedChunks: [
35+
"""
36+
/**
37+
* Downcall to Swift:
38+
* {@snippet lang=swift :
39+
* internal func catchMeIfYouCan()
40+
* }
41+
*/
42+
public static void catchMeIfYouCan() {
43+
swiftjava_SwiftModule_catchMeIfYouCan.call();
44+
}
45+
""",
46+
]
47+
)
48+
}
49+
}

0 commit comments

Comments
 (0)