Skip to content

Commit b1cc816

Browse files
committed
Small rework of analyzing imported modules and printing import statements to output swift file.
1 parent d4222ae commit b1cc816

File tree

8 files changed

+237
-108
lines changed

8 files changed

+237
-108
lines changed

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,51 @@ extension FFMSwift2JavaGenerator {
125125
}
126126

127127
func printSwiftThunkImports(_ printer: inout CodePrinter) {
128+
let mainSymbolSourceModules = Set(
129+
self.lookupContext.symbolTable.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName)
130+
)
131+
128132
for module in self.lookupContext.symbolTable.importedModules.keys.sorted() {
129133
guard module != "Swift" else {
130134
continue
131135
}
136+
137+
guard let alternativeModules = self.lookupContext.symbolTable.importedModules[module]?.alternativeModules else {
138+
printer.print("import \(module)")
139+
continue
140+
}
141+
142+
// Try to print only on main module from relation chain as it has every other module.
143+
guard !mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) || alternativeModules.isMainSourceOfSymbols else {
144+
if !alternativeModules.isMainSourceOfSymbols {
145+
printer.print("import \(module)")
146+
}
147+
continue
148+
}
149+
150+
var importGroups: [String: [String]] = [:]
151+
for name in alternativeModules.moduleNames {
152+
guard let otherModule = self.lookupContext.symbolTable.importedModules[name] else { continue }
153+
154+
let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName
155+
importGroups[groupKey, default: []].append(otherModule.moduleName)
156+
}
157+
158+
for (index, group) in importGroups.keys.sorted().enumerated() {
159+
if index > 0 && importGroups.keys.count > 1 {
160+
printer.print("#elseif canImport(\(group))")
161+
} else {
162+
printer.print("#if canImport(\(group))")
163+
}
164+
165+
for groupModule in importGroups[group] ?? [] {
166+
printer.print("import \(groupModule)")
167+
}
168+
}
169+
170+
printer.print("#else")
132171
printer.print("import \(module)")
172+
printer.print("#endif")
133173
}
134174
printer.println()
135175
}

Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,74 @@
1515
import SwiftSyntax
1616

