Skip to content

Commit 0b1fc44

Browse files
authored
Merge pull request #223 from rintaro/jextract-misc1
[jextract] Misc improvements
2 parents e18a26b + 5c6d7ba commit 0b1fc44

File tree

7 files changed

+200
-65
lines changed

7 files changed

+200
-65
lines changed

Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift

Lines changed: 145 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,7 @@
1515
import SwiftDiagnostics
1616
import SwiftSyntax
1717

18-
extension DeclGroupSyntax {
19-
internal var accessControlModifiers: DeclModifierListSyntax {
20-
modifiers.filter { modifier in
21-
modifier.isAccessControl
22-
}
23-
}
24-
}
25-
26-
extension FunctionDeclSyntax {
27-
internal var accessControlModifiers: DeclModifierListSyntax {
28-
modifiers.filter { modifier in
29-
modifier.isAccessControl
30-
}
31-
}
32-
}
33-
34-
extension VariableDeclSyntax {
18+
extension WithModifiersSyntax {
3519
internal var accessControlModifiers: DeclModifierListSyntax {
3620
modifiers.filter { modifier in
3721
modifier.isAccessControl
@@ -89,7 +73,7 @@ extension DeclModifierSyntax {
8973
var isAccessControl: Bool {
9074
switch self.name.tokenKind {
9175
case .keyword(.private), .keyword(.fileprivate), .keyword(.internal), .keyword(.package),
92-
.keyword(.public):
76+
.keyword(.public), .keyword(.open):
9377
return true
9478
default:
9579
return false
@@ -105,7 +89,150 @@ extension DeclModifierSyntax {
10589
case .keyword(.internal): return false
10690
case .keyword(.package): return false
10791
case .keyword(.public): return true
92+
case .keyword(.open): return true
10893
default: return false
10994
}
11095
}
11196
}
97+
98+
extension WithModifiersSyntax {
99+
var isPublic: Bool {
100+
self.modifiers.contains { modifier in
101+
modifier.isPublic
102+
}
103+
}
104+
}
105+
106+
extension AttributeListSyntax.Element {
107+
/// Whether this node has `JavaKit` attributes.
108+
var isJava: Bool {
109+
guard case let .attribute(attr) = self else {
110+
// FIXME: Handle #if.
111+
return false
112+
}
113+
let attrName = attr.attributeName.description
114+
switch attrName {
115+
case "JavaClass", "JavaInterface", "JavaField", "JavaStaticField", "JavaMethod", "JavaStaticMethod", "JavaImplementation":
116+
return true
117+
default:
118+
return false
119+
}
120+
}
121+
}
122+
123+
extension DeclSyntaxProtocol {
124+
/// Find inner most "decl" node in ancestors.
125+
var ancestorDecl: DeclSyntax? {
126+
var node: Syntax = Syntax(self)
127+
while let parent = node.parent {
128+
if let decl = parent.as(DeclSyntax.self) {
129+
return decl
130+
}
131+
node = parent
132+
}
133+
return nil
134+
}
135+
136+
/// Declaration name primarily for debugging.
137+
var nameForDebug: String {
138+
return switch DeclSyntax(self).as(DeclSyntaxEnum.self) {
139+
case .accessorDecl(let node):
140+
node.accessorSpecifier.text
141+
case .actorDecl(let node):
142+
node.name.text
143+
case .associatedTypeDecl(let node):
144+
node.name.text
145+
case .classDecl(let node):
146+
node.name.text
147+
case .deinitializerDecl(_):
148+
"deinit"
149+
case .editorPlaceholderDecl:
150+
""
151+
case .enumCaseDecl(let node):
152+
// FIXME: Handle multiple elements.
153+
if let element = node.elements.first {
154+
element.name.text
155+
} else {
156+
"case"
157+
}
158+
case .enumDecl(let node):
159+
node.name.text
160+
case .extensionDecl(let node):
161+
node.extendedType.description
162+
case .functionDecl(let node):
163+
node.name.text + "(" + node.signature.parameterClause.parameters.map({ $0.firstName.text + ":" }).joined() + ")"
164+
case .ifConfigDecl(_):
165+
"#if"
166+
case .importDecl(_):
167+
"import"
168+
case .initializerDecl(let node):
169+
"init" + "(" + node.signature.parameterClause.parameters.map({ $0.firstName.text + ":" }).joined() + ")"
170+
case .macroDecl(let node):
171+
node.name.text
172+
case .macroExpansionDecl(let node):
173+
"#" + node.macroName.trimmedDescription
174+
case .missingDecl(_):
175+
"(missing)"
176+
case .operatorDecl(let node):
177+
node.name.text
178+
case .poundSourceLocation(_):
179+
"#sourceLocation"
180+
case .precedenceGroupDecl(let node):
181+
node.name.text
182+
case .protocolDecl(let node):
183+
node.name.text
184+
case .structDecl(let node):
185+
node.name.text
186+
case .subscriptDecl(let node):
187+
"subscript" + "(" + node.parameterClause.parameters.map({ $0.firstName.text + ":" }).joined() + ")"
188+
case .typeAliasDecl(let node):
189+
node.name.text
190+
case .variableDecl(let node):
191+
// FIXME: Handle multiple variables.
192+
if let element = node.bindings.first {
193+
element.pattern.trimmedDescription
194+
} else {
195+
"var"
196+
}
197+
}
198+
}
199+
200+
/// Qualified declaration name primarily for debugging.
201+
var qualifiedNameForDebug: String {
202+
if let parent = ancestorDecl {
203+
parent.qualifiedNameForDebug + "." + nameForDebug
204+
} else {
205+
nameForDebug
206+
}
207+
}
208+
209+
/// Signature part of the declaration. I.e. without body or member block.
210+
var signatureString: String {
211+
return switch DeclSyntax(self.detached).as(DeclSyntaxEnum.self) {
212+
case .functionDecl(let node):
213+
node.with(\.body, nil).trimmedDescription
214+
case .initializerDecl(let node):
215+
node.with(\.body, nil).trimmedDescription
216+
case .classDecl(let node):
217+
node.with(\.memberBlock, "").trimmedDescription
218+
case .structDecl(let node):
219+
node.with(\.memberBlock, "").trimmedDescription
220+
case .protocolDecl(let node):
221+
node.with(\.memberBlock, "").trimmedDescription
222+
case .accessorDecl(let node):
223+
node.with(\.body, nil).trimmedDescription
224+
case .variableDecl(let node):
225+
node
226+
.with(\.bindings, PatternBindingListSyntax(
227+
node.bindings.map {
228+
$0.detached
229+
.with(\.accessorBlock, nil)
230+
.with(\.initializer, nil)
231+
}
232+
))
233+
.trimmedDescription
234+
default:
235+
fatalError("unimplemented \(self.kind)")
236+
}
237+
}
238+
}

Sources/JExtractSwift/ImportedDecls.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
261261
public var swiftDecl: any DeclSyntaxProtocol
262262

263263
public var syntax: String? {
264-
"\(self.swiftDecl)"
264+
self.swiftDecl.signatureString
265265
}
266266

267267
public var isInit: Bool = false

Sources/JExtractSwift/NominalTypeResolution.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class NominalTypeResolution {
3838

3939
/// A syntax node for a nominal type declaration.
4040
@_spi(Testing)
41-
public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax
41+
public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax
4242

4343
// MARK: Nominal type name resolution.
4444
extension NominalTypeResolution {

Sources/JExtractSwift/Swift2JavaTranslator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ extension Swift2JavaTranslator {
159159
/// Try to resolve the given nominal type node into its imported
160160
/// representation.
161161
func importedNominalType(
162-
_ nominal: some DeclGroupSyntax & NamedDeclSyntax
162+
_ nominal: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax
163163
) -> ImportedNominalType? {
164164
if !nominal.shouldImport(log: log) {
165165
return nil

Sources/JExtractSwift/Swift2JavaVisitor.swift

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ final class Swift2JavaVisitor: SyntaxVisitor {
2626
/// store this along with type names as we import them.
2727
let targetJavaPackage: String
2828

29-
var currentType: ImportedNominalType? = nil
29+
/// Type context stack associated with the syntax.
30+
var typeContext: [(syntaxID: Syntax.ID, type: ImportedNominalType)] = []
31+
32+
/// Innermost type context.
33+
var currentType: ImportedNominalType? { typeContext.last?.type }
3034

3135
/// The current type name as a nested name like A.B.C.
3236
var currentTypeName: String? { self.currentType?.swiftTypeName }
@@ -41,37 +45,50 @@ final class Swift2JavaVisitor: SyntaxVisitor {
4145
super.init(viewMode: .all)
4246
}
4347

48+
/// Push specified type to the type context associated with the syntax.
49+
func pushTypeContext(syntax: some SyntaxProtocol, importedNominal: ImportedNominalType) {
50+
typeContext.append((syntax.id, importedNominal))
51+
}
52+
53+
/// Pop type context if the current context is associated with the syntax.
54+
func popTypeContext(syntax: some SyntaxProtocol) -> Bool {
55+
if typeContext.last?.syntaxID == syntax.id {
56+
typeContext.removeLast()
57+
return true
58+
} else {
59+
return false
60+
}
61+
}
62+
4463
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
45-
log.debug("Visit \(node.kind): \(node)")
64+
log.debug("Visit \(node.kind): '\(node.qualifiedNameForDebug)'")
4665
guard let importedNominalType = translator.importedNominalType(node) else {
4766
return .skipChildren
4867
}
4968

50-
self.currentType = importedNominalType
69+
self.pushTypeContext(syntax: node, importedNominal: importedNominalType)
5170
return .visitChildren
5271
}
5372

5473
override func visitPost(_ node: ClassDeclSyntax) {
55-
if currentType != nil {
74+
if self.popTypeContext(syntax: node) {
5675
log.debug("Completed import: \(node.kind) \(node.name)")
57-
self.currentType = nil
5876
}
5977
}
6078

6179
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
62-
log.debug("Visit \(node.kind): \(node)")
80+
log.debug("Visit \(node.kind): \(node.qualifiedNameForDebug)")
6381
guard let importedNominalType = translator.importedNominalType(node) else {
6482
return .skipChildren
6583
}
6684

67-
self.currentType = importedNominalType
85+
self.pushTypeContext(syntax: node, importedNominal: importedNominalType)
6886
return .visitChildren
6987
}
7088

7189
override func visitPost(_ node: StructDeclSyntax) {
72-
if currentType != nil {
73-
log.debug("Completed import: \(node.kind) \(node.name)")
74-
self.currentType = nil
90+
if self.popTypeContext(syntax: node) {
91+
log.debug("Completed import: \(node.kind) \(node.qualifiedNameForDebug)")
7592
}
7693
}
7794

@@ -84,13 +101,13 @@ final class Swift2JavaVisitor: SyntaxVisitor {
84101
return .skipChildren
85102
}
86103

87-
self.currentType = importedNominalType
104+
self.pushTypeContext(syntax: node, importedNominal: importedNominalType)
88105
return .visitChildren
89106
}
90107

91108
override func visitPost(_ node: ExtensionDeclSyntax) {
92-
if currentType != nil {
93-
self.currentType = nil
109+
if self.popTypeContext(syntax: node) {
110+
log.debug("Completed import: \(node.kind) \(node.qualifiedNameForDebug)")
94111
}
95112
}
96113

@@ -148,6 +165,10 @@ final class Swift2JavaVisitor: SyntaxVisitor {
148165
}
149166

150167
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
168+
guard node.shouldImport(log: log) else {
169+
return .skipChildren
170+
}
171+
151172
guard let binding = node.bindings.first else {
152173
return .skipChildren
153174
}
@@ -156,7 +177,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
156177

157178
// TODO: filter out kinds of variables we cannot import
158179

159-
self.log.debug("Import variable: \(node.kind) \(fullName)")
180+
self.log.debug("Import variable: \(node.kind) '\(node.qualifiedNameForDebug)'")
160181

161182
let returnTy: TypeSyntax
162183
if let typeAnnotation = binding.typeAnnotation {
@@ -169,7 +190,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
169190
do {
170191
javaResultType = try cCompatibleType(for: returnTy)
171192
} catch {
172-
self.log.info("Unable to import variable \(node.debugDescription) - \(error)")
193+
log.info("Unable to import variable '\(node.qualifiedNameForDebug)' - \(error)")
173194
return .skipChildren
174195
}
175196

@@ -190,7 +211,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
190211
log.debug("Record variable in \(currentTypeName)")
191212
translator.importedTypes[currentTypeName]!.variables.append(varDecl)
192213
} else {
193-
fatalError("Global variables are not supported yet: \(node.debugDescription)")
214+
fatalError("Global variables are not supported yet: \(node.qualifiedNameForDebug)")
194215
}
195216

196217
return .skipChildren
@@ -206,7 +227,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
206227
return .skipChildren
207228
}
208229

209-
self.log.debug("Import initializer: \(node.kind) \(currentType.javaType.description)")
230+
self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'")
210231
let params: [ImportedParam]
211232
do {
212233
params = try node.signature.parameterClause.parameters.map { param in
@@ -247,37 +268,24 @@ final class Swift2JavaVisitor: SyntaxVisitor {
247268
}
248269
}
249270

250-
extension DeclGroupSyntax where Self: NamedDeclSyntax {
271+
extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax {
251272
func shouldImport(log: Logger) -> Bool {
252-
guard (accessControlModifiers.first { $0.isPublic }) != nil else {
253-
log.trace("Cannot import \(self.name) because: is not public")
273+
guard accessControlModifiers.contains(where: { $0.isPublic }) else {
274+
log.trace("Skip import '\(self.qualifiedNameForDebug)': not public")
254275
return false
255276
}
256-
257-
return true
258-
}
259-
}
260-
261-
extension InitializerDeclSyntax {
262-
func shouldImport(log: Logger) -> Bool {
263-
let isFailable = self.optionalMark != nil
264-
265-
if isFailable {
266-
log.warning("Skip importing failable initializer: \(self)")
277+
guard !attributes.contains(where: { $0.isJava }) else {
278+
log.trace("Skip import '\(self.qualifiedNameForDebug)': is Java")
267279
return false
268280
}
269281

270-
// Ok, import it
271-
log.warning("Import initializer: \(self)")
272-
return true
273-
}
274-
}
282+
if let node = self.as(InitializerDeclSyntax.self) {
283+
let isFailable = node.optionalMark != nil
275284

276-
extension FunctionDeclSyntax {
277-
func shouldImport(log: Logger) -> Bool {
278-
guard (accessControlModifiers.first { $0.isPublic }) != nil else {
279-
log.trace("Cannot import \(self.name) because: is not public")
280-
return false
285+
if isFailable {
286+
log.warning("Skip import '\(self.qualifiedNameForDebug)': failable initializer")
287+
return false
288+
}
281289
}
282290

283291
return true

0 commit comments

Comments
 (0)