forked from swiftlang/swift-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSwift2JavaTranslator.swift
More file actions
313 lines (263 loc) · 10.2 KB
/
Swift2JavaTranslator.swift
File metadata and controls
313 lines (263 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
//===----------------------------------------------------------------------===//
//
// 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 SwiftBasicFormat
import SwiftIfConfig
import SwiftJavaConfigurationShared
import SwiftJavaJNICore
import SwiftParser
import SwiftSyntax
/// Takes swift interfaces and translates them into Java used to access those.
public final class Swift2JavaTranslator {
static let SWIFT_INTERFACE_SUFFIX = ".swiftinterface"
package var log: Logger
let config: Configuration
/// The build configuration used to resolve #if conditional compilation blocks.
let buildConfig: any BuildConfiguration
/// The name of the Swift module being translated.
let swiftModuleName: String
// ==== Input
var inputs: [SwiftJavaInputFile] = []
/// File paths that were skipped by swift filters but still need empty output
/// files written (when --write-empty-files is set) so SwiftPM doesn't
/// complain about missing declared outputs
var filteredOutPaths: [String] = []
/// A list of used Swift class names that live in dependencies, e.g. `JavaInteger`
package var dependenciesClasses: [String] = []
// ==== Output state
package var importedGlobalVariables: [ImportedFunc] = []
package var importedGlobalFuncs: [ImportedFunc] = []
/// A mapping from Swift type names (e.g., A.B) over to the imported nominal
/// type representation.
package var importedTypes: [String: ImportedNominalType] = [:]
/// Specializations of generic types that will get their concrete Java declarations, "as if" they were independent types
package var specializations: [ImportedNominalType: Set<ImportedNominalType>] = [:]
var lookupContext: SwiftTypeLookupContext! = nil
var symbolTable: SwiftSymbolTable! {
lookupContext?.symbolTable
}
public init(
config: Configuration
) {
guard let swiftModule = config.swiftModule else {
fatalError("Missing 'swiftModule' name.") // FIXME: can we make it required in config? but we shared config for many cases
}
self.log = Logger(label: "translator", logLevel: config.logLevel ?? .info)
self.config = config
self.swiftModuleName = swiftModule
if let staticBuildConfigPath = config.staticBuildConfigurationFile {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: staticBuildConfigPath))
let decoder = JSONDecoder()
self.buildConfig = try decoder.decode(StaticBuildConfiguration.self, from: data)
self.log.info("Using custom static build configuration from: \(staticBuildConfigPath)")
} catch {
fatalError("Failed to load static build configuration from '\(staticBuildConfigPath)': \(error)")
}
} else {
self.buildConfig = .jextractDefault
}
}
}
// ===== --------------------------------------------------------------------------------------------------------------
// MARK: Analysis
extension Swift2JavaTranslator {
var result: AnalysisResult {
AnalysisResult(
importedTypes: self.importedTypes,
importedGlobalVariables: self.importedGlobalVariables,
importedGlobalFuncs: self.importedGlobalFuncs,
)
}
package func add(filePath: String, text: String) {
log.debug("Adding: \(filePath)")
let sourceFileSyntax = Parser.parse(source: text)
self.inputs.append(SwiftJavaInputFile(syntax: sourceFileSyntax, path: filePath))
}
/// Convenient method for analyzing single file.
package func analyze(path: String, text: String) throws {
self.add(filePath: path, text: text)
try self.analyze()
}
/// Analyze registered inputs.
func analyze() throws {
prepareForTranslation()
let visitor = Swift2JavaVisitor(translator: self)
for input in self.inputs {
log.trace("Analyzing \(input.path)")
visitor.visit(inputFile: input)
}
// Apply any specializations registered after their target types were visited
visitor.applyPendingSpecializations()
self.visitFoundationDeclsIfNeeded(with: visitor)
}
private func visitFoundationDeclsIfNeeded(with visitor: Swift2JavaVisitor) {
// If any API uses 'Foundation.Data' or 'FoundationEssentials.Data',
// import 'Data' as if it's declared in this module.
if let dataDecl = self.symbolTable[.foundationData] ?? self.symbolTable[.essentialsData] {
let dataProtocolDecl = (self.symbolTable[.foundationDataProtocol] ?? self.symbolTable[.essentialsDataProtocol])!
if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) {
visitor.visit(
nominalDecl: dataDecl.syntax!.asNominal!,
in: nil,
sourceFilePath: "Foundation/FAKE_FOUNDATION_DATA.swift",
)
visitor.visit(
nominalDecl: dataProtocolDecl.syntax!.asNominal!,
in: nil,
sourceFilePath: "Foundation/FAKE_FOUNDATION_DATAPROTOCOL.swift",
)
}
}
// Foundation.Date
if let dateDecl = self.symbolTable[.foundationDate] ?? self.symbolTable[.essentialsDate] {
if self.isUsing(where: { $0 == dateDecl }) {
visitor.visit(
nominalDecl: dateDecl.syntax!.asNominal!,
in: nil,
sourceFilePath: "Foundation/FAKE_FOUNDATION_DATE.swift",
)
}
}
}
package func prepareForTranslation() {
let dependenciesSource = self.buildDependencyClassesSourceFile()
let symbolTable = SwiftSymbolTable.setup(
moduleName: self.swiftModuleName,
inputs + [dependenciesSource],
config: self.config,
buildConfig: self.buildConfig,
log: self.log,
)
self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable)
}
/// Check if any of the imported decls uses a nominal declaration that satisfies
/// the given predicate.
func isUsing(where predicate: (SwiftNominalTypeDeclaration) -> Bool) -> Bool {
func check(_ type: SwiftType) -> Bool {
switch type {
case .nominal(let nominal):
if let genericArguments = nominal.genericArguments {
if genericArguments.contains(where: check) {
return true
}
}
return predicate(nominal.nominalTypeDecl)
case .tuple(let tuple):
return tuple.contains(where: { check($0.type) })
case .function(let fn):
return check(fn.resultType) || fn.parameters.contains(where: { check($0.type) })
case .metatype(let ty):
return check(ty)
case .existential(let ty), .opaque(let ty):
return check(ty)
case .composite(let types):
return types.contains(where: check)
case .genericParameter:
return false
}
}
func check(_ fn: ImportedFunc) -> Bool {
if check(fn.functionSignature.result.type) {
return true
}
if fn.functionSignature.parameters.contains(where: { check($0.type) }) {
return true
}
return false
}
if self.importedGlobalFuncs.contains(where: check) {
return true
}
if self.importedGlobalVariables.contains(where: check) {
return true
}
for importedType in self.importedTypes.values {
if importedType.initializers.contains(where: check) {
return true
}
if importedType.methods.contains(where: check) {
return true
}
if importedType.variables.contains(where: check) {
return true
}
}
return false
}
/// Returns a source file that contains all the available dependency classes.
private func buildDependencyClassesSourceFile() -> SwiftJavaInputFile {
let contents = self.dependenciesClasses.map {
"@JavaClass public class \($0) {}"
}
.joined(separator: "\n")
let syntax = SourceFileSyntax(stringLiteral: contents)
return SwiftJavaInputFile(syntax: syntax, path: "FakeDependencyClassesSourceFile.swift")
}
}
// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Type translation
extension Swift2JavaTranslator {
/// Try to resolve the given nominal declaration node into its imported representation.
func importedNominalType(
_ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax,
parent: ImportedNominalType?,
) -> ImportedNominalType? {
if !nominalNode.shouldExtract(config: config, log: log, in: parent) {
return nil
}
guard let nominal = symbolTable.lookupType(nominalNode.name.text, parent: parent?.swiftNominal) else {
return nil
}
return self.importedNominalType(nominal)
}
/// Try to resolve the given nominal type node into its imported representation.
func importedNominalType(
_ typeNode: TypeSyntax
) -> ImportedNominalType? {
guard let swiftType = try? SwiftType(typeNode, lookupContext: lookupContext) else {
return nil
}
guard let swiftNominalDecl = swiftType.asNominalTypeDeclaration else {
return nil
}
// Whether to import this extension?
let isFromThisModule = swiftNominalDecl.moduleName == self.swiftModuleName
let isFromStubbedModule = config.hasImportedModuleStub(moduleOfNominal: swiftNominalDecl.moduleName)
guard isFromThisModule || isFromStubbedModule else {
return nil
}
guard swiftNominalDecl.syntax!.shouldExtract(config: config, log: log, in: nil) else {
return nil
}
return importedNominalType(swiftNominalDecl)
}
func importedNominalType(_ nominal: SwiftNominalTypeDeclaration) -> ImportedNominalType? {
let fullName = nominal.qualifiedName
if let alreadyImported = importedTypes[fullName] {
return alreadyImported
}
let importedNominal = try? ImportedNominalType(swiftNominal: nominal, lookupContext: lookupContext)
importedTypes[fullName] = importedNominal
return importedNominal
}
}
// ==== -----------------------------------------------------------------------
// MARK: Errors
public struct Swift2JavaTranslatorError: Error {
let message: String
public init(message: String) {
self.message = message
}
}