1717
/// Scan importing modules.
18-
func importingModuleNames(sourceFile: SourceFileSyntax) -> [String] {
19-
var importingModuleNames: [String] = []
18+
func importingModules(sourceFile: SourceFileSyntax) -> [ImportedSwiftModule] {
19+
var importingModuleNames: [ImportedSwiftModule] = []
2020
for item in sourceFile.statements {
2121
if let importDecl = item.item.as(ImportDeclSyntax.self) {
2222
guard let moduleName = importDecl.path.first?.name.text else {
2323
continue
2424
}
25-
importingModuleNames.append(moduleName)
25+
importingModuleNames.append(ImportedSwiftModule(name: moduleName, availableWithModuleName: nil, alternativeModuleNames: []))
26+
} else if let ifConfigDecl = item.item.as(IfConfigDeclSyntax.self) {
27+
importingModuleNames.append(contentsOf: modules(from: ifConfigDecl))
2628
}
2729
}
2830
return importingModuleNames
2931
}
32+
33+
private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ImportedSwiftModule] {
34+
guard
35+
let firstClause = ifConfigDecl.clauses.first,
36+
let calledExpression = firstClause.condition?.as(FunctionCallExprSyntax.self)?.calledExpression.as(DeclReferenceExprSyntax.self),
37+
calledExpression.baseName.text == "canImport" else {
38+
return []
39+
}
40+
41+
var modules: [ImportedSwiftModule] = []
42+
modules.reserveCapacity(ifConfigDecl.clauses.count)
43+
44+
for (index, clause) in ifConfigDecl.clauses.enumerated() {
45+
let importedModuleNames = clause.elements?.as(CodeBlockItemListSyntax.self)?
46+
.compactMap { CodeBlockItemSyntax($0) }
47+
.compactMap { $0.item.as(ImportDeclSyntax.self) }
48+
.compactMap { $0.path.first?.name.text } ?? []
49+
50+
let importModuleName: String? = if
51+
let funcCallExpr = clause.condition?.as(FunctionCallExprSyntax.self),
52+
let calledDeclReference = funcCallExpr.calledExpression.as(DeclReferenceExprSyntax.self),
53+
calledDeclReference.baseName.text == "canImport",
54+
let moduleNameSyntax = funcCallExpr.arguments.first?.expression.as(DeclReferenceExprSyntax.self) {
55+
moduleNameSyntax.baseName.text
56+
} else {
57+
nil
58+
}
59+
60+
let clauseModules = importedModuleNames.map {
61+
ImportedSwiftModule(name: $0,
62+
availableWithModuleName: importModuleName,
63+
alternativeModuleNames: [])
64+
}
65+
66+
// Assume single import from #else statement is fallback and use it as main source of symbols
67+
if
68+
clauseModules.count == 1 &&
69+
index == (ifConfigDecl.clauses.count - 1) &&
70+
clause.poundKeyword.tokenKind == .poundElse {
71+
var fallbackModule: ImportedSwiftModule = clauseModules[0]
72+
var moduleNames: [String] = []
73+
moduleNames.reserveCapacity(modules.count)
74+
75+
for i in 0..<modules.count {
76+
modules[i].alternativeModuleNames.insert(fallbackModule.name)
77+
moduleNames.append(modules[i].name)
78+
}
79+
80+
fallbackModule.alternativeModuleNames = Set(modules.map(\.name))
81+
fallbackModule.isMainSourceOfSymbols = true
82+
}
83+
84+
modules.append(contentsOf: clauseModules)
85+
}
86+
87+
return modules
88+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 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+
16+
struct ImportedSwiftModule: Hashable {
17+
let name: String
18+
let availableWithModuleName: String?
19+
var alternativeModuleNames: Set<String>
20+
var isMainSourceOfSymbols: Bool
21+
22+
init(name: String, availableWithModuleName: String? = nil, alternativeModuleNames: Set<String> = [], isMainSourceOfSymbols: Bool = false) {
23+
self.name = name
24+
self.availableWithModuleName = availableWithModuleName
25+
self.alternativeModuleNames = alternativeModuleNames
26+
self.isMainSourceOfSymbols = isMainSourceOfSymbols
27+
}
28+
29+
func hash(into hasher: inout Hasher) {
30+
hasher.combine(name)
31+
}
32+
}

Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,20 @@ private var swiftSymbolTable: SwiftModuleSymbolTable {
4848
}
4949

5050
private var foundationEssentialsSymbolTable: SwiftModuleSymbolTable {
51-
var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "FoundationEssentials", importedModules: ["Swift": swiftSymbolTable])
51+
var builder = SwiftParsedModuleSymbolTableBuilder(
52+
moduleName: "FoundationEssentials",
53+
requiredAvailablityOfModuleWithName: "FoundationEssentials",
54+
alternativeModules: .init(isMainSourceOfSymbols: false, moduleNames: ["Foundation"]),
55+
importedModules: ["Swift": swiftSymbolTable])
5256
builder.handle(sourceFile: foundationEssentialsSourceFile)
5357
return builder.finalize()
5458
}
5559

5660
private var foundationSymbolTable: SwiftModuleSymbolTable {
57-
var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Foundation", importedModules: ["Swift": swiftSymbolTable])
61+
var builder = SwiftParsedModuleSymbolTableBuilder(
62+
moduleName: "Foundation",
63+
alternativeModules: .init(isMainSourceOfSymbols: true, moduleNames: ["FoundationEssentials"]),
64+
importedModules: ["Swift": swiftSymbolTable])
5865
builder.handle(sourceFile: foundationSourceFile)
5966
return builder.finalize()
6067
}

Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol {
1919
/// The name of this module.
2020
let moduleName: String
2121

22+
/// The name of module required to be imported and checked via canImport statement.
23+
let requiredAvailablityOfModuleWithName: String?
24+
25+
/// Data about alternative modules which provides desired symbos e.g. FoundationEssentials is non-Darwin platform alternative for Foundation
26+
let alternativeModules: AlternativeModuleNamesData?
27+
2228
/// The top-level nominal types, found by name.
2329
var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:]
2430

@@ -36,4 +42,18 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol {
3642
func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? {
3743
nestedTypes[parent]?[name]
3844
}
45+
46+
func isAlternative(for moduleName: String) -> Bool {
47+
alternativeModules.flatMap { $0.moduleNames.contains(moduleName) } ?? false
48+
}
3949
}
50+
51+
extension SwiftModuleSymbolTable {
52+
struct AlternativeModuleNamesData {
53+
/// Flag indicating module should be used as source of symbols to avoid duplication of symbols.
54+
let isMainSourceOfSymbols: Bool
55+
56+
/// Names of modules which are alternative for currently checked module.
57+
let moduleNames: Set<String>
58+
}
59+
}

Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,18 @@ struct SwiftParsedModuleSymbolTableBuilder {
2626
/// Extension decls their extended type hasn't been resolved.
2727
var unresolvedExtensions: [ExtensionDeclSyntax]
2828

29-
init(moduleName: String, importedModules: [String: SwiftModuleSymbolTable], log: Logger? = nil) {
29+
init(
30+
moduleName: String,
31+
requiredAvailablityOfModuleWithName: String? = nil,
32+
alternativeModules: SwiftModuleSymbolTable.AlternativeModuleNamesData? = nil,
33+
importedModules: [String: SwiftModuleSymbolTable],
34+
log: Logger? = nil
35+
) {
3036
self.log = log
31-
self.symbolTable = .init(moduleName: moduleName)
37+
self.symbolTable = .init(
38+
moduleName: moduleName,
39+
requiredAvailablityOfModuleWithName: requiredAvailablityOfModuleWithName,
40+
alternativeModules: alternativeModules)
3241
self.importedModules = importedModules
3342
self.unresolvedExtensions = []
3443
}

Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ package class SwiftSymbolTable {
4242
let parsedModule:SwiftModuleSymbolTable
4343

4444
private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:]
45+
private var prioritySortedImportedModules: [SwiftModuleSymbolTable] {
46+
importedModules.values.sorted(by: { ($0.alternativeModules?.isMainSourceOfSymbols ?? false) && $0.moduleName < $1.moduleName })
47+
}
4548

4649
init(parsedModule: SwiftModuleSymbolTable, importedModules: [String: SwiftModuleSymbolTable]) {
4750
self.parsedModule = parsedModule
@@ -58,18 +61,22 @@ extension SwiftSymbolTable {
5861

5962
// Prepare imported modules.
6063
// FIXME: Support arbitrary dependencies.
61-
var moduleNames: Set<String> = []
64+
var modules: Set<ImportedSwiftModule> = []
6265
for sourceFile in sourceFiles {
63-
moduleNames.formUnion(importingModuleNames(sourceFile: sourceFile))
66+
modules.formUnion(importingModules(sourceFile: sourceFile))
6467
}
6568
var importedModules: [String: SwiftModuleSymbolTable] = [:]
6669
importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable
67-
for moduleName in moduleNames.sorted() {
70+
for module in modules {
71+
// We don't need duplicates of symbols, first known definition is enough to parse module
72+
// e.g Data from FoundationEssentials and Foundation collide and lead to different results due to random order of keys in Swift's Dictionary
73+
// guard module.isMainSourceOfSymbols || !importedModules.contains(where: { $0.value.isAlternative(for: String)}) else { continue }
74+
6875
if
69-
importedModules[moduleName] == nil,
70-
let knownModule = SwiftKnownModule(rawValue: moduleName)
76+
importedModules[module.name] == nil,
77+
let knownModule = SwiftKnownModule(rawValue: module.name)
7178
{
72-
importedModules[moduleName] = knownModule.symbolTable
79+
importedModules[module.name] = knownModule.symbolTable
7380
}
7481
}
7582

@@ -95,7 +102,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol {
95102
return parsedResult
96103
}
97104

98-
for importedModule in importedModules.values {
105+
for importedModule in prioritySortedImportedModules {
99106
if let result = importedModule.lookupTopLevelNominalType(name) {
100107
return result
101108
}

0 commit comments

Comments
 (0)