From e23ae2cc17c2157ac9e3fa3fbff1a4d889b0902f Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 27 May 2026 15:28:14 +0900 Subject: [PATCH 01/13] Separate out SwiftExtract, a reusable Swift analysis module This will take a while to finish up but this introduces a new core foundational module called `SwiftExtract` that allows for analysis of swift code into an analysis result that then source generators -- such as swift-java can use. This is to prepare reuse from other language generators, with a strong well maintianed analysis core that swift-java has spearheaded here. This is a massive "move stuff around" so reviewing everyting might be hard but the long term benefit will be massive. --- Package.swift | 49 ++- Sources/JExtractSwiftLib/AnalysisResult.swift | 19 -- .../Common/JavaTypeAnnotations.swift | 1 + .../Convenience/JavaType+Extensions.swift | 1 + ...sions.swift => String+JNIExtensions.swift} | 46 +-- .../FFM/CDeclLowering/CRepresentation.swift | 2 + ...Swift2JavaGenerator+FunctionLowering.swift | 1 + .../JExtractSwiftLib/FFM/ConversionStep.swift | 1 + ...FMSwift2JavaGenerator+FoundationData.swift | 1 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 1 + ...MSwift2JavaGenerator+JavaTranslation.swift | 1 + ...ift2JavaGenerator+SwiftThunkPrinting.swift | 1 + .../FFM/FFMSwift2JavaGenerator.swift | 3 +- .../ImportedDecls+JavaNaming.swift | 79 +++++ Sources/JExtractSwiftLib/JNI/JNICaching.swift | 2 + .../JNI/JNIJavaTypeTranslator.swift | 1 + ...Generator+InterfaceWrapperGeneration.swift | 1 + ...t2JavaGenerator+JavaBindingsPrinting.swift | 1 + ...ISwift2JavaGenerator+JavaTranslation.swift | 1 + ...wift2JavaGenerator+NativeTranslation.swift | 1 + ...ift2JavaGenerator+SwiftThunkPrinting.swift | 1 + .../JNI/JNISwift2JavaGenerator.swift | 3 +- .../JExtractSwiftLib/JavaExtractDecider.swift | 38 +++ .../JavaIdentifierFactory.swift | 10 +- ...ies.swift => JavaSourceDependencies.swift} | 51 +-- .../Logger+ArgumentParser.swift | 27 ++ Sources/JExtractSwiftLib/Swift2Java.swift | 7 +- .../JExtractSwiftLib/SwiftKit+Printing.swift | 72 ++++ .../JExtractSwiftLib/SwiftSyntax+Java.swift | 53 +++ .../JExtractSwiftLib/ThunkNameRegistry.swift | 2 + .../TranslatedDocumentation.swift | 1 + Sources/SwiftExtract/AnalysisResult.swift | 29 ++ .../Convenience/Collection+Extensions.swift | 4 +- .../Convenience/String+Extensions.swift | 59 ++++ .../Convenience/SwiftSyntax+Extensions.swift | 42 +-- Sources/SwiftExtract/ExtractDecider.swift | 33 ++ .../ImportedDecls.swift | 141 +++----- .../Logger.swift | 13 +- .../Resources/dummy.json | 0 Sources/SwiftExtract/SourceDependencies.swift | 48 +++ .../SwiftAnalysisVisitor.swift} | 81 +++-- .../SwiftAnalyzer.swift} | 83 +++-- ...iftExtractDefaultBuildConfiguration.swift} | 40 +-- .../SwiftFileFilter.swift} | 24 +- .../SwiftTypes/ImportedSwiftModule.swift | 16 +- .../SwiftTypes/SwiftDependencyScanner.swift} | 2 +- .../SwiftTypes/SwiftEffectSpecifier.swift | 2 +- .../SwiftTypes/SwiftEnumCaseParameter.swift | 8 +- .../SwiftTypes/SwiftFunctionSignature.swift | 58 ++-- .../SwiftTypes/SwiftFunctionType.swift | 28 +- .../SwiftTypes/SwiftKnownModules.swift | 8 +- .../SwiftTypes/SwiftKnownTypeDecls.swift | 14 +- .../SwiftTypes/SwiftKnownTypes.swift | 72 ++-- .../SwiftTypes/SwiftModuleSymbolTable.swift | 32 +- .../SwiftNominalTypeDeclaration.swift | 76 ++--- .../SwiftTypes/SwiftParameter.swift | 32 +- .../SwiftParsedModuleSymbolTableBuilder.swift | 45 +-- .../SwiftTypes/SwiftQualifiedTypeName.swift | 20 +- .../SwiftTypes/SwiftResult.swift | 15 +- .../SwiftTypes/SwiftSymbolTable.swift | 135 +++----- .../SwiftTypes/SwiftType+GenericTypes.swift | 4 +- .../SwiftTypes/SwiftType.swift | 81 ++--- .../SwiftTypes/SwiftTypeLookupContext.swift | 16 +- Sources/SwiftJavaTool/CommonOptions.swift | 3 +- .../SwiftJavaBaseAsyncParsableCommand.swift | 5 +- .../Asserts/LoweringAssertions.swift | 5 +- .../Asserts/TextAssertions.swift | 7 +- .../FFMNestedTypesTests.swift | 3 +- .../FuncCallbackImportTests.swift | 7 +- .../FunctionDescriptorImportTests.swift | 5 +- .../JExtractFileFilterTests.swift | 65 ++-- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 3 +- .../JavaTypeAnnotationsTests.swift | 6 +- .../MethodImportTests.swift | 21 +- .../SpecializationTests.swift | 9 +- .../SwiftSymbolTableTests.swift | 64 +--- .../TypealiasResolutionTests.swift | 27 +- .../AnalysisResultTests.swift | 317 ++++++++++++++++++ .../SourceDependenciesTests.swift | 113 +++++++ .../Support/SymbolTableFixture.swift | 45 +++ .../SwiftKnownModuleTests.swift | 62 ++++ .../SwiftSymbolTableTests.swift | 192 +++++++++++ 82 files changed, 1879 insertions(+), 819 deletions(-) delete mode 100644 Sources/JExtractSwiftLib/AnalysisResult.swift rename Sources/JExtractSwiftLib/Convenience/{String+Extensions.swift => String+JNIExtensions.swift} (65%) create mode 100644 Sources/JExtractSwiftLib/ImportedDecls+JavaNaming.swift create mode 100644 Sources/JExtractSwiftLib/JavaExtractDecider.swift rename Sources/JExtractSwiftLib/{SourceDependencies.swift => JavaSourceDependencies.swift} (58%) create mode 100644 Sources/JExtractSwiftLib/Logger+ArgumentParser.swift create mode 100644 Sources/JExtractSwiftLib/SwiftSyntax+Java.swift create mode 100644 Sources/SwiftExtract/AnalysisResult.swift rename Sources/{JExtractSwiftLib => SwiftExtract}/Convenience/Collection+Extensions.swift (91%) create mode 100644 Sources/SwiftExtract/Convenience/String+Extensions.swift rename Sources/{JExtractSwiftLib => SwiftExtract}/Convenience/SwiftSyntax+Extensions.swift (84%) create mode 100644 Sources/SwiftExtract/ExtractDecider.swift rename Sources/{JExtractSwiftLib => SwiftExtract}/ImportedDecls.swift (71%) rename Sources/{JExtractSwiftLib => SwiftExtract}/Logger.swift (89%) rename Sources/{JExtractSwiftLib => SwiftExtract}/Resources/dummy.json (100%) create mode 100644 Sources/SwiftExtract/SourceDependencies.swift rename Sources/{JExtractSwiftLib/Swift2JavaVisitor.swift => SwiftExtract/SwiftAnalysisVisitor.swift} (92%) rename Sources/{JExtractSwiftLib/Swift2JavaTranslator.swift => SwiftExtract/SwiftAnalyzer.swift} (79%) rename Sources/{JExtractSwiftLib/JExtractDefaultBuildConfiguration.swift => SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift} (54%) rename Sources/{JExtractSwiftLib/JExtractFileFilter.swift => SwiftExtract/SwiftFileFilter.swift} (92%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/ImportedSwiftModule.swift (73%) rename Sources/{JExtractSwiftLib/SwiftTypes/DependencyScanner.swift => SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift} (97%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftEffectSpecifier.swift (92%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftEnumCaseParameter.swift (86%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftFunctionSignature.swift (92%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftFunctionType.swift (77%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftKnownModules.swift (96%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftKnownTypeDecls.swift (95%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftKnownTypes.swift (51%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftModuleSymbolTable.swift (63%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftNominalTypeDeclaration.swift (77%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftParameter.swift (79%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift (90%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftQualifiedTypeName.swift (71%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftResult.swift (64%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftSymbolTable.swift (66%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftType+GenericTypes.swift (97%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftType.swift (91%) rename Sources/{JExtractSwiftLib => SwiftExtract}/SwiftTypes/SwiftTypeLookupContext.swift (92%) create mode 100644 Tests/SwiftExtractTests/AnalysisResultTests.swift create mode 100644 Tests/SwiftExtractTests/SourceDependenciesTests.swift create mode 100644 Tests/SwiftExtractTests/Support/SymbolTableFixture.swift create mode 100644 Tests/SwiftExtractTests/SwiftKnownModuleTests.swift create mode 100644 Tests/SwiftExtractTests/SwiftSymbolTableTests.swift diff --git a/Package.swift b/Package.swift index 3f07a08c2..6a1b0e28e 100644 --- a/Package.swift +++ b/Package.swift @@ -108,6 +108,11 @@ let package = Package( targets: ["SwiftRuntimeFunctions"] ), + .library( + name: "SwiftExtract", + targets: ["SwiftExtract"] + ), + .library( name: "JExtractSwiftLib", targets: ["JExtractSwiftLib"] @@ -336,6 +341,30 @@ let package = Package( ] ), + .target( + name: "SwiftExtract", + dependencies: [ + .product(name: "SwiftBasicFormat", package: "swift-syntax"), + .product(name: "SwiftIfConfig", package: "swift-syntax"), + .product(name: "SwiftLexicalLookup", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + .product(name: "Logging", package: "swift-log"), + "SwiftJavaConfigurationShared", + ], + path: "Sources/SwiftExtract", + resources: [ + .process("Resources") + ], + swiftSettings: [ + .swiftLanguageMode(.v5) + ], + plugins: [ + .plugin(name: "_StaticBuildConfigPlugin") + ] + ), + .target( name: "JExtractSwiftLib", dependencies: [ @@ -347,19 +376,14 @@ let package = Package( .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "OrderedCollections", package: "swift-collections"), .product(name: "SwiftJavaJNICore", package: "swift-java-jni-core"), + "SwiftExtract", "SwiftJavaShared", "SwiftJavaConfigurationShared", "CodePrinting", ], - resources: [ - .process("Resources") - ], swiftSettings: [ .swiftLanguageMode(.v5), .enableUpcomingFeature("BareSlashRegexLiterals"), - ], - plugins: [ - .plugin(name: "_StaticBuildConfigPlugin") ] ), @@ -435,6 +459,7 @@ let package = Package( name: "JExtractSwiftTests", dependencies: [ "JExtractSwiftLib", + "SwiftExtract", "CodePrinting", ], swiftSettings: [ @@ -442,6 +467,18 @@ let package = Package( ] ), + .testTarget( + name: "SwiftExtractTests", + dependencies: [ + "SwiftExtract", + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + ], + swiftSettings: [ + .swiftLanguageMode(.v5) + ] + ), + .testTarget( name: "SwiftRuntimeFunctionsTests", dependencies: [ diff --git a/Sources/JExtractSwiftLib/AnalysisResult.swift b/Sources/JExtractSwiftLib/AnalysisResult.swift deleted file mode 100644 index 4d33bea19..000000000 --- a/Sources/JExtractSwiftLib/AnalysisResult.swift +++ /dev/null @@ -1,19 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 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 -// -//===----------------------------------------------------------------------===// - -struct AnalysisResult { - let importedTypes: [String: ImportedNominalType] - let importedGlobalVariables: [ImportedFunc] - let importedGlobalFuncs: [ImportedFunc] -} diff --git a/Sources/JExtractSwiftLib/Common/JavaTypeAnnotations.swift b/Sources/JExtractSwiftLib/Common/JavaTypeAnnotations.swift index 7f540e527..2c733cb0e 100644 --- a/Sources/JExtractSwiftLib/Common/JavaTypeAnnotations.swift +++ b/Sources/JExtractSwiftLib/Common/JavaTypeAnnotations.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore diff --git a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift index b2d5394a5..023910767 100644 --- a/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/JavaType+Extensions.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftJavaJNICore extension JavaType { diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+JNIExtensions.swift similarity index 65% rename from Sources/JExtractSwiftLib/Convenience/String+Extensions.swift rename to Sources/JExtractSwiftLib/Convenience/String+JNIExtensions.swift index 9fdb6a64c..d317d1b06 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+JNIExtensions.swift @@ -15,34 +15,7 @@ import SwiftJavaJNICore extension String { - - var firstCharacterUppercased: String { - guard let f = first else { - return self - } - - return "\(f.uppercased())\(String(dropFirst()))" - } - - var firstCharacterLowercased: String { - guard let f = first else { - return self - } - - return "\(f.lowercased())\(String(dropFirst()))" - } - - /// Returns whether the string is of the format `isX` - var hasJavaBooleanNamingConvention: Bool { - guard self.hasPrefix("is"), self.count > 2 else { - return false - } - - let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) - return self[thirdCharacterIndex].isUppercase - } - - /// Returns a version of the string correctly escaped for a JNI + /// Returns a version of the string correctly escaped for a JNI identifier var escapedJNIIdentifier: String { self.map { if $0 == "_" { @@ -66,15 +39,6 @@ extension String { .joined() } - /// If the string ends with `.swift`, return it without that suffix; - /// otherwise return self unchanged - func dropSwiftFileSuffix() -> String { - if hasSuffix(".swift") { - return String(dropLast(".swift".count)) - } - return self - } - /// Looks up self as a SwiftJava wrapped class name and converts it /// into a `JavaType.class` if it exists in `lookupTable`. func parseJavaClassFromSwiftJavaName(in lookupTable: [String: String]) -> JavaType? { @@ -87,14 +51,6 @@ extension String { return .class(package: javaPackageName, name: javaClassName) } - - /// Unescapes the name if it is surrounded by backticks. - var unescapedSwiftName: String { - if count >= 2 && hasPrefix("`") && hasSuffix("`") { - return String(dropFirst().dropLast()) - } - return self - } } extension Array where Element == String { diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 58dc89263..d5956b424 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract + extension CType { /// Lower the given Swift type down to a its corresponding C type. /// diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 565c5c9de..5a39f845b 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftJavaJNICore import SwiftSyntax diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index 03541539c..a086828f2 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftSyntax import SwiftSyntaxBuilder diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift index 7dd054f22..5d84b983f 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore import SwiftSyntax diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 1357a2492..3f84fca7c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index bfcc02efb..2bd12a50c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index ccecc17ee..49e71d9af 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftSyntax import SwiftSyntaxBuilder diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 118f4ad20..08890b3b9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore import SwiftSyntax @@ -69,7 +70,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { package init( config: Configuration, - translator: Swift2JavaTranslator, + translator: SwiftAnalyzer, javaPackage: String, swiftOutputDirectory: String, javaOutputDirectory: String, diff --git a/Sources/JExtractSwiftLib/ImportedDecls+JavaNaming.swift b/Sources/JExtractSwiftLib/ImportedDecls+JavaNaming.swift new file mode 100644 index 000000000..6f71df330 --- /dev/null +++ b/Sources/JExtractSwiftLib/ImportedDecls+JavaNaming.swift @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 SwiftExtract + +// ==== ----------------------------------------------------------------------- +// MARK: Java-facing name aliases for ImportedNominalType + +extension ImportedNominalType { + package var effectiveJavaTypeName: SwiftQualifiedTypeName { effectiveOutputTypeName } + package var effectiveJavaName: String { effectiveOutputName } + package var effectiveJavaSimpleName: String { effectiveOutputSimpleName } + package var javaGenericClause: String { outputGenericClause } +} + +// ==== ----------------------------------------------------------------------- +// MARK: Java-facing name aliases for ImportedFunc + +extension ImportedFunc { + /// The Java getter name for a Swift property/subscript getter, following + /// Java Beans conventions: `get` for non-boolean, `is` for + /// boolean (unless the property already starts with `is`, in which case + /// the original name is preserved). + /// + /// Returns `nil` when the underlying declaration is not a getter — i.e. a + /// regular function, initializer, enum case, or setter — since those don't + /// have a Java getter name. + package var javaGetterName: String? { + switch apiKind { + case .getter, .subscriptGetter: break + case .setter, .subscriptSetter, .function, .initializer, .enumCase: return nil + } + + let returnsBoolean = self.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool + + if !returnsBoolean { + return "get\(self.name.firstCharacterUppercased)" + } else if !self.name.hasJavaBooleanNamingConvention { + return "is\(self.name.firstCharacterUppercased)" + } else { + return self.name + } + } + + /// The Java setter name for a Swift property/subscript setter. If the + /// property already starts with `is` (boolean naming), the `is` prefix is + /// stripped so the setter becomes `set` per Java Beans spec. + /// + /// Returns `nil` when the underlying declaration is not a setter — i.e. a + /// regular function, initializer, enum case, or getter — since those don't + /// have a Java setter name. + package var javaSetterName: String? { + switch apiKind { + case .setter, .subscriptSetter: break + case .getter, .subscriptGetter, .function, .initializer, .enumCase: return nil + } + + let isBooleanSetter = self.functionSignature.parameters.first?.type.asNominalTypeDeclaration?.knownTypeKind == .bool + + if isBooleanSetter && self.name.hasJavaBooleanNamingConvention { + // Safe to force unwrap due to `hasJavaBooleanNamingConvention` check. + let propertyName = self.name.split(separator: "is", maxSplits: 1).last! + return "set\(propertyName)" + } else { + return "set\(self.name.firstCharacterUppercased)" + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNICaching.swift b/Sources/JExtractSwiftLib/JNI/JNICaching.swift index 4ce16af9a..6cec38886 100644 --- a/Sources/JExtractSwiftLib/JNI/JNICaching.swift +++ b/Sources/JExtractSwiftLib/JNI/JNICaching.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract + enum JNICaching { static func cacheName(for type: ImportedNominalType) -> String { cacheName(for: type.effectiveJavaTypeName) diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index 84fabeb77..b676a8385 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift index 3d36ee34f..abdc1db2a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore import SwiftSyntax diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index d6191a475..90aaafc38 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -15,6 +15,7 @@ import CodePrinting import Foundation import OrderedCollections +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 2d7762bef..f69ea1918 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore import SwiftSyntax diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index c0cdd7a06..66f89459f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 823fe7485..dac2f44aa 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftJavaJNICore import SwiftSyntax diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 208a3228f..134c829ee 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaJNICore @@ -65,7 +66,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { package init( config: Configuration, - translator: Swift2JavaTranslator, + translator: SwiftAnalyzer, javaPackage: String, swiftOutputDirectory: String, javaOutputDirectory: String, diff --git a/Sources/JExtractSwiftLib/JavaExtractDecider.swift b/Sources/JExtractSwiftLib/JavaExtractDecider.swift new file mode 100644 index 000000000..a7687a381 --- /dev/null +++ b/Sources/JExtractSwiftLib/JavaExtractDecider.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 SwiftExtract +import SwiftSyntax + +/// Java-specific extraction overrides applied on top of SwiftExtract's +/// built-in access-level filter: +/// +/// - `@JavaExport` forces extraction even of non-public decls +/// - `@JavaClass` / `@JavaInterface` / `@JavaField` / `@JavaStaticField` / +/// `@JavaMethod` / `@JavaStaticMethod` / `@JavaImplementation` are Swift +/// wrappers of Java types — skip them during extraction +public struct JavaExtractDecider: ExtractDecider { + public init() {} + + public func shouldExtract(decl: DeclSyntax, accessLevelPasses: Bool) -> Bool? { + let attrs = decl.asProtocol(WithAttributesSyntax.self)?.attributes + if attrs?.contains(where: { $0.isJavaExport }) == true { + return true + } + if attrs?.contains(where: { $0.isSwiftJavaMacro }) == true { + return false + } + return nil + } +} diff --git a/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift b/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift index 2706d8ff9..dbaf85cdd 100644 --- a/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift +++ b/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract + /// Detects Java method name conflicts caused by Swift overloads that differ /// only in parameter labels. When a conflict is detected, the affected methods /// get a camelCase suffix derived from their parameter labels (e.g. `takeValueA`, @@ -33,8 +35,8 @@ package struct JavaIdentifierFactory { for method in methods { let baseName: String = switch method.apiKind { - case .getter, .subscriptGetter: method.javaGetterName - case .setter, .subscriptSetter: method.javaSetterName + case .getter, .subscriptGetter: method.javaGetterName! + case .setter, .subscriptSetter: method.javaSetterName! case .function, .initializer, .enumCase: method.name } methodsByBaseName[baseName, default: []].append(method) @@ -64,8 +66,8 @@ package struct JavaIdentifierFactory { package func makeJavaMethodName(_ decl: ImportedFunc) -> String { let baseName: String = switch decl.apiKind { - case .getter, .subscriptGetter: decl.javaGetterName - case .setter, .subscriptSetter: decl.javaSetterName + case .getter, .subscriptGetter: decl.javaGetterName! + case .setter, .subscriptSetter: decl.javaSetterName! case .function, .initializer, .enumCase: decl.name } var methodName = baseName + paramsSuffix(decl, baseName: baseName) diff --git a/Sources/JExtractSwiftLib/SourceDependencies.swift b/Sources/JExtractSwiftLib/JavaSourceDependencies.swift similarity index 58% rename from Sources/JExtractSwiftLib/SourceDependencies.swift rename to Sources/JExtractSwiftLib/JavaSourceDependencies.swift index a1128e51b..55fb17e6c 100644 --- a/Sources/JExtractSwiftLib/SourceDependencies.swift +++ b/Sources/JExtractSwiftLib/JavaSourceDependencies.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import OrderedCollections +import SwiftExtract import SwiftJavaConfigurationShared import SwiftParser import SwiftSyntax @@ -22,50 +24,25 @@ import FoundationEssentials import Foundation #endif - -package typealias SwiftModuleName = String -package typealias SwiftTypeName = String -package typealias SwiftSourceText = String package typealias JavaClassName = String package typealias JavaFullyQualifiedClassName = String package typealias JavaPackageName = String -/// Holds the inputs jextract needs for symbol resolution but does not generate -/// bindings for. -/// -/// Two flavours of "dependency" are tracked: -/// - Wrapped Java classes referenced from this module's API (e.g. `JavaInteger`). -/// - Real Swift sources from dependency Swift modules (passed via `--depends-on`), -/// parsed once and registered as imported `SwiftModuleSymbolTable`s so that -/// cross-module type references in this module's API can resolve them. -package struct SourceDependencies { - /// Swift wrapper type names for Java classes referenced from this module's - /// API (by convention `Java`, e.g. `JavaVector`). - package var javaClasses: [SwiftTypeName] = [] - - /// Parsed Swift inputs from dependency modules, keyed by Swift module name. - package var swiftModuleInputs: [SwiftModuleName: [SwiftJavaInputFile]] = [:] - - package init() {} - - /// Names of all dependency modules with associated Swift sources. - package var swiftModuleNames: Dictionary.Keys { - swiftModuleInputs.keys - } - - /// Synthetic Swift source registering `@JavaClass public class {}` stubs - package var syntheticJavaWrappersSwiftSource: SwiftJavaInputFile? { - guard !javaClasses.isEmpty else { return nil } +extension SourceDependencies { + /// Inject synthetic `@JavaClass public class {}` stubs so the symbol + /// table can resolve Java wrapper types referenced in the Swift API. + package mutating func addJavaWrapperStubs(_ javaClasses: [SwiftTypeName]) { + guard !javaClasses.isEmpty else { return } let text = javaClasses .map { "@JavaClass public class \($0) {}" } .joined(separator: "\n") - return SwiftJavaInputFile( - syntax: Parser.parse(source: text), - path: ".swift" - ) + let stub = SwiftInputFile(syntax: SwiftParser.Parser.parse(source: text), path: ".swift") + syntheticStubInputs[""] = [stub] } + /// Load Swift sources from a dependency module described by `dependency` and + /// register them in `swiftModuleInputs` for cross-module type resolution. package mutating func loadSwiftSources(from dependency: DependencyConfig, log: Logger) { guard let moduleName = dependency.swiftModuleName else { log.debug( @@ -85,15 +62,15 @@ package struct SourceDependencies { in: dependency.swiftSourcePaths, log: log, ) - var inputs: [SwiftJavaInputFile] = [] + var inputs: [SwiftInputFile] = [] let fm = FileManager.default for url in files where canExtract(from: url) { guard let data = fm.contents(atPath: url.path), let text = String(data: data, encoding: .utf8) else { continue } - let syntax = Parser.parse(source: text) - inputs.append(SwiftJavaInputFile(syntax: syntax, path: url.path)) + let syntax = SwiftParser.Parser.parse(source: text) + inputs.append(SwiftInputFile(syntax: syntax, path: url.path)) } if inputs.isEmpty { diff --git a/Sources/JExtractSwiftLib/Logger+ArgumentParser.swift b/Sources/JExtractSwiftLib/Logger+ArgumentParser.swift new file mode 100644 index 000000000..d6e6a828f --- /dev/null +++ b/Sources/JExtractSwiftLib/Logger+ArgumentParser.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 ArgumentParser +import SwiftExtract +import SwiftJavaConfigurationShared + +extension Logger.Level: ExpressibleByArgument { + public var defaultValueDescription: String { + "log level" + } + public private(set) static var allValueStrings: [String] = + ["trace", "debug", "info", "notice", "warning", "error", "critical"] + + public private(set) static var defaultCompletionKind: CompletionKind = .default +} diff --git a/Sources/JExtractSwiftLib/Swift2Java.swift b/Sources/JExtractSwiftLib/Swift2Java.swift index 4350ffcde..2bf5ef037 100644 --- a/Sources/JExtractSwiftLib/Swift2Java.swift +++ b/Sources/JExtractSwiftLib/Swift2Java.swift @@ -14,6 +14,7 @@ import Foundation import OrderedCollections +import SwiftExtract import SwiftJavaConfigurationShared import SwiftJavaShared import SwiftParser @@ -34,7 +35,7 @@ public struct SwiftToJava { fatalError("Missing '--swift-module' name.") } - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) let log = translator.log if config.javaPackage == nil || config.javaPackage!.isEmpty { @@ -73,7 +74,7 @@ public struct SwiftToJava { // Apply jextract include/exclude filters if configured if hasFilters { let relativePath = computeRelativePath(file: file, inputPaths: inputPaths) - guard shouldJExtractFile(relativePath: relativePath, config: config) else { + guard shouldExtractSwiftFile(relativePath: relativePath, config: config) else { log.info("Skipping file (filtered out): \(file.path)") translator.filteredOutPaths.append(file.path) continue @@ -113,7 +114,7 @@ public struct SwiftToJava { partialResult[moduleName] = javaPackage } - translator.sourceDependencies.javaClasses = Array(wrappedJavaClassesLookupTable.keys) + translator.sourceDependencies.addJavaWrapperStubs(Array(wrappedJavaClassesLookupTable.keys)) for config in dependencyConfigs { translator.sourceDependencies.loadSwiftSources(from: config, log: translator.log) } diff --git a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift index f5ac6a4a4..4e0de0e68 100644 --- a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift +++ b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift @@ -12,8 +12,10 @@ // //===----------------------------------------------------------------------===// +import CodePrinting import Foundation import SwiftBasicFormat +import SwiftExtract import SwiftParser import SwiftSyntax @@ -56,3 +58,73 @@ extension SwiftKitPrinting.Names { } } + +// ==== ----------------------------------------------------------------------- +// MARK: SwiftSymbolTable printing helpers + +extension SwiftSymbolTable { + package func printImportedModules(_ printer: inout CodePrinter) { + let mainSymbolSourceModules = Set( + self.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName) + ) + + for module in self.importedModules.keys.sorted() { + guard module != "Swift" else { + continue + } + + // Synthetic stub modules (e.g. ) exist purely for + // symbol-table resolution; they are not real Swift modules and must + // not be emitted as `import` statements. + guard !self.syntheticImportedModuleNames.contains(module) else { + continue + } + + guard let alternativeModules = self.importedModules[module]?.alternativeModules else { + printer.print("import \(module)") + continue + } + + // Only the main source of symbols emits the conditional import block. + // Secondary modules (e.g. FoundationEssentials when Foundation is the main source) + // are skipped when their main source is already present, because the main source's + // block already covers the import. If no main source is present, fall back to a + // plain import so the module is still imported. + guard alternativeModules.isMainSourceOfSymbols else { + if mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) { + printer.print("import \(module)") + } + continue + } + + var importGroups: [String: [String]] = [:] + for name in alternativeModules.moduleNames { + guard let otherModule = self.importedModules[name] else { continue } + + let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName + importGroups[groupKey, default: []].append(otherModule.moduleName) + } + + for (index, group) in importGroups.keys.sorted().enumerated() { + if index > 0 && importGroups.keys.count > 1 { + printer.print("#elseif canImport(\(group))") + } else { + printer.print("#if canImport(\(group))") + } + + for groupModule in importGroups[group] ?? [] { + printer.print("import \(groupModule)") + } + } + + if importGroups.keys.isEmpty { + printer.print("import \(module)") + } else { + printer.print("#else") + printer.print("import \(module)") + printer.print("#endif") + } + } + printer.println() + } +} diff --git a/Sources/JExtractSwiftLib/SwiftSyntax+Java.swift b/Sources/JExtractSwiftLib/SwiftSyntax+Java.swift new file mode 100644 index 000000000..74acf3fff --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftSyntax+Java.swift @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import SwiftExtract +import SwiftSyntax + +extension AttributeListSyntax.Element { + /// Whether this node has `SwiftJava` wrapping attributes (types that wrap Java classes). + /// These are skipped during jextract because they represent Java->Swift wrappers. + /// Note: `@JavaExport` is NOT included here — it forces export of Swift types to Java. + package var isSwiftJavaMacro: Bool { + guard case let .attribute(attr) = self else { + // FIXME: Handle #if. + return false + } + guard let attrName = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text else { return false } + switch attrName { + case "JavaClass", "JavaInterface", "JavaField", "JavaStaticField", "JavaMethod", "JavaStaticMethod", + "JavaImplementation": + return true + default: + return false + } + } + + /// Whether this is a `@JavaExport` attribute (used on typealiases for specialization, + /// or on struct/class/enum to force-include them even when excluded by filters) + package var isJavaExport: Bool { + guard case let .attribute(attr) = self else { return false } + guard let attrName = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text else { return false } + return attrName == "JavaExport" + } +} + +extension SwiftNominalType { + /// True iff the underlying Swift declaration uses one of the Java-wrapper + /// macros (`@JavaClass`, `@JavaInterface`, …) — meaning the type represents + /// a Java class wrapped for Swift, not a Swift type to be re-exported + public var isSwiftJavaWrapper: Bool { + nominalTypeDecl.syntax.attributes.contains(where: \.isSwiftJavaMacro) + } +} diff --git a/Sources/JExtractSwiftLib/ThunkNameRegistry.swift b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift index ac783f5c8..9b99aa5e7 100644 --- a/Sources/JExtractSwiftLib/ThunkNameRegistry.swift +++ b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract + /// Registry of names we've already emitted as @_cdecl and must be kept unique. /// In order to avoid duplicate symbols, the registry can append some unique identifier to duplicated names package struct ThunkNameRegistry { diff --git a/Sources/JExtractSwiftLib/TranslatedDocumentation.swift b/Sources/JExtractSwiftLib/TranslatedDocumentation.swift index bab21fdd9..664061973 100644 --- a/Sources/JExtractSwiftLib/TranslatedDocumentation.swift +++ b/Sources/JExtractSwiftLib/TranslatedDocumentation.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import CodePrinting +import SwiftExtract import SwiftJavaConfigurationShared import SwiftSyntax diff --git a/Sources/SwiftExtract/AnalysisResult.swift b/Sources/SwiftExtract/AnalysisResult.swift new file mode 100644 index 000000000..dedfbfa4a --- /dev/null +++ b/Sources/SwiftExtract/AnalysisResult.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +public struct AnalysisResult { + public let importedTypes: [String: ImportedNominalType] + public let importedGlobalVariables: [ImportedFunc] + public let importedGlobalFuncs: [ImportedFunc] + + public init( + importedTypes: [String: ImportedNominalType], + importedGlobalVariables: [ImportedFunc], + importedGlobalFuncs: [ImportedFunc] + ) { + self.importedTypes = importedTypes + self.importedGlobalVariables = importedGlobalVariables + self.importedGlobalFuncs = importedGlobalFuncs + } +} diff --git a/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift b/Sources/SwiftExtract/Convenience/Collection+Extensions.swift similarity index 91% rename from Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift rename to Sources/SwiftExtract/Convenience/Collection+Extensions.swift index 4286d9e16..e04341368 100644 --- a/Sources/JExtractSwiftLib/Convenience/Collection+Extensions.swift +++ b/Sources/SwiftExtract/Convenience/Collection+Extensions.swift @@ -27,8 +27,8 @@ extension Dictionary { } extension Collection { - typealias IsLastElement = Bool - var withIsLast: any Collection<(Element, IsLastElement)> { + package typealias IsLastElement = Bool + package var withIsLast: any Collection<(Element, IsLastElement)> { var i = 1 let totalCount = self.count diff --git a/Sources/SwiftExtract/Convenience/String+Extensions.swift b/Sources/SwiftExtract/Convenience/String+Extensions.swift new file mode 100644 index 000000000..34fcafe0c --- /dev/null +++ b/Sources/SwiftExtract/Convenience/String+Extensions.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + + package var firstCharacterUppercased: String { + guard let f = first else { + return self + } + + return "\(f.uppercased())\(String(dropFirst()))" + } + + package var firstCharacterLowercased: String { + guard let f = first else { + return self + } + + return "\(f.lowercased())\(String(dropFirst()))" + } + + /// Returns whether the string is of the format `isX` + package var hasJavaBooleanNamingConvention: Bool { + guard self.hasPrefix("is"), self.count > 2 else { + return false + } + + let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) + return self[thirdCharacterIndex].isUppercase + } + + /// If the string ends with `.swift`, return it without that suffix; + /// otherwise return self unchanged + package func dropSwiftFileSuffix() -> String { + if hasSuffix(".swift") { + return String(dropLast(".swift".count)) + } + return self + } + + /// Unescapes the name if it is surrounded by backticks. + package var unescapedSwiftName: String { + if count >= 2 && hasPrefix("`") && hasSuffix("`") { + return String(dropFirst().dropLast()) + } + return self + } +} diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/SwiftExtract/Convenience/SwiftSyntax+Extensions.swift similarity index 84% rename from Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift rename to Sources/SwiftExtract/Convenience/SwiftSyntax+Extensions.swift index 76e5ba688..3b0194db2 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/SwiftExtract/Convenience/SwiftSyntax+Extensions.swift @@ -95,7 +95,7 @@ extension DeclModifierSyntax { } extension WithModifiersSyntax { - func isPublic(in type: NominalTypeDeclSyntaxNode?) -> Bool { + package func isPublic(in type: NominalTypeDeclSyntaxNode?) -> Bool { if let protocolDecl = type?.as(ProtocolDeclSyntax.self) { return protocolDecl.isPublic(in: nil) } @@ -105,13 +105,13 @@ extension WithModifiersSyntax { } } - var isAtLeastPackage: Bool { + package var isAtLeastPackage: Bool { self.modifiers.contains { modifier in modifier.isAtLeastPackage } } - var isAtLeastInternal: Bool { + package var isAtLeastInternal: Bool { if self.modifiers.isEmpty { // we assume that default access level is internal return true @@ -123,37 +123,9 @@ extension WithModifiersSyntax { } } -extension AttributeListSyntax.Element { - /// Whether this node has `SwiftJava` wrapping attributes (types that wrap Java classes). - /// These are skipped during jextract because they represent Java->Swift wrappers. - /// Note: `@JavaExport` is NOT included here — it forces export of Swift types to Java. - var isSwiftJavaMacro: Bool { - guard case let .attribute(attr) = self else { - // FIXME: Handle #if. - return false - } - guard let attrName = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text else { return false } - switch attrName { - case "JavaClass", "JavaInterface", "JavaField", "JavaStaticField", "JavaMethod", "JavaStaticMethod", - "JavaImplementation": - return true - default: - return false - } - } - - /// Whether this is a `@JavaExport` attribute (used on typealiases for specialization, - /// or on struct/class/enum to force-include them even when excluded by filters) - var isJavaExport: Bool { - guard case let .attribute(attr) = self else { return false } - guard let attrName = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text else { return false } - return attrName == "JavaExport" - } -} - extension DeclSyntaxProtocol { /// Find inner most "decl" node in ancestors. - var ancestorDecl: DeclSyntax? { + package var ancestorDecl: DeclSyntax? { var node: Syntax = Syntax(self) while let parent = node.parent { if let decl = parent.as(DeclSyntax.self) { @@ -165,7 +137,7 @@ extension DeclSyntaxProtocol { } /// Declaration name primarily for debugging. - var nameForDebug: String { + package var nameForDebug: String { switch DeclSyntax(self).as(DeclSyntaxEnum.self) { case .accessorDecl(let node): node.accessorSpecifier.text @@ -233,7 +205,7 @@ extension DeclSyntaxProtocol { } /// Qualified declaration name primarily for debugging. - var qualifiedNameForDebug: String { + package var qualifiedNameForDebug: String { if let parent = ancestorDecl { parent.qualifiedNameForDebug + "." + nameForDebug } else { @@ -242,7 +214,7 @@ extension DeclSyntaxProtocol { } /// Signature part of the declaration. I.e. without body or member block. - var signatureString: String { + package var signatureString: String { switch DeclSyntax(self.detached).as(DeclSyntaxEnum.self) { case .functionDecl(let node): node.with(\.body, nil).triviaSanitizedDescription diff --git a/Sources/SwiftExtract/ExtractDecider.swift b/Sources/SwiftExtract/ExtractDecider.swift new file mode 100644 index 000000000..9e576660c --- /dev/null +++ b/Sources/SwiftExtract/ExtractDecider.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 SwiftSyntax + +/// A pluggable extraction decision for downstream language generators +/// +/// The built-in analyzer always applies its access-level filter; a supplied +/// `ExtractDecider` can override that decision on a per-decl basis to encode +/// language-specific rules. For example, the Java target uses one to honor +/// `@JavaExport` (force-include even when access-level would skip) and to +/// skip Swift wrappers of Java types (`@JavaClass`, `@JavaInterface`, …) +public protocol ExtractDecider { + /// - Parameters: + /// - decl: the declaration being considered + /// - accessLevelPasses: whether the analyzer's built-in access-level + /// check admits the decl + /// - Returns: `true` to force-extract (even when `accessLevelPasses` + /// is `false`), `false` to skip (even when `accessLevelPasses` is + /// `true`), or `nil` to defer to the default behavior + func shouldExtract(decl: DeclSyntax, accessLevelPasses: Bool) -> Bool? +} diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/SwiftExtract/ImportedDecls.swift similarity index 71% rename from Sources/JExtractSwiftLib/ImportedDecls.swift rename to Sources/SwiftExtract/ImportedDecls.swift index 1f6b80d28..81f622cdb 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/SwiftExtract/ImportedDecls.swift @@ -14,10 +14,10 @@ import SwiftSyntax -/// Any imported (Swift) declaration -protocol ImportedDecl: AnyObject {} +/// Any extracted Swift declaration +public protocol ExtractedSwiftDecl: AnyObject {} -package enum SwiftAPIKind: Equatable { +public enum SwiftAPIKind: Equatable { case function case initializer case getter @@ -34,51 +34,50 @@ package enum SwiftAPIKind: Equatable { /// (e.g. `FishBox` specializing `Box` with `Element` = `Fish`). /// The specialization delegates its member collections to the base type /// so that extensions discovered later are visible through all specializations. -package final class ImportedNominalType: ImportedDecl { - let swiftNominal: SwiftNominalTypeDeclaration +public final class ImportedNominalType: ExtractedSwiftDecl { + public let swiftNominal: SwiftNominalTypeDeclaration - /// If this type is a specialization (FishTank), then this points at the Tank base type of the specialization. - /// His allows simplified - package let specializationBaseType: ImportedNominalType? + /// If this type is a specialization (FishTank), it points at the Tank base type of the specialization + public let specializationBaseType: ImportedNominalType? // The short path from module root to the file in which this nominal was originally declared. // E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. - package var sourceFilePath: String { + public var sourceFilePath: String { self.swiftNominal.sourceFilePath } // Backing storage for member collections - package var initializers: [ImportedFunc] = [] - package var methods: [ImportedFunc] = [] - package var variables: [ImportedFunc] = [] - package var cases: [ImportedEnumCase] = [] - var inheritedTypes: [SwiftType] - package var parent: SwiftNominalTypeDeclaration? + public var initializers: [ImportedFunc] = [] + public var methods: [ImportedFunc] = [] + public var variables: [ImportedFunc] = [] + public var cases: [ImportedEnumCase] = [] + public var inheritedTypes: [SwiftType] + public var parent: SwiftNominalTypeDeclaration? /// The Swift base type name, e.g. "Box" — always the unparameterized name - package var baseTypeName: String { swiftNominal.qualifiedName } + public var baseTypeName: String { swiftNominal.qualifiedName } - /// The specialized/Java-facing name, e.g. "FishBox" — nil for base types - package private(set) var specializedTypeName: String? + /// The specialized output-facing name, e.g. "FishBox" — nil for base types + public private(set) var specializedTypeName: String? /// Whether this type is a specialization of a generic type - package var isSpecialization: Bool { specializationBaseType != nil } + public var isSpecialization: Bool { specializationBaseType != nil } /// Generic parameter names (e.g. ["Element"] for Box). Empty for non-generic types - package var genericParameterNames: [String] { + public var genericParameterNames: [String] { swiftNominal.genericParameters.map(\.name) } /// Maps generic parameter -> concrete type argument. Empty for unspecialized types /// e.g. {"Element": "Fish"} for FishBox - package var genericArguments: [String: String] = [:] + public var genericArguments: [String: String] = [:] /// True when all generic parameters have corresponding arguments - package var isFullySpecialized: Bool { + public var isFullySpecialized: Bool { !genericParameterNames.isEmpty && genericParameterNames.allSatisfy { genericArguments.keys.contains($0) } } - init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { + public init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { self.swiftNominal = swiftNominal self.specializationBaseType = nil self.inheritedTypes = @@ -119,41 +118,41 @@ package final class ImportedNominalType: ImportedDecl { self.swiftType = selfType } - let swiftType: SwiftType + public let swiftType: SwiftType - /// Structured Java-facing type name — "FishBox" for specialized, "Box" for base - package var effectiveJavaTypeName: SwiftQualifiedTypeName { + /// Structured output-facing type name — "FishBox" for specialized, "Box" for base + public var effectiveOutputTypeName: SwiftQualifiedTypeName { if let specializedTypeName { return SwiftQualifiedTypeName(specializedTypeName) } return swiftNominal.qualifiedTypeName } - /// The effective Java-facing name — "FishBox" for specialized, "Box" for base - var effectiveJavaName: String { - effectiveJavaTypeName.fullName + /// The effective output-facing name — "FishBox" for specialized, "Box" for base + public var effectiveOutputName: String { + effectiveOutputTypeName.fullName } - /// The simple Java class name (no qualification) for file naming purposes - var effectiveJavaSimpleName: String { + /// The simple output-facing class name (no qualification) for file naming purposes + public var effectiveOutputSimpleName: String { specializedTypeName ?? swiftNominal.name } /// The Swift type for thunk generation — "Box" for specialized, "Box" for base /// Computed from baseTypeName + genericArguments - var effectiveSwiftTypeName: String { + public var effectiveSwiftTypeName: String { guard !genericArguments.isEmpty else { return baseTypeName } let orderedArgs = genericParameterNames.compactMap { genericArguments[$0] } guard !orderedArgs.isEmpty else { return baseTypeName } return "\(baseTypeName)<\(orderedArgs.joined(separator: ", "))>" } - var qualifiedName: String { + public var qualifiedName: String { self.swiftNominal.qualifiedName } - /// The Java generic clause, e.g. "" for generic base types, "" for specialized or non-generic - var javaGenericClause: String { + /// The output generic clause, e.g. "" for generic base types, "" for specialized or non-generic + public var outputGenericClause: String { if isSpecialization { "" } else if genericParameterNames.isEmpty { @@ -164,7 +163,7 @@ package final class ImportedNominalType: ImportedDecl { } /// Create a specialized version of this generic type - package func specialize( + public func specialize( as specializedName: String, with substitutions: [String: String], ) throws -> ImportedNominalType { @@ -187,7 +186,7 @@ package final class ImportedNominalType: ImportedDecl { } /// Checks if this type, or any of types it inherits from, conforms to the passed in protocol. - package func conformsTo(_ protocolName: String, in importedTypes: [String: ImportedNominalType]) -> Bool { + public func conformsTo(_ protocolName: String, in importedTypes: [String: ImportedNominalType]) -> Bool { var visited: Set = [] var queue: [ImportedNominalType] = [self] while let current = queue.popLast() { @@ -203,25 +202,25 @@ package final class ImportedNominalType: ImportedDecl { } } -struct SpecializationError: Error { - let message: String +public struct SpecializationError: Error { + public let message: String } -public final class ImportedEnumCase: ImportedDecl, CustomStringConvertible { +public final class ImportedEnumCase: ExtractedSwiftDecl, CustomStringConvertible { /// The case name public let name: String /// The enum parameters - let parameters: [SwiftEnumCaseParameter] + public let parameters: [SwiftEnumCaseParameter] - let swiftDecl: any DeclSyntaxProtocol + public let swiftDecl: any DeclSyntaxProtocol - let enumType: SwiftNominalType + public let enumType: SwiftNominalType /// A function that represents the Swift static "initializer" for cases - let caseFunction: ImportedFunc + public let caseFunction: ImportedFunc - init( + public init( name: String, parameters: [SwiftEnumCaseParameter], swiftDecl: any DeclSyntaxProtocol, @@ -247,7 +246,7 @@ public final class ImportedEnumCase: ImportedDecl, CustomStringConvertible { """ } - func clone(for parent: SwiftType) -> ImportedEnumCase { + public func clone(for parent: SwiftType) -> ImportedEnumCase { ImportedEnumCase( name: name, parameters: parameters, @@ -267,7 +266,7 @@ extension ImportedEnumCase: Hashable { } } -public final class ImportedFunc: ImportedDecl, CustomStringConvertible { +public final class ImportedFunc: ExtractedSwiftDecl, CustomStringConvertible { /// Swift module name (e.g. the target name where a type or function was declared) public let module: String @@ -277,26 +276,26 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { public let swiftDecl: any DeclSyntaxProtocol - package let apiKind: SwiftAPIKind + public let apiKind: SwiftAPIKind - let functionSignature: SwiftFunctionSignature + public let functionSignature: SwiftFunctionSignature public var signatureString: String { self.swiftDecl.signatureString } - var parentType: SwiftType? { + public var parentType: SwiftType? { functionSignature.selfParameter?.selfType } - var isStatic: Bool { + public var isStatic: Bool { if case .staticMethod = functionSignature.selfParameter { return true } return false } - var isInitializer: Bool { + public var isInitializer: Bool { if case .initializer = functionSignature.selfParameter { return true } @@ -305,8 +304,6 @@ public final class ImportedFunc: 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 hasParent: Bool { functionSignature.selfParameter != nil } /// A display name to use to refer to the Swift declaration with its @@ -332,15 +329,15 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { return prefix + context + self.name } - var isThrowing: Bool { + public var isThrowing: Bool { self.functionSignature.effectSpecifiers.contains(.throws) } - var isAsync: Bool { + public var isAsync: Bool { self.functionSignature.isAsync } - init( + public init( module: String, swiftDecl: any DeclSyntaxProtocol, name: String, @@ -365,7 +362,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { """ } - func clone(for parent: SwiftType) -> ImportedFunc { + public func clone(for parent: SwiftType) -> ImportedFunc { var functionSignature = functionSignature assert(functionSignature.selfParameter?.selfType != nil) functionSignature.selfParameter?.selfType = parent @@ -388,34 +385,6 @@ extension ImportedFunc: Hashable { } } -extension ImportedFunc { - var javaGetterName: String { - let returnsBoolean = self.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool - - if !returnsBoolean { - return "get\(self.name.firstCharacterUppercased)" - } else if !self.name.hasJavaBooleanNamingConvention { - return "is\(self.name.firstCharacterUppercased)" - } else { - return self.name - } - } - - var javaSetterName: String { - let isBooleanSetter = self.functionSignature.parameters.first?.type.asNominalTypeDeclaration?.knownTypeKind == .bool - - // If the variable is already named "isX", then we make - // the setter "setX" to match beans spec. - if isBooleanSetter && self.name.hasJavaBooleanNamingConvention { - // Safe to force unwrap due to `hasJavaBooleanNamingConvention` check. - let propertyName = self.name.split(separator: "is", maxSplits: 1).last! - return "set\(propertyName)" - } else { - return "set\(self.name.firstCharacterUppercased)" - } - } -} - extension ImportedNominalType: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) diff --git a/Sources/JExtractSwiftLib/Logger.swift b/Sources/SwiftExtract/Logger.swift similarity index 89% rename from Sources/JExtractSwiftLib/Logger.swift rename to Sources/SwiftExtract/Logger.swift index 5c4267830..aae0148af 100644 --- a/Sources/JExtractSwiftLib/Logger.swift +++ b/Sources/SwiftExtract/Logger.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -import ArgumentParser import Foundation import SwiftJavaConfigurationShared import SwiftSyntax @@ -117,16 +116,6 @@ extension Logger { public typealias Level = SwiftJavaConfigurationShared.LogLevel } -extension Logger.Level: ExpressibleByArgument { - public var defaultValueDescription: String { - "log level" - } - public private(set) static var allValueStrings: [String] = - ["trace", "debug", "info", "notice", "warning", "error", "critical"] - - public private(set) static var defaultCompletionKind: CompletionKind = .default -} - extension Logger.Level { var naturalIntegralValue: Int { switch self { diff --git a/Sources/JExtractSwiftLib/Resources/dummy.json b/Sources/SwiftExtract/Resources/dummy.json similarity index 100% rename from Sources/JExtractSwiftLib/Resources/dummy.json rename to Sources/SwiftExtract/Resources/dummy.json diff --git a/Sources/SwiftExtract/SourceDependencies.swift b/Sources/SwiftExtract/SourceDependencies.swift new file mode 100644 index 000000000..6d97a9637 --- /dev/null +++ b/Sources/SwiftExtract/SourceDependencies.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 +// +//===----------------------------------------------------------------------===// + +public typealias SwiftModuleName = String +public typealias SwiftTypeName = String +public typealias SwiftSourceText = String + +/// Holds inputs for symbol resolution that are not themselves the primary +/// extraction target: real Swift sources from dependency modules. +/// +/// Dependency inputs are parsed once and registered as imported +/// `SwiftModuleSymbolTable`s so that cross-module type references in the +/// analysed module's API can resolve them. +public struct SourceDependencies { + /// Parsed Swift inputs from dependency modules, keyed by Swift module name. + public var swiftModuleInputs: [SwiftModuleName: [SwiftInputFile]] = [:] + + /// Synthetic stub inputs keyed by a synthetic module name (e.g. for + /// generated `@JavaClass` placeholders). These are needed for symbol-table + /// resolution but must NOT be emitted as `import ` statements in + /// generated Swift code, because their names are not real Swift modules. + public var syntheticStubInputs: [SwiftModuleName: [SwiftInputFile]] = [:] + + public init() {} + + /// Names of all dependency modules (real + synthetic) with associated Swift + /// sources. Used by callers that need to resolve types belonging to either. + public var swiftModuleNames: Set { + Set(swiftModuleInputs.keys).union(syntheticStubInputs.keys) + } + + /// Names of synthetic stub modules. These should be skipped at Swift import + /// printing time. + public var syntheticModuleNames: Set { + Set(syntheticStubInputs.keys) + } +} diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/SwiftExtract/SwiftAnalysisVisitor.swift similarity index 92% rename from Sources/JExtractSwiftLib/Swift2JavaVisitor.swift rename to Sources/SwiftExtract/SwiftAnalysisVisitor.swift index ed6b4ed8b..84458800f 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/SwiftExtract/SwiftAnalysisVisitor.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,18 +13,19 @@ //===----------------------------------------------------------------------===// import Foundation +import Logging import SwiftIfConfig import SwiftJavaConfigurationShared import SwiftParser import SwiftSyntax -final class Swift2JavaVisitor { - let translator: Swift2JavaTranslator +final class SwiftAnalysisVisitor { + let translator: SwiftAnalyzer var config: Configuration { self.translator.config } - init(translator: Swift2JavaTranslator) { + init(translator: SwiftAnalyzer) { self.translator = translator } @@ -38,7 +39,7 @@ final class Swift2JavaVisitor { } private var deferredConstrainedExtensions: [DeferredConstrainedExtension] = [] - func visit(inputFile: SwiftJavaInputFile) { + func visit(inputFile: SwiftInputFile) { let node = inputFile.syntax for codeItem in node.statements { if let declNode = codeItem.item.as(DeclSyntax.self) { @@ -179,7 +180,7 @@ final class Swift2JavaVisitor { in typeContext: ImportedNominalType?, sourceFilePath: String, ) { - guard node.shouldExtract(config: config, log: log, in: typeContext) else { + guard node.shouldExtract(config: config, log: log, in: typeContext, decider: translator.extractDecider) else { return } @@ -281,7 +282,7 @@ final class Swift2JavaVisitor { in typeContext: ImportedNominalType?, sourceFilePath: String, ) { - guard node.shouldExtract(config: config, log: log, in: typeContext) else { + guard node.shouldExtract(config: config, log: log, in: typeContext, decider: translator.extractDecider) else { return } @@ -328,7 +329,7 @@ final class Swift2JavaVisitor { self.log.info("Initializer must be within a current type; \(node)") return } - guard node.shouldExtract(config: config, log: log, in: typeContext) else { + guard node.shouldExtract(config: config, log: log, in: typeContext, decider: translator.extractDecider) else { return } @@ -369,7 +370,7 @@ final class Swift2JavaVisitor { subscriptDecl node: SubscriptDeclSyntax, in typeContext: ImportedNominalType?, ) { - guard node.shouldExtract(config: config, log: log, in: typeContext) else { + guard node.shouldExtract(config: config, log: log, in: typeContext, decider: translator.extractDecider) else { return } @@ -517,7 +518,7 @@ final class Swift2JavaVisitor { in typeContext: ImportedNominalType?, sourceFilePath: String, ) { - let javaName = node.name.text + let outputName = node.name.text let rhsType = node.initializer.value let genericArgs: [String] @@ -539,7 +540,7 @@ final class Swift2JavaVisitor { } registerSpecialization( - javaName: javaName, + outputName: outputName, baseType: baseType, genericArgs: genericArgs, rhsDescription: rhsType.trimmedDescription, @@ -548,7 +549,7 @@ final class Swift2JavaVisitor { /// Register a specialization from a typealias that specializes a generic type private func registerSpecialization( - javaName: String, + outputName: String, baseType: ImportedNominalType, genericArgs: [String], rhsDescription: String, @@ -566,13 +567,13 @@ final class Swift2JavaVisitor { let specialized: ImportedNominalType do { - specialized = try baseType.specialize(as: javaName, with: substitutions) + specialized = try baseType.specialize(as: outputName, with: substitutions) } catch { - log.warning("Failed to specialize \(baseType.baseTypeName) as \(javaName): \(error)") + log.warning("Failed to specialize \(baseType.baseTypeName) as \(outputName): \(error)") return } translator.specializations[baseType, default: []].insert(specialized) - log.info("Registered specialization: \(javaName) = \(rhsDescription)") + log.info("Registered specialization: \(outputName) = \(rhsDescription)") } // ==== ----------------------------------------------------------------------- @@ -585,8 +586,8 @@ final class Swift2JavaVisitor { } for specialized in specializations { - translator.importedTypes[specialized.effectiveJavaName] = specialized - log.info("Applied specialization: \(specialized.effectiveJavaName) -> \(specialized.effectiveSwiftTypeName)") + translator.importedTypes[specialized.effectiveOutputName] = specialized + log.info("Applied specialization: \(specialized.effectiveOutputName) -> \(specialized.effectiveSwiftTypeName)") } } @@ -595,11 +596,11 @@ final class Swift2JavaVisitor { func applyPendingSpecializations() { for (_, specializations) in translator.specializations { for specialized in specializations { - if translator.importedTypes[specialized.effectiveJavaName] != nil { + if translator.importedTypes[specialized.effectiveOutputName] != nil { continue } - translator.importedTypes[specialized.effectiveJavaName] = specialized - log.info("Applied pending specialization: \(specialized.effectiveJavaName) -> \(specialized.effectiveSwiftTypeName)") + translator.importedTypes[specialized.effectiveOutputName] = specialized + log.info("Applied pending specialization: \(specialized.effectiveOutputName) -> \(specialized.effectiveSwiftTypeName)") } } @@ -712,30 +713,40 @@ final class Swift2JavaVisitor { } extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax { - func shouldExtract(config: Configuration, log: Logger, in parent: ImportedNominalType?) -> Bool { - // @JavaExport overrides all filters — always extract - if attributes.contains(where: { $0.isJavaExport }) { - return true - } - - let meetsRequiredAccessLevel: Bool = + /// Decide whether this declaration should be extracted + /// + /// Built-in logic checks only the access level required by `config`. An + /// optional `decider` supplied by a downstream language target can override + /// the result on a per-decl basis (e.g. Java honors `@JavaExport` / + /// `@JavaClass` here) + func shouldExtract( + config: Configuration, + log: Logger, + in parent: ImportedNominalType?, + decider: (any ExtractDecider)? + ) -> Bool { + let accessLevelPasses: Bool = switch config.effectiveMinimumInputAccessLevelMode { case .public: self.isPublic(in: parent?.swiftNominal.syntax) case .package: self.isAtLeastPackage case .internal: self.isAtLeastInternal } - guard meetsRequiredAccessLevel else { + if let override = decider?.shouldExtract( + decl: DeclSyntax(self), + accessLevelPasses: accessLevelPasses + ) { + if !override { + log.debug("Skip import '\(self.qualifiedNameForDebug)': decider rejected") + } + return override + } + + if !accessLevelPasses { log.debug( "Skip import '\(self.qualifiedNameForDebug)': not at least \(config.effectiveMinimumInputAccessLevelMode)" ) - return false - } - guard !attributes.contains(where: { $0.isSwiftJavaMacro }) else { - log.debug("Skip import '\(self.qualifiedNameForDebug)': is Java") - return false } - - return true + return accessLevelPasses } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/SwiftExtract/SwiftAnalyzer.swift similarity index 79% rename from Sources/JExtractSwiftLib/Swift2JavaTranslator.swift rename to Sources/SwiftExtract/SwiftAnalyzer.swift index 6afa5b385..6a4818d34 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/SwiftExtract/SwiftAnalyzer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,35 +13,39 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftBasicFormat +import Logging 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 { +/// Drives the analysis of Swift source code into an `AnalysisResult` that +/// downstream language generators (e.g. Java/JNI/FFM, others) can consume +/// +/// The analysis is language-neutral; language-specific extraction rules +/// (such as honoring Java's `@JavaExport` or skipping `@JavaClass`-wrapped +/// types) are layered in via an optional `ExtractDecider` +public final class SwiftAnalyzer { static let SWIFT_INTERFACE_SUFFIX = ".swiftinterface" package var log: Logger - let config: Configuration + package let config: Configuration /// The build configuration used to resolve #if conditional compilation blocks. - let buildConfig: any BuildConfiguration + package let buildConfig: any BuildConfiguration /// The name of the Swift module being translated. - let swiftModuleName: String + package let swiftModuleName: String // ==== Input - var inputs: [SwiftJavaInputFile] = [] + package var inputs: [SwiftInputFile] = [] /// 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] = [] + package var filteredOutPaths: [String] = [] /// Sources jextract needs for symbol resolution but does not generate bindings /// for: wrapped Java classes plus real Swift sources from dependency modules. @@ -61,14 +65,19 @@ public final class Swift2JavaTranslator { /// Specializations of generic types that will get their concrete Java declarations, "as if" they were independent types package var specializations: [ImportedNominalType: Set] = [:] - var lookupContext: SwiftTypeLookupContext! = nil + package var lookupContext: SwiftTypeLookupContext! = nil - var symbolTable: SwiftSymbolTable! { + package var symbolTable: SwiftSymbolTable! { lookupContext?.symbolTable } + /// Optional language-specific extraction decider that can override the + /// built-in access-level filter on a per-decl basis + package let extractDecider: (any ExtractDecider)? + public init( - config: Configuration + config: Configuration, + extractDecider: (any ExtractDecider)? = nil ) { 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 @@ -76,6 +85,7 @@ public final class Swift2JavaTranslator { self.log = Logger(label: "translator", logLevel: config.logLevel ?? .info) self.config = config self.swiftModuleName = swiftModule + self.extractDecider = extractDecider if let staticBuildConfigPath = config.staticBuildConfigurationFile { do { @@ -87,7 +97,7 @@ public final class Swift2JavaTranslator { fatalError("Failed to load static build configuration from '\(staticBuildConfigPath)': \(error)") } } else { - self.buildConfig = .jextractDefault + self.buildConfig = .swiftExtractDefault } } } @@ -95,8 +105,9 @@ public final class Swift2JavaTranslator { // ===== -------------------------------------------------------------------------------------------------------------- // MARK: Analysis -extension Swift2JavaTranslator { - var result: AnalysisResult { +extension SwiftAnalyzer { + /// Snapshot of the analysis state as a value-typed `AnalysisResult`. + public var result: AnalysisResult { AnalysisResult( importedTypes: self.importedTypes, importedGlobalVariables: self.importedGlobalVariables, @@ -107,7 +118,7 @@ extension Swift2JavaTranslator { 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)) + self.inputs.append(SwiftInputFile(syntax: sourceFileSyntax, path: filePath)) } /// Convenient method for analyzing single file. @@ -117,10 +128,10 @@ extension Swift2JavaTranslator { } /// Analyze registered inputs. - func analyze() throws { + package func analyze() throws { prepareForTranslation() - let visitor = Swift2JavaVisitor(translator: self) + let visitor = SwiftAnalysisVisitor(translator: self) for input in self.inputs { log.trace("Analyzing \(input.path)") @@ -133,7 +144,28 @@ extension Swift2JavaTranslator { self.visitFoundationDeclsIfNeeded(with: visitor) } - private func visitFoundationDeclsIfNeeded(with visitor: Swift2JavaVisitor) { + /// Top-level convenience: run analysis on the given Swift sources and return + /// the resulting `AnalysisResult`. Useful for tests and for callers that only + /// need analysis (no code generation). + public static func analyze( + sources: [(path: String, text: String)], + moduleName: String, + config: Configuration? = nil, + sourceDependencies: SourceDependencies = SourceDependencies(), + extractDecider: (any ExtractDecider)? = nil + ) throws -> AnalysisResult { + var effectiveConfig = config ?? Configuration() + effectiveConfig.swiftModule = moduleName + let translator = SwiftAnalyzer(config: effectiveConfig, extractDecider: extractDecider) + translator.sourceDependencies = sourceDependencies + for source in sources { + translator.add(filePath: source.path, text: source.text) + } + try translator.analyze() + return translator.result + } + + private func visitFoundationDeclsIfNeeded(with visitor: SwiftAnalysisVisitor) { // Each entry pairs a Foundation/FoundationEssentials counterpart so the // user-code reference can match either. Entries within the same group are // visited together when any one of the candidates is referenced — so using @@ -206,7 +238,6 @@ extension Swift2JavaTranslator { config: self.config, sourceDependencies: self.sourceDependencies, buildConfig: self.buildConfig, - log: self.log, ) self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable) } @@ -269,13 +300,13 @@ extension Swift2JavaTranslator { // ==== ---------------------------------------------------------------------------------------------------------------- // MARK: Type translation -extension Swift2JavaTranslator { +extension SwiftAnalyzer { /// 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) { + if !nominalNode.shouldExtract(config: config, log: log, in: parent, decider: extractDecider) { return nil } @@ -303,7 +334,7 @@ extension Swift2JavaTranslator { return nil } - guard swiftNominalDecl.syntax.shouldExtract(config: config, log: log, in: nil) else { + guard swiftNominalDecl.syntax.shouldExtract(config: config, log: log, in: nil as ImportedNominalType?, decider: extractDecider) else { return nil } @@ -313,7 +344,7 @@ extension Swift2JavaTranslator { func importedNominalType(_ nominal: SwiftNominalTypeDeclaration) -> ImportedNominalType? { let fullName = nominal.qualifiedName - guard shouldJExtractType(qualifiedName: fullName, config: config) else { + guard shouldExtractSwiftType(qualifiedName: fullName, config: config) else { log.debug("Skip import '\(fullName)': filtered by swiftFilterInclude/swiftFilterExclude") return nil } @@ -332,7 +363,7 @@ extension Swift2JavaTranslator { // ==== ----------------------------------------------------------------------- // MARK: Errors -public struct Swift2JavaTranslatorError: Error { +public struct SwiftAnalyzerError: Error { let message: String public init(message: String) { diff --git a/Sources/JExtractSwiftLib/JExtractDefaultBuildConfiguration.swift b/Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift similarity index 54% rename from Sources/JExtractSwiftLib/JExtractDefaultBuildConfiguration.swift rename to Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift index 65adf8ff5..41f588974 100644 --- a/Sources/JExtractSwiftLib/JExtractDefaultBuildConfiguration.swift +++ b/Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift @@ -17,12 +17,12 @@ import SwiftIfConfig import SwiftSyntax /// A default, fixed build configuration during static analysis for interface extraction. -struct JExtractDefaultBuildConfiguration: BuildConfiguration { - static let shared = JExtractDefaultBuildConfiguration() +package struct SwiftExtractDefaultBuildConfiguration: BuildConfiguration { + package static let shared = SwiftExtractDefaultBuildConfiguration() private var base: StaticBuildConfiguration - init() { + package init() { guard let url = Bundle.module.url(forResource: "static-build-config", withExtension: "json") else { fatalError("static-build-config.json is not found in module bundle") } @@ -35,69 +35,69 @@ struct JExtractDefaultBuildConfiguration: BuildConfiguration { } } - func isCustomConditionSet(name: String) throws -> Bool { + package func isCustomConditionSet(name: String) throws -> Bool { base.isCustomConditionSet(name: name) } - func hasFeature(name: String) throws -> Bool { + package func hasFeature(name: String) throws -> Bool { base.hasFeature(name: name) } - func hasAttribute(name: String) throws -> Bool { + package func hasAttribute(name: String) throws -> Bool { base.hasAttribute(name: name) } - func canImport(importPath: [(TokenSyntax, String)], version: CanImportVersion) throws -> Bool { + package func canImport(importPath: [(TokenSyntax, String)], version: CanImportVersion) throws -> Bool { try base.canImport(importPath: importPath, version: version) } - func isActiveTargetOS(name: String) throws -> Bool { + package func isActiveTargetOS(name: String) throws -> Bool { true } - func isActiveTargetArchitecture(name: String) throws -> Bool { + package func isActiveTargetArchitecture(name: String) throws -> Bool { true } - func isActiveTargetEnvironment(name: String) throws -> Bool { + package func isActiveTargetEnvironment(name: String) throws -> Bool { true } - func isActiveTargetRuntime(name: String) throws -> Bool { + package func isActiveTargetRuntime(name: String) throws -> Bool { true } - func isActiveTargetPointerAuthentication(name: String) throws -> Bool { + package func isActiveTargetPointerAuthentication(name: String) throws -> Bool { true } - func isActiveTargetObjectFormat(name: String) throws -> Bool { + package func isActiveTargetObjectFormat(name: String) throws -> Bool { true } - var targetPointerBitWidth: Int { + package var targetPointerBitWidth: Int { base.targetPointerBitWidth } - var targetAtomicBitWidths: [Int] { + package var targetAtomicBitWidths: [Int] { base.targetAtomicBitWidths } - var endianness: Endianness { + package var endianness: Endianness { base.endianness } - var languageVersion: VersionTuple { + package var languageVersion: VersionTuple { base.languageVersion } - var compilerVersion: VersionTuple { + package var compilerVersion: VersionTuple { base.compilerVersion } } -extension BuildConfiguration where Self == JExtractDefaultBuildConfiguration { - static var jextractDefault: JExtractDefaultBuildConfiguration { +extension BuildConfiguration where Self == SwiftExtractDefaultBuildConfiguration { + package static var swiftExtractDefault: SwiftExtractDefaultBuildConfiguration { .shared } } diff --git a/Sources/JExtractSwiftLib/JExtractFileFilter.swift b/Sources/SwiftExtract/SwiftFileFilter.swift similarity index 92% rename from Sources/JExtractSwiftLib/JExtractFileFilter.swift rename to Sources/SwiftExtract/SwiftFileFilter.swift index aef41240f..40c6ef6b3 100644 --- a/Sources/JExtractSwiftLib/JExtractFileFilter.swift +++ b/Sources/SwiftExtract/SwiftFileFilter.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -20,7 +20,7 @@ import SwiftJavaConfigurationShared /// A filter pattern is either a file-path pattern (uses `/` or a Swift file /// extension) or a type-name pattern (uses `.`). Plain names with neither /// match both -enum SwiftFilterPatternKind { +package enum SwiftFilterPatternKind { /// Pattern contains `/`, ends in a Swift source extension, or is a bare `**` /// glob — matches against relative file paths case filePath @@ -31,7 +31,7 @@ enum SwiftFilterPatternKind { case plain } -func classifyPattern(_ pattern: String) -> SwiftFilterPatternKind { +package func classifyPattern(_ pattern: String) -> SwiftFilterPatternKind { // Directory separator is the strongest signal for a file path if pattern.contains("/") { return .filePath @@ -167,7 +167,7 @@ private func matchSegment(_ segment: String, against pattern: String) -> Bool { /// - `**` matches zero or more path segments /// - `*` at the end of a segment matches any suffix (e.g. `Us*` matches `User.swift`) /// - exact segment match otherwise -func matchesFilePathFilter(relativePath: String, pattern: String) -> Bool { +package func matchesFilePathFilter(relativePath: String, pattern: String) -> Bool { matchesGlob(value: relativePath, pattern: pattern, separator: "/") } @@ -181,7 +181,7 @@ func matchesFilePathFilter(relativePath: String, pattern: String) -> Bool { /// - `**` matches zero or more name components /// - `*` at the end of a component matches any suffix /// - exact component match otherwise -func matchesTypeNameFilter(qualifiedName: String, pattern: String) -> Bool { +package func matchesTypeNameFilter(qualifiedName: String, pattern: String) -> Bool { matchesGlob(value: qualifiedName, pattern: pattern, separator: ".") } @@ -189,13 +189,13 @@ func matchesTypeNameFilter(qualifiedName: String, pattern: String) -> Bool { // MARK: Combined filter application /// Determine whether a file at the given `relativePath` (including `.swift` -/// extension) should be included in jextract processing, based on the -/// include/exclude filters in `config`. +/// extension) should be included in extraction, based on the include/exclude +/// filters in `config`. /// /// Only file-path patterns (containing `/`) and plain patterns (no `/` or `.`) -/// are checked here. Type-name patterns are skipped — use `shouldJExtractType` -/// for those -func shouldJExtractFile(relativePath: String, config: Configuration) -> Bool { +/// are checked here. Type-name patterns are skipped — use +/// `shouldExtractSwiftType` for those +package func shouldExtractSwiftFile(relativePath: String, config: Configuration) -> Bool { if let includeFilters = config.swiftFilterInclude, !includeFilters.isEmpty { // Must match at least one file-level include pattern. // If all include patterns are type-name patterns, don't filter at file level @@ -244,9 +244,9 @@ private func matchesFilePattern(relativePath: String, pattern: String) -> Bool { /// `config`. /// /// Only type-name patterns (containing `.`) and plain patterns (no `/` or `.`) -/// are checked here. File-path patterns are skipped — use `shouldJExtractFile` +/// are checked here. File-path patterns are skipped — use `shouldExtractSwiftFile` /// for those -func shouldJExtractType(qualifiedName: String, config: Configuration) -> Bool { +package func shouldExtractSwiftType(qualifiedName: String, config: Configuration) -> Bool { if let includeFilters = config.swiftFilterInclude, !includeFilters.isEmpty { let typePatterns = includeFilters.filter { classifyPattern($0) != .filePath } if !typePatterns.isEmpty { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/ImportedSwiftModule.swift b/Sources/SwiftExtract/SwiftTypes/ImportedSwiftModule.swift similarity index 73% rename from Sources/JExtractSwiftLib/SwiftTypes/ImportedSwiftModule.swift rename to Sources/SwiftExtract/SwiftTypes/ImportedSwiftModule.swift index 8db800d63..5c22b2654 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/ImportedSwiftModule.swift +++ b/Sources/SwiftExtract/SwiftTypes/ImportedSwiftModule.swift @@ -12,13 +12,13 @@ // //===----------------------------------------------------------------------===// -struct ImportedSwiftModule: Hashable { - let name: String - let availableWithModuleName: String? - var alternativeModuleNames: Set - var isMainSourceOfSymbols: Bool +public struct ImportedSwiftModule: Hashable { + public let name: String + public let availableWithModuleName: String? + public var alternativeModuleNames: Set + public var isMainSourceOfSymbols: Bool - init( + public init( name: String, availableWithModuleName: String? = nil, alternativeModuleNames: Set = [], @@ -30,11 +30,11 @@ struct ImportedSwiftModule: Hashable { self.isMainSourceOfSymbols = isMainSourceOfSymbols } - static func == (lhs: Self, rhs: Self) -> Bool { + public static func == (lhs: Self, rhs: Self) -> Bool { lhs.name == rhs.name } - func hash(into hasher: inout Hasher) { + public func hash(into hasher: inout Hasher) { hasher.combine(name) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift b/Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift similarity index 97% rename from Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift index a36475c7b..e24c2eea7 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/DependencyScanner.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift @@ -15,7 +15,7 @@ import SwiftSyntax /// Scan importing modules. -func importingModules(sourceFile: SourceFileSyntax) -> [ImportedSwiftModule] { +package func importingModules(sourceFile: SourceFileSyntax) -> [ImportedSwiftModule] { var importingModuleNames: [ImportedSwiftModule] = [] for item in sourceFile.statements { if let importDecl = item.item.as(ImportDeclSyntax.self) { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift b/Sources/SwiftExtract/SwiftTypes/SwiftEffectSpecifier.swift similarity index 92% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftEffectSpecifier.swift index fb28586b1..c59d316d0 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEffectSpecifier.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftEffectSpecifier.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -enum SwiftEffectSpecifier: Equatable { +public enum SwiftEffectSpecifier: Equatable { case `throws` case `async` } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift b/Sources/SwiftExtract/SwiftTypes/SwiftEnumCaseParameter.swift similarity index 86% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftEnumCaseParameter.swift index 55682152d..335739397 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftEnumCaseParameter.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftEnumCaseParameter.swift @@ -14,13 +14,13 @@ import SwiftSyntax -struct SwiftEnumCaseParameter: Equatable { - var name: String? - var type: SwiftType +public struct SwiftEnumCaseParameter: Equatable { + public var name: String? + public var type: SwiftType } extension SwiftEnumCaseParameter { - init( + public init( _ node: EnumCaseParameterSyntax, lookupContext: SwiftTypeLookupContext ) throws { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/SwiftExtract/SwiftTypes/SwiftFunctionSignature.swift similarity index 92% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftFunctionSignature.swift index a957c0ea1..9a0261c14 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftFunctionSignature.swift @@ -15,7 +15,7 @@ import SwiftSyntax import SwiftSyntaxBuilder -enum SwiftGenericRequirement: Equatable { +public enum SwiftGenericRequirement: Equatable { case inherits(SwiftType, SwiftType) case equals(SwiftType, SwiftType) } @@ -23,22 +23,22 @@ enum SwiftGenericRequirement: Equatable { /// Provides a complete signature for a Swift function, which includes its /// parameters and return type. public struct SwiftFunctionSignature: Equatable { - var selfParameter: SwiftSelfParameter? - var parameters: [SwiftParameter] - var result: SwiftResult - var effectSpecifiers: [SwiftEffectSpecifier] - var genericParameters: [SwiftGenericParameterDeclaration] - var genericRequirements: [SwiftGenericRequirement] - - var isAsync: Bool { + public var selfParameter: SwiftSelfParameter? + public var parameters: [SwiftParameter] + public var result: SwiftResult + public var effectSpecifiers: [SwiftEffectSpecifier] + public var genericParameters: [SwiftGenericParameterDeclaration] + public var genericRequirements: [SwiftGenericRequirement] + + public var isAsync: Bool { effectSpecifiers.contains(.async) } - var isThrowing: Bool { + public var isThrowing: Bool { effectSpecifiers.contains(.throws) } - init( + public init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], result: SwiftResult, @@ -56,7 +56,7 @@ public struct SwiftFunctionSignature: Equatable { } /// Describes the "self" parameter of a Swift function signature. -enum SwiftSelfParameter: Equatable { +public enum SwiftSelfParameter: Equatable { /// 'self' is an instance parameter. case instance(convention: SwiftParameterConvention, swiftType: SwiftType) @@ -68,7 +68,7 @@ enum SwiftSelfParameter: Equatable { /// to form the call. case initializer(SwiftType) - var selfType: SwiftType { + public var selfType: SwiftType { get { switch self { case .instance(_, let swiftType), .staticMethod(let swiftType), .initializer(let swiftType): @@ -89,7 +89,7 @@ enum SwiftSelfParameter: Equatable { } extension SwiftFunctionSignature { - init( + public init( _ node: InitializerDeclSyntax, enclosingType: SwiftType?, lookupContext: SwiftTypeLookupContext @@ -126,7 +126,7 @@ extension SwiftFunctionSignature { ) } - init( + public init( _ node: EnumCaseElementSyntax, enclosingType: SwiftType, lookupContext: SwiftTypeLookupContext @@ -145,7 +145,7 @@ extension SwiftFunctionSignature { ) } - init( + public init( _ node: FunctionDeclSyntax, enclosingType: SwiftType?, lookupContext: SwiftTypeLookupContext @@ -212,7 +212,7 @@ extension SwiftFunctionSignature { ) } - static func translateGenericParameters( + public static func translateGenericParameters( parameterClause: GenericParameterClauseSyntax?, whereClause: GenericWhereClauseSyntax?, lookupContext: SwiftTypeLookupContext @@ -270,7 +270,7 @@ extension SwiftFunctionSignature { /// Translate the function signature, returning the list of translated /// parameters and effect specifiers. - static func translateFunctionSignature( + public static func translateFunctionSignature( _ signature: FunctionSignatureSyntax, lookupContext: SwiftTypeLookupContext ) throws -> ([SwiftParameter], [SwiftEffectSpecifier]) { @@ -289,7 +289,7 @@ extension SwiftFunctionSignature { return (parameters, effectSpecifiers) } - init( + public init( _ varNode: VariableDeclSyntax, isSet: Bool, enclosingType: SwiftType?, @@ -340,7 +340,7 @@ extension SwiftFunctionSignature { self.genericRequirements = [] } - init( + public init( _ subscriptNode: SubscriptDeclSyntax, isSet: Bool, enclosingType: SwiftType?, @@ -443,7 +443,7 @@ extension VariableDeclSyntax { /// /// - Parameters: /// - binding the pattern binding in this declaration. - func supportedAccessorKinds(binding: PatternBindingSyntax) -> AccessorBlockSyntax.SupportedAccessorKinds { + public func supportedAccessorKinds(binding: PatternBindingSyntax) -> AccessorBlockSyntax.SupportedAccessorKinds { if self.bindingSpecifier.tokenKind == .keyword(.let) { return [.get] } @@ -457,15 +457,19 @@ extension VariableDeclSyntax { } extension AccessorBlockSyntax { - struct SupportedAccessorKinds: OptionSet { - var rawValue: UInt8 + public struct SupportedAccessorKinds: OptionSet { + public var rawValue: UInt8 - static var get: Self = .init(rawValue: 1 << 0) - static var set: Self = .init(rawValue: 1 << 1) + public init(rawValue: UInt8) { + self.rawValue = rawValue + } + + public static var get: Self = .init(rawValue: 1 << 0) + public static var set: Self = .init(rawValue: 1 << 1) } /// Determine what operations (i.e. get and/or set) supported in this `AccessorBlockSyntax` - func supportedAccessorKinds() -> SupportedAccessorKinds { + public func supportedAccessorKinds() -> SupportedAccessorKinds { switch self.accessors { case .getter: return [.get] @@ -485,7 +489,7 @@ extension AccessorBlockSyntax { } } -enum SwiftFunctionTranslationError: Error { +public enum SwiftFunctionTranslationError: Error { case `throws`(ThrowsClauseSyntax) case async(TokenSyntax) case classMethod(TokenSyntax) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift b/Sources/SwiftExtract/SwiftTypes/SwiftFunctionType.swift similarity index 77% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftFunctionType.swift index 628cbc7a1..b127e6fb7 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftFunctionType.swift @@ -14,20 +14,32 @@ import SwiftSyntax -struct SwiftFunctionType: Equatable { - enum Convention: Equatable { +public struct SwiftFunctionType: Equatable { + public enum Convention: Equatable { case swift case c } - var convention: Convention - var parameters: [SwiftParameter] - var resultType: SwiftType - var isEscaping: Bool = false + public var convention: Convention + public var parameters: [SwiftParameter] + public var resultType: SwiftType + public var isEscaping: Bool = false + + public init( + convention: Convention, + parameters: [SwiftParameter], + resultType: SwiftType, + isEscaping: Bool = false + ) { + self.convention = convention + self.parameters = parameters + self.resultType = resultType + self.isEscaping = isEscaping + } } extension SwiftFunctionType: CustomStringConvertible { - var description: String { + public var description: String { let parameterString = parameters.map { $0.descriptionInType }.joined(separator: ", ") let conventionPrefix = switch convention { @@ -40,7 +52,7 @@ extension SwiftFunctionType: CustomStringConvertible { } extension SwiftFunctionType { - init( + public init( _ node: FunctionTypeSyntax, convention: Convention, isEscaping: Bool = false, diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift similarity index 96% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift index 696b1f1a5..47e360146 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift @@ -15,16 +15,16 @@ import SwiftSyntax import SwiftSyntaxBuilder -enum SwiftKnownModule: String { +public enum SwiftKnownModule: String { case swift = "Swift" case foundation = "Foundation" case foundationEssentials = "FoundationEssentials" - var name: String { + public var name: String { self.rawValue } - var symbolTable: SwiftModuleSymbolTable { + public var symbolTable: SwiftModuleSymbolTable { switch self { case .swift: swiftSymbolTable case .foundation: foundationSymbolTable @@ -32,7 +32,7 @@ enum SwiftKnownModule: String { } } - var sourceFile: SourceFileSyntax { + public var sourceFile: SourceFileSyntax { switch self { case .swift: swiftSourceFile case .foundation: foundationEssentialsSourceFile diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift similarity index 95% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift index 5b1e28933..57a851b1d 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift @@ -14,7 +14,7 @@ import SwiftSyntax -enum SwiftKnownType: Equatable { +public enum SwiftKnownType: Equatable { case bool case int case uint @@ -56,7 +56,7 @@ enum SwiftKnownType: Equatable { // SwiftRuntimeFunctions case swiftJavaError - init?(kind: SwiftKnownTypeDeclKind, genericArguments: [SwiftType]?) { + public init?(kind: SwiftKnownTypeDeclKind, genericArguments: [SwiftType]?) { switch kind { case .bool: self = .bool case .int: self = .int @@ -113,7 +113,7 @@ enum SwiftKnownType: Equatable { } } - var kind: SwiftKnownTypeDeclKind { + public var kind: SwiftKnownTypeDeclKind { switch self { case .bool: .bool case .int: .int @@ -155,7 +155,7 @@ enum SwiftKnownType: Equatable { } } -enum SwiftKnownTypeDeclKind: String, Hashable { +public enum SwiftKnownTypeDeclKind: String, Hashable { // Swift case bool = "Swift.Bool" case int = "Swift.Int" @@ -198,7 +198,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { // SwiftRuntimeFunctions case swiftJavaError = "SwiftRuntimeFunctions.SwiftJavaError" - var moduleAndName: (module: String, name: String) { + public var moduleAndName: (module: String, name: String) { let qualified = self.rawValue let period = qualified.firstIndex(of: ".")! return ( @@ -207,7 +207,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { ) } - var isPointer: Bool { + public var isPointer: Bool { switch self { case .unsafePointer, .unsafeMutablePointer, .unsafeRawPointer, .unsafeMutableRawPointer: return true @@ -221,7 +221,7 @@ enum SwiftKnownTypeDeclKind: String, Hashable { /// /// This means we do not have to perform any mapping when passing /// this type between jextract and wrap-java - var isDirectlyTranslatedToWrapJava: Bool { + public var isDirectlyTranslatedToWrapJava: Bool { switch self { case .bool, .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string, .void: diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypes.swift similarity index 51% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftKnownTypes.swift index 198a1c14d..6c9c93a5d 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypes.swift @@ -12,50 +12,50 @@ // //===----------------------------------------------------------------------===// -struct SwiftKnownTypes { +public struct SwiftKnownTypes { private let symbolTable: SwiftSymbolTable - init(symbolTable: SwiftSymbolTable) { + public init(symbolTable: SwiftSymbolTable) { self.symbolTable = symbolTable } - var bool: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.bool])) } - var int: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int])) } - var uint: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint])) } - var int8: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int8])) } - var uint8: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint8])) } - var int16: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int16])) } - var uint16: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint16])) } - var int32: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int32])) } - var uint32: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint32])) } - var int64: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int64])) } - var uint64: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint64])) } - var float: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.float])) } - var double: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.double])) } - var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } - var unsafeRawBufferPointer: SwiftType { + public var bool: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.bool])) } + public var int: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int])) } + public var uint: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint])) } + public var int8: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int8])) } + public var uint8: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint8])) } + public var int16: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int16])) } + public var uint16: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint16])) } + public var int32: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int32])) } + public var uint32: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint32])) } + public var int64: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.int64])) } + public var uint64: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.uint64])) } + public var float: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.float])) } + public var double: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.double])) } + public var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } + public var unsafeRawBufferPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawBufferPointer])) } - var unsafeMutableRawPointer: SwiftType { + public var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) } - var string: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.string])) } + public var string: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.string])) } - var foundationDataProtocol: SwiftType { + public var foundationDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationDataProtocol])) } - var foundationData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationData])) } - var essentialsDataProtocol: SwiftType { + public var foundationData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationData])) } + public var essentialsDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsDataProtocol])) } - var essentialsData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsData])) } - var foundationUUID: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationUUID])) } - var essentialsUUID: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsUUID])) } + public var essentialsData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsData])) } + public var foundationUUID: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationUUID])) } + public var essentialsUUID: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsUUID])) } /// `(UnsafeRawPointer, Long) -> ()` function type. /// /// Commonly used to initialize a buffer using the passed bytes and length. - var functionInitializeByteBuffer: SwiftType { + public var functionInitializeByteBuffer: SwiftType { .function( SwiftFunctionType( convention: .c, @@ -68,7 +68,7 @@ struct SwiftKnownTypes { ) } - func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { + public func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( nominalTypeDecl: symbolTable[.unsafePointer], @@ -77,7 +77,7 @@ struct SwiftKnownTypes { ) } - func unsafeMutablePointer(_ pointeeType: SwiftType) -> SwiftType { + public func unsafeMutablePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( nominalTypeDecl: symbolTable[.unsafeMutablePointer], @@ -86,7 +86,7 @@ struct SwiftKnownTypes { ) } - func unsafeBufferPointer(_ elementType: SwiftType) -> SwiftType { + public func unsafeBufferPointer(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( nominalTypeDecl: symbolTable[.unsafeBufferPointer], @@ -95,7 +95,7 @@ struct SwiftKnownTypes { ) } - func unsafeMutableBufferPointer(_ elementType: SwiftType) -> SwiftType { + public func unsafeMutableBufferPointer(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( nominalTypeDecl: symbolTable[.unsafeMutableBufferPointer], @@ -104,7 +104,7 @@ struct SwiftKnownTypes { ) } - func optionalSugar(_ wrappedType: SwiftType) -> SwiftType { + public func optionalSugar(_ wrappedType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( sugarName: .optional, @@ -114,7 +114,7 @@ struct SwiftKnownTypes { ) } - func arraySugar(_ elementType: SwiftType) -> SwiftType { + public func arraySugar(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( sugarName: .array, @@ -124,7 +124,7 @@ struct SwiftKnownTypes { ) } - func dictionarySugar(_ keyType: SwiftType, _ valueType: SwiftType) -> SwiftType { + public func dictionarySugar(_ keyType: SwiftType, _ valueType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( sugarName: .dictionary, @@ -134,7 +134,7 @@ struct SwiftKnownTypes { ) } - func set(_ elementType: SwiftType) -> SwiftType { + public func set(_ elementType: SwiftType) -> SwiftType { .nominal( SwiftNominalType( nominalTypeDecl: symbolTable[.set], @@ -145,13 +145,13 @@ struct SwiftKnownTypes { /// Returns the known representative concrete type if there is one for the /// given protocol kind. E.g. `Data` for `DataProtocol` - func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? { + public func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftType? { guard let kind = Self.representativeType(of: knownProtocol) else { return nil } return .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[kind])) } /// Returns the representative concrete type kind for a protocol, if one exists - static func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftKnownTypeDeclKind? { + public static func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftKnownTypeDeclKind? { switch knownProtocol { case .foundationDataProtocol: return .foundationData case .essentialsDataProtocol: return .essentialsData diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/SwiftExtract/SwiftTypes/SwiftModuleSymbolTable.swift similarity index 63% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftModuleSymbolTable.swift index c77038596..5e4f5ba6a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftModuleSymbolTable.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftModuleSymbolTable.swift @@ -15,63 +15,63 @@ import SwiftSyntax import SwiftSyntaxBuilder -struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { +public struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { /// The name of this module. - let moduleName: String + public let moduleName: String /// The name of module required to be imported and checked via canImport statement. - let requiredAvailablityOfModuleWithName: String? + public let requiredAvailablityOfModuleWithName: String? /// Data about alternative modules which provides desired symbos e.g. FoundationEssentials is non-Darwin platform alternative for Foundation - let alternativeModules: AlternativeModuleNamesData? + public let alternativeModules: AlternativeModuleNamesData? /// The top-level nominal types, found by name. - var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:] + public var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:] /// The top-level typealias declarations, found by name. - var topLevelTypeAliases: [String: SwiftTypeAliasDeclaration] = [:] + public var topLevelTypeAliases: [String: SwiftTypeAliasDeclaration] = [:] /// The nested types defined within this module. The map itself is indexed by the /// identifier of the nominal type declaration, and each entry is a map from the nested /// type name to the nominal type declaration. - var nestedTypes: [SwiftNominalTypeDeclaration: [String: SwiftNominalTypeDeclaration]] = [:] + public var nestedTypes: [SwiftNominalTypeDeclaration: [String: SwiftNominalTypeDeclaration]] = [:] /// The nested typealias declarations defined within this module. The map itself is indexed /// by the nominal type declaration, and each entry is a map from the nested typealias /// name to the typealias declaration. - var nestedTypeAliases: [SwiftNominalTypeDeclaration: [String: SwiftTypeAliasDeclaration]] = [:] + public var nestedTypeAliases: [SwiftNominalTypeDeclaration: [String: SwiftTypeAliasDeclaration]] = [:] /// Look for a top-level type with the given name. - func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { + public func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { topLevelTypes[name] } /// Look for a top-level typealias with the given name. - func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration? { + public func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration? { topLevelTypeAliases[name] } // Look for a nested type with the given name. - func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { + public func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { nestedTypes[parent]?[name] } // Look for a nested typealias with the given name. - func lookupNestedTypealias(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftTypeAliasDeclaration? { + public func lookupNestedTypealias(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftTypeAliasDeclaration? { nestedTypeAliases[parent]?[name] } - func isAlternative(for moduleName: String) -> Bool { + public func isAlternative(for moduleName: String) -> Bool { alternativeModules.flatMap { $0.moduleNames.contains(moduleName) } ?? false } } extension SwiftModuleSymbolTable { - struct AlternativeModuleNamesData { + public struct AlternativeModuleNamesData { /// Flag indicating module should be used as source of symbols to avoid duplication of symbols. - let isMainSourceOfSymbols: Bool + public let isMainSourceOfSymbols: Bool /// Names of modules which are alternative for currently checked module. - let moduleNames: Set + public let moduleNames: Set } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/SwiftExtract/SwiftTypes/SwiftNominalTypeDeclaration.swift similarity index 77% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftNominalTypeDeclaration.swift index 07543bf23..b6f6399e8 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -19,21 +19,21 @@ import SwiftSyntax public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax -package class SwiftTypeDeclaration { +public class SwiftTypeDeclaration { // The short path from module root to the file in which this nominal was originally declared. // E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. - let sourceFilePath: String + public let sourceFilePath: String /// The module in which this nominal type is defined. If this is a nested type, the /// module might be different from that of the parent type, if this nominal type /// is defined in an extension within another module. - let moduleName: String + public let moduleName: String /// The name of this nominal type, e.g. 'MyCollection'. - let name: String + public let name: String - init(sourceFilePath: String, moduleName: String, name: String) { + public init(sourceFilePath: String, moduleName: String, name: String) { self.sourceFilePath = sourceFilePath self.moduleName = moduleName self.name = name @@ -41,11 +41,11 @@ package class SwiftTypeDeclaration { } /// A syntax node paired with a simple file path -package struct SwiftJavaInputFile { - let syntax: SourceFileSyntax +public struct SwiftInputFile { + public let syntax: SourceFileSyntax /// Simple file path of the file from which the syntax node was parsed. - let path: String - package init(syntax: SourceFileSyntax, path: String) { + public let path: String + public init(syntax: SourceFileSyntax, path: String) { self.syntax = syntax self.path = path } @@ -53,8 +53,8 @@ package struct SwiftJavaInputFile { /// Describes a nominal type declaration, which can be of any kind (class, struct, etc.) /// and has a name, parent type (if nested), and owning module. -package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { - enum Kind { +public class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { + public enum Kind { case actor case `class` case `enum` @@ -63,27 +63,27 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { } /// The syntax node this declaration is derived from. - let syntax: NominalTypeDeclSyntaxNode + @_spi(Testing) public let syntax: NominalTypeDeclSyntaxNode /// The kind of nominal type. - let kind: Kind + public let kind: Kind /// The parent nominal type when this nominal type is nested inside another type, e.g., /// MyCollection.Iterator. - let parent: SwiftNominalTypeDeclaration? + public let parent: SwiftNominalTypeDeclaration? /// The generic parameters of this nominal type. - let genericParameters: [SwiftGenericParameterDeclaration] + public let genericParameters: [SwiftGenericParameterDeclaration] /// Identify this nominal declaration as one of the known standard library /// types, like 'Swift.Int[. - private(set) lazy var knownTypeKind: SwiftKnownTypeDeclKind? = { + public private(set) lazy var knownTypeKind: SwiftKnownTypeDeclKind? = { self.computeKnownStandardLibraryType() }() /// Create a nominal type declaration from the syntax node for a nominal type /// declaration. - init( + @_spi(Testing) public init( name: String, sourceFilePath: String, moduleName: String, @@ -109,7 +109,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: name) } - private(set) lazy var firstInheritanceType: TypeSyntax? = { + public private(set) lazy var firstInheritanceType: TypeSyntax? = { guard let firstInheritanceType = self.syntax.inheritanceClause?.inheritedTypes.first else { return nil } @@ -117,16 +117,16 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { return firstInheritanceType.type }() - var inheritanceTypes: InheritedTypeListSyntax? { + public var inheritanceTypes: InheritedTypeListSyntax? { self.syntax.inheritanceClause?.inheritedTypes } - var genericWhereClause: GenericWhereClauseSyntax? { + public var genericWhereClause: GenericWhereClauseSyntax? { self.syntax.asProtocol(WithGenericParametersSyntax.self)?.genericWhereClause } /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". - private(set) lazy var isSendable: Bool = { + public private(set) lazy var isSendable: Bool = { // Check if Sendable is in the inheritance list guard let inheritanceClause = self.syntax.inheritanceClause else { return false @@ -152,7 +152,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { } /// Structured qualified type name built from the parent chain - package var qualifiedTypeName: SwiftQualifiedTypeName { + public var qualifiedTypeName: SwiftQualifiedTypeName { if let parent = self.parent { return SwiftQualifiedTypeName(parent.qualifiedTypeName.components + [name]) } else { @@ -160,17 +160,17 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { } } - package var qualifiedName: String { + public var qualifiedName: String { qualifiedTypeName.fullName } /// Like `qualifiedName` but with dots replaced by underscores, suitable for /// use in C symbol names and Java identifiers - package var flatName: String { + public var flatName: String { qualifiedTypeName.fullFlatName } - var isReferenceType: Bool { + public var isReferenceType: Bool { switch kind { case .actor, .class: return true @@ -179,13 +179,13 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { } } - var isGeneric: Bool { + public var isGeneric: Bool { !genericParameters.isEmpty } } extension SwiftNominalTypeDeclaration: CustomStringConvertible { - package var description: String { + public var description: String { if isGeneric { "\(qualifiedName)<\(genericParameters.map(\.name).joined(separator: ", "))>" } else { @@ -194,10 +194,10 @@ extension SwiftNominalTypeDeclaration: CustomStringConvertible { } } -package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { - let syntax: GenericParameterSyntax +public class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { + public let syntax: GenericParameterSyntax - init( + public init( sourceFilePath: String, moduleName: String, node: GenericParameterSyntax @@ -206,11 +206,11 @@ package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) } - var hasEach: Bool { + public var hasEach: Bool { syntax.specifier?.tokenKind == .keyword(.each) } - var packReferenceName: String { + public var packReferenceName: String { if hasEach { "each \(name)" } else { @@ -218,7 +218,7 @@ package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { } } - var packExpansionName: String { + public var packExpansionName: String { if hasEach { "repeat each \(name)" } else { @@ -232,10 +232,10 @@ package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { /// A typealias used as a specialization of a generic type will be emitted as /// a new concrete type in the Java. This way we can specialize `FishBox` from /// `Box` by doing `typealias FishBox = Box`. -package final class SwiftTypeAliasDeclaration: SwiftTypeDeclaration { - let syntax: TypeAliasDeclSyntax +public final class SwiftTypeAliasDeclaration: SwiftTypeDeclaration { + public let syntax: TypeAliasDeclSyntax - init( + public init( sourceFilePath: String, moduleName: String, node: TypeAliasDeclSyntax @@ -246,13 +246,13 @@ package final class SwiftTypeAliasDeclaration: SwiftTypeDeclaration { } extension SwiftTypeDeclaration: Equatable { - package static func == (lhs: SwiftTypeDeclaration, rhs: SwiftTypeDeclaration) -> Bool { + public static func == (lhs: SwiftTypeDeclaration, rhs: SwiftTypeDeclaration) -> Bool { lhs === rhs } } extension SwiftTypeDeclaration: Hashable { - package func hash(into hasher: inout Hasher) { + public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift b/Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift similarity index 79% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift index 56fe3cb9c..90b694a42 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift @@ -14,22 +14,34 @@ import SwiftSyntax -struct SwiftParameter: Equatable { - var convention: SwiftParameterConvention - var argumentLabel: String? - var parameterName: String? - var type: SwiftType +public struct SwiftParameter: Equatable { + public var convention: SwiftParameterConvention + public var argumentLabel: String? + public var parameterName: String? + public var type: SwiftType + + public init( + convention: SwiftParameterConvention, + argumentLabel: String? = nil, + parameterName: String? = nil, + type: SwiftType + ) { + self.convention = convention + self.argumentLabel = argumentLabel + self.parameterName = parameterName + self.type = type + } } extension SwiftParameter: CustomStringConvertible { - var description: String { + public var description: String { let argumentLabel = self.argumentLabel ?? "_" let parameterName = self.parameterName ?? "_" return "\(argumentLabel) \(parameterName): \(descriptionInType)" } - var descriptionInType: String { + public var descriptionInType: String { let conventionString: String switch convention { case .byValue: @@ -47,7 +59,7 @@ extension SwiftParameter: CustomStringConvertible { } /// Describes how a parameter is passed. -enum SwiftParameterConvention: Equatable { +public enum SwiftParameterConvention: Equatable { /// The parameter is passed by-value or borrowed. case byValue /// The parameter is passed by-value but consumed. @@ -57,7 +69,7 @@ enum SwiftParameterConvention: Equatable { } extension SwiftParameter { - init(_ node: EnumCaseParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { + public init(_ node: EnumCaseParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { self.convention = .byValue self.type = try SwiftType(node.type, lookupContext: lookupContext) self.argumentLabel = nil @@ -67,7 +79,7 @@ extension SwiftParameter { } extension SwiftParameter { - init(_ node: FunctionParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { + public init(_ node: FunctionParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { // Determine the convention. The default is by-value, but there are // specifiers on the type for other conventions (like `inout`). var type = node.type diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift similarity index 90% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 8691cdfa1..560ed9c0c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -12,30 +12,31 @@ // //===----------------------------------------------------------------------===// +import Logging import SwiftIfConfig import SwiftSyntax -struct SwiftParsedModuleSymbolTableBuilder { - let log: Logger? +package struct SwiftParsedModuleSymbolTableBuilder { + package let log: Logger? /// The symbol table being built. - var symbolTable: SwiftModuleSymbolTable + package var symbolTable: SwiftModuleSymbolTable /// Imported modules to resolve type syntax. - let importedModules: [String: SwiftModuleSymbolTable] + package let importedModules: [String: SwiftModuleSymbolTable] /// The build configuration used to resolve #if conditional compilation blocks. - let buildConfig: any BuildConfiguration + package let buildConfig: any BuildConfiguration /// Extension decls their extended type hasn't been resolved. - var unresolvedExtensions: [ExtensionDeclSyntax] + package var unresolvedExtensions: [ExtensionDeclSyntax] - init( + package init( moduleName: String, requiredAvailablityOfModuleWithName: String? = nil, alternativeModules: SwiftModuleSymbolTable.AlternativeModuleNamesData? = nil, importedModules: [String: SwiftModuleSymbolTable], - buildConfig: any BuildConfiguration = .jextractDefault, + buildConfig: any BuildConfiguration = .swiftExtractDefault, log: Logger? = nil ) { self.log = log @@ -49,14 +50,14 @@ struct SwiftParsedModuleSymbolTableBuilder { self.unresolvedExtensions = [] } - var moduleName: String { + package var moduleName: String { symbolTable.moduleName } } extension SwiftParsedModuleSymbolTableBuilder { - mutating func handle( + package mutating func handle( sourceFile: SourceFileSyntax, sourceFilePath: String ) { @@ -66,7 +67,7 @@ extension SwiftParsedModuleSymbolTableBuilder { } } - mutating func handle( + package mutating func handle( codeBlockItem node: CodeBlockItemSyntax.Item, sourceFilePath: String ) { @@ -88,7 +89,7 @@ extension SwiftParsedModuleSymbolTableBuilder { } } - mutating func handle( + package mutating func handle( typeAliasDecl node: TypeAliasDeclSyntax, sourceFilePath: String ) { @@ -108,7 +109,7 @@ extension SwiftParsedModuleSymbolTableBuilder { /// Add a nominal type declaration and all of the nested types within it to the symbol /// table. - mutating func handle( + package mutating func handle( sourceFilePath: String, nominalTypeDecl node: NominalTypeDeclSyntaxNode, parent: SwiftNominalTypeDeclaration? @@ -118,7 +119,7 @@ extension SwiftParsedModuleSymbolTableBuilder { if symbolTable.lookupType(node.name.text, parent: parent) != nil || symbolTable.lookupTypealias(node.name.text, parent: parent) != nil { - log?.debug("Failed to add a decl into symbol table: redeclaration; " + node.nameForDebug) + log?.debug("Failed to add a decl into symbol table: redeclaration; \(node.nameForDebug)") return } @@ -142,7 +143,7 @@ extension SwiftParsedModuleSymbolTableBuilder { self.handle(sourceFilePath: sourceFilePath, memberBlock: node.memberBlock, parent: nominalTypeDecl) } - mutating func handle( + package mutating func handle( sourceFilePath: String, typeAliasDecl node: TypeAliasDeclSyntax, parent: SwiftNominalTypeDeclaration? @@ -150,7 +151,7 @@ extension SwiftParsedModuleSymbolTableBuilder { if symbolTable.lookupType(node.name.text, parent: parent) != nil || symbolTable.lookupTypealias(node.name.text, parent: parent) != nil { - log?.debug("Failed to add a decl into symbol table: redeclaration; " + node.nameForDebug) + log?.debug("Failed to add a decl into symbol table: redeclaration; \(node.nameForDebug)") return } @@ -167,7 +168,7 @@ extension SwiftParsedModuleSymbolTableBuilder { } } - mutating func handle( + package mutating func handle( sourceFilePath: String, memberBlock node: MemberBlockSyntax, parent: SwiftNominalTypeDeclaration @@ -182,7 +183,7 @@ extension SwiftParsedModuleSymbolTableBuilder { } } - mutating func handle( + package mutating func handle( extensionDecl node: ExtensionDeclSyntax, sourceFilePath: String ) { @@ -193,7 +194,7 @@ extension SwiftParsedModuleSymbolTableBuilder { /// Add any nested types within the given extension to the symbol table. /// If the extended nominal type can't be resolved, returns false. - mutating func tryHandle( + package mutating func tryHandle( extension node: ExtensionDeclSyntax, sourceFilePath: String ) -> Bool { @@ -217,7 +218,7 @@ extension SwiftParsedModuleSymbolTableBuilder { return true } - mutating func handle( + package mutating func handle( ifConfig node: IfConfigDeclSyntax, sourceFilePath: String ) { @@ -235,7 +236,7 @@ extension SwiftParsedModuleSymbolTableBuilder { } /// Finalize the symbol table and return it. - mutating func finalize() -> SwiftModuleSymbolTable { + package mutating func finalize() -> SwiftModuleSymbolTable { // Handle the unresolved extensions. // The work queue is required because, the extending type might be declared // in another extension that hasn't been processed. E.g.: @@ -264,7 +265,7 @@ extension SwiftParsedModuleSymbolTableBuilder { } extension DeclSyntaxProtocol { - var asNominal: NominalTypeDeclSyntaxNode? { + package var asNominal: NominalTypeDeclSyntaxNode? { switch DeclSyntax(self).as(DeclSyntaxEnum.self) { case .actorDecl(let actorDecl): actorDecl case .classDecl(let classDecl): classDecl diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftQualifiedTypeName.swift b/Sources/SwiftExtract/SwiftTypes/SwiftQualifiedTypeName.swift similarity index 71% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftQualifiedTypeName.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftQualifiedTypeName.swift index 2c170ddb7..71cf956ca 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftQualifiedTypeName.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftQualifiedTypeName.swift @@ -18,33 +18,33 @@ /// - **qualifiedName** (`Logger.Message`) - for Swift source /// - **flatName** (`Logger_Message`) - for C symbols / `@_cdecl` and Java identifiers /// - **leafName** (`Message`) - innermost component only -package struct SwiftQualifiedTypeName: Hashable, Sendable, CustomStringConvertible { +public struct SwiftQualifiedTypeName: Hashable, Sendable, CustomStringConvertible { /// Name components from outermost to innermost, e.g. ["Logger", "Message"] - let components: [String] + public let components: [String] - init(_ components: [String]) { + public init(_ components: [String]) { precondition(!components.isEmpty) self.components = components } - init(_ leafName: String) { + public init(_ leafName: String) { self.components = [leafName] } /// Leaf name (innermost), e.g. "Message" - var leafName: String { components.last! } + public var leafName: String { components.last! } /// Dot-separated for Swift source, e.g. "Logger.Message" - var fullName: String { components.joined(separator: ".") } + public var fullName: String { components.joined(separator: ".") } /// Underscore-separated for C symbols and Java identifiers, e.g. "Logger_Message" - var fullFlatName: String { components.joined(separator: "_") } + public var fullFlatName: String { components.joined(separator: "_") } /// Dollar-separated for JNI C symbol parent names, e.g. "Logger$Message" - var jniEscapedName: String { components.joined(separator: "$") } + public var jniEscapedName: String { components.joined(separator: "$") } /// CustomStringConvertible - uses fullName - package var description: String { fullName } + public var description: String { fullName } - var isNested: Bool { components.count > 1 } + public var isNested: Bool { components.count > 1 } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftResult.swift b/Sources/SwiftExtract/SwiftTypes/SwiftResult.swift similarity index 64% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftResult.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftResult.swift index b7aa7b568..87fb57ea6 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftResult.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftResult.swift @@ -14,18 +14,23 @@ import SwiftSyntax -struct SwiftResult: Equatable { - var convention: SwiftResultConvention // currently not used. - var type: SwiftType +public struct SwiftResult: Equatable { + public var convention: SwiftResultConvention // currently not used. + public var type: SwiftType + + public init(convention: SwiftResultConvention, type: SwiftType) { + self.convention = convention + self.type = type + } } -enum SwiftResultConvention: Equatable { +public enum SwiftResultConvention: Equatable { case direct case indirect } extension SwiftResult { - static var void: Self { + public static var void: Self { Self(convention: .direct, type: .void) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift similarity index 66% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift index a4c4de73b..e8fb51020 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift @@ -12,13 +12,13 @@ // //===----------------------------------------------------------------------===// -import CodePrinting +import Logging import SwiftIfConfig import SwiftJavaConfigurationShared import SwiftParser import SwiftSyntax -package protocol SwiftSymbolTableProtocol { +public protocol SwiftSymbolTableProtocol { /// The module name that this symbol table describes. var moduleName: String { get } @@ -38,7 +38,7 @@ package protocol SwiftSymbolTableProtocol { extension SwiftSymbolTableProtocol { /// Look for a type - package func lookupType(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftNominalTypeDeclaration? { + public func lookupType(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftNominalTypeDeclaration? { if let parent { return lookupNestedType(name, parent: parent) } @@ -46,7 +46,7 @@ extension SwiftSymbolTableProtocol { return lookupTopLevelNominalType(name) } - package func lookupTypealias(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftTypeAliasDeclaration? { + public func lookupTypealias(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftTypeAliasDeclaration? { if let parent { return lookupNestedTypealias(name, parent: parent) } @@ -55,9 +55,14 @@ extension SwiftSymbolTableProtocol { } } -package class SwiftSymbolTable { - let importedModules: [String: SwiftModuleSymbolTable] - let parsedModule: SwiftModuleSymbolTable +public class SwiftSymbolTable { + public let importedModules: [String: SwiftModuleSymbolTable] + public let parsedModule: SwiftModuleSymbolTable + + /// Module names within `importedModules` that are synthetic — they exist + /// purely to drive type resolution and must NOT be emitted as + /// `import ` statements in generated Swift code. + public let syntheticImportedModuleNames: Set private var knownTypeToNominal: [SwiftKnownTypeDeclKind: SwiftNominalTypeDeclaration] = [:] private var prioritySortedImportedModules: [SwiftModuleSymbolTable] { @@ -72,12 +77,17 @@ package class SwiftSymbolTable { }) } - init(parsedModule: SwiftModuleSymbolTable, importedModules: [String: SwiftModuleSymbolTable]) { + public init( + parsedModule: SwiftModuleSymbolTable, + importedModules: [String: SwiftModuleSymbolTable], + syntheticImportedModuleNames: Set = [] + ) { self.parsedModule = parsedModule self.importedModules = importedModules + self.syntheticImportedModuleNames = syntheticImportedModuleNames } - func isModuleName(_ name: String) -> Bool { + public func isModuleName(_ name: String) -> Bool { if name == moduleName { return true } @@ -88,11 +98,12 @@ package class SwiftSymbolTable { extension SwiftSymbolTable { package static func setup( moduleName: String, - _ inputFiles: some Collection, + _ inputFiles: some Collection, + additionalInputFiles: [SwiftInputFile] = [], config: Configuration?, sourceDependencies: SourceDependencies, - buildConfig: any BuildConfiguration = .jextractDefault, - log: Logger, + buildConfig: any BuildConfiguration = .swiftExtractDefault, + log: Logger? = nil, ) -> SwiftSymbolTable { // Prepare imported modules. @@ -122,7 +133,10 @@ extension SwiftSymbolTable { guard importedModules[dependencyModuleName] == nil else { continue } - let dependencyInputs = sourceDependencies.swiftModuleInputs[dependencyModuleName] ?? [] + let dependencyInputs = + sourceDependencies.swiftModuleInputs[dependencyModuleName] + ?? sourceDependencies.syntheticStubInputs[dependencyModuleName] + ?? [] // TODO: build a `dependencyImportedModules` dict by scanning the dep's // own source files with `importingModules(sourceFile:)`, instead of // reusing the primary's `importedModules`. The current set is too broad @@ -138,9 +152,8 @@ extension SwiftSymbolTable { } let dependencyModule = dependencyModuleBuilder.finalize() importedModules[dependencyModuleName] = dependencyModule - log.info( - "Loaded dependency module '\(dependencyModuleName)' from \(dependencyInputs.count) source(s); " - + "top-level types [\(dependencyModule.topLevelTypes.count)]: \(dependencyModule.topLevelTypes.keys.sorted())" + log?.info( + "Loaded dependency module '\(dependencyModuleName)' from \(dependencyInputs.count) source(s); top-level types [\(dependencyModule.topLevelTypes.count)]: \(dependencyModule.topLevelTypes.keys.sorted())" ) } @@ -160,13 +173,13 @@ extension SwiftSymbolTable { stubBuilder.handle(sourceFile: sourceFile, sourceFilePath: "\(stubModuleName)_stub.swift") let stubModule = stubBuilder.finalize() importedModules[stubModuleName] = stubModule - log.info("Loaded module stub for '\(stubModuleName)' with \(declarations.count) declaration(s), top-level types: \(stubModule.topLevelTypes.keys.sorted())") + log?.info("Loaded module stub for '\(stubModuleName)' with \(declarations.count) declaration(s), top-level types: \(stubModule.topLevelTypes.keys.sorted())") } else { - log.info("Module '\(stubModuleName)' already known, skipping stub") + log?.info("Module '\(stubModuleName)' already known, skipping stub") } } } else { - log.debug("No importedModuleStubs in config") + log?.debug("No importedModuleStubs in config") } // FIXME: Support granular lookup context (file, type context). @@ -181,20 +194,24 @@ extension SwiftSymbolTable { for sourceFile in inputFiles { builder.handle(sourceFile: sourceFile.syntax, sourceFilePath: sourceFile.path) } - if let stubs = sourceDependencies.syntheticJavaWrappersSwiftSource { - builder.handle(sourceFile: stubs.syntax, sourceFilePath: stubs.path) + for sourceFile in additionalInputFiles { + builder.handle(sourceFile: sourceFile.syntax, sourceFilePath: sourceFile.path) } let parsedModule = builder.finalize() - return SwiftSymbolTable(parsedModule: parsedModule, importedModules: importedModules) + return SwiftSymbolTable( + parsedModule: parsedModule, + importedModules: importedModules, + syntheticImportedModuleNames: sourceDependencies.syntheticModuleNames, + ) } } extension SwiftSymbolTable: SwiftSymbolTableProtocol { - package var moduleName: String { parsedModule.moduleName } + public var moduleName: String { parsedModule.moduleName } /// Look for a top-level nominal type with the given name. This should only /// return nominal types within this module. - package func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { + public func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { if let parsedResult = parsedModule.lookupTopLevelNominalType(name) { return parsedResult } @@ -209,7 +226,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { } /// Look for a top-level nominal type in a specific module by name - package func lookupTopLevelNominalType(_ name: String, inModule moduleName: String) -> SwiftNominalTypeDeclaration? { + public func lookupTopLevelNominalType(_ name: String, inModule moduleName: String) -> SwiftNominalTypeDeclaration? { if moduleName == self.moduleName { return parsedModule.lookupTopLevelNominalType(name) } @@ -217,7 +234,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { } /// Look for a top-level typealias with the given name. - package func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration? { + public func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration? { if let parsedResult = parsedModule.lookupTopLevelTypealias(name) { return parsedResult } @@ -232,7 +249,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { } // Look for a nested type with the given name. - package func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { + public func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { if let parsedResult = parsedModule.lookupNestedType(name, parent: parent) { return parsedResult } @@ -247,7 +264,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { } // Look for a nested typealias with the given name. - package func lookupNestedTypealias(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftTypeAliasDeclaration? { + public func lookupNestedTypealias(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftTypeAliasDeclaration? { if let parsedResult = parsedModule.lookupNestedTypealias(name, parent: parent) { return parsedResult } @@ -264,7 +281,7 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol { extension SwiftSymbolTable { /// Map 'SwiftKnownTypeDeclKind' to the declaration. - subscript(knownType: SwiftKnownTypeDeclKind) -> SwiftNominalTypeDeclaration! { + public subscript(knownType: SwiftKnownTypeDeclKind) -> SwiftNominalTypeDeclaration! { if let known = knownTypeToNominal[knownType] { return known } @@ -279,63 +296,3 @@ extension SwiftSymbolTable { return found } } - -extension SwiftSymbolTable { - func printImportedModules(_ printer: inout CodePrinter) { - let mainSymbolSourceModules = Set( - self.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName) - ) - - for module in self.importedModules.keys.sorted() { - guard module != "Swift" else { - continue - } - - guard let alternativeModules = self.importedModules[module]?.alternativeModules else { - printer.print("import \(module)") - continue - } - - // Only the main source of symbols emits the conditional import block. - // Secondary modules (e.g. FoundationEssentials when Foundation is the main source) - // are skipped when their main source is already present, because the main source's - // block already covers the import. If no main source is present, fall back to a - // plain import so the module is still imported. - guard alternativeModules.isMainSourceOfSymbols else { - if mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) { - printer.print("import \(module)") - } - continue - } - - var importGroups: [String: [String]] = [:] - for name in alternativeModules.moduleNames { - guard let otherModule = self.importedModules[name] else { continue } - - let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName - importGroups[groupKey, default: []].append(otherModule.moduleName) - } - - for (index, group) in importGroups.keys.sorted().enumerated() { - if index > 0 && importGroups.keys.count > 1 { - printer.print("#elseif canImport(\(group))") - } else { - printer.print("#if canImport(\(group))") - } - - for groupModule in importGroups[group] ?? [] { - printer.print("import \(groupModule)") - } - } - - if importGroups.keys.isEmpty { - printer.print("import \(module)") - } else { - printer.print("#else") - printer.print("import \(module)") - printer.print("#endif") - } - } - printer.println() - } -} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+GenericTypes.swift b/Sources/SwiftExtract/SwiftTypes/SwiftType+GenericTypes.swift similarity index 97% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftType+GenericTypes.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftType+GenericTypes.swift index f6a8c5871..7949bf489 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+GenericTypes.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftType+GenericTypes.swift @@ -15,7 +15,7 @@ extension SwiftType { /// Returns a concrete type if this is a generic parameter in the list and it /// conforms to a protocol with representative concrete type. - func representativeConcreteTypeIn( + package func representativeConcreteTypeIn( knownTypes: SwiftKnownTypes, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] @@ -29,7 +29,7 @@ extension SwiftType { } /// Returns the protocol type if this is a generic parameter in the list - func typeIn( + package func typeIn( genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] ) -> SwiftType? { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/SwiftExtract/SwiftTypes/SwiftType.swift similarity index 91% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftType.swift index 49bea52d7..d1b17a20e 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftType.swift @@ -15,11 +15,16 @@ import SwiftSyntax /// An element of a Swift tuple type, preserving the optional label. -struct SwiftTupleElement: Equatable, CustomStringConvertible { - var label: String? - var type: SwiftType +public struct SwiftTupleElement: Equatable, CustomStringConvertible { + public var label: String? + public var type: SwiftType - var description: String { + public init(label: String? = nil, type: SwiftType) { + self.label = label + self.type = type + } + + public var description: String { if let label { return "\(label): \(type)" } @@ -28,7 +33,7 @@ struct SwiftTupleElement: Equatable, CustomStringConvertible { } /// Describes a type in the Swift type system. -enum SwiftType: Equatable { +public enum SwiftType: Equatable { case nominal(SwiftNominalType) case genericParameter(SwiftGenericParameterDeclaration) @@ -50,11 +55,11 @@ enum SwiftType: Equatable { /// `type1` & `type2` indirect case composite([SwiftType]) - static var void: Self { + public static var void: Self { .tuple([]) } - var asNominalType: SwiftNominalType? { + public var asNominalType: SwiftNominalType? { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].type.asNominalType : nil @@ -62,12 +67,12 @@ enum SwiftType: Equatable { } } - var asNominalTypeDeclaration: SwiftNominalTypeDeclaration? { + public var asNominalTypeDeclaration: SwiftNominalTypeDeclaration? { asNominalType?.nominalTypeDecl } /// Whether this is the "Void" type, which is actually an empty tuple. - var isVoid: Bool { + public var isVoid: Bool { switch self { case .tuple([]): return true @@ -80,7 +85,7 @@ enum SwiftType: Equatable { } /// Whether this is a pointer type. I.e 'Unsafe[Mutable][Raw]Pointer' - var isPointer: Bool { + public var isPointer: Bool { switch self { case .nominal(let nominal): if let knownType = nominal.nominalTypeDecl.knownTypeKind { @@ -96,7 +101,7 @@ enum SwiftType: Equatable { /// /// * Mutations don't require 'inout' convention. /// * The value is a pointer of the instance data, - var isReferenceType: Bool { + public var isReferenceType: Bool { switch self { case .nominal(let nominal): return nominal.nominalTypeDecl.isReferenceType @@ -107,7 +112,7 @@ enum SwiftType: Equatable { } } - var isUnsignedInteger: Bool { + public var isUnsignedInteger: Bool { switch self { case .nominal(let nominal): switch nominal.nominalTypeDecl.knownTypeKind { @@ -118,7 +123,7 @@ enum SwiftType: Equatable { } } - var isArchDependingInteger: Bool { + public var isArchDependingInteger: Bool { switch self { case .nominal(let nominal): switch nominal.nominalTypeDecl.knownTypeKind { @@ -129,7 +134,7 @@ enum SwiftType: Equatable { } } - var isRawTypeCompatible: Bool { + public var isRawTypeCompatible: Bool { switch self { case .nominal(let nominal): switch nominal.nominalTypeDecl.knownTypeKind { @@ -153,7 +158,7 @@ extension SwiftType: CustomStringConvertible { } } - var description: String { + public var description: String { switch self { case .nominal(let nominal): return nominal.description case .genericParameter(let genericParam): return genericParam.packExpansionName @@ -186,23 +191,23 @@ extension SwiftType: CustomStringConvertible { } } -struct SwiftNominalType: Equatable { - indirect enum Parent: Equatable { +public struct SwiftNominalType: Equatable { + public indirect enum Parent: Equatable { case nominal(SwiftNominalType) } - enum SugarName: Equatable { + public enum SugarName: Equatable { case optional case array case dictionary } private var storedParent: Parent? - var sugarName: SugarName? - var nominalTypeDecl: SwiftNominalTypeDeclaration - var genericArguments: [SwiftType] + public var sugarName: SugarName? + public var nominalTypeDecl: SwiftNominalTypeDeclaration + public var genericArguments: [SwiftType] - init( + public init( parent: SwiftNominalType? = nil, sugarName: SugarName? = nil, nominalTypeDecl: SwiftNominalTypeDeclaration, @@ -215,7 +220,7 @@ struct SwiftNominalType: Equatable { self.genericArguments = genericArguments } - var parent: SwiftNominalType? { + public var parent: SwiftNominalType? { if case .nominal(let parent) = storedParent ?? .none { return parent } @@ -223,13 +228,13 @@ struct SwiftNominalType: Equatable { return nil } - package var asKnownType: SwiftKnownType? { + public var asKnownType: SwiftKnownType? { nominalTypeDecl.knownTypeKind.flatMap { SwiftKnownType(kind: $0, genericArguments: genericArguments) } } - var hasGenericParameter: Bool { + public var hasGenericParameter: Bool { genericArguments.contains { if case .genericParameter = $0 { return true @@ -240,7 +245,7 @@ struct SwiftNominalType: Equatable { } extension SwiftNominalType: CustomStringConvertible { - var description: String { + public var description: String { var resultString: String if let parent { resultString = parent.description + "." @@ -267,7 +272,7 @@ extension SwiftNominalType: CustomStringConvertible { } extension SwiftNominalType.Parent: CustomStringConvertible { - var description: String { + public var description: String { switch self { case .nominal(let nominal): return nominal.description @@ -276,17 +281,13 @@ extension SwiftNominalType.Parent: CustomStringConvertible { } extension SwiftNominalType { - var isSwiftJavaWrapper: Bool { - nominalTypeDecl.syntax.attributes.contains(where: \.isSwiftJavaMacro) - } - - var isProtocol: Bool { + public var isProtocol: Bool { nominalTypeDecl.kind == .protocol } } extension SwiftType { - init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { + public init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { var knownTypes: SwiftKnownTypes { SwiftKnownTypes(symbolTable: lookupContext.symbolTable) } @@ -438,7 +439,7 @@ extension SwiftType { } } - init( + public init( originalType: TypeSyntax, parent: SwiftType?, name: TokenSyntax, @@ -505,7 +506,7 @@ extension SwiftType { } } - init?( + public init?( nominalDecl: NamedDeclSyntax & DeclGroupSyntax, parent: SwiftType?, symbolTable: SwiftSymbolTable @@ -532,7 +533,7 @@ extension SwiftType { /// /// This is used e.g. by typealiases like `typealias Ano = Array`, /// so usages like `Ano` become `Array`. - func substituting(genericParameters substitutions: [String: SwiftType]) -> SwiftType { + public func substituting(genericParameters substitutions: [String: SwiftType]) -> SwiftType { guard !substitutions.isEmpty else { return self } switch self { @@ -579,7 +580,7 @@ extension SwiftType { /// Produce an expression that creates the metatype for this type in /// Swift source code. - var metatypeReferenceExprSyntax: ExprSyntax { + public var metatypeReferenceExprSyntax: ExprSyntax { let type: ExprSyntax = "\(raw: description)" if postfixRequiresParentheses { return "(\(type)).self" @@ -588,7 +589,7 @@ extension SwiftType { } } -enum TypeTranslationError: Error { +public enum TypeTranslationError: Error { /// We haven't yet implemented support for this type. case unimplementedType(TypeSyntax, file: StaticString = #file, line: Int = #line) @@ -600,7 +601,7 @@ enum TypeTranslationError: Error { } extension SwiftNominalTypeDeclaration { - var asSwiftNominalType: SwiftNominalType { + public var asSwiftNominalType: SwiftNominalType { let genericArguments = genericParameters.map { SwiftType.genericParameter($0) } return SwiftNominalType( parent: parent?.asSwiftNominalType, @@ -609,7 +610,7 @@ extension SwiftNominalTypeDeclaration { ) } - var asSwiftType: SwiftType { + public var asSwiftType: SwiftType { .nominal(asSwiftNominalType) } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/SwiftExtract/SwiftTypes/SwiftTypeLookupContext.swift similarity index 92% rename from Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift rename to Sources/SwiftExtract/SwiftTypes/SwiftTypeLookupContext.swift index 664fb7463..bee0ba061 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftTypeLookupContext.swift @@ -19,8 +19,8 @@ import SwiftSyntax /// All type lookups should be done via this instance. This caches the /// association of `Syntax.ID` to `SwiftTypeDeclaration`, and guarantees that /// there's only one `SwiftTypeDeclaration` per declaration `Syntax`. -class SwiftTypeLookupContext { - var symbolTable: SwiftSymbolTable +public class SwiftTypeLookupContext { + public var symbolTable: SwiftSymbolTable private var typeDecls: [Syntax.ID: SwiftTypeDeclaration] = [:] @@ -28,7 +28,7 @@ class SwiftTypeLookupContext { /// cycles like `typealias A = B; typealias B = A`. private var resolvingAliases: Set = [] - init(symbolTable: SwiftSymbolTable) { + public init(symbolTable: SwiftSymbolTable) { self.symbolTable = symbolTable } @@ -37,7 +37,7 @@ class SwiftTypeLookupContext { /// - Parameters: /// - name: name to lookup /// - moduleName: the module to look in - func moduleQualifiedLookup(name: String, in moduleName: String) -> SwiftTypeDeclaration? { + public func moduleQualifiedLookup(name: String, in moduleName: String) -> SwiftTypeDeclaration? { symbolTable.lookupTopLevelNominalType(name, inModule: moduleName) } @@ -46,7 +46,7 @@ class SwiftTypeLookupContext { /// - Parameters: /// - name: name to lookup /// - node: `Syntax` node the lookup happened - func unqualifiedLookup(name: Identifier, from node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + public func unqualifiedLookup(name: Identifier, from node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { for result in node.lookup(name) { switch result { @@ -110,7 +110,7 @@ class SwiftTypeLookupContext { /// Returns the type declaration object associated with the `Syntax` node. /// If there's no declaration created, create an instance on demand, and cache it. - func typeDeclaration(for node: some SyntaxProtocol, sourceFilePath: String) throws -> SwiftTypeDeclaration? { + public func typeDeclaration(for node: some SyntaxProtocol, sourceFilePath: String) throws -> SwiftTypeDeclaration? { if let found = typeDecls[node.id] { return found } @@ -207,7 +207,7 @@ class SwiftTypeLookupContext { } /// Resolve a typealias to the `SwiftType` of its right-hand side. - func resolve(typeAlias decl: SwiftTypeAliasDeclaration) throws -> SwiftType { + public func resolve(typeAlias decl: SwiftTypeAliasDeclaration) throws -> SwiftType { let id = decl.syntax.id guard !resolvingAliases.contains(id) else { throw TypeTranslationError.unimplementedType(TypeSyntax(decl.syntax.initializer.value)) @@ -218,6 +218,6 @@ class SwiftTypeLookupContext { } } -enum TypeLookupError: Error { +public enum TypeLookupError: Error { case notType(Syntax) } diff --git a/Sources/SwiftJavaTool/CommonOptions.swift b/Sources/SwiftJavaTool/CommonOptions.swift index 8f5e7ccc1..552cfea74 100644 --- a/Sources/SwiftJavaTool/CommonOptions.swift +++ b/Sources/SwiftJavaTool/CommonOptions.swift @@ -18,6 +18,7 @@ import JExtractSwiftLib import JavaNet import JavaUtilJar import Logging +import SwiftExtract import SwiftJava import SwiftJavaConfigurationShared import SwiftJavaShared @@ -63,7 +64,7 @@ extension SwiftJava { var inputSwift: String? = nil @Option(name: .shortAndLong, help: "Configure the level of logs that should be printed") - var logLevel: JExtractSwiftLib.Logger.Level = .info + var logLevel: SwiftExtract.Logger.Level = .info @Option(help: "A path to a custom swift-java.config to use") var config: String? = nil diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 19e3761b9..748114c30 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -19,6 +19,7 @@ import JavaLangReflect import JavaNet import JavaUtilJar import Logging +import SwiftExtract import SwiftJava import SwiftJavaConfigurationShared import SwiftJavaShared @@ -30,7 +31,7 @@ protocol SwiftJavaBaseAsyncParsableCommand: AsyncParsableCommand { var log: Logging.Logger { get } - var logLevel: JExtractSwiftLib.Logger.Level { get set } + var logLevel: SwiftExtract.Logger.Level { get set } /// Must be implemented with an `@OptionGroup` in Command implementations var commonOptions: SwiftJava.CommonOptions { get set } @@ -103,7 +104,7 @@ extension SwiftJavaBaseAsyncParsableCommand { .init(label: "swift-java") } - var logLevel: JExtractSwiftLib.Logger.Level { + var logLevel: SwiftExtract.Logger.Level { get { self.commonOptions.logLevel } diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift index 06d1d5553..adc338058 100644 --- a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// @_spi(Testing) import JExtractSwiftLib +import SwiftExtract import SwiftJavaConfigurationShared import SwiftSyntax import Testing @@ -34,7 +35,7 @@ func assertLoweredFunction( ) throws { var config = Configuration() config.swiftModule = swiftModuleName - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) if let sourceFile { translator.add(filePath: "Fake.swift", text: sourceFile) @@ -120,7 +121,7 @@ func assertLoweredVariableAccessor( ) throws { var config = Configuration() config.swiftModule = swiftModuleName - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) if let sourceFile { translator.add(filePath: "Fake.swift", text: sourceFile) diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index a4632f8ba..baeefe26d 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -14,6 +14,7 @@ import CodePrinting import JExtractSwiftLib +import SwiftExtract import SwiftJavaConfigurationShared import SwiftParser import SwiftSyntax @@ -51,11 +52,11 @@ func assertOutput( ) throws { var config = config ?? Configuration() config.swiftModule = swiftModuleName - let translator = Swift2JavaTranslator(config: config) - translator.sourceDependencies.javaClasses = Array(javaClassLookupTable.keys) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) + translator.sourceDependencies.addJavaWrapperStubs(Array(javaClassLookupTable.keys)) for (depModule, depSource) in dependencySwiftSources { let syntax = Parser.parse(source: depSource) - let input = SwiftJavaInputFile(syntax: syntax, path: "/fake/\(depModule).swift") + let input = SwiftInputFile(syntax: syntax, path: "/fake/\(depModule).swift") translator.sourceDependencies.swiftModuleInputs[depModule] = [input] } diff --git a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift index 4ed587812..4c4050cfc 100644 --- a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift +++ b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import JExtractSwiftLib +import SwiftExtract import SwiftJavaConfigurationShared import Testing @@ -33,7 +34,7 @@ final class FFMNestedTypesTests { func test_nested_in_extension() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: class_interfaceFile) diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index fd9203750..8ff99fd82 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -14,6 +14,7 @@ import CodePrinting import JExtractSwiftLib +import SwiftExtract import SwiftJavaConfigurationShared import Testing @@ -41,7 +42,7 @@ final class FuncCallbackImportTests { func func_callMeFunc_callback() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) @@ -131,7 +132,7 @@ final class FuncCallbackImportTests { func func_callMeMoreFunc_callback() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) @@ -246,7 +247,7 @@ final class FuncCallbackImportTests { func func_withBuffer_body() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index c514a12fd..1733bab9e 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -14,6 +14,7 @@ import CodePrinting import JExtractSwiftLib +import SwiftExtract import SwiftJavaConfigurationShared import Testing @@ -237,7 +238,7 @@ extension FunctionDescriptorTests { ) throws { var config = Configuration() config.swiftModule = swiftModuleName - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = logLevel try st.analyze(path: "/fake/Sample.swiftinterface", text: interfaceFile) @@ -271,7 +272,7 @@ extension FunctionDescriptorTests { ) throws { var config = Configuration() config.swiftModule = swiftModuleName - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = logLevel try st.analyze(path: "/fake/Sample.swiftinterface", text: interfaceFile) diff --git a/Tests/JExtractSwiftTests/JExtractFileFilterTests.swift b/Tests/JExtractSwiftTests/JExtractFileFilterTests.swift index b89e574f7..643a09042 100644 --- a/Tests/JExtractSwiftTests/JExtractFileFilterTests.swift +++ b/Tests/JExtractSwiftTests/JExtractFileFilterTests.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftJavaConfigurationShared import Testing @@ -146,16 +147,16 @@ struct JExtractFileFilterTests { } // ==== ------------------------------------------------------------------- - // MARK: shouldJExtractFile tests + // MARK: shouldExtractSwiftFile tests @Test("No filters means everything passes") func noFilters() { var config = Configuration() - #expect(shouldJExtractFile(relativePath: "Anything.swift", config: config)) + #expect(shouldExtractSwiftFile(relativePath: "Anything.swift", config: config)) config.swiftFilterInclude = [] config.swiftFilterExclude = [] - #expect(shouldJExtractFile(relativePath: "Anything.swift", config: config)) + #expect(shouldExtractSwiftFile(relativePath: "Anything.swift", config: config)) } @Test("File include filter only") @@ -163,9 +164,9 @@ struct JExtractFileFilterTests { var config = Configuration() config.swiftFilterInclude = ["Models/**"] - #expect(shouldJExtractFile(relativePath: "Models/User.swift", config: config)) - #expect(shouldJExtractFile(relativePath: "Models/Sub/Deep.swift", config: config)) - #expect(!shouldJExtractFile(relativePath: "Other/Thing.swift", config: config)) + #expect(shouldExtractSwiftFile(relativePath: "Models/User.swift", config: config)) + #expect(shouldExtractSwiftFile(relativePath: "Models/Sub/Deep.swift", config: config)) + #expect(!shouldExtractSwiftFile(relativePath: "Other/Thing.swift", config: config)) } @Test("File exclude filter only") @@ -173,8 +174,8 @@ struct JExtractFileFilterTests { var config = Configuration() config.swiftFilterExclude = ["Internal/*"] - #expect(shouldJExtractFile(relativePath: "Models/User.swift", config: config)) - #expect(!shouldJExtractFile(relativePath: "Internal/Secret.swift", config: config)) + #expect(shouldExtractSwiftFile(relativePath: "Models/User.swift", config: config)) + #expect(!shouldExtractSwiftFile(relativePath: "Internal/Secret.swift", config: config)) } @Test("File include and exclude combined") @@ -183,28 +184,28 @@ struct JExtractFileFilterTests { config.swiftFilterInclude = ["Models/**"] config.swiftFilterExclude = ["Models/Internal*"] - #expect(shouldJExtractFile(relativePath: "Models/User.swift", config: config)) - #expect(!shouldJExtractFile(relativePath: "Models/InternalHelper.swift", config: config)) - #expect(!shouldJExtractFile(relativePath: "Other/Thing.swift", config: config)) + #expect(shouldExtractSwiftFile(relativePath: "Models/User.swift", config: config)) + #expect(!shouldExtractSwiftFile(relativePath: "Models/InternalHelper.swift", config: config)) + #expect(!shouldExtractSwiftFile(relativePath: "Other/Thing.swift", config: config)) } - @Test("Type-name patterns are ignored by shouldJExtractFile") + @Test("Type-name patterns are ignored by shouldExtractSwiftFile") func typeNamePatternsIgnoredByFileFilter() { var config = Configuration() config.swiftFilterInclude = ["Something.Other"] // Type-name-only includes should not restrict file-level filtering - #expect(shouldJExtractFile(relativePath: "Anything.swift", config: config)) + #expect(shouldExtractSwiftFile(relativePath: "Anything.swift", config: config)) } // ==== ------------------------------------------------------------------- - // MARK: shouldJExtractType tests + // MARK: shouldExtractSwiftType tests @Test("No filters means all types pass") func noFiltersAllTypesPass() { let config = Configuration() - #expect(shouldJExtractType(qualifiedName: "Anything", config: config)) - #expect(shouldJExtractType(qualifiedName: "A.B.C", config: config)) + #expect(shouldExtractSwiftType(qualifiedName: "Anything", config: config)) + #expect(shouldExtractSwiftType(qualifiedName: "A.B.C", config: config)) } @Test("Type include filter") @@ -212,8 +213,8 @@ struct JExtractFileFilterTests { var config = Configuration() config.swiftFilterInclude = ["Something.Other"] - #expect(shouldJExtractType(qualifiedName: "Something.Other", config: config)) - #expect(!shouldJExtractType(qualifiedName: "Something.Wrong", config: config)) + #expect(shouldExtractSwiftType(qualifiedName: "Something.Other", config: config)) + #expect(!shouldExtractSwiftType(qualifiedName: "Something.Wrong", config: config)) } @Test("Type exclude filter") @@ -221,17 +222,17 @@ struct JExtractFileFilterTests { var config = Configuration() config.swiftFilterExclude = ["Something.Internal*"] - #expect(shouldJExtractType(qualifiedName: "Something.Other", config: config)) - #expect(!shouldJExtractType(qualifiedName: "Something.InternalHelper", config: config)) + #expect(shouldExtractSwiftType(qualifiedName: "Something.Other", config: config)) + #expect(!shouldExtractSwiftType(qualifiedName: "Something.InternalHelper", config: config)) } - @Test("File-path patterns are ignored by shouldJExtractType") + @Test("File-path patterns are ignored by shouldExtractSwiftType") func filePathPatternsIgnoredByTypeFilter() { var config = Configuration() config.swiftFilterInclude = ["Models/**"] // File-path-only includes should not restrict type-level filtering - #expect(shouldJExtractType(qualifiedName: "Anything", config: config)) + #expect(shouldExtractSwiftType(qualifiedName: "Anything", config: config)) } @Test("Plain pattern matches both file and type") @@ -240,12 +241,12 @@ struct JExtractFileFilterTests { config.swiftFilterInclude = ["MyType"] // Plain pattern works at file level (matched against filename segment) - #expect(shouldJExtractFile(relativePath: "MyType.swift", config: config)) - #expect(!shouldJExtractFile(relativePath: "OtherType.swift", config: config)) + #expect(shouldExtractSwiftFile(relativePath: "MyType.swift", config: config)) + #expect(!shouldExtractSwiftFile(relativePath: "OtherType.swift", config: config)) // Plain pattern works at type level - #expect(shouldJExtractType(qualifiedName: "MyType", config: config)) - #expect(!shouldJExtractType(qualifiedName: "OtherType", config: config)) + #expect(shouldExtractSwiftType(qualifiedName: "MyType", config: config)) + #expect(!shouldExtractSwiftType(qualifiedName: "OtherType", config: config)) } @Test("Mixed file and type patterns in same config") @@ -254,12 +255,12 @@ struct JExtractFileFilterTests { config.swiftFilterInclude = ["Models/**", "Something.Other"] // File filter applies the file-path pattern - #expect(shouldJExtractFile(relativePath: "Models/User.swift", config: config)) - #expect(!shouldJExtractFile(relativePath: "Other/Thing.swift", config: config)) + #expect(shouldExtractSwiftFile(relativePath: "Models/User.swift", config: config)) + #expect(!shouldExtractSwiftFile(relativePath: "Other/Thing.swift", config: config)) // Type filter applies the type-name pattern - #expect(shouldJExtractType(qualifiedName: "Something.Other", config: config)) - #expect(!shouldJExtractType(qualifiedName: "Something.Wrong", config: config)) + #expect(shouldExtractSwiftType(qualifiedName: "Something.Other", config: config)) + #expect(!shouldExtractSwiftType(qualifiedName: "Something.Wrong", config: config)) } // ==== ------------------------------------------------------------------- @@ -329,12 +330,12 @@ struct JExtractFileFilterTests { private func makeTranslator( include: [String]? = nil, exclude: [String]? = nil, - ) throws -> Swift2JavaTranslator { + ) throws -> SwiftAnalyzer { var config = Configuration() config.swiftModule = "__FakeModule" config.swiftFilterInclude = include config.swiftFilterExclude = exclude - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) translator.log.logLevel = .error try translator.analyze(path: "Fake.swift", text: Self.nestedTypeSource) return translator diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 5339e07d6..9743dbfaa 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -14,6 +14,7 @@ import CodePrinting import JExtractSwiftLib +import SwiftExtract import SwiftJavaConfigurationShared import Testing @@ -354,7 +355,7 @@ struct JNIEnumTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try! translator.analyze(path: "/fake/Fake.swiftinterface", text: input) var printer: CodePrinter = CodePrinter(mode: .accumulateAll) diff --git a/Tests/JExtractSwiftTests/JavaTypeAnnotationsTests.swift b/Tests/JExtractSwiftTests/JavaTypeAnnotationsTests.swift index 031487368..9291fcde1 100644 --- a/Tests/JExtractSwiftTests/JavaTypeAnnotationsTests.swift +++ b/Tests/JExtractSwiftTests/JavaTypeAnnotationsTests.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftJavaConfigurationShared import SwiftSyntax import Testing @@ -27,10 +28,9 @@ struct JavaTypeAnnotationsTests { init() { let symbolTable = SwiftSymbolTable.setup( moduleName: "TestModule", - [SwiftJavaInputFile(syntax: "" as SourceFileSyntax, path: "Fake.swift")], + [SwiftInputFile(syntax: "" as SourceFileSyntax, path: "Fake.swift")], config: nil, - sourceDependencies: SourceDependencies(), - log: Logger(label: "test", logLevel: .critical) + sourceDependencies: SourceDependencies() ) self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) self.config = Configuration() diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 298ef0738..96e1b8d02 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -14,6 +14,7 @@ import CodePrinting import JExtractSwiftLib +import SwiftExtract import SwiftJavaConfigurationShared import Testing @@ -71,7 +72,7 @@ final class MethodImportTests { func method_helloWorld() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: class_interfaceFile) @@ -113,7 +114,7 @@ final class MethodImportTests { func func_globalTakeInt() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: class_interfaceFile) @@ -162,7 +163,7 @@ final class MethodImportTests { func func_globalTakeIntLongString() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: class_interfaceFile) @@ -208,7 +209,7 @@ final class MethodImportTests { func func_globalReturnClass() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: class_interfaceFile) @@ -254,7 +255,7 @@ final class MethodImportTests { func func_globalSwapRawBufferPointer() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: class_interfaceFile) @@ -303,7 +304,7 @@ final class MethodImportTests { func method_class_helloMemberFunction() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: class_interfaceFile) @@ -348,7 +349,7 @@ final class MethodImportTests { func method_class_makeInt() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .info try st.analyze(path: "Fake.swift", text: class_interfaceFile) @@ -399,7 +400,7 @@ final class MethodImportTests { func class_constructor() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .info try st.analyze(path: "Fake.swift", text: class_interfaceFile) @@ -453,7 +454,7 @@ final class MethodImportTests { func struct_constructor() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .info @@ -508,7 +509,7 @@ final class MethodImportTests { func func_globalReturnAny() throws { var config = Configuration() config.swiftModule = "__FakeModule" - let st = Swift2JavaTranslator(config: config) + let st = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) st.log.logLevel = .error try st.analyze(path: "Fake.swift", text: class_interfaceFile) diff --git a/Tests/JExtractSwiftTests/SpecializationTests.swift b/Tests/JExtractSwiftTests/SpecializationTests.swift index f2303d453..876342941 100644 --- a/Tests/JExtractSwiftTests/SpecializationTests.swift +++ b/Tests/JExtractSwiftTests/SpecializationTests.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftJavaConfigurationShared import Testing @@ -75,7 +76,7 @@ struct SpecializationTests { func multipleSpecializationsProduceDistinctTypes() throws { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swiftinterface", text: multiSpecializationInput) // Both specialized types should be registered @@ -132,7 +133,7 @@ struct SpecializationTests { func specializationEntriesContainAll() throws { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swiftinterface", text: multiSpecializationInput) let baseBox = try #require(translator.importedTypes["Box"]) @@ -192,7 +193,7 @@ struct SpecializationTests { // Verify observeTheFish does NOT appear inside ToolBox's class body var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swiftinterface", text: multiSpecializationInput) let toolBox = try #require(translator.importedTypes["ToolBox"]) let methodNames = toolBox.methods.map(\.name) @@ -337,7 +338,7 @@ struct SpecializationTests { func specializeNonGenericTypeThrows() throws { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze( path: "/fake/Fake.swiftinterface", text: """ diff --git a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift index 864457aa2..2517b884f 100644 --- a/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift +++ b/Tests/JExtractSwiftTests/SwiftSymbolTableTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// @_spi(Testing) import JExtractSwiftLib +import SwiftExtract import SwiftJavaConfigurationShared import SwiftParser import SwiftSyntax @@ -21,38 +22,6 @@ import Testing @Suite("Swift symbol table") struct SwiftSymbolTableSuite { - @Test func lookupBindingTests() throws { - let sourceFile1: SourceFileSyntax = """ - extension X.Y { - struct Z { } - } - extension X { - struct Y {} - } - """ - let sourceFile2: SourceFileSyntax = """ - struct X {} - """ - let symbolTable = SwiftSymbolTable.setup( - moduleName: "MyModule", - [ - .init(syntax: sourceFile1, path: "Fake.swift"), - .init(syntax: sourceFile2, path: "Fake2.swift"), - ], - config: nil, - sourceDependencies: SourceDependencies(), - log: Logger(label: "swift-java", logLevel: .critical), - ) - - let x = try #require(symbolTable.lookupType("X", parent: nil)) - let xy = try #require(symbolTable.lookupType("Y", parent: x)) - let xyz = try #require(symbolTable.lookupType("Z", parent: xy)) - #expect(xyz.qualifiedName == "X.Y.Z") - - #expect(symbolTable.lookupType("Y", parent: nil) == nil) - #expect(symbolTable.lookupType("Z", parent: nil) == nil) - } - @Test(arguments: [JExtractGenerationMode.jni, .ffm]) func resolveSelfModuleName(mode: JExtractGenerationMode) throws { try assertOutput( @@ -94,35 +63,6 @@ struct SwiftSymbolTableSuite { ) } - @Test func moduleScopedLookup() throws { - let sourceFile: SourceFileSyntax = """ - public struct MyClass {} - """ - let symbolTable = SwiftSymbolTable.setup( - moduleName: "MyModule", - [ - .init(syntax: sourceFile, path: "Fake.swift") - ], - config: nil, - sourceDependencies: SourceDependencies(), - log: Logger(label: "swift-java", logLevel: .critical), - ) - - // Lookup in self-module by qualified name - let myClass = symbolTable.lookupTopLevelNominalType("MyClass", inModule: "MyModule") - #expect(myClass != nil) - #expect(myClass?.qualifiedName == "MyClass") - - // Lookup in imported module (Swift) - let swiftInt = symbolTable.lookupTopLevelNominalType("Int", inModule: "Swift") - #expect(swiftInt != nil) - #expect(swiftInt?.qualifiedName == "Int") - - // Lookup in unknown module returns nil - let unknown = symbolTable.lookupTopLevelNominalType("Foo", inModule: "NoSuchModule") - #expect(unknown == nil) - } - @Test(arguments: [JExtractGenerationMode.jni, .ffm]) func resolveQualifiedTypesInFunctionSignatures(mode: JExtractGenerationMode) throws { try assertOutput( diff --git a/Tests/JExtractSwiftTests/TypealiasResolutionTests.swift b/Tests/JExtractSwiftTests/TypealiasResolutionTests.swift index 55cabb115..dff3ed40f 100644 --- a/Tests/JExtractSwiftTests/TypealiasResolutionTests.swift +++ b/Tests/JExtractSwiftTests/TypealiasResolutionTests.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import SwiftExtract import SwiftJavaConfigurationShared import Testing @@ -48,7 +49,7 @@ struct TypealiasResolutionTests { func primitiveAliasResolvesStructMembers() throws { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: primitiveAliasInput) let user = try #require(translator.importedTypes["TypealiasUser"]) @@ -62,7 +63,7 @@ struct TypealiasResolutionTests { func primitiveAliasResolvesFreeFunc() throws { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: primitiveAliasInput) #expect( @@ -88,7 +89,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let holder = try #require(translator.importedTypes["Holder"]) @@ -112,7 +113,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "unwrapOrZero" }) @@ -143,7 +144,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "describe" }) @@ -175,7 +176,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let holder = try #require(translator.importedTypes["Holder"]) @@ -200,7 +201,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) // The struct itself is still imported, but its members are dropped @@ -259,7 +260,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "passA" }) @@ -284,7 +285,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "unwrap" }) @@ -309,7 +310,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "first" }) @@ -343,7 +344,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "add" }) @@ -375,7 +376,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let player = try #require(translator.importedTypes["Player"]) @@ -400,7 +401,7 @@ struct TypealiasResolutionTests { var config = Configuration() config.swiftModule = "SwiftModule" - let translator = Swift2JavaTranslator(config: config) + let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "openIntBag" }) diff --git a/Tests/SwiftExtractTests/AnalysisResultTests.swift b/Tests/SwiftExtractTests/AnalysisResultTests.swift new file mode 100644 index 000000000..ac3123ba5 --- /dev/null +++ b/Tests/SwiftExtractTests/AnalysisResultTests.swift @@ -0,0 +1,317 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 SwiftExtract +import SwiftJavaConfigurationShared +import Testing + +/// End-to-end tests that drive the analysis pipeline (Swift source → +/// `AnalysisResult`) without touching any code-generation layer. These verify +/// `SwiftExtract` produces a correct analysis snapshot independent of any +/// downstream language target. +@Suite("AnalysisResult") +struct AnalysisResultSuite { + + // ==== ----------------------------------------------------------------------- + // MARK: Top-level types + + @Test func topLevelTypesAreRecorded() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public struct Tank { + public init() {} + } + public class FishTank { + public init() {} + } + public enum Status { + case open, closed + } + """ + ) + ], + moduleName: "Aquarium" + ) + + #expect(result.importedTypes["Tank"] != nil) + #expect(result.importedTypes["FishTank"] != nil) + #expect(result.importedTypes["Status"] != nil) + + let tank = try #require(result.importedTypes["Tank"]) + #expect(tank.swiftNominal.kind == .struct) + #expect(tank.swiftNominal.isGeneric) + + let fishTank = try #require(result.importedTypes["FishTank"]) + #expect(fishTank.swiftNominal.kind == .class) + + let status = try #require(result.importedTypes["Status"]) + #expect(status.swiftNominal.kind == .enum) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Methods on a type + + @Test func methodsAreRecordedOnEnclosingType() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public class FishTank { + public init() {} + public func feed() {} + public func count() -> Int { 0 } + } + """ + ) + ], + moduleName: "Aquarium" + ) + + let fishTank = try #require(result.importedTypes["FishTank"]) + let methodNames = Set(fishTank.methods.map(\.name)) + #expect(methodNames == ["feed", "count"]) + #expect(fishTank.initializers.count == 1) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Properties (variables) — getter/setter pair + + @Test func storedPropertyProducesGetterAndSetter() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public class FishTank { + public init() {} + public var capacity: Int = 0 + } + """ + ) + ], + moduleName: "Aquarium" + ) + + let fishTank = try #require(result.importedTypes["FishTank"]) + let capacityAccessors = fishTank.variables.filter { $0.name == "capacity" } + let kinds = Set(capacityAccessors.map(\.apiKind)) + #expect(kinds == [.getter, .setter]) + } + + @Test func readOnlyPropertyHasOnlyGetter() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public class FishTank { + public init() {} + public var name: String { "Fish Tank" } + } + """ + ) + ], + moduleName: "Aquarium" + ) + + let fishTank = try #require(result.importedTypes["FishTank"]) + let nameAccessors = fishTank.variables.filter { $0.name == "name" } + let kinds = nameAccessors.map(\.apiKind) + #expect(kinds == [.getter]) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Global functions and variables + + @Test func globalFunctionLandsInImportedGlobalFuncs() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public func feedAll() {} + public func mood() -> String { "" } + """ + ) + ], + moduleName: "Aquarium" + ) + + let names = Set(result.importedGlobalFuncs.map(\.name)) + #expect(names == ["feedAll", "mood"]) + #expect(result.importedTypes.isEmpty) + } + + @Test func globalVariableProducesGetterSetterPair() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public var globalCounter: Int = 0 + """ + ) + ], + moduleName: "Aquarium" + ) + + let counterAccessors = result.importedGlobalVariables.filter { $0.name == "globalCounter" } + let kinds = Set(counterAccessors.map(\.apiKind)) + #expect(kinds == [.getter, .setter]) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Effect specifiers (throws / async) + + @Test func effectSpecifiersAreCapturedOnFunctionSignatures() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public func plain() {} + public func throwing() throws {} + public func asynchronous() async {} + public func both() async throws {} + """ + ) + ], + moduleName: "Aquarium" + ) + + let byName = Dictionary(uniqueKeysWithValues: result.importedGlobalFuncs.map { ($0.name, $0) }) + let plain = try #require(byName["plain"]) + #expect(plain.functionSignature.effectSpecifiers.isEmpty) + + let throwing = try #require(byName["throwing"]) + #expect(throwing.functionSignature.effectSpecifiers.contains(.throws)) + #expect(!throwing.functionSignature.effectSpecifiers.contains(.async)) + + let asynchronous = try #require(byName["asynchronous"]) + #expect(asynchronous.functionSignature.effectSpecifiers.contains(.async)) + #expect(!asynchronous.functionSignature.effectSpecifiers.contains(.throws)) + + let both = try #require(byName["both"]) + #expect(both.functionSignature.effectSpecifiers.contains(.async)) + #expect(both.functionSignature.effectSpecifiers.contains(.throws)) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Access-level filtering + + @Test func internalDeclarationsAreNotImportedByDefault() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public class Public { + public init() {} + } + internal class Internal { + init() {} + } + private class Private { + init() {} + } + """ + ) + ], + moduleName: "Aquarium" + ) + + #expect(result.importedTypes["Public"] != nil) + #expect(result.importedTypes["Internal"] == nil) + #expect(result.importedTypes["Private"] == nil) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Filter include/exclude + + @Test func swiftFilterExcludeSkipsMatchingTypes() throws { + var config = Configuration() + config.swiftFilterExclude = ["Skip*"] + + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public class Tank { + public init() {} + } + public class SkipMe { + public init() {} + } + public class SkipAlso { + public init() {} + } + """ + ) + ], + moduleName: "Aquarium", + config: config + ) + + #expect(result.importedTypes["Tank"] != nil) + #expect(result.importedTypes["SkipMe"] == nil) + #expect(result.importedTypes["SkipAlso"] == nil) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Generic typealias produces a specialization + + @Test func genericTypealiasProducesSpecializationEntry() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public struct Tank { + public init() {} + } + public struct Fish {} + public typealias FishTank = Tank + """ + ) + ], + moduleName: "Aquarium" + ) + + // Both the generic base and its specialization land in importedTypes. + #expect(result.importedTypes["Tank"] != nil) + let fishTank = try #require(result.importedTypes["FishTank"]) + #expect(fishTank.isSpecialization) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Empty input + + @Test func emptyModuleProducesEmptyResult() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ("/fake/Source.swift", "// nothing here") + ], + moduleName: "Empty" + ) + + #expect(result.importedTypes.isEmpty) + #expect(result.importedGlobalFuncs.isEmpty) + #expect(result.importedGlobalVariables.isEmpty) + } +} diff --git a/Tests/SwiftExtractTests/SourceDependenciesTests.swift b/Tests/SwiftExtractTests/SourceDependenciesTests.swift new file mode 100644 index 000000000..278a1dde5 --- /dev/null +++ b/Tests/SwiftExtractTests/SourceDependenciesTests.swift @@ -0,0 +1,113 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 SwiftExtract +import SwiftParser +import SwiftSyntax +import Testing + +@Suite("SourceDependencies") +struct SourceDependenciesSuite { + + // ==== ----------------------------------------------------------------------- + // MARK: Real dependency module + + @Test func realDependencyModuleResolvesItsTypes() throws { + var deps = SourceDependencies() + deps.swiftModuleInputs["DepModule"] = [ + makeInputFile("public class DepClass {}", path: "Dep.swift") + ] + + let symbolTable = makeSymbolTable( + moduleName: "MyModule", + sources: ["public func use(_ x: DepClass) {}"], + sourceDependencies: deps + ) + + let dep = try #require(symbolTable.lookupTopLevelNominalType("DepClass")) + #expect(dep.moduleName == "DepModule") + #expect(dep.kind == .class) + + // Module-scoped lookup also works. + let depViaModule = symbolTable.lookupTopLevelNominalType("DepClass", inModule: "DepModule") + #expect(depViaModule === dep) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Synthetic stubs + + /// Verifies the fix that keeps `` resolvable for type lookup + /// while excluding it from anything that would emit `import `. + @Test func syntheticStubsAreResolvableButNotPrintable() throws { + var deps = SourceDependencies() + deps.syntheticStubInputs[""] = [ + makeInputFile("@JavaClass public class JavaUtilFunction {}", path: ".swift") + ] + + let symbolTable = makeSymbolTable( + moduleName: "MyModule", + sources: ["public struct Anything {}"], + sourceDependencies: deps + ) + + // The stub type is resolvable. + let stub = try #require(symbolTable.lookupTopLevelNominalType("JavaUtilFunction")) + #expect(stub.moduleName == "") + + // The synthetic name is recorded as such. + #expect(symbolTable.syntheticImportedModuleNames.contains("")) + + // It must NOT be confused with a real module — `Swift` is not synthetic. + #expect(!symbolTable.syntheticImportedModuleNames.contains("Swift")) + } + + // ==== ----------------------------------------------------------------------- + // MARK: swiftModuleNames / syntheticModuleNames + + @Test func moduleNameSetsAreSeparateAndUnion() { + var deps = SourceDependencies() + deps.swiftModuleInputs["DepModule"] = [makeInputFile("public class A {}")] + deps.syntheticStubInputs[""] = [ + makeInputFile("@JavaClass public class B {}") + ] + + #expect(deps.swiftModuleNames == Set(["DepModule", ""])) + #expect(deps.syntheticModuleNames == Set([""])) + } + + @Test func mutatingOneFieldDoesNotAffectTheOther() { + var deps = SourceDependencies() + deps.swiftModuleInputs["DepModule"] = [makeInputFile("public class A {}")] + #expect(deps.syntheticModuleNames.isEmpty) + + deps.syntheticStubInputs[""] = [ + makeInputFile("@JavaClass public class B {}") + ] + #expect(deps.swiftModuleInputs.keys.contains("DepModule")) + #expect(!deps.swiftModuleInputs.keys.contains("")) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Empty dependencies + + @Test func emptyDependenciesProduceUsableSymbolTable() throws { + let symbolTable = makeSymbolTable(sources: ["public struct X {}"]) + + // Built-in Swift module is still available even with no dependencies. + #expect(symbolTable.lookupTopLevelNominalType("Int") != nil) + + // No synthetic modules. + #expect(symbolTable.syntheticImportedModuleNames.isEmpty) + } +} diff --git a/Tests/SwiftExtractTests/Support/SymbolTableFixture.swift b/Tests/SwiftExtractTests/Support/SymbolTableFixture.swift new file mode 100644 index 000000000..4d5e36ac0 --- /dev/null +++ b/Tests/SwiftExtractTests/Support/SymbolTableFixture.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 SwiftExtract +import SwiftParser +import SwiftSyntax + +/// Build a `SwiftSymbolTable` from inline Swift source strings. +/// +/// Each entry in `sources` becomes a synthetic `Test.swift` file feeding +/// the primary module being analysed. +func makeSymbolTable( + moduleName: String = "TestModule", + sources: [String], + sourceDependencies: SourceDependencies = SourceDependencies() +) -> SwiftSymbolTable { + let inputs: [SwiftInputFile] = sources.enumerated().map { (i, src) in + SwiftInputFile( + syntax: Parser.parse(source: src), + path: "Test\(i).swift" + ) + } + return SwiftSymbolTable.setup( + moduleName: moduleName, + inputs, + config: nil, + sourceDependencies: sourceDependencies, + ) +} + +/// Convenience: build a single `SwiftInputFile` from a source string. +func makeInputFile(_ source: String, path: String = "Dep.swift") -> SwiftInputFile { + SwiftInputFile(syntax: Parser.parse(source: source), path: path) +} diff --git a/Tests/SwiftExtractTests/SwiftKnownModuleTests.swift b/Tests/SwiftExtractTests/SwiftKnownModuleTests.swift new file mode 100644 index 000000000..a1912cc2c --- /dev/null +++ b/Tests/SwiftExtractTests/SwiftKnownModuleTests.swift @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 SwiftExtract +import Testing + +@Suite("SwiftKnownModule and SwiftKnownTypes") +struct SwiftKnownModuleSuite { + + // ==== ----------------------------------------------------------------------- + // MARK: Built-in Swift module catalog + + @Test(arguments: [ + "Int", "Int8", "Int16", "Int32", "Int64", + "UInt", "UInt8", "UInt16", "UInt32", "UInt64", + "Float", "Double", + "Bool", "String", + "Array", "Dictionary", "Set", "Optional", + ]) + func swiftModuleContains(_ typeName: String) throws { + let table = SwiftKnownModule.swift.symbolTable + let decl = try #require(table.lookupTopLevelNominalType(typeName)) + #expect(decl.name == typeName) + #expect(decl.moduleName == "Swift") + } + + // ==== ----------------------------------------------------------------------- + // MARK: SwiftKnownTypes accessor + + @Test func knownTypesExposeNominalSwiftStdlibTypes() throws { + let symbolTable = makeSymbolTable(sources: ["public struct X {}"]) + let known = SwiftKnownTypes(symbolTable: symbolTable) + + // Each accessor must yield a nominal type whose decl is the corresponding + // Swift stdlib type. + let int8Decl = try #require(known.int8.asNominalTypeDeclaration) + #expect(int8Decl.knownTypeKind == .int8) + + let uint8Decl = try #require(known.uint8.asNominalTypeDeclaration) + #expect(uint8Decl.knownTypeKind == .uint8) + + let stringDecl = try #require(known.string.asNominalTypeDeclaration) + #expect(stringDecl.knownTypeKind == .string) + + let boolDecl = try #require(known.bool.asNominalTypeDeclaration) + #expect(boolDecl.knownTypeKind == .bool) + + let doubleDecl = try #require(known.double.asNominalTypeDeclaration) + #expect(doubleDecl.knownTypeKind == .double) + } +} diff --git a/Tests/SwiftExtractTests/SwiftSymbolTableTests.swift b/Tests/SwiftExtractTests/SwiftSymbolTableTests.swift new file mode 100644 index 000000000..4b8e44dbd --- /dev/null +++ b/Tests/SwiftExtractTests/SwiftSymbolTableTests.swift @@ -0,0 +1,192 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 SwiftExtract +import SwiftParser +import SwiftSyntax +import Testing + +@Suite("SwiftSymbolTable") +struct SwiftSymbolTableSuite { + + // ==== ----------------------------------------------------------------------- + // MARK: Lookup binding (moved from JExtractSwiftTests) + + @Test func lookupBindingTests() throws { + let sourceFile1: SourceFileSyntax = """ + extension X.Y { + struct Z { } + } + extension X { + struct Y {} + } + """ + let sourceFile2: SourceFileSyntax = """ + struct X {} + """ + let symbolTable = SwiftSymbolTable.setup( + moduleName: "MyModule", + [ + .init(syntax: sourceFile1, path: "Fake.swift"), + .init(syntax: sourceFile2, path: "Fake2.swift"), + ], + config: nil, + sourceDependencies: SourceDependencies(), + ) + + let x = try #require(symbolTable.lookupType("X", parent: nil)) + let xy = try #require(symbolTable.lookupType("Y", parent: x)) + let xyz = try #require(symbolTable.lookupType("Z", parent: xy)) + #expect(xyz.qualifiedName == "X.Y.Z") + + #expect(symbolTable.lookupType("Y", parent: nil) == nil) + #expect(symbolTable.lookupType("Z", parent: nil) == nil) + } + + @Test func moduleScopedLookup() throws { + let symbolTable = makeSymbolTable( + moduleName: "MyModule", + sources: ["public struct MyClass {}"] + ) + + // Lookup in self-module by qualified name + let myClass = symbolTable.lookupTopLevelNominalType("MyClass", inModule: "MyModule") + #expect(myClass != nil) + #expect(myClass?.qualifiedName == "MyClass") + + // Lookup in imported module (Swift) + let swiftInt = symbolTable.lookupTopLevelNominalType("Int", inModule: "Swift") + #expect(swiftInt != nil) + #expect(swiftInt?.qualifiedName == "Int") + + // Lookup in unknown module returns nil + let unknown = symbolTable.lookupTopLevelNominalType("Foo", inModule: "NoSuchModule") + #expect(unknown == nil) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Top-level lookup by nominal kind + + @Test func topLevelLookupResolvesEachNominalKind() throws { + let symbolTable = makeSymbolTable(sources: [ + """ + public struct S {} + public class C {} + public enum E { case a } + public actor A {} + public protocol P {} + """ + ]) + + let s = try #require(symbolTable.lookupTopLevelNominalType("S")) + #expect(s.kind == .struct) + + let c = try #require(symbolTable.lookupTopLevelNominalType("C")) + #expect(c.kind == .class) + + let e = try #require(symbolTable.lookupTopLevelNominalType("E")) + #expect(e.kind == .enum) + + let a = try #require(symbolTable.lookupTopLevelNominalType("A")) + #expect(a.kind == .actor) + + let p = try #require(symbolTable.lookupTopLevelNominalType("P")) + #expect(p.kind == .protocol) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Nested-type lookup, multiple levels + + @Test func nestedLookupTwoLevelsDeep() throws { + let symbolTable = makeSymbolTable(sources: [ + """ + public struct A { + public struct B { + public struct C {} + } + } + """ + ]) + + let a = try #require(symbolTable.lookupTopLevelNominalType("A")) + let b = try #require(symbolTable.lookupNestedType("B", parent: a)) + let c = try #require(symbolTable.lookupNestedType("C", parent: b)) + #expect(c.qualifiedName == "A.B.C") + + // C is not a top-level type + #expect(symbolTable.lookupTopLevelNominalType("C") == nil) + // B is not nested under C + #expect(symbolTable.lookupNestedType("B", parent: c) == nil) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Negative lookups + + @Test func unknownNamesReturnNil() throws { + let symbolTable = makeSymbolTable(sources: [ + """ + public struct Known { + public struct Inner {} + } + """ + ]) + + #expect(symbolTable.lookupTopLevelNominalType("DoesNotExist") == nil) + #expect(symbolTable.lookupTopLevelTypealias("AlsoMissing") == nil) + + let known = try #require(symbolTable.lookupTopLevelNominalType("Known")) + #expect(symbolTable.lookupNestedType("Missing", parent: known) == nil) + } + + // ==== ----------------------------------------------------------------------- + // MARK: Typealias resolution + + @Test func topLevelTypealiasResolvesUnderlyingType() throws { + let symbolTable = makeSymbolTable(sources: [ + """ + public typealias Alias = Int + """ + ]) + + let alias = try #require(symbolTable.lookupTopLevelTypealias("Alias")) + #expect(alias.name == "Alias") + } + + // ==== ----------------------------------------------------------------------- + // MARK: Built-in module presence + + @Test func builtInSwiftModuleIsAlwaysRegistered() throws { + let symbolTable = makeSymbolTable(sources: ["public struct Anything {}"]) + + // Same lookup, two reachable paths: implicit cross-module, and module-scoped. + let int = try #require(symbolTable.lookupTopLevelNominalType("Int")) + #expect(int.moduleName == "Swift") + + let intInSwift = try #require(symbolTable.lookupTopLevelNominalType("Int", inModule: "Swift")) + #expect(intInSwift === int) + + #expect(symbolTable.lookupTopLevelNominalType("Int", inModule: "NoSuchModule") == nil) + } + + // ==== ----------------------------------------------------------------------- + // MARK: isModuleName + + @Test func isModuleNameRecognisesOwnAndImportedModules() throws { + let symbolTable = makeSymbolTable(moduleName: "MyModule", sources: ["public struct X {}"]) + + #expect(symbolTable.isModuleName("MyModule")) + #expect(symbolTable.isModuleName("Swift")) + #expect(!symbolTable.isModuleName("NotAModule")) + } +} From c1bced790e196108819798af139ef671bf30ef33 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 1 Jun 2026 14:23:14 +0900 Subject: [PATCH 02/13] SwiftExtract: move java bean conventions out of SwiftExtract --- .../Convenience/String+JavaNaming.swift | 26 +++++++++++++++++++ .../Convenience/String+Extensions.swift | 10 ------- 2 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 Sources/JExtractSwiftLib/Convenience/String+JavaNaming.swift diff --git a/Sources/JExtractSwiftLib/Convenience/String+JavaNaming.swift b/Sources/JExtractSwiftLib/Convenience/String+JavaNaming.swift new file mode 100644 index 000000000..8d77383ef --- /dev/null +++ b/Sources/JExtractSwiftLib/Convenience/String+JavaNaming.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 { + /// Returns whether the string is of the format `isX` (Java Beans boolean + /// property naming convention) + package var hasJavaBooleanNamingConvention: Bool { + guard self.hasPrefix("is"), self.count > 2 else { + return false + } + + let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) + return self[thirdCharacterIndex].isUppercase + } +} diff --git a/Sources/SwiftExtract/Convenience/String+Extensions.swift b/Sources/SwiftExtract/Convenience/String+Extensions.swift index 34fcafe0c..443984b4e 100644 --- a/Sources/SwiftExtract/Convenience/String+Extensions.swift +++ b/Sources/SwiftExtract/Convenience/String+Extensions.swift @@ -30,16 +30,6 @@ extension String { return "\(f.lowercased())\(String(dropFirst()))" } - /// Returns whether the string is of the format `isX` - package var hasJavaBooleanNamingConvention: Bool { - guard self.hasPrefix("is"), self.count > 2 else { - return false - } - - let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) - return self[thirdCharacterIndex].isUppercase - } - /// If the string ends with `.swift`, return it without that suffix; /// otherwise return self unchanged package func dropSwiftFileSuffix() -> String { From 6c86315e41e60ecd3b6526baac34bc31ef717bfd Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 1 Jun 2026 15:46:07 +0900 Subject: [PATCH 03/13] SwiftExtract: remove swiftjavaerror special handling from swiftextract --- ...Swift2JavaGenerator+FunctionLowering.swift | 4 -- ...MSwift2JavaGenerator+JavaTranslation.swift | 8 +--- .../FFM/FFMSwift2JavaGenerator.swift | 42 +++++++++---------- .../JNI/JNIJavaTypeTranslator.swift | 9 ++-- .../SwiftKnownTypes+Java.swift | 40 ++++++++++++++++++ .../SwiftTypes/SwiftKnownTypeDecls.swift | 23 ---------- 6 files changed, 66 insertions(+), 60 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftKnownTypes+Java.swift diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 5a39f845b..31172d11b 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -365,10 +365,6 @@ struct CdeclLowering { case .foundationData, .essentialsData: break - case .swiftJavaError: - // SwiftJavaError is a class — treat as arbitrary nominal type below - break - default: // Unreachable? Should be handled by `CType(cdeclType:)` lowering above. throw LoweringError.unhandledType(type) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 2bd12a50c..c3182c79d 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -18,7 +18,7 @@ import SwiftJavaJNICore extension FFMSwift2JavaGenerator { func translatedDecl( - for decl: ImportedFunc + for decl: ExtractedFunc ) -> TranslatedFunctionDecl? { if let cached = translatedDecls[decl] { return cached @@ -176,7 +176,7 @@ extension FFMSwift2JavaGenerator { self.javaIdentifiers = javaIdentifiers } - func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { + func translate(_ decl: ExtractedFunc) throws -> TranslatedFunctionDecl { let lowering = CdeclLowering(knownTypes: knownTypes) let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) @@ -451,10 +451,6 @@ extension FFMSwift2JavaGenerator { case .foundationData, .essentialsData: break - case .swiftJavaError: - // SwiftJavaError is a class — treat as arbitrary nominal type below - break - default: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 08890b3b9..5d22e3baa 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -38,7 +38,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry() /// Cached Java translation result. 'nil' indicates failed translation. - var translatedDecls: [ImportedFunc: TranslatedFunctionDecl?] = [:] + var translatedDecls: [ExtractedFunc: TranslatedFunctionDecl?] = [:] /// Duplicate identifier tracking for the current batch of methods being generated. var currentJavaIdentifiers: JavaIdentifierFactory = JavaIdentifierFactory() @@ -152,8 +152,8 @@ extension FFMSwift2JavaGenerator { ] /// Returns the Java class name for a nominal type, applying known-type overrides - func javaClassName(for decl: ImportedNominalType) -> String { - if decl.swiftNominal.knownTypeKind == .swiftJavaError { + func javaClassName(for decl: ExtractedNominalType) -> String { + if decl.swiftNominal.isSwiftJavaErrorType { return JavaType.swiftJavaErrorException.className! } return decl.swiftNominal.name @@ -171,13 +171,13 @@ extension FFMSwift2JavaGenerator { /// Every imported public type becomes a public class in its own file in Java. package func writeExportedJavaSources(printer: inout CodePrinter) throws { - let typesToExport: [(key: String, value: ImportedNominalType)] + let typesToExport: [(key: String, value: ExtractedNominalType)] if let singleType = config.singleType { - typesToExport = analysis.importedTypes + typesToExport = analysis.extractedTypes .filter { $0.key == singleType } .sorted(by: { $0.key < $1.key }) } else { - typesToExport = analysis.importedTypes + typesToExport = analysis.extractedTypes .sorted(by: { $0.key < $1.key }) } @@ -227,24 +227,24 @@ extension FFMSwift2JavaGenerator { printImports(&printer) self.currentJavaIdentifiers = JavaIdentifierFactory( - self.analysis.importedGlobalFuncs + self.analysis.importedGlobalVariables + self.analysis.extractedGlobalFuncs + self.analysis.extractedGlobalVariables ) printModuleClass(&printer) { printer in - for decl in analysis.importedGlobalVariables { + for decl in analysis.extractedGlobalVariables { self.log.trace("Print imported decl: \(decl)") printFunctionDowncallMethods(&printer, decl) } - for decl in analysis.importedGlobalFuncs { + for decl in analysis.extractedGlobalFuncs { self.log.trace("Print imported decl: \(decl)") printFunctionDowncallMethods(&printer, decl) } } } - func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + func printImportedNominal(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { printHeader(&printer) printPackage(&printer) printImports(&printer) // TODO: we could have some imports be driven from types used in the generated decl @@ -253,7 +253,7 @@ extension FFMSwift2JavaGenerator { decl.initializers + decl.variables + decl.methods ) - let isErrorType = decl.swiftNominal.knownTypeKind == .swiftJavaError + let isErrorType = decl.swiftNominal.isSwiftJavaErrorType self.currentSymbolLookup = isErrorType ? .swiftRuntime : .module printNominal(&printer, decl) { printer in @@ -387,10 +387,10 @@ extension FFMSwift2JavaGenerator { func printNominal( _ printer: inout CodePrinter, - _ decl: ImportedNominalType, + _ decl: ExtractedNominalType, body: (inout CodePrinter) -> Void, ) { - let isErrorType = decl.swiftNominal.knownTypeKind == .swiftJavaError + let isErrorType = decl.swiftNominal.isSwiftJavaErrorType let baseClass: String let parentProtocol: String @@ -425,8 +425,8 @@ extension FFMSwift2JavaGenerator { /// Returns a closure that prints the constructor and related extras for special nominal types /// (e.g. error types), or `nil` for normal types that use the default layout + constructor - func getSpecialNominalConstructorPrinting(_ decl: ImportedNominalType) -> ((inout CodePrinter) -> Void)? { - if decl.swiftNominal.knownTypeKind == .swiftJavaError { + func getSpecialNominalConstructorPrinting(_ decl: ExtractedNominalType) -> ((inout CodePrinter) -> Void)? { + if decl.swiftNominal.isSwiftJavaErrorType { return { printer in // Error constructor: wrap the opaque pointer so it becomes a pointer-to-reference // (matching the convention used by normal class instance thunks) @@ -449,8 +449,8 @@ extension FFMSwift2JavaGenerator { /// Returns a closure that prints post-members extras for special nominal types /// (e.g. `fetchDescription` for error types), or `nil` for normal types that use `toString()` - func getSpecialNominalPostMembersPrinting(_ decl: ImportedNominalType) -> ((inout CodePrinter) -> Void)? { - if decl.swiftNominal.knownTypeKind == .swiftJavaError { + func getSpecialNominalPostMembersPrinting(_ decl: ExtractedNominalType) -> ((inout CodePrinter) -> Void)? { + if decl.swiftNominal.isSwiftJavaErrorType { return { printer in // Error types inherit toString() from Exception; print fetchDescription helper instead self.printSwiftJavaErrorFetchDescriptionMethod(&printer, decl) @@ -569,7 +569,7 @@ extension FFMSwift2JavaGenerator { ) } - private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { printer.print( """ public static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment()); @@ -582,7 +582,7 @@ extension FFMSwift2JavaGenerator { func printToStringMethod( _ printer: inout CodePrinter, - _ decl: ImportedNominalType, + _ decl: ExtractedNominalType, ) { printer.print( """ @@ -599,7 +599,7 @@ extension FFMSwift2JavaGenerator { } /// Print special helper methods for known types like Foundation.Data - func printSpecificTypeHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + func printSpecificTypeHelpers(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { guard let knownType = decl.swiftNominal.knownTypeKind else { return } @@ -615,7 +615,7 @@ extension FFMSwift2JavaGenerator { /// Print the `fetchDescription` static helper for SwiftJavaError. /// This calls the `errorDescription()` downcall to get the error message /// for the super constructor - func printSwiftJavaErrorFetchDescriptionMethod(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + func printSwiftJavaErrorFetchDescriptionMethod(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { // Find the errorDescription method's thunk name let errorDescMethod = decl.methods.first { $0.name == "errorDescription" } guard let errorDescMethod, let _ = translatedDecl(for: errorDescMethod) else { diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index b676a8385..3e7821d48 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -53,8 +53,7 @@ enum JNIJavaTypeTranslator { .dictionary, .set, .foundationDate, .essentialsDate, - .foundationUUID, .essentialsUUID, - .swiftJavaError: + .foundationUUID, .essentialsUUID: return nil } } @@ -80,8 +79,7 @@ enum JNIJavaTypeTranslator { .dictionary, .set, .foundationDate, .essentialsDate, - .foundationUUID, .essentialsUUID, - .swiftJavaError: + .foundationUUID, .essentialsUUID: nil } } @@ -107,8 +105,7 @@ enum JNIJavaTypeTranslator { .dictionary, .set, .foundationDate, .essentialsDate, - .foundationUUID, .essentialsUUID, - .swiftJavaError: + .foundationUUID, .essentialsUUID: nil } } diff --git a/Sources/JExtractSwiftLib/SwiftKnownTypes+Java.swift b/Sources/JExtractSwiftLib/SwiftKnownTypes+Java.swift new file mode 100644 index 000000000..8f1ba13bf --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftKnownTypes+Java.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 SwiftExtract + +extension SwiftNominalTypeDeclaration { + /// True if this is swift-java's runtime `SwiftJavaError` type, which jextract + /// maps to a thrown Java exception rather than an ordinary wrapped nominal + package var isSwiftJavaErrorType: Bool { + moduleName == "SwiftRuntimeFunctions" && name == "SwiftJavaError" + } +} + +extension SwiftKnownTypeDeclKind { + /// Indicates whether this known type is translated by `wrap-java` + /// into the same type as `jextract`. + /// + /// This means we do not have to perform any mapping when passing + /// this type between jextract and wrap-java + package var isDirectlyTranslatedToWrapJava: Bool { + switch self { + case .bool, .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string, + .void: + return true + default: + return false + } + } +} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift index 57a851b1d..7b4ea12ea 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift @@ -53,9 +53,6 @@ public enum SwiftKnownType: Equatable { case foundationUUID case essentialsUUID - // SwiftRuntimeFunctions - case swiftJavaError - public init?(kind: SwiftKnownTypeDeclKind, genericArguments: [SwiftType]?) { switch kind { case .bool: self = .bool @@ -109,7 +106,6 @@ public enum SwiftKnownType: Equatable { case .essentialsDate: self = .essentialsDate case .foundationUUID: self = .foundationUUID case .essentialsUUID: self = .essentialsUUID - case .swiftJavaError: self = .swiftJavaError } } @@ -150,7 +146,6 @@ public enum SwiftKnownType: Equatable { case .essentialsDate: .essentialsDate case .foundationUUID: .foundationUUID case .essentialsUUID: .essentialsUUID - case .swiftJavaError: .swiftJavaError } } } @@ -195,9 +190,6 @@ public enum SwiftKnownTypeDeclKind: String, Hashable { case foundationUUID = "Foundation.UUID" case essentialsUUID = "FoundationEssentials.UUID" - // SwiftRuntimeFunctions - case swiftJavaError = "SwiftRuntimeFunctions.SwiftJavaError" - public var moduleAndName: (module: String, name: String) { let qualified = self.rawValue let period = qualified.firstIndex(of: ".")! @@ -215,19 +207,4 @@ public enum SwiftKnownTypeDeclKind: String, Hashable { return false } } - - /// Indicates whether this known type is translated by `wrap-java` - /// into the same type as `jextract`. - /// - /// This means we do not have to perform any mapping when passing - /// this type between jextract and wrap-java - public var isDirectlyTranslatedToWrapJava: Bool { - switch self { - case .bool, .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string, - .void: - return true - default: - return false - } - } } From 942a4dc92ac5bbf78f451f62805966f0d5567a36 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 1 Jun 2026 16:03:03 +0900 Subject: [PATCH 04/13] SwiftExtract: change naming scheme; types are "extracted" since SwiftExtract --- ....swift => ExtractedDecls+JavaNaming.swift} | 15 +++- ...FMSwift2JavaGenerator+FoundationData.swift | 2 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 12 +-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 34 ++++---- Sources/JExtractSwiftLib/JNI/JNICaching.swift | 6 +- ...Generator+InterfaceWrapperGeneration.swift | 16 ++-- ...t2JavaGenerator+JavaBindingsPrinting.swift | 50 +++++------ ...ISwift2JavaGenerator+JavaTranslation.swift | 24 +++--- ...wift2JavaGenerator+NativeTranslation.swift | 2 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 60 ++++++------- .../JNI/JNISwift2JavaGenerator.swift | 12 +-- .../JavaIdentifierFactory.swift | 10 +-- .../JExtractSwiftLib/SwiftKit+Printing.swift | 74 +--------------- .../JExtractSwiftLib/ThunkNameRegistry.swift | 4 +- .../TranslatedDocumentation.swift | 4 +- Sources/SwiftExtract/AnalysisResult.swift | 18 ++-- ...portedDecls.swift => ExtractedDecls.swift} | 58 ++++++------- .../SwiftExtract/SwiftAnalysisVisitor.swift | 84 +++++++++---------- Sources/SwiftExtract/SwiftAnalyzer.swift | 46 +++++----- ...odule.swift => ExtractedSwiftModule.swift} | 2 +- .../SwiftTypes/SwiftDependencyScanner.swift | 14 ++-- .../SwiftTypes/SwiftSymbolTable.swift | 2 +- Sources/SwiftJavaToolLib/JavaTranslator.swift | 6 +- .../FFMNestedTypesTests.swift | 2 +- .../FuncCallbackImportTests.swift | 6 +- .../FunctionDescriptorImportTests.swift | 6 +- .../JExtractFileFilterTests.swift | 56 ++++++------- .../MethodImportTests.swift | 28 +++---- .../SpecializationTests.swift | 30 +++---- .../TypealiasResolutionTests.swift | 26 +++--- .../AnalysisResultTests.swift | 50 +++++------ 31 files changed, 348 insertions(+), 411 deletions(-) rename Sources/JExtractSwiftLib/{ImportedDecls+JavaNaming.swift => ExtractedDecls+JavaNaming.swift} (87%) rename Sources/SwiftExtract/{ImportedDecls.swift => ExtractedDecls.swift} (87%) rename Sources/SwiftExtract/SwiftTypes/{ImportedSwiftModule.swift => ExtractedSwiftModule.swift} (96%) diff --git a/Sources/JExtractSwiftLib/ImportedDecls+JavaNaming.swift b/Sources/JExtractSwiftLib/ExtractedDecls+JavaNaming.swift similarity index 87% rename from Sources/JExtractSwiftLib/ImportedDecls+JavaNaming.swift rename to Sources/JExtractSwiftLib/ExtractedDecls+JavaNaming.swift index 6f71df330..d8de44734 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls+JavaNaming.swift +++ b/Sources/JExtractSwiftLib/ExtractedDecls+JavaNaming.swift @@ -15,9 +15,16 @@ import SwiftExtract // ==== ----------------------------------------------------------------------- -// MARK: Java-facing name aliases for ImportedNominalType +// MARK: Java name typealiases -extension ImportedNominalType { +package typealias JavaClassName = String +package typealias JavaFullyQualifiedClassName = String +package typealias JavaPackageName = String + +// ==== ----------------------------------------------------------------------- +// MARK: Java-facing name aliases for ExtractedNominalType + +extension ExtractedNominalType { package var effectiveJavaTypeName: SwiftQualifiedTypeName { effectiveOutputTypeName } package var effectiveJavaName: String { effectiveOutputName } package var effectiveJavaSimpleName: String { effectiveOutputSimpleName } @@ -25,9 +32,9 @@ extension ImportedNominalType { } // ==== ----------------------------------------------------------------------- -// MARK: Java-facing name aliases for ImportedFunc +// MARK: Java-facing name aliases for ExtractedFunc -extension ImportedFunc { +extension ExtractedFunc { /// The Java getter name for a Swift property/subscript getter, following /// Java Beans conventions: `get` for non-boolean, `is` for /// boolean (unless the property already starts with `is`, in which case diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift index 5d84b983f..a482041c5 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+FoundationData.swift @@ -24,7 +24,7 @@ import struct Foundation.URL extension FFMSwift2JavaGenerator { /// Print Java helper methods for Foundation.Data type - package func printFoundationDataHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + package func printFoundationDataHelpers(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { let typeName = decl.swiftNominal.name let thunkNameCopyBytes = "swiftjava_\(swiftModuleName)_\(typeName)_copyBytes__" diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 3f84fca7c..af5bb32e7 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -20,7 +20,7 @@ import SwiftJavaJNICore extension FFMSwift2JavaGenerator { package func printFunctionDowncallMethods( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, ) { guard let _ = translatedDecl(for: decl) else { // Failed to translate. Skip. @@ -40,7 +40,7 @@ extension FFMSwift2JavaGenerator { /// Print FFM Java binding descriptors for the imported Swift API. package func printJavaBindingDescriptorClass( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, ) { let thunkName = thunkNameRegistry.functionThunkName(decl: decl) let translated = self.translatedDecl(for: decl)! @@ -271,7 +271,7 @@ extension FFMSwift2JavaGenerator { /// * User-facing functional interfaces. func printJavaBindingWrapperHelperClass( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, ) { let translated = self.translatedDecl(for: decl)! let bindingDescriptorName = self.thunkNameRegistry.functionThunkName(decl: decl) @@ -360,7 +360,7 @@ extension FFMSwift2JavaGenerator { /// with adding `SwiftArena.ofAuto()` at the end. package func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, ) { let translated = self.translatedDecl(for: decl)! let methodName = translated.name @@ -421,7 +421,7 @@ extension FFMSwift2JavaGenerator { /// This assumes that all the parameters are passed-in with appropriate names. package func printDowncall( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, ) { //=== Part 1: prepare temporary arena if needed. let translatedSignature = self.translatedDecl(for: decl)!.translatedSignature @@ -455,7 +455,7 @@ extension FFMSwift2JavaGenerator { let arena = if let className = type.className, - analysis.importedTypes[className] != nil + analysis.extractedTypes[className] != nil { // Use passed-in 'SwiftArena' for 'SwiftValue'. "swiftArena" diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 49e71d9af..9e39cefdc 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -74,20 +74,20 @@ extension FFMSwift2JavaGenerator { // We have to write all types to their corresponding output file that matches the file they were declared in, // because otherwise SwiftPM plugins will not pick up files apropriately -- we expect 1 output +SwiftJava.swift file for every input. - let filteredTypes: [String: ImportedNominalType] + let filteredTypes: [String: ExtractedNominalType] if let singleType = config.singleType { - filteredTypes = self.analysis.importedTypes.filter { $0.key == singleType } + filteredTypes = self.analysis.extractedTypes.filter { $0.key == singleType } } else { - filteredTypes = self.analysis.importedTypes + filteredTypes = self.analysis.extractedTypes } - for group: (key: String, value: [Dictionary.Element]) in Dictionary( + for group: (key: String, value: [Dictionary.Element]) in Dictionary( grouping: filteredTypes, by: { $0.value.sourceFilePath }, ) { log.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))") - let importedTypesForThisFile = group.value + let extractedTypesForThisFile = group.value .map(\.value) .sorted(by: { $0.qualifiedName < $1.qualifiedName }) @@ -105,7 +105,7 @@ extension FFMSwift2JavaGenerator { ) self.lookupContext.symbolTable.printImportedModules(&printer) - for ty in importedTypesForThisFile { + for ty in extractedTypesForThisFile { log.info("Printing Swift thunks for type: \(ty.qualifiedName.bold)") printer.printSeparator("Thunks for \(ty.qualifiedName)") @@ -148,7 +148,7 @@ extension FFMSwift2JavaGenerator { self.lookupContext.symbolTable.printImportedModules(&printer) self.currentJavaIdentifiers = JavaIdentifierFactory( - self.analysis.importedGlobalFuncs + self.analysis.importedGlobalVariables + self.analysis.extractedGlobalFuncs + self.analysis.extractedGlobalVariables ) for thunk in stt.renderGlobalThunks() { @@ -157,7 +157,7 @@ extension FFMSwift2JavaGenerator { } } - public func printSwiftThunkSources(_ printer: inout CodePrinter, decl: ImportedFunc) { + public func printSwiftThunkSources(_ printer: inout CodePrinter, decl: ExtractedFunc) { let stt = SwiftThunkTranslator(self) for thunk in stt.render(forFunc: decl) { @@ -166,7 +166,7 @@ extension FFMSwift2JavaGenerator { } } - package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws { + package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ExtractedNominalType) throws { let stt = SwiftThunkTranslator(self) self.currentJavaIdentifiers = JavaIdentifierFactory( @@ -191,14 +191,14 @@ struct SwiftThunkTranslator { func renderGlobalThunks() -> [DeclSyntax] { var decls: [DeclSyntax] = [] decls.reserveCapacity( - st.analysis.importedGlobalVariables.count + st.analysis.importedGlobalFuncs.count + st.analysis.extractedGlobalVariables.count + st.analysis.extractedGlobalFuncs.count ) - for decl in st.analysis.importedGlobalVariables { + for decl in st.analysis.extractedGlobalVariables { decls.append(contentsOf: render(forFunc: decl)) } - for decl in st.analysis.importedGlobalFuncs { + for decl in st.analysis.extractedGlobalFuncs { decls.append(contentsOf: render(forFunc: decl)) } @@ -206,7 +206,7 @@ struct SwiftThunkTranslator { } /// Render all the thunks that make Swift methods accessible to Java. - func renderThunks(forType nominal: ImportedNominalType) -> [DeclSyntax] { + func renderThunks(forType nominal: ExtractedNominalType) -> [DeclSyntax] { var decls: [DeclSyntax] = [] decls.reserveCapacity( 1 + nominal.initializers.count + nominal.variables.count + nominal.methods.count @@ -233,7 +233,7 @@ struct SwiftThunkTranslator { } /// Accessor to get the `T.self` of the Swift type, without having to rely on mangled name lookups. - func renderSwiftTypeAccessor(_ nominal: ImportedNominalType) -> DeclSyntax { + func renderSwiftTypeAccessor(_ nominal: ExtractedNominalType) -> DeclSyntax { let funcName = SwiftKitPrinting.Names.getType( module: st.swiftModuleName, nominal: nominal, @@ -248,7 +248,7 @@ struct SwiftThunkTranslator { """ } - func render(forFunc decl: ImportedFunc) -> [DeclSyntax] { + func render(forFunc decl: ExtractedFunc) -> [DeclSyntax] { st.log.trace("Rendering thunks for: \(decl.displayName)") let thunkName = st.thunkNameRegistry.functionThunkName(decl: decl) @@ -265,7 +265,7 @@ struct SwiftThunkTranslator { } /// Render special thunks for known types like Foundation.Data - func renderSpecificTypeThunks(_ nominal: ImportedNominalType) -> [DeclSyntax] { + func renderSpecificTypeThunks(_ nominal: ExtractedNominalType) -> [DeclSyntax] { guard let knownType = nominal.swiftNominal.knownTypeKind else { return [] } @@ -279,7 +279,7 @@ struct SwiftThunkTranslator { } /// Render Swift thunks for Foundation.Data helper methods - private func renderFoundationDataThunks(_ nominal: ImportedNominalType) -> [DeclSyntax] { + private func renderFoundationDataThunks(_ nominal: ExtractedNominalType) -> [DeclSyntax] { let thunkName = "swiftjava_\(st.swiftModuleName)_\(nominal.swiftNominal.name)_copyBytes__" let qualifiedName = nominal.swiftNominal.qualifiedName diff --git a/Sources/JExtractSwiftLib/JNI/JNICaching.swift b/Sources/JExtractSwiftLib/JNI/JNICaching.swift index 6cec38886..4173d26f7 100644 --- a/Sources/JExtractSwiftLib/JNI/JNICaching.swift +++ b/Sources/JExtractSwiftLib/JNI/JNICaching.swift @@ -15,7 +15,7 @@ import SwiftExtract enum JNICaching { - static func cacheName(for type: ImportedNominalType) -> String { + static func cacheName(for type: ExtractedNominalType) -> String { cacheName(for: type.effectiveJavaTypeName) } @@ -23,7 +23,7 @@ enum JNICaching { cacheName(for: type.nominalTypeDecl.qualifiedTypeName) } - static func bridgeName(for type: ImportedNominalType) -> String { + static func bridgeName(for type: ExtractedNominalType) -> String { bridgeName(for: type.swiftNominal.qualifiedTypeName) } @@ -39,7 +39,7 @@ enum JNICaching { "_JNIBridge_\(typeName.fullFlatName)" } - static func cacheMemberName(for enumCase: ImportedEnumCase) -> String { + static func cacheMemberName(for enumCase: ExtractedEnumCase) -> String { "\(enumCase.enumType.nominalTypeDecl.name.firstCharacterLowercased)\(enumCase.name.firstCharacterUppercased)Cache" } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift index abdc1db2a..da4a6216f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+InterfaceWrapperGeneration.swift @@ -21,9 +21,9 @@ import SwiftSyntax extension JNISwift2JavaGenerator { func generateInterfaceWrappers( - _ types: [ImportedNominalType] - ) -> [ImportedNominalType: JavaInterfaceSwiftWrapper] { - var wrappers = [ImportedNominalType: JavaInterfaceSwiftWrapper]() + _ types: [ExtractedNominalType] + ) -> [ExtractedNominalType: JavaInterfaceSwiftWrapper] { + var wrappers = [ExtractedNominalType: JavaInterfaceSwiftWrapper]() for type in types where type.swiftNominal.kind == .protocol { // Skip protocols that have a known representative concrete type (e.g. DataProtocol). @@ -51,7 +51,7 @@ extension JNISwift2JavaGenerator { let protocolType: SwiftNominalType let functions: [Function] let variables: [Variable] - let importedType: ImportedNominalType + let importedType: ExtractedNominalType var wrapperName: String { protocolType.nominalTypeDecl.javaInterfaceSwiftProtocolWrapperName @@ -102,7 +102,7 @@ extension JNISwift2JavaGenerator { } struct JavaInterfaceProtocolWrapperGenerator { - func generate(for type: ImportedNominalType) throws -> JavaInterfaceSwiftWrapper { + func generate(for type: ExtractedNominalType) throws -> JavaInterfaceSwiftWrapper { if !type.initializers.isEmpty || type.methods.contains(where: \.isStatic) || type.variables.contains(where: \.isStatic) @@ -164,7 +164,7 @@ extension JNISwift2JavaGenerator { ) } - private func translate(function: ImportedFunc) throws -> JavaInterfaceSwiftWrapper.Function { + private func translate(function: ExtractedFunc) throws -> JavaInterfaceSwiftWrapper.Function { let parameters = try function.functionSignature.parameters.map { try self.translateParameter($0) } @@ -181,8 +181,8 @@ extension JNISwift2JavaGenerator { } private func translateVariable( - getter: ImportedFunc, - setter: ImportedFunc? + getter: ExtractedFunc, + setter: ExtractedFunc? ) throws -> JavaInterfaceSwiftWrapper.Variable { try JavaInterfaceSwiftWrapper.Variable( swiftDecl: getter.swiftDecl, // they should be the same diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 90aaafc38..6a3ad1e5f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -43,13 +43,13 @@ extension JNISwift2JavaGenerator { } package func writeExportedJavaSources(_ printer: inout CodePrinter) throws { - let typesToExport: [(key: String, value: ImportedNominalType)] + let typesToExport: [(key: String, value: ExtractedNominalType)] if let singleType = config.singleType { - typesToExport = analysis.importedTypes + typesToExport = analysis.extractedTypes .filter { $0.key == singleType } .sorted(by: { $0.key < $1.key }) } else { - typesToExport = analysis.importedTypes + typesToExport = analysis.extractedTypes .sorted(by: { $0.key < $1.key }) } @@ -106,7 +106,7 @@ extension JNISwift2JavaGenerator { printImports(&printer) self.currentJavaIdentifiers = JavaIdentifierFactory( - self.analysis.importedGlobalFuncs + self.analysis.importedGlobalVariables + self.analysis.extractedGlobalFuncs + self.analysis.extractedGlobalVariables ) printModuleClass(&printer) { printer in @@ -140,13 +140,13 @@ extension JNISwift2JavaGenerator { ) } - for decl in analysis.importedGlobalFuncs { + for decl in analysis.extractedGlobalFuncs { self.logger.trace("Print global function: \(decl)") printFunctionDowncallMethods(&printer, decl) printer.println() } - for decl in analysis.importedGlobalVariables { + for decl in analysis.extractedGlobalVariables { self.logger.trace("Print global variable: \(decl)") printFunctionDowncallMethods(&printer, decl) printer.println() @@ -154,7 +154,7 @@ extension JNISwift2JavaGenerator { } } - private func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printImportedNominal(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { printHeader(&printer) printPackage(&printer) printImports(&printer) @@ -171,7 +171,7 @@ extension JNISwift2JavaGenerator { } } - private func printProtocol(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printProtocol(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { var extends = self.inheritedProtocols(of: decl).map(\.effectiveJavaSimpleName) // If we cannot generate Swift wrappers @@ -207,7 +207,7 @@ extension JNISwift2JavaGenerator { } } - private func printConcreteType(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printConcreteType(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { printNominal(&printer, decl) { printer in printer.print( """ @@ -245,7 +245,7 @@ extension JNISwift2JavaGenerator { ) } - let nestedTypes = self.analysis.importedTypes.filter { _, type in + let nestedTypes = self.analysis.extractedTypes.filter { _, type in type.parent == decl.swiftNominal } @@ -399,7 +399,7 @@ extension JNISwift2JavaGenerator { } /// Prints helpers for specific types like `Foundation.Date` - private func printSpecificTypeHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printSpecificTypeHelpers(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { guard let knownType = decl.swiftNominal.knownTypeKind else { return } switch knownType { @@ -442,7 +442,7 @@ extension JNISwift2JavaGenerator { private func printNominal( _ printer: inout CodePrinter, - _ decl: ImportedNominalType, + _ decl: ExtractedNominalType, body: (inout CodePrinter) -> Void, ) { if decl.swiftNominal.isSendable { @@ -474,7 +474,7 @@ extension JNISwift2JavaGenerator { } } - private func printEnumHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printEnumHelpers(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { printEnumDiscriminator(&printer, decl) printer.println() printEnumCaseInterface(&printer, decl) @@ -484,7 +484,7 @@ extension JNISwift2JavaGenerator { printEnumCases(&printer, decl) } - private func printEnumDiscriminator(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printEnumDiscriminator(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { if decl.cases.isEmpty { return } @@ -501,7 +501,7 @@ extension JNISwift2JavaGenerator { } } - private func printEnumCaseInterface(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printEnumCaseInterface(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { if decl.cases.isEmpty { return } @@ -558,7 +558,7 @@ extension JNISwift2JavaGenerator { } } - private func printEnumStaticInitializers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printEnumStaticInitializers(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { let isEffectivelyGeneric = decl.swiftNominal.isGeneric && !decl.isSpecialization if !decl.cases.isEmpty && isEffectivelyGeneric { self.logger.debug("Skipping generic static initializers in '\(decl.effectiveJavaSimpleName)'") @@ -570,7 +570,7 @@ extension JNISwift2JavaGenerator { } } - private func printEnumCases(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printEnumCases(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { let caseTypeParameters: [JavaType] = decl.genericParameterNames.map { .class(package: nil, name: $0) } @@ -626,7 +626,7 @@ extension JNISwift2JavaGenerator { private func printFunctionDowncallMethods( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, skipMethodBody: Bool = false, ) { guard translatedDecl(for: decl) != nil else { @@ -650,7 +650,7 @@ extension JNISwift2JavaGenerator { /// * User-facing functional interfaces. private func printJavaBindingWrapperHelperClass( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, ) { let translated = self.translatedDecl(for: decl)! if translated.functionTypes.isEmpty { @@ -687,7 +687,7 @@ extension JNISwift2JavaGenerator { private func printNecessarySupportTypes( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ decl: ExtractedFunc ) { let translatedDecl = translatedDecl(for: decl)! @@ -698,7 +698,7 @@ extension JNISwift2JavaGenerator { private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, skipMethodBody: Bool, ) { guard let translatedDecl = translatedDecl(for: decl) else { @@ -710,7 +710,7 @@ extension JNISwift2JavaGenerator { private func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, _ translatedDecl: TranslatedFunctionDecl, - importedFunc: ImportedFunc? = nil, + importedFunc: ExtractedFunc? = nil, skipMethodBody: Bool, ) { var modifiers = ["public"] @@ -879,7 +879,7 @@ extension JNISwift2JavaGenerator { } } - private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + private func printTypeMetadataAddressFunction(_ printer: inout CodePrinter, _ type: ExtractedNominalType) { let isEffectivelyGeneric = type.swiftNominal.isGeneric && !type.isSpecialization if isEffectivelyGeneric { printer.print("@Override") @@ -897,7 +897,7 @@ extension JNISwift2JavaGenerator { } } - private func printFoundationDateHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printFoundationDateHelpers(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { printer.print( """ /** @@ -950,7 +950,7 @@ extension JNISwift2JavaGenerator { ) } - private func printFoundationDataHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + private func printFoundationDataHelpers(_ printer: inout CodePrinter, _ decl: ExtractedNominalType) { printer.print( """ /** diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index f69ea1918..3012fba79 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -30,12 +30,12 @@ extension JNISwift2JavaGenerator { protocolWrappers: self.interfaceProtocolWrappers, logger: self.logger, javaIdentifiers: self.currentJavaIdentifiers, - importedTypes: self.analysis.importedTypes, + extractedTypes: self.analysis.extractedTypes, ) } func translatedDecl( - for decl: ImportedFunc + for decl: ExtractedFunc ) -> TranslatedFunctionDecl? { if let cached = translatedDecls[decl] { return cached @@ -54,7 +54,7 @@ extension JNISwift2JavaGenerator { } func translatedEnumCase( - for decl: ImportedEnumCase + for decl: ExtractedEnumCase ) -> TranslatedEnumCase? { if let cached = translatedEnumCases[decl] { return cached @@ -72,7 +72,7 @@ extension JNISwift2JavaGenerator { protocolWrappers: self.interfaceProtocolWrappers, logger: self.logger, javaIdentifiers: self.currentJavaIdentifiers, - importedTypes: self.analysis.importedTypes, + extractedTypes: self.analysis.extractedTypes, ) translated = try translation.translate(enumCase: decl) } catch { @@ -91,12 +91,12 @@ extension JNISwift2JavaGenerator { let javaClassLookupTable: JavaClassLookupTable let moduleJavaPackages: ModuleJavaPackages var knownTypes: SwiftKnownTypes - let protocolWrappers: [ImportedNominalType: JavaInterfaceSwiftWrapper] + let protocolWrappers: [ExtractedNominalType: JavaInterfaceSwiftWrapper] let logger: Logger var javaIdentifiers: JavaIdentifierFactory - let importedTypes: [String: ImportedNominalType] + let extractedTypes: [String: ExtractedNominalType] - func translate(enumCase: ImportedEnumCase) throws -> TranslatedEnumCase { + func translate(enumCase: ExtractedEnumCase) throws -> TranslatedEnumCase { let methodName = "" // TODO: Used for closures, replace with better name? let parameterResults = try enumCase.parameters.enumerated().map { idx, parameter in @@ -123,9 +123,9 @@ extension JNISwift2JavaGenerator { } ) ) - let getAsCaseFunction: ImportedFunc? = + let getAsCaseFunction: ExtractedFunc? = if !enumCase.parameters.isEmpty { - ImportedFunc( + ExtractedFunc( module: enumCase.enumType.nominalTypeDecl.moduleName, swiftDecl: DeclSyntax("func getAs\(raw: javaCaseClassName)() -> (\(raw: associatedValueTypes))?"), name: "getAs\(javaCaseClassName)", @@ -152,7 +152,7 @@ extension JNISwift2JavaGenerator { ) } - func translate(_ decl: ImportedFunc) throws -> TranslatedFunctionDecl { + func translate(_ decl: ExtractedFunc) throws -> TranslatedFunctionDecl { let nativeTranslation = NativeJavaTranslation( config: self.config, javaPackage: self.javaPackage, @@ -1579,12 +1579,12 @@ extension JNISwift2JavaGenerator { let name: String /// The oringinal enum case. - let original: ImportedEnumCase + let original: ExtractedEnumCase /// A list of the translated associated values let parameters: [JavaParameter] - let getAsCaseFunction: ImportedFunc? + let getAsCaseFunction: ExtractedFunc? /// Returns whether the associated values require an arena let requiresSwiftArena: Bool diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 66f89459f..1009f4c79 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -24,7 +24,7 @@ extension JNISwift2JavaGenerator { let javaPackage: String let javaClassLookupTable: JavaClassLookupTable var knownTypes: SwiftKnownTypes - let protocolWrappers: [ImportedNominalType: JavaInterfaceSwiftWrapper] + let protocolWrappers: [ExtractedNominalType: JavaInterfaceSwiftWrapper] let logger: Logger /// Translates a Swift function into the native JNI method signature. diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index dac2f44aa..21063d3dc 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -79,27 +79,27 @@ extension JNISwift2JavaGenerator { // We have to write all types to their corresponding output file that matches the file they were declared in, // because otherwise SwiftPM plugins will not pick up files apropriately -- we expect 1 output +SwiftJava.swift file for every input. - let filteredTypes: [String: ImportedNominalType] + let filteredTypes: [String: ExtractedNominalType] if let singleType = config.singleType { - filteredTypes = self.analysis.importedTypes.filter { $0.key == singleType } + filteredTypes = self.analysis.extractedTypes.filter { $0.key == singleType } } else { - filteredTypes = self.analysis.importedTypes + filteredTypes = self.analysis.extractedTypes } - for group: (key: String, value: [Dictionary.Element]) in Dictionary( + for group: (key: String, value: [Dictionary.Element]) in Dictionary( grouping: filteredTypes, by: { $0.value.sourceFilePath }, ) { logger.warning("Writing types in file group: \(group.key): \(group.value.map(\.key))") - let importedTypesForThisFile = group.value + let extractedTypesForThisFile = group.value .map(\.value) .sorted(by: { $0.qualifiedName < $1.qualifiedName }) let inputFileName = "\(group.key)".split(separator: "/").last ?? "__Unknown.swift" let filename = "\(inputFileName)".replacing(/\.swift(interface)?/, with: "+SwiftJava.swift") - for ty in importedTypesForThisFile { + for ty in extractedTypesForThisFile { logger.info("Printing Swift thunks for type: \(ty.effectiveJavaName.bold)") printer.printSeparator("Thunks for \(ty.effectiveJavaName)") @@ -292,21 +292,21 @@ extension JNISwift2JavaGenerator { printHeader(&printer) self.currentJavaIdentifiers = JavaIdentifierFactory( - self.analysis.importedGlobalFuncs + self.analysis.importedGlobalVariables + self.analysis.extractedGlobalFuncs + self.analysis.extractedGlobalVariables ) - for decl in analysis.importedGlobalFuncs { + for decl in analysis.extractedGlobalFuncs { printSwiftFunctionThunk(&printer, decl) printer.println() } - for decl in analysis.importedGlobalVariables { + for decl in analysis.extractedGlobalVariables { printSwiftFunctionThunk(&printer, decl) printer.println() } } - private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { + private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ExtractedNominalType) throws { printHeader(&printer) printer.println() @@ -322,7 +322,7 @@ extension JNISwift2JavaGenerator { } } - private func printConcreteTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + private func printConcreteTypeThunks(_ printer: inout CodePrinter, _ type: ExtractedNominalType) { // Specialized types are treated as concrete even if the underlying Swift type is generic let isEffectivelyGeneric = type.swiftNominal.isGeneric && !type.isSpecialization @@ -371,7 +371,7 @@ extension JNISwift2JavaGenerator { printer.println() } - private func printProtocolThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { + private func printProtocolThunks(_ printer: inout CodePrinter, _ type: ExtractedNominalType) throws { guard let protocolWrapper = self.interfaceProtocolWrappers[type] else { return } @@ -379,7 +379,7 @@ extension JNISwift2JavaGenerator { try printSwiftInterfaceWrapper(&printer, protocolWrapper) } - private func printEnumRawDiscriminator(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + private func printEnumRawDiscriminator(_ printer: inout CodePrinter, _ type: ExtractedNominalType) { if type.cases.isEmpty { return } @@ -395,7 +395,7 @@ extension JNISwift2JavaGenerator { } } - private func printEnumCase(_ printer: inout CodePrinter, _ enumType: ImportedNominalType, _ enumCase: ImportedEnumCase) { + private func printEnumCase(_ printer: inout CodePrinter, _ enumType: ExtractedNominalType, _ enumCase: ExtractedEnumCase) { guard let translatedCase = self.translatedEnumCase(for: enumCase) else { return } @@ -418,7 +418,7 @@ extension JNISwift2JavaGenerator { private func printEnumGetAsCaseThunk( _ printer: inout CodePrinter, - _ enumType: ImportedNominalType, + _ enumType: ExtractedNominalType, _ enumCase: TranslatedEnumCase, ) { if let getAsCaseFunction = enumCase.getAsCaseFunction { @@ -443,7 +443,7 @@ extension JNISwift2JavaGenerator { private func printSwiftFunctionThunk( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, ) { guard let translatedDecl = translatedDecl(for: decl) else { // Failed to translate. Skip. @@ -466,7 +466,7 @@ extension JNISwift2JavaGenerator { private func printSwiftFunctionHelperClasses( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, ) { let protocolParameters = decl.functionSignature.parameters.compactMap { parameter in if let concreteType = parameter.type.typeIn( @@ -499,7 +499,7 @@ extension JNISwift2JavaGenerator { // we generate a Swift class that conforms to all of those. for (parameter, protocolTypes) in protocolParameters { let protocolWrappers: [JavaInterfaceSwiftWrapper] = protocolTypes.compactMap { protocolType in - guard let importedType = self.asImportedNominalTypeDecl(protocolType), + guard let importedType = self.asExtractedNominalTypeDecl(protocolType), let wrapper = self.interfaceProtocolWrappers[importedType] else { return nil @@ -545,8 +545,8 @@ extension JNISwift2JavaGenerator { } } - private func asImportedNominalTypeDecl(_ type: SwiftType) -> ImportedNominalType? { - self.analysis.importedTypes.first( + private func asExtractedNominalTypeDecl(_ type: SwiftType) -> ExtractedNominalType? { + self.analysis.extractedTypes.first( where: ({ name, nominalType in nominalType.swiftType == type }) @@ -557,7 +557,7 @@ extension JNISwift2JavaGenerator { private func printFunctionDowncall( _ printer: inout CodePrinter, - _ decl: ImportedFunc, + _ decl: ExtractedFunc, ) { guard let translatedDecl = self.translatedDecl(for: decl) else { fatalError("Cannot print function downcall for a function that can't be translated: \(decl)") @@ -783,7 +783,7 @@ extension JNISwift2JavaGenerator { } } - private func printJNICache(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + private func printJNICache(_ printer: inout CodePrinter, _ type: ExtractedNominalType) { let cacheName = JNICaching.cacheName(for: type) let jniClassName = "\(javaPackagePath)/\(type.effectiveJavaTypeName.jniEscapedName)" let isEffectivelyGeneric = type.swiftNominal.isGeneric && type.effectiveJavaTypeName == type.swiftNominal.qualifiedTypeName @@ -820,7 +820,7 @@ extension JNISwift2JavaGenerator { } } - private func printNominalJavaBridge(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + private func printNominalJavaBridge(_ printer: inout CodePrinter, _ type: ExtractedNominalType) { let bridgeName = JNICaching.bridgeName(for: type) let cacheName = JNICaching.cacheName(for: type) let isEffectivelyGeneric = type.swiftNominal.isGeneric && !type.isSpecialization @@ -881,7 +881,7 @@ extension JNISwift2JavaGenerator { self.lookupContext.symbolTable.printImportedModules(&printer) } - private func printTypeMetadataAddressThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + private func printTypeMetadataAddressThunk(_ printer: inout CodePrinter, _ type: ExtractedNominalType) { // Specialized types are treated as concrete let isEffectivelyGeneric = type.swiftNominal.isGeneric && !type.isSpecialization if isEffectivelyGeneric { @@ -905,7 +905,7 @@ extension JNISwift2JavaGenerator { } /// Prints thunks for specific known types like Foundation.Date, Foundation.Data - private func printSpecificTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + private func printSpecificTypeThunks(_ printer: inout CodePrinter, _ type: ExtractedNominalType) { guard let knownType = type.swiftNominal.knownTypeKind else { return } switch knownType { @@ -919,7 +919,7 @@ extension JNISwift2JavaGenerator { } /// Prints Swift thunks for Foundation.Data helper methods - private func printFoundationDataThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + private func printFoundationDataThunks(_ printer: inout CodePrinter, _ type: ExtractedNominalType) { let selfPointerParam = JavaParameter(name: "selfPointer", type: .long) let parentName = type.qualifiedName @@ -965,7 +965,7 @@ extension JNISwift2JavaGenerator { } } - private func printFunctionOpenerCall(_ printer: inout CodePrinter, _ decl: ImportedFunc) { + private func printFunctionOpenerCall(_ printer: inout CodePrinter, _ decl: ExtractedFunc) { guard let translatedDecl = self.translatedDecl(for: decl) else { fatalError("Cannot print function opener for a function that can't be translated: \(decl)") } @@ -1005,10 +1005,10 @@ extension JNISwift2JavaGenerator { "_\(swiftModuleName)_\(type.flatName)_opener" } - private func printOpenerProtocol(_ printer: inout CodePrinter, _ type: ImportedNominalType) { + private func printOpenerProtocol(_ printer: inout CodePrinter, _ type: ExtractedNominalType) { let protocolName = openerProtocolName(for: type.swiftNominal) - func printFunctionDecl(_ printer: inout CodePrinter, decl: ImportedFunc, skipMethodBody: Bool) { + func printFunctionDecl(_ printer: inout CodePrinter, decl: ExtractedFunc, skipMethodBody: Bool) { guard let translatedDecl = self.translatedDecl(for: decl) else { return } let nativeSignature = translatedDecl.nativeFunctionSignature @@ -1128,7 +1128,7 @@ extension SwiftNominalTypeDeclaration { } } -extension ImportedFunc { +extension ExtractedFunc { fileprivate var openerMethodName: String { let prefix = switch apiKind { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 134c829ee..564a54c7f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -50,9 +50,9 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { var generatedCDeclSymbolNames: [String] = [] /// Cached Java translation result. 'nil' indicates failed translation. - var translatedDecls: [ImportedFunc: TranslatedFunctionDecl] = [:] - var translatedEnumCases: [ImportedEnumCase: TranslatedEnumCase] = [:] - var interfaceProtocolWrappers: [ImportedNominalType: JavaInterfaceSwiftWrapper] = [:] + var translatedDecls: [ExtractedFunc: TranslatedFunctionDecl] = [:] + var translatedEnumCases: [ExtractedEnumCase: TranslatedEnumCase] = [:] + var interfaceProtocolWrappers: [ExtractedNominalType: JavaInterfaceSwiftWrapper] = [:] /// Duplicate identifier tracking for the current batch of methods being generated. var currentJavaIdentifiers: JavaIdentifierFactory = JavaIdentifierFactory() @@ -121,7 +121,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { // We translate all the protocol wrappers // as we need them to know what protocols we can allow the user to implement themselves // in Java. - self.interfaceProtocolWrappers = self.generateInterfaceWrappers(Array(self.analysis.importedTypes.values)) + self.interfaceProtocolWrappers = self.generateInterfaceWrappers(Array(self.analysis.extractedTypes.values)) } } @@ -143,12 +143,12 @@ extension JNISwift2JavaGenerator { "\(parameterName)$indirect" } - func inheritedProtocols(of type: ImportedNominalType) -> [ImportedNominalType] { + func inheritedProtocols(of type: ExtractedNominalType) -> [ExtractedNominalType] { type.inheritedTypes .compactMap(\.asNominalTypeDeclaration) .filter { $0.kind == .protocol } .compactMap { - self.analysis.importedTypes[$0.qualifiedName] + self.analysis.extractedTypes[$0.qualifiedName] } } } diff --git a/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift b/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift index dbaf85cdd..823246305 100644 --- a/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift +++ b/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift @@ -23,15 +23,15 @@ package struct JavaIdentifierFactory { package init() {} - package init(_ methods: [ImportedFunc]) { + package init(_ methods: [ExtractedFunc]) { self.init() record(methods) } /// Analyze the given methods and record any base names that have conflicts. - private mutating func record(_ methods: [ImportedFunc]) { + private mutating func record(_ methods: [ExtractedFunc]) { // Group methods by their Java base name. - var methodsByBaseName: [String: [ImportedFunc]] = [:] + var methodsByBaseName: [String: [ExtractedFunc]] = [:] for method in methods { let baseName: String = switch method.apiKind { @@ -63,7 +63,7 @@ package struct JavaIdentifierFactory { } /// Compute the disambiguated Java method name for a declaration. - package func makeJavaMethodName(_ decl: ImportedFunc) -> String { + package func makeJavaMethodName(_ decl: ExtractedFunc) -> String { let baseName: String = switch decl.apiKind { case .getter, .subscriptGetter: decl.javaGetterName! @@ -77,7 +77,7 @@ package struct JavaIdentifierFactory { return methodName } - private func paramsSuffix(_ decl: ImportedFunc, baseName: String) -> String { + private func paramsSuffix(_ decl: ExtractedFunc, baseName: String) -> String { switch decl.apiKind { case .getter, .subscriptGetter, .setter, .subscriptSetter: return "" diff --git a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift index 4e0de0e68..881d33899 100644 --- a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift +++ b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift @@ -23,7 +23,7 @@ import SwiftSyntax package struct SwiftKitPrinting { /// Forms syntax for a Java call to a swiftkit exposed function. - static func renderCallGetSwiftType(module: String, nominal: ImportedNominalType) -> String { + static func renderCallGetSwiftType(module: String, nominal: ExtractedNominalType) -> String { """ SwiftRuntime.swiftjava.getType("\(module)", "\(nominal.swiftNominal.qualifiedName)") """ @@ -53,78 +53,8 @@ extension SwiftKitPrinting { } extension SwiftKitPrinting.Names { - static func getType(module: String, nominal: ImportedNominalType) -> String { + static func getType(module: String, nominal: ExtractedNominalType) -> String { "swiftjava_getType_\(module)_\(nominal.swiftNominal.qualifiedTypeName.fullFlatName)" } } - -// ==== ----------------------------------------------------------------------- -// MARK: SwiftSymbolTable printing helpers - -extension SwiftSymbolTable { - package func printImportedModules(_ printer: inout CodePrinter) { - let mainSymbolSourceModules = Set( - self.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName) - ) - - for module in self.importedModules.keys.sorted() { - guard module != "Swift" else { - continue - } - - // Synthetic stub modules (e.g. ) exist purely for - // symbol-table resolution; they are not real Swift modules and must - // not be emitted as `import` statements. - guard !self.syntheticImportedModuleNames.contains(module) else { - continue - } - - guard let alternativeModules = self.importedModules[module]?.alternativeModules else { - printer.print("import \(module)") - continue - } - - // Only the main source of symbols emits the conditional import block. - // Secondary modules (e.g. FoundationEssentials when Foundation is the main source) - // are skipped when their main source is already present, because the main source's - // block already covers the import. If no main source is present, fall back to a - // plain import so the module is still imported. - guard alternativeModules.isMainSourceOfSymbols else { - if mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) { - printer.print("import \(module)") - } - continue - } - - var importGroups: [String: [String]] = [:] - for name in alternativeModules.moduleNames { - guard let otherModule = self.importedModules[name] else { continue } - - let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName - importGroups[groupKey, default: []].append(otherModule.moduleName) - } - - for (index, group) in importGroups.keys.sorted().enumerated() { - if index > 0 && importGroups.keys.count > 1 { - printer.print("#elseif canImport(\(group))") - } else { - printer.print("#if canImport(\(group))") - } - - for groupModule in importGroups[group] ?? [] { - printer.print("import \(groupModule)") - } - } - - if importGroups.keys.isEmpty { - printer.print("import \(module)") - } else { - printer.print("#else") - printer.print("import \(module)") - printer.print("#endif") - } - } - printer.println() - } -} diff --git a/Sources/JExtractSwiftLib/ThunkNameRegistry.swift b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift index 9b99aa5e7..5beb710e3 100644 --- a/Sources/JExtractSwiftLib/ThunkNameRegistry.swift +++ b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift @@ -19,13 +19,13 @@ import SwiftExtract package struct ThunkNameRegistry { /// Maps base names such as "swiftjava_Module_Type_method_a_b_c" to the number of times we've seen them. /// This is used to de-duplicate symbols as we emit them. - private var registry: [ImportedFunc: String] = [:] + private var registry: [ExtractedFunc: String] = [:] private var duplicateNames: [String: Int] = [:] package init() {} package mutating func functionThunkName( - decl: ImportedFunc, + decl: ExtractedFunc, file: String = #fileID, line: UInt = #line ) -> String { diff --git a/Sources/JExtractSwiftLib/TranslatedDocumentation.swift b/Sources/JExtractSwiftLib/TranslatedDocumentation.swift index 664061973..a6b01e08b 100644 --- a/Sources/JExtractSwiftLib/TranslatedDocumentation.swift +++ b/Sources/JExtractSwiftLib/TranslatedDocumentation.swift @@ -19,7 +19,7 @@ import SwiftSyntax enum TranslatedDocumentation { static func printDocumentation( - importedFunc: ImportedFunc, + importedFunc: ExtractedFunc, translatedDecl: FFMSwift2JavaGenerator.TranslatedFunctionDecl, config: Configuration, in printer: inout CodePrinter @@ -39,7 +39,7 @@ enum TranslatedDocumentation { } static func printDocumentation( - importedFunc: ImportedFunc, + importedFunc: ExtractedFunc, translatedDecl: JNISwift2JavaGenerator.TranslatedFunctionDecl, config: Configuration, in printer: inout CodePrinter diff --git a/Sources/SwiftExtract/AnalysisResult.swift b/Sources/SwiftExtract/AnalysisResult.swift index dedfbfa4a..6c6f0858e 100644 --- a/Sources/SwiftExtract/AnalysisResult.swift +++ b/Sources/SwiftExtract/AnalysisResult.swift @@ -13,17 +13,17 @@ //===----------------------------------------------------------------------===// public struct AnalysisResult { - public let importedTypes: [String: ImportedNominalType] - public let importedGlobalVariables: [ImportedFunc] - public let importedGlobalFuncs: [ImportedFunc] + public let extractedTypes: [String: ExtractedNominalType] + public let extractedGlobalVariables: [ExtractedFunc] + public let extractedGlobalFuncs: [ExtractedFunc] public init( - importedTypes: [String: ImportedNominalType], - importedGlobalVariables: [ImportedFunc], - importedGlobalFuncs: [ImportedFunc] + extractedTypes: [String: ExtractedNominalType], + extractedGlobalVariables: [ExtractedFunc], + extractedGlobalFuncs: [ExtractedFunc] ) { - self.importedTypes = importedTypes - self.importedGlobalVariables = importedGlobalVariables - self.importedGlobalFuncs = importedGlobalFuncs + self.extractedTypes = extractedTypes + self.extractedGlobalVariables = extractedGlobalVariables + self.extractedGlobalFuncs = extractedGlobalFuncs } } diff --git a/Sources/SwiftExtract/ImportedDecls.swift b/Sources/SwiftExtract/ExtractedDecls.swift similarity index 87% rename from Sources/SwiftExtract/ImportedDecls.swift rename to Sources/SwiftExtract/ExtractedDecls.swift index 81f622cdb..c6a322188 100644 --- a/Sources/SwiftExtract/ImportedDecls.swift +++ b/Sources/SwiftExtract/ExtractedDecls.swift @@ -28,17 +28,17 @@ public enum SwiftAPIKind: Equatable { } /// Describes a Swift nominal type (e.g., a class, struct, enum) that has been -/// imported and is being translated into Java. +/// extracted by the analyzer for a downstream language target. /// /// When `base` is non-nil, this is a specialization of a generic type /// (e.g. `FishBox` specializing `Box` with `Element` = `Fish`). /// The specialization delegates its member collections to the base type /// so that extensions discovered later are visible through all specializations. -public final class ImportedNominalType: ExtractedSwiftDecl { +public final class ExtractedNominalType: ExtractedSwiftDecl { public let swiftNominal: SwiftNominalTypeDeclaration /// If this type is a specialization (FishTank), it points at the Tank base type of the specialization - public let specializationBaseType: ImportedNominalType? + public let specializationBaseType: ExtractedNominalType? // The short path from module root to the file in which this nominal was originally declared. // E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. @@ -47,10 +47,10 @@ public final class ImportedNominalType: ExtractedSwiftDecl { } // Backing storage for member collections - public var initializers: [ImportedFunc] = [] - public var methods: [ImportedFunc] = [] - public var variables: [ImportedFunc] = [] - public var cases: [ImportedEnumCase] = [] + public var initializers: [ExtractedFunc] = [] + public var methods: [ExtractedFunc] = [] + public var variables: [ExtractedFunc] = [] + public var cases: [ExtractedEnumCase] = [] public var inheritedTypes: [SwiftType] public var parent: SwiftNominalTypeDeclaration? @@ -89,7 +89,7 @@ public final class ImportedNominalType: ExtractedSwiftDecl { } /// Init for creating a specialization - private init(base: ImportedNominalType, specializedTypeName: String, genericArguments: [String: String]) { + private init(base: ExtractedNominalType, specializedTypeName: String, genericArguments: [String: String]) { self.swiftNominal = base.swiftNominal self.specializationBaseType = base @@ -166,7 +166,7 @@ public final class ImportedNominalType: ExtractedSwiftDecl { public func specialize( as specializedName: String, with substitutions: [String: String], - ) throws -> ImportedNominalType { + ) throws -> ExtractedNominalType { guard !genericParameterNames.isEmpty else { throw SpecializationError( message: "Unable to specialize non-generic type '\(baseTypeName)' as '\(specializedName)'" @@ -178,7 +178,7 @@ public final class ImportedNominalType: ExtractedSwiftDecl { message: "Missing type arguments for: \(missingParams) when specializing \(baseTypeName) as \(specializedName)" ) } - return ImportedNominalType( + return ExtractedNominalType( base: self, specializedTypeName: specializedName, genericArguments: substitutions, @@ -186,14 +186,14 @@ public final class ImportedNominalType: ExtractedSwiftDecl { } /// Checks if this type, or any of types it inherits from, conforms to the passed in protocol. - public func conformsTo(_ protocolName: String, in importedTypes: [String: ImportedNominalType]) -> Bool { + public func conformsTo(_ protocolName: String, in extractedTypes: [String: ExtractedNominalType]) -> Bool { var visited: Set = [] - var queue: [ImportedNominalType] = [self] + var queue: [ExtractedNominalType] = [self] while let current = queue.popLast() { for inherited in current.inheritedTypes { guard let name = inherited.asNominalTypeDeclaration?.name else { continue } if name == protocolName { return true } - if let next = importedTypes[name], visited.insert(ObjectIdentifier(next)).inserted { + if let next = extractedTypes[name], visited.insert(ObjectIdentifier(next)).inserted { queue.append(next) } } @@ -206,7 +206,7 @@ public struct SpecializationError: Error { public let message: String } -public final class ImportedEnumCase: ExtractedSwiftDecl, CustomStringConvertible { +public final class ExtractedEnumCase: ExtractedSwiftDecl, CustomStringConvertible { /// The case name public let name: String @@ -218,14 +218,14 @@ public final class ImportedEnumCase: ExtractedSwiftDecl, CustomStringConvertible public let enumType: SwiftNominalType /// A function that represents the Swift static "initializer" for cases - public let caseFunction: ImportedFunc + public let caseFunction: ExtractedFunc public init( name: String, parameters: [SwiftEnumCaseParameter], swiftDecl: any DeclSyntaxProtocol, enumType: SwiftNominalType, - caseFunction: ImportedFunc, + caseFunction: ExtractedFunc, ) { self.name = name self.parameters = parameters @@ -236,7 +236,7 @@ public final class ImportedEnumCase: ExtractedSwiftDecl, CustomStringConvertible public var description: String { """ - ImportedEnumCase { + ExtractedEnumCase { name: \(name), parameters: \(parameters), swiftDecl: \(swiftDecl), @@ -246,8 +246,8 @@ public final class ImportedEnumCase: ExtractedSwiftDecl, CustomStringConvertible """ } - public func clone(for parent: SwiftType) -> ImportedEnumCase { - ImportedEnumCase( + public func clone(for parent: SwiftType) -> ExtractedEnumCase { + ExtractedEnumCase( name: name, parameters: parameters, swiftDecl: swiftDecl, @@ -257,16 +257,16 @@ public final class ImportedEnumCase: ExtractedSwiftDecl, CustomStringConvertible } } -extension ImportedEnumCase: Hashable { +extension ExtractedEnumCase: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } - public static func == (lhs: ImportedEnumCase, rhs: ImportedEnumCase) -> Bool { + public static func == (lhs: ExtractedEnumCase, rhs: ExtractedEnumCase) -> Bool { lhs === rhs } } -public final class ImportedFunc: ExtractedSwiftDecl, CustomStringConvertible { +public final class ExtractedFunc: ExtractedSwiftDecl, CustomStringConvertible { /// Swift module name (e.g. the target name where a type or function was declared) public let module: String @@ -353,7 +353,7 @@ public final class ImportedFunc: ExtractedSwiftDecl, CustomStringConvertible { public var description: String { """ - ImportedFunc { + ExtractedFunc { apiKind: \(apiKind) module: \(module) name: \(name) @@ -362,11 +362,11 @@ public final class ImportedFunc: ExtractedSwiftDecl, CustomStringConvertible { """ } - public func clone(for parent: SwiftType) -> ImportedFunc { + public func clone(for parent: SwiftType) -> ExtractedFunc { var functionSignature = functionSignature assert(functionSignature.selfParameter?.selfType != nil) functionSignature.selfParameter?.selfType = parent - return ImportedFunc( + return ExtractedFunc( module: module, swiftDecl: swiftDecl, name: name, @@ -376,20 +376,20 @@ public final class ImportedFunc: ExtractedSwiftDecl, CustomStringConvertible { } } -extension ImportedFunc: Hashable { +extension ExtractedFunc: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } - public static func == (lhs: ImportedFunc, rhs: ImportedFunc) -> Bool { + public static func == (lhs: ExtractedFunc, rhs: ExtractedFunc) -> Bool { lhs === rhs } } -extension ImportedNominalType: Hashable { +extension ExtractedNominalType: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } - public static func == (lhs: ImportedNominalType, rhs: ImportedNominalType) -> Bool { + public static func == (lhs: ExtractedNominalType, rhs: ExtractedNominalType) -> Bool { lhs === rhs } } diff --git a/Sources/SwiftExtract/SwiftAnalysisVisitor.swift b/Sources/SwiftExtract/SwiftAnalysisVisitor.swift index 84458800f..81b311757 100644 --- a/Sources/SwiftExtract/SwiftAnalysisVisitor.swift +++ b/Sources/SwiftExtract/SwiftAnalysisVisitor.swift @@ -48,7 +48,7 @@ final class SwiftAnalysisVisitor { } } - func visit(decl node: DeclSyntax, in parent: ImportedNominalType?, sourceFilePath: String) { + func visit(decl node: DeclSyntax, in parent: ExtractedNominalType?, sourceFilePath: String) { switch node.as(DeclSyntaxEnum.self) { case .actorDecl(let node): self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) @@ -87,24 +87,24 @@ final class SwiftAnalysisVisitor { func visit( nominalDecl node: some DeclSyntaxProtocol & DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax, - in parent: ImportedNominalType?, + in parent: ExtractedNominalType?, sourceFilePath: String, ) { - guard let importedNominalType = translator.importedNominalType(node, parent: parent) else { + guard let extractedNominalType = translator.extractedNominalType(node, parent: parent) else { return } // Check if there's a specialization entry for this type - applySpecialization(to: importedNominalType) + applySpecialization(to: extractedNominalType) for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: importedNominalType, sourceFilePath: sourceFilePath) + self.visit(decl: memberItem.decl, in: extractedNominalType, sourceFilePath: sourceFilePath) } } func visit( enumDecl node: EnumDeclSyntax, - in parent: ImportedNominalType?, + in parent: ExtractedNominalType?, sourceFilePath: String, ) { self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) @@ -114,14 +114,14 @@ final class SwiftAnalysisVisitor { func visit( extensionDecl node: ExtensionDeclSyntax, - in parent: ImportedNominalType?, + in parent: ExtractedNominalType?, sourceFilePath: String, ) { guard parent == nil else { // 'extension' in a nominal type is invalid. Ignore return } - guard let importedNominalType = translator.importedNominalType(node.extendedType) else { + guard let extractedNominalType = translator.extractedNominalType(node.extendedType) else { return } @@ -134,12 +134,12 @@ final class SwiftAnalysisVisitor { guard !constraints.isEmpty else { // The extension is unconstrained: add to the base type (visible through all specializations) - importedNominalType.inheritedTypes += + extractedNominalType.inheritedTypes += node.inheritanceClause?.inheritedTypes.compactMap { try? SwiftType($0.type, lookupContext: translator.lookupContext) } ?? [] for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: importedNominalType, sourceFilePath: sourceFilePath) + self.visit(decl: memberItem.decl, in: extractedNominalType, sourceFilePath: sourceFilePath) } return } @@ -156,7 +156,7 @@ final class SwiftAnalysisVisitor { } let matchingSpecializations = findMatchingSpecializations( - extendedType: importedNominalType, + extendedType: extractedNominalType, whereConstraints: constraints, ) if matchingSpecializations.isEmpty { @@ -177,7 +177,7 @@ final class SwiftAnalysisVisitor { func visit( functionDecl node: FunctionDeclSyntax, - in typeContext: ImportedNominalType?, + in typeContext: ExtractedNominalType?, sourceFilePath: String, ) { guard node.shouldExtract(config: config, log: log, in: typeContext, decider: translator.extractDecider) else { @@ -210,7 +210,7 @@ final class SwiftAnalysisVisitor { return } - let imported = ImportedFunc( + let imported = ExtractedFunc( module: translator.swiftModuleName, swiftDecl: node, name: node.name.text.unescapedSwiftName, @@ -222,13 +222,13 @@ final class SwiftAnalysisVisitor { if let typeContext { typeContext.methods.append(imported) } else { - translator.importedGlobalFuncs.append(imported) + translator.extractedGlobalFuncs.append(imported) } } func visit( enumCaseDecl node: EnumCaseDeclSyntax, - in typeContext: ImportedNominalType?, + in typeContext: ExtractedNominalType?, ) { guard let typeContext else { self.log.info("Enum case must be within a current type; \(node)") @@ -250,7 +250,7 @@ final class SwiftAnalysisVisitor { ) let caseName = caseElement.name.text.unescapedSwiftName - let caseFunction = ImportedFunc( + let caseFunction = ExtractedFunc( module: translator.swiftModuleName, swiftDecl: node, name: caseName, @@ -258,7 +258,7 @@ final class SwiftAnalysisVisitor { functionSignature: signature, ) - let importedCase = ImportedEnumCase( + let importedCase = ExtractedEnumCase( name: caseName, parameters: parameters ?? [], swiftDecl: node, @@ -279,7 +279,7 @@ final class SwiftAnalysisVisitor { func visit( variableDecl node: VariableDeclSyntax, - in typeContext: ImportedNominalType?, + in typeContext: ExtractedNominalType?, sourceFilePath: String, ) { guard node.shouldExtract(config: config, log: log, in: typeContext, decider: translator.extractDecider) else { @@ -323,7 +323,7 @@ final class SwiftAnalysisVisitor { func visit( initializerDecl node: InitializerDeclSyntax, - in typeContext: ImportedNominalType?, + in typeContext: ExtractedNominalType?, ) { guard let typeContext else { self.log.info("Initializer must be within a current type; \(node)") @@ -355,7 +355,7 @@ final class SwiftAnalysisVisitor { ) return } - let imported = ImportedFunc( + let imported = ExtractedFunc( module: translator.swiftModuleName, swiftDecl: node, name: "init", @@ -368,7 +368,7 @@ final class SwiftAnalysisVisitor { private func visit( subscriptDecl node: SubscriptDeclSyntax, - in typeContext: ImportedNominalType?, + in typeContext: ExtractedNominalType?, ) { guard node.shouldExtract(config: config, log: log, in: typeContext, decider: translator.extractDecider) else { return @@ -409,7 +409,7 @@ final class SwiftAnalysisVisitor { private func visit( ifConfigDecl node: IfConfigDeclSyntax, - in parent: ImportedNominalType?, + in parent: ExtractedNominalType?, sourceFilePath: String ) { let (clause, _) = node.activeClause(in: translator.buildConfig) @@ -433,7 +433,7 @@ final class SwiftAnalysisVisitor { private func importAccessor( from node: DeclSyntax, - in typeContext: ImportedNominalType?, + in typeContext: ExtractedNominalType?, kind: SwiftAPIKind, name: String, ) throws { @@ -459,7 +459,7 @@ final class SwiftAnalysisVisitor { return } - let imported = ImportedFunc( + let imported = ExtractedFunc( module: translator.swiftModuleName, swiftDecl: node, name: name, @@ -473,15 +473,15 @@ final class SwiftAnalysisVisitor { if let typeContext { typeContext.variables.append(imported) } else { - translator.importedGlobalVariables.append(imported) + translator.extractedGlobalVariables.append(imported) } } private func synthesizeRawRepresentableConformance( enumDecl node: EnumDeclSyntax, - in parent: ImportedNominalType?, + in parent: ExtractedNominalType?, ) { - guard let imported = translator.importedNominalType(node, parent: parent) else { + guard let imported = translator.extractedNominalType(node, parent: parent) else { return } @@ -515,7 +515,7 @@ final class SwiftAnalysisVisitor { func visit( typeAliasDecl node: TypeAliasDeclSyntax, - in typeContext: ImportedNominalType?, + in typeContext: ExtractedNominalType?, sourceFilePath: String, ) { let outputName = node.name.text @@ -534,7 +534,7 @@ final class SwiftAnalysisVisitor { guard !genericArgs.isEmpty else { return } // Resolve the base type through the symbol table - guard let baseType = translator.importedNominalType(rhsType) else { + guard let baseType = translator.extractedNominalType(rhsType) else { log.debug("Could not resolve base type for specialization: \(rhsType.trimmedDescription)") return } @@ -550,7 +550,7 @@ final class SwiftAnalysisVisitor { /// Register a specialization from a typealias that specializes a generic type private func registerSpecialization( outputName: String, - baseType: ImportedNominalType, + baseType: ExtractedNominalType, genericArgs: [String], rhsDescription: String, ) { @@ -565,7 +565,7 @@ final class SwiftAnalysisVisitor { } } - let specialized: ImportedNominalType + let specialized: ExtractedNominalType do { specialized = try baseType.specialize(as: outputName, with: substitutions) } catch { @@ -580,13 +580,13 @@ final class SwiftAnalysisVisitor { // MARK: Specialization support /// Apply specializations to a type if matching entries exist - func applySpecialization(to importedType: ImportedNominalType) { + func applySpecialization(to importedType: ExtractedNominalType) { guard let specializations = translator.specializations[importedType] else { return } for specialized in specializations { - translator.importedTypes[specialized.effectiveOutputName] = specialized + translator.extractedTypes[specialized.effectiveOutputName] = specialized log.info("Applied specialization: \(specialized.effectiveOutputName) -> \(specialized.effectiveSwiftTypeName)") } } @@ -596,17 +596,17 @@ final class SwiftAnalysisVisitor { func applyPendingSpecializations() { for (_, specializations) in translator.specializations { for specialized in specializations { - if translator.importedTypes[specialized.effectiveOutputName] != nil { + if translator.extractedTypes[specialized.effectiveOutputName] != nil { continue } - translator.importedTypes[specialized.effectiveOutputName] = specialized + translator.extractedTypes[specialized.effectiveOutputName] = specialized log.info("Applied pending specialization: \(specialized.effectiveOutputName) -> \(specialized.effectiveSwiftTypeName)") } } // Process constrained extensions that were deferred for deferred in deferredConstrainedExtensions { - guard let baseType = translator.importedNominalType(deferred.node.extendedType) else { + guard let baseType = translator.extractedNominalType(deferred.node.extendedType) else { continue } let matchingSpecializations = findMatchingSpecializations( @@ -668,9 +668,9 @@ final class SwiftAnalysisVisitor { /// Find specializations whose type args match the given where-clause constraints private func findMatchingSpecializations( - extendedType: ImportedNominalType, + extendedType: ExtractedNominalType, whereConstraints: [ParsedWhereConstraint], - ) -> [ImportedNominalType] { + ) -> [ExtractedNominalType] { guard let specializations = translator.specializations[extendedType] else { return [] } @@ -683,7 +683,7 @@ final class SwiftAnalysisVisitor { /// Where-clauses are conjunctive: every constraint must hold. private func constraintsMatchSpecialization( _ constraints: [ParsedWhereConstraint], - specialized: ImportedNominalType, + specialized: ExtractedNominalType, ) -> Bool { for constraint in constraints { switch constraint { @@ -696,10 +696,10 @@ final class SwiftAnalysisVisitor { guard let concreteName = specialized.genericArguments[typeParam] else { return false } - guard let concreteType = translator.importedTypes[concreteName] else { + guard let concreteType = translator.extractedTypes[concreteName] else { return false } - guard concreteType.conformsTo(proto, in: translator.importedTypes) else { + guard concreteType.conformsTo(proto, in: translator.extractedTypes) else { return false } } @@ -722,7 +722,7 @@ extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyn func shouldExtract( config: Configuration, log: Logger, - in parent: ImportedNominalType?, + in parent: ExtractedNominalType?, decider: (any ExtractDecider)? ) -> Bool { let accessLevelPasses: Bool = diff --git a/Sources/SwiftExtract/SwiftAnalyzer.swift b/Sources/SwiftExtract/SwiftAnalyzer.swift index 6a4818d34..909468d53 100644 --- a/Sources/SwiftExtract/SwiftAnalyzer.swift +++ b/Sources/SwiftExtract/SwiftAnalyzer.swift @@ -54,16 +54,16 @@ public final class SwiftAnalyzer { // ==== Output state - package var importedGlobalVariables: [ImportedFunc] = [] + package var extractedGlobalVariables: [ExtractedFunc] = [] - package var importedGlobalFuncs: [ImportedFunc] = [] + package var extractedGlobalFuncs: [ExtractedFunc] = [] /// A mapping from Swift type names (e.g., A.B) over to the imported nominal /// type representation. - package var importedTypes: [String: ImportedNominalType] = [:] + package var extractedTypes: [String: ExtractedNominalType] = [:] /// Specializations of generic types that will get their concrete Java declarations, "as if" they were independent types - package var specializations: [ImportedNominalType: Set] = [:] + package var specializations: [ExtractedNominalType: Set] = [:] package var lookupContext: SwiftTypeLookupContext! = nil @@ -109,9 +109,9 @@ extension SwiftAnalyzer { /// Snapshot of the analysis state as a value-typed `AnalysisResult`. public var result: AnalysisResult { AnalysisResult( - importedTypes: self.importedTypes, - importedGlobalVariables: self.importedGlobalVariables, - importedGlobalFuncs: self.importedGlobalFuncs, + extractedTypes: self.extractedTypes, + extractedGlobalVariables: self.extractedGlobalVariables, + extractedGlobalFuncs: self.extractedGlobalFuncs, ) } @@ -267,7 +267,7 @@ extension SwiftAnalyzer { } } - func check(_ fn: ImportedFunc) -> Bool { + func check(_ fn: ExtractedFunc) -> Bool { if check(fn.functionSignature.result.type) { return true } @@ -277,13 +277,13 @@ extension SwiftAnalyzer { return false } - if self.importedGlobalFuncs.contains(where: check) { + if self.extractedGlobalFuncs.contains(where: check) { return true } - if self.importedGlobalVariables.contains(where: check) { + if self.extractedGlobalVariables.contains(where: check) { return true } - for importedType in self.importedTypes.values { + for importedType in self.extractedTypes.values { if importedType.initializers.contains(where: check) { return true } @@ -302,10 +302,10 @@ extension SwiftAnalyzer { // MARK: Type translation extension SwiftAnalyzer { /// Try to resolve the given nominal declaration node into its imported representation. - func importedNominalType( + func extractedNominalType( _ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax, - parent: ImportedNominalType?, - ) -> ImportedNominalType? { + parent: ExtractedNominalType?, + ) -> ExtractedNominalType? { if !nominalNode.shouldExtract(config: config, log: log, in: parent, decider: extractDecider) { return nil } @@ -313,13 +313,13 @@ extension SwiftAnalyzer { guard let nominal = symbolTable.lookupType(nominalNode.name.text, parent: parent?.swiftNominal) else { return nil } - return self.importedNominalType(nominal) + return self.extractedNominalType(nominal) } /// Try to resolve the given nominal type node into its imported representation. - func importedNominalType( + func extractedNominalType( _ typeNode: TypeSyntax - ) -> ImportedNominalType? { + ) -> ExtractedNominalType? { guard let swiftType = try? SwiftType(typeNode, lookupContext: lookupContext) else { return nil } @@ -334,14 +334,14 @@ extension SwiftAnalyzer { return nil } - guard swiftNominalDecl.syntax.shouldExtract(config: config, log: log, in: nil as ImportedNominalType?, decider: extractDecider) else { + guard swiftNominalDecl.syntax.shouldExtract(config: config, log: log, in: nil as ExtractedNominalType?, decider: extractDecider) else { return nil } - return importedNominalType(swiftNominalDecl) + return extractedNominalType(swiftNominalDecl) } - func importedNominalType(_ nominal: SwiftNominalTypeDeclaration) -> ImportedNominalType? { + func extractedNominalType(_ nominal: SwiftNominalTypeDeclaration) -> ExtractedNominalType? { let fullName = nominal.qualifiedName guard shouldExtractSwiftType(qualifiedName: fullName, config: config) else { @@ -349,13 +349,13 @@ extension SwiftAnalyzer { return nil } - if let alreadyImported = importedTypes[fullName] { + if let alreadyImported = extractedTypes[fullName] { return alreadyImported } - let importedNominal = try? ImportedNominalType(swiftNominal: nominal, lookupContext: lookupContext) + let importedNominal = try? ExtractedNominalType(swiftNominal: nominal, lookupContext: lookupContext) - importedTypes[fullName] = importedNominal + extractedTypes[fullName] = importedNominal return importedNominal } } diff --git a/Sources/SwiftExtract/SwiftTypes/ImportedSwiftModule.swift b/Sources/SwiftExtract/SwiftTypes/ExtractedSwiftModule.swift similarity index 96% rename from Sources/SwiftExtract/SwiftTypes/ImportedSwiftModule.swift rename to Sources/SwiftExtract/SwiftTypes/ExtractedSwiftModule.swift index 5c22b2654..f8c1a6d6f 100644 --- a/Sources/SwiftExtract/SwiftTypes/ImportedSwiftModule.swift +++ b/Sources/SwiftExtract/SwiftTypes/ExtractedSwiftModule.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -public struct ImportedSwiftModule: Hashable { +public struct ExtractedSwiftModule: Hashable { public let name: String public let availableWithModuleName: String? public var alternativeModuleNames: Set diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift b/Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift index e24c2eea7..ed20722e6 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift @@ -15,15 +15,15 @@ import SwiftSyntax /// Scan importing modules. -package func importingModules(sourceFile: SourceFileSyntax) -> [ImportedSwiftModule] { - var importingModuleNames: [ImportedSwiftModule] = [] +package func importingModules(sourceFile: SourceFileSyntax) -> [ExtractedSwiftModule] { + var importingModuleNames: [ExtractedSwiftModule] = [] for item in sourceFile.statements { if let importDecl = item.item.as(ImportDeclSyntax.self) { guard let moduleName = importDecl.path.first?.name.text else { continue } importingModuleNames.append( - ImportedSwiftModule(name: moduleName, availableWithModuleName: nil, alternativeModuleNames: []) + ExtractedSwiftModule(name: moduleName, availableWithModuleName: nil, alternativeModuleNames: []) ) } else if let ifConfigDecl = item.item.as(IfConfigDeclSyntax.self) { importingModuleNames.append(contentsOf: modules(from: ifConfigDecl)) @@ -32,7 +32,7 @@ package func importingModules(sourceFile: SourceFileSyntax) -> [ImportedSwiftMod return importingModuleNames } -private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ImportedSwiftModule] { +private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ExtractedSwiftModule] { guard let firstClause = ifConfigDecl.clauses.first, let calledExpression = firstClause.condition?.as(FunctionCallExprSyntax.self)?.calledExpression.as( @@ -43,7 +43,7 @@ private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ImportedSwiftMod return [] } - var modules: [ImportedSwiftModule] = [] + var modules: [ExtractedSwiftModule] = [] modules.reserveCapacity(ifConfigDecl.clauses.count) for (index, clause) in ifConfigDecl.clauses.enumerated() { @@ -65,7 +65,7 @@ private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ImportedSwiftMod } let clauseModules = importedModuleNames.map { - ImportedSwiftModule( + ExtractedSwiftModule( name: $0, availableWithModuleName: importModuleName, alternativeModuleNames: [] @@ -76,7 +76,7 @@ private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ImportedSwiftMod if clauseModules.count == 1 && index == (ifConfigDecl.clauses.count - 1) && clause.poundKeyword.tokenKind == .poundElse { - var fallbackModule: ImportedSwiftModule = clauseModules[0] + var fallbackModule: ExtractedSwiftModule = clauseModules[0] var moduleNames: [String] = [] moduleNames.reserveCapacity(modules.count) diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift b/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift index e8fb51020..5e38f43d6 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift @@ -108,7 +108,7 @@ extension SwiftSymbolTable { // Prepare imported modules. // FIXME: Support arbitrary dependencies. - var modules: Set = [] + var modules: Set = [] for inputFile in inputFiles { let importedModules = importingModules(sourceFile: inputFile.syntax) modules.formUnion(importedModules) diff --git a/Sources/SwiftJavaToolLib/JavaTranslator.swift b/Sources/SwiftJavaToolLib/JavaTranslator.swift index 46682bae3..354d37cf5 100644 --- a/Sources/SwiftJavaToolLib/JavaTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaTranslator.swift @@ -60,7 +60,7 @@ package class JavaTranslator { /// The set of Swift modules that need to be imported to make the generated /// code compile. Use `getImportDecls()` to format this into a list of /// import declarations. - package var importedSwiftModules: Set = JavaTranslator.defaultImportedSwiftModules + package var importedSwiftModules: Set = JavaTranslator.defaultExtractedSwiftModules /// The canonical names of Java classes whose declared 'native' /// methods will be implemented in Swift. @@ -97,7 +97,7 @@ package class JavaTranslator { /// Clear out any per-file state when we want to start a new file. package func startNewFile() { - importedSwiftModules = Self.defaultImportedSwiftModules + importedSwiftModules = Self.defaultExtractedSwiftModules } /// Simplistic logging for all entities that couldn't be translated. @@ -111,7 +111,7 @@ extension JavaTranslator { private static let defaultFormat = BasicFormat(indentationWidth: .spaces(2)) /// Default set of modules that will always be imported. - private static let defaultImportedSwiftModules: Set = [ + private static let defaultExtractedSwiftModules: Set = [ "SwiftJava", "SwiftJavaJNICore", ] diff --git a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift index 4c4050cfc..f8b17c893 100644 --- a/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift +++ b/Tests/JExtractSwiftTests/FFMNestedTypesTests.swift @@ -47,6 +47,6 @@ final class FFMNestedTypesTests { javaOutputDirectory: "/fake" ) - #expect(st.importedTypes["MyNamespace.MyNestedStruct"] != nil, "Didn't import nested type!") + #expect(st.extractedTypes["MyNamespace.MyNestedStruct"] != nil, "Didn't import nested type!") } } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 8ff99fd82..32be4c646 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -47,7 +47,7 @@ final class FuncCallbackImportTests { try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) - let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }! + let funcDecl = st.extractedGlobalFuncs.first { $0.name == "callMe" }! let generator = FFMSwift2JavaGenerator( config: config, @@ -136,7 +136,7 @@ final class FuncCallbackImportTests { try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) - let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMeMore" }! + let funcDecl = st.extractedGlobalFuncs.first { $0.name == "callMeMore" }! let generator = FFMSwift2JavaGenerator( config: config, @@ -252,7 +252,7 @@ final class FuncCallbackImportTests { try st.analyze(path: "Fake.swift", text: Self.class_interfaceFile) - let funcDecl = st.importedGlobalFuncs.first { $0.name == "withBuffer" }! + let funcDecl = st.extractedGlobalFuncs.first { $0.name == "withBuffer" }! let generator = FFMSwift2JavaGenerator( config: config, diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 1733bab9e..6cdacab12 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -243,7 +243,7 @@ extension FunctionDescriptorTests { try st.analyze(path: "/fake/Sample.swiftinterface", text: interfaceFile) - let funcDecl = st.importedGlobalFuncs.first { + let funcDecl = st.extractedGlobalFuncs.first { $0.name == methodIdentifier }! @@ -285,8 +285,8 @@ extension FunctionDescriptorTests { javaOutputDirectory: "/fake" ) - let accessorDecl: ImportedFunc? = - st.importedTypes.values.compactMap { + let accessorDecl: ExtractedFunc? = + st.extractedTypes.values.compactMap { $0.variables.first { $0.name == identifier && $0.apiKind == accessorKind } diff --git a/Tests/JExtractSwiftTests/JExtractFileFilterTests.swift b/Tests/JExtractSwiftTests/JExtractFileFilterTests.swift index 643a09042..077db64ce 100644 --- a/Tests/JExtractSwiftTests/JExtractFileFilterTests.swift +++ b/Tests/JExtractSwiftTests/JExtractFileFilterTests.swift @@ -344,37 +344,37 @@ struct JExtractFileFilterTests { @Test("swiftFilterExclude with exact nested type name") func excludeExactNested() throws { let st = try makeTranslator(exclude: ["Tank.Internal"]) - #expect(st.importedTypes["Tank"] != nil) - #expect(st.importedTypes["Tank.Fish"] != nil) - #expect(st.importedTypes["Tank.Internal"] == nil) - #expect(st.importedTypes["FishTank"] != nil) + #expect(st.extractedTypes["Tank"] != nil) + #expect(st.extractedTypes["Tank.Fish"] != nil) + #expect(st.extractedTypes["Tank.Internal"] == nil) + #expect(st.extractedTypes["FishTank"] != nil) } @Test("swiftFilterExclude with `Type.*` excludes direct children only") func excludeDirectChildren() throws { let st = try makeTranslator(exclude: ["Tank.*"]) - #expect(st.importedTypes["Tank"] != nil, "Top-level Tank itself should not be excluded by `Tank.*`") - #expect(st.importedTypes["Tank.Fish"] == nil) - #expect(st.importedTypes["Tank.Internal"] == nil) - #expect(st.importedTypes["FishTank"] != nil) + #expect(st.extractedTypes["Tank"] != nil, "Top-level Tank itself should not be excluded by `Tank.*`") + #expect(st.extractedTypes["Tank.Fish"] == nil) + #expect(st.extractedTypes["Tank.Internal"] == nil) + #expect(st.extractedTypes["FishTank"] != nil) } @Test("swiftFilterExclude with suffix wildcard inside nested name") func excludeSuffixWildcard() throws { let st = try makeTranslator(exclude: ["Tank.Inter*"]) - #expect(st.importedTypes["Tank"] != nil) - #expect(st.importedTypes["Tank.Fish"] != nil) - #expect(st.importedTypes["Tank.Internal"] == nil) - #expect(st.importedTypes["FishTank"] != nil) + #expect(st.extractedTypes["Tank"] != nil) + #expect(st.extractedTypes["Tank.Fish"] != nil) + #expect(st.extractedTypes["Tank.Internal"] == nil) + #expect(st.extractedTypes["FishTank"] != nil) } @Test("swiftFilterExclude with `**.Name` matches at any depth") func excludeRecursiveLeaf() throws { let st = try makeTranslator(exclude: ["**.Internal"]) - #expect(st.importedTypes["Tank"] != nil) - #expect(st.importedTypes["Tank.Fish"] != nil) - #expect(st.importedTypes["Tank.Internal"] == nil) - #expect(st.importedTypes["FishTank"] != nil) + #expect(st.extractedTypes["Tank"] != nil) + #expect(st.extractedTypes["Tank.Fish"] != nil) + #expect(st.extractedTypes["Tank.Internal"] == nil) + #expect(st.extractedTypes["FishTank"] != nil) } @Test("plain-name swiftFilterExclude excludes top-level type and its nested members") @@ -382,10 +382,10 @@ struct JExtractFileFilterTests { // Plain pattern matches the top-level component, so excluding `Tank` also // prevents the visitor from descending into its nested types let st = try makeTranslator(exclude: ["Tank"]) - #expect(st.importedTypes["Tank"] == nil) - #expect(st.importedTypes["Tank.Fish"] == nil) - #expect(st.importedTypes["Tank.Internal"] == nil) - #expect(st.importedTypes["FishTank"] != nil) + #expect(st.extractedTypes["Tank"] == nil) + #expect(st.extractedTypes["Tank.Fish"] == nil) + #expect(st.extractedTypes["Tank.Internal"] == nil) + #expect(st.extractedTypes["FishTank"] != nil) } @Test("swiftFilterInclude with `Type.**` keeps the parent and all nested members") @@ -393,10 +393,10 @@ struct JExtractFileFilterTests { // `Tank.**` is a type-name pattern; via the trailing-`**` rule it matches // both `Tank` itself and any nested type underneath let st = try makeTranslator(include: ["Tank.**"]) - #expect(st.importedTypes["Tank"] != nil) - #expect(st.importedTypes["Tank.Fish"] != nil) - #expect(st.importedTypes["Tank.Internal"] != nil) - #expect(st.importedTypes["FishTank"] == nil) + #expect(st.extractedTypes["Tank"] != nil) + #expect(st.extractedTypes["Tank.Fish"] != nil) + #expect(st.extractedTypes["Tank.Internal"] != nil) + #expect(st.extractedTypes["FishTank"] == nil) } @Test("file-path-only filter does not interfere with nested-type extraction") @@ -404,9 +404,9 @@ struct JExtractFileFilterTests { // A file-path-only filter must not accidentally gate type-level filtering; // every nested type in the included file should still be extracted let st = try makeTranslator(include: ["**/Fake.swift", "Fake.swift"]) - #expect(st.importedTypes["Tank"] != nil) - #expect(st.importedTypes["Tank.Fish"] != nil) - #expect(st.importedTypes["Tank.Internal"] != nil) - #expect(st.importedTypes["FishTank"] != nil) + #expect(st.extractedTypes["Tank"] != nil) + #expect(st.extractedTypes["Tank.Fish"] != nil) + #expect(st.extractedTypes["Tank.Internal"] != nil) + #expect(st.extractedTypes["FishTank"] != nil) } } diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 96e1b8d02..49b6e4eb3 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -85,7 +85,7 @@ final class MethodImportTests { javaOutputDirectory: "/fake" ) - let funcDecl = try #require(st.importedGlobalFuncs.first { $0.name == "helloWorld" }) + let funcDecl = try #require(st.extractedGlobalFuncs.first { $0.name == "helloWorld" }) let output = CodePrinter.toString { printer in generator.printJavaBindingWrapperMethod(&printer, funcDecl) @@ -120,7 +120,7 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = try #require( - st.importedGlobalFuncs.first { + st.extractedGlobalFuncs.first { $0.name == "globalTakeInt" } ) @@ -169,7 +169,7 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = try #require( - st.importedGlobalFuncs.first { + st.extractedGlobalFuncs.first { $0.name == "globalTakeIntLongString" } ) @@ -215,7 +215,7 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = try #require( - st.importedGlobalFuncs.first { + st.extractedGlobalFuncs.first { $0.name == "globalReturnClass" } ) @@ -261,7 +261,7 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) let funcDecl = try #require( - st.importedGlobalFuncs.first { + st.extractedGlobalFuncs.first { $0.name == "swapRawBufferPointer" } ) @@ -309,8 +309,8 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) - let funcDecl: ImportedFunc = try #require( - st.importedTypes["MySwiftClass"]!.methods.first { + let funcDecl: ExtractedFunc = try #require( + st.extractedTypes["MySwiftClass"]!.methods.first { $0.name == "helloMemberFunction" } ) @@ -354,8 +354,8 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) - let funcDecl: ImportedFunc = try #require( - st.importedTypes["MySwiftClass"]!.methods.first { + let funcDecl: ExtractedFunc = try #require( + st.extractedTypes["MySwiftClass"]!.methods.first { $0.name == "makeInt" } ) @@ -405,8 +405,8 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) - let initDecl: ImportedFunc = try #require( - st.importedTypes["MySwiftClass"]!.initializers.first { + let initDecl: ExtractedFunc = try #require( + st.extractedTypes["MySwiftClass"]!.initializers.first { $0.name == "init" } ) @@ -460,8 +460,8 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) - let initDecl: ImportedFunc = try #require( - st.importedTypes["MySwiftStruct"]!.initializers.first { + let initDecl: ExtractedFunc = try #require( + st.extractedTypes["MySwiftStruct"]!.initializers.first { $0.name == "init" } ) @@ -515,7 +515,7 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) #expect( - !st.importedGlobalFuncs.contains { + !st.extractedGlobalFuncs.contains { $0.name == "globalReturnAny" }, "'Any' return type is not supported yet" diff --git a/Tests/JExtractSwiftTests/SpecializationTests.swift b/Tests/JExtractSwiftTests/SpecializationTests.swift index 876342941..ef4797713 100644 --- a/Tests/JExtractSwiftTests/SpecializationTests.swift +++ b/Tests/JExtractSwiftTests/SpecializationTests.swift @@ -70,9 +70,9 @@ struct SpecializationTests { """# // ==== ----------------------------------------------------------------------- - // MARK: importedTypes structure + // MARK: extractedTypes structure - @Test("Multiple specializations of same base type produce distinct importedTypes") + @Test("Multiple specializations of same base type produce distinct extractedTypes") func multipleSpecializationsProduceDistinctTypes() throws { var config = Configuration() config.swiftModule = "SwiftModule" @@ -80,19 +80,19 @@ struct SpecializationTests { try translator.analyze(path: "/fake/Fake.swiftinterface", text: multiSpecializationInput) // Both specialized types should be registered - #expect(translator.importedTypes["FishBox"] != nil, "FishBox should be in importedTypes") - #expect(translator.importedTypes["ToolBox"] != nil, "ToolBox should be in importedTypes") + #expect(translator.extractedTypes["FishBox"] != nil, "FishBox should be in extractedTypes") + #expect(translator.extractedTypes["ToolBox"] != nil, "ToolBox should be in extractedTypes") - // The base generic type remains in importedTypes (not removed) - let baseBox = try #require(translator.importedTypes["Box"]) + // The base generic type remains in extractedTypes (not removed) + let baseBox = try #require(translator.extractedTypes["Box"]) #expect(!baseBox.isSpecialization, "Base 'Box' should not be a specialization") #expect(baseBox.genericParameterNames == ["Element"]) #expect(baseBox.genericArguments.isEmpty) #expect(!baseBox.isFullySpecialized) // Specialized types link back to their base - let fishBox = try #require(translator.importedTypes["FishBox"]) - let toolBox = try #require(translator.importedTypes["ToolBox"]) + let fishBox = try #require(translator.extractedTypes["FishBox"]) + let toolBox = try #require(translator.extractedTypes["ToolBox"]) #expect(fishBox.isSpecialization) #expect(toolBox.isSpecialization) @@ -118,12 +118,12 @@ struct SpecializationTests { // Both wrappers delegate to the same base type #expect(fishBox.specializationBaseType === toolBox.specializationBaseType, "Both should wrap the same base Box type") - #expect(fishBox.specializationBaseType === translator.importedTypes["Box"], "Base should be the original Box") + #expect(fishBox.specializationBaseType === translator.extractedTypes["Box"], "Base should be the original Box") // Both wrappers have owned method models - let baseCountFunc: ImportedFunc = try #require(baseBox.methods.first(where: { $0.name == "count" })) - let fishCountFunc: ImportedFunc = try #require(fishBox.methods.first(where: { $0.name == "count" })) - let toolCountFunc: ImportedFunc = try #require(toolBox.methods.first(where: { $0.name == "count" })) + let baseCountFunc: ExtractedFunc = try #require(baseBox.methods.first(where: { $0.name == "count" })) + let fishCountFunc: ExtractedFunc = try #require(fishBox.methods.first(where: { $0.name == "count" })) + let toolCountFunc: ExtractedFunc = try #require(toolBox.methods.first(where: { $0.name == "count" })) #expect(baseCountFunc.parentType?.description == "Box") #expect(fishCountFunc.parentType?.description == "FishBox") #expect(toolCountFunc.parentType?.description == "ToolBox") @@ -136,7 +136,7 @@ struct SpecializationTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swiftinterface", text: multiSpecializationInput) - let baseBox = try #require(translator.importedTypes["Box"]) + let baseBox = try #require(translator.extractedTypes["Box"]) let specializations = try #require(translator.specializations[baseBox]) #expect(specializations.count == 2, "Should have exactly 2 specializations for Box") @@ -195,7 +195,7 @@ struct SpecializationTests { config.swiftModule = "SwiftModule" let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swiftinterface", text: multiSpecializationInput) - let toolBox = try #require(translator.importedTypes["ToolBox"]) + let toolBox = try #require(translator.extractedTypes["ToolBox"]) let methodNames = toolBox.methods.map(\.name) #expect(!methodNames.contains("observeTheFish"), "ToolBox should not have Fish-constrained method") } @@ -348,7 +348,7 @@ struct SpecializationTests { """, ) - let fish = try #require(translator.importedTypes["Fish"]) + let fish = try #require(translator.extractedTypes["Fish"]) #expect(!fish.swiftNominal.isGeneric) #expect(throws: SpecializationError.self) { diff --git a/Tests/JExtractSwiftTests/TypealiasResolutionTests.swift b/Tests/JExtractSwiftTests/TypealiasResolutionTests.swift index dff3ed40f..a849b2474 100644 --- a/Tests/JExtractSwiftTests/TypealiasResolutionTests.swift +++ b/Tests/JExtractSwiftTests/TypealiasResolutionTests.swift @@ -52,7 +52,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: primitiveAliasInput) - let user = try #require(translator.importedTypes["TypealiasUser"]) + let user = try #require(translator.extractedTypes["TypealiasUser"]) #expect(user.variables.contains { $0.name == "amount" }, "Property `amount: Amount` should be extracted") #expect(user.methods.contains { $0.name == "doubled" }, "Method `doubled() -> Amount` should be extracted") @@ -67,7 +67,7 @@ struct TypealiasResolutionTests { try translator.analyze(path: "/fake/Fake.swift", text: primitiveAliasInput) #expect( - translator.importedGlobalFuncs.contains { $0.name == "makeAmount" }, + translator.extractedGlobalFuncs.contains { $0.name == "makeAmount" }, "Global func `makeAmount(_:)` should be extracted" ) } @@ -92,7 +92,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let holder = try #require(translator.importedTypes["Holder"]) + let holder = try #require(translator.extractedTypes["Holder"]) #expect(holder.variables.contains { $0.name == "value" }) #expect(!holder.initializers.isEmpty) } @@ -116,7 +116,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "unwrapOrZero" }) + let fn = try #require(translator.extractedGlobalFuncs.first { $0.name == "unwrapOrZero" }) let paramType = try #require(fn.functionSignature.parameters.first?.type) // The parameter type should be Optional (substituted), preserving @@ -147,7 +147,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "describe" }) + let fn = try #require(translator.extractedGlobalFuncs.first { $0.name == "describe" }) let paramType = try #require(fn.functionSignature.parameters.first?.type) guard case .nominal(let nominal) = paramType else { @@ -179,7 +179,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let holder = try #require(translator.importedTypes["Holder"]) + let holder = try #require(translator.extractedTypes["Holder"]) #expect(holder.variables.isEmpty, "Property `bad: OneArg` should be dropped (arity mismatch)") } @@ -206,7 +206,7 @@ struct TypealiasResolutionTests { // The struct itself is still imported, but its members are dropped // because the alias never resolves. - let usesAlias = try #require(translator.importedTypes["UsesAlias"]) + let usesAlias = try #require(translator.extractedTypes["UsesAlias"]) #expect(usesAlias.variables.isEmpty, "Property `x: A` should be silently dropped (cycle)") } @@ -263,7 +263,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "passA" }) + let fn = try #require(translator.extractedGlobalFuncs.first { $0.name == "passA" }) let paramType = try #require(fn.functionSignature.parameters.first?.type) #expect( paramType.description == "Int64", @@ -288,7 +288,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "unwrap" }) + let fn = try #require(translator.extractedGlobalFuncs.first { $0.name == "unwrap" }) let paramType = try #require(fn.functionSignature.parameters.first?.type) guard case .nominal(let nominal) = paramType else { @@ -313,7 +313,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "first" }) + let fn = try #require(translator.extractedGlobalFuncs.first { $0.name == "first" }) let paramType = try #require(fn.functionSignature.parameters.first?.type) guard case .nominal(let nominal) = paramType else { @@ -347,7 +347,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "add" }) + let fn = try #require(translator.extractedGlobalFuncs.first { $0.name == "add" }) let paramType = try #require(fn.functionSignature.parameters.first?.type) #expect( paramType.description == "Int64", @@ -379,7 +379,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let player = try #require(translator.importedTypes["Player"]) + let player = try #require(translator.extractedTypes["Player"]) let bump = try #require(player.methods.first { $0.name == "bump" }) let paramType = try #require(bump.functionSignature.parameters.first?.type) #expect(paramType.description == "Int64") @@ -404,7 +404,7 @@ struct TypealiasResolutionTests { let translator = SwiftAnalyzer(config: config, extractDecider: JavaExtractDecider()) try translator.analyze(path: "/fake/Fake.swift", text: input) - let fn = try #require(translator.importedGlobalFuncs.first { $0.name == "openIntBag" }) + let fn = try #require(translator.extractedGlobalFuncs.first { $0.name == "openIntBag" }) let paramType = try #require(fn.functionSignature.parameters.first?.type) guard case .nominal(let nominal) = paramType else { Issue.record("Expected Optional nominal, got \(paramType)") diff --git a/Tests/SwiftExtractTests/AnalysisResultTests.swift b/Tests/SwiftExtractTests/AnalysisResultTests.swift index ac3123ba5..420da6ec2 100644 --- a/Tests/SwiftExtractTests/AnalysisResultTests.swift +++ b/Tests/SwiftExtractTests/AnalysisResultTests.swift @@ -47,18 +47,18 @@ struct AnalysisResultSuite { moduleName: "Aquarium" ) - #expect(result.importedTypes["Tank"] != nil) - #expect(result.importedTypes["FishTank"] != nil) - #expect(result.importedTypes["Status"] != nil) + #expect(result.extractedTypes["Tank"] != nil) + #expect(result.extractedTypes["FishTank"] != nil) + #expect(result.extractedTypes["Status"] != nil) - let tank = try #require(result.importedTypes["Tank"]) + let tank = try #require(result.extractedTypes["Tank"]) #expect(tank.swiftNominal.kind == .struct) #expect(tank.swiftNominal.isGeneric) - let fishTank = try #require(result.importedTypes["FishTank"]) + let fishTank = try #require(result.extractedTypes["FishTank"]) #expect(fishTank.swiftNominal.kind == .class) - let status = try #require(result.importedTypes["Status"]) + let status = try #require(result.extractedTypes["Status"]) #expect(status.swiftNominal.kind == .enum) } @@ -82,7 +82,7 @@ struct AnalysisResultSuite { moduleName: "Aquarium" ) - let fishTank = try #require(result.importedTypes["FishTank"]) + let fishTank = try #require(result.extractedTypes["FishTank"]) let methodNames = Set(fishTank.methods.map(\.name)) #expect(methodNames == ["feed", "count"]) #expect(fishTank.initializers.count == 1) @@ -107,7 +107,7 @@ struct AnalysisResultSuite { moduleName: "Aquarium" ) - let fishTank = try #require(result.importedTypes["FishTank"]) + let fishTank = try #require(result.extractedTypes["FishTank"]) let capacityAccessors = fishTank.variables.filter { $0.name == "capacity" } let kinds = Set(capacityAccessors.map(\.apiKind)) #expect(kinds == [.getter, .setter]) @@ -129,7 +129,7 @@ struct AnalysisResultSuite { moduleName: "Aquarium" ) - let fishTank = try #require(result.importedTypes["FishTank"]) + let fishTank = try #require(result.extractedTypes["FishTank"]) let nameAccessors = fishTank.variables.filter { $0.name == "name" } let kinds = nameAccessors.map(\.apiKind) #expect(kinds == [.getter]) @@ -152,9 +152,9 @@ struct AnalysisResultSuite { moduleName: "Aquarium" ) - let names = Set(result.importedGlobalFuncs.map(\.name)) + let names = Set(result.extractedGlobalFuncs.map(\.name)) #expect(names == ["feedAll", "mood"]) - #expect(result.importedTypes.isEmpty) + #expect(result.extractedTypes.isEmpty) } @Test func globalVariableProducesGetterSetterPair() throws { @@ -170,7 +170,7 @@ struct AnalysisResultSuite { moduleName: "Aquarium" ) - let counterAccessors = result.importedGlobalVariables.filter { $0.name == "globalCounter" } + let counterAccessors = result.extractedGlobalVariables.filter { $0.name == "globalCounter" } let kinds = Set(counterAccessors.map(\.apiKind)) #expect(kinds == [.getter, .setter]) } @@ -194,7 +194,7 @@ struct AnalysisResultSuite { moduleName: "Aquarium" ) - let byName = Dictionary(uniqueKeysWithValues: result.importedGlobalFuncs.map { ($0.name, $0) }) + let byName = Dictionary(uniqueKeysWithValues: result.extractedGlobalFuncs.map { ($0.name, $0) }) let plain = try #require(byName["plain"]) #expect(plain.functionSignature.effectSpecifiers.isEmpty) @@ -235,9 +235,9 @@ struct AnalysisResultSuite { moduleName: "Aquarium" ) - #expect(result.importedTypes["Public"] != nil) - #expect(result.importedTypes["Internal"] == nil) - #expect(result.importedTypes["Private"] == nil) + #expect(result.extractedTypes["Public"] != nil) + #expect(result.extractedTypes["Internal"] == nil) + #expect(result.extractedTypes["Private"] == nil) } // ==== ----------------------------------------------------------------------- @@ -268,9 +268,9 @@ struct AnalysisResultSuite { config: config ) - #expect(result.importedTypes["Tank"] != nil) - #expect(result.importedTypes["SkipMe"] == nil) - #expect(result.importedTypes["SkipAlso"] == nil) + #expect(result.extractedTypes["Tank"] != nil) + #expect(result.extractedTypes["SkipMe"] == nil) + #expect(result.extractedTypes["SkipAlso"] == nil) } // ==== ----------------------------------------------------------------------- @@ -293,9 +293,9 @@ struct AnalysisResultSuite { moduleName: "Aquarium" ) - // Both the generic base and its specialization land in importedTypes. - #expect(result.importedTypes["Tank"] != nil) - let fishTank = try #require(result.importedTypes["FishTank"]) + // Both the generic base and its specialization land in extractedTypes. + #expect(result.extractedTypes["Tank"] != nil) + let fishTank = try #require(result.extractedTypes["FishTank"]) #expect(fishTank.isSpecialization) } @@ -310,8 +310,8 @@ struct AnalysisResultSuite { moduleName: "Empty" ) - #expect(result.importedTypes.isEmpty) - #expect(result.importedGlobalFuncs.isEmpty) - #expect(result.importedGlobalVariables.isEmpty) + #expect(result.extractedTypes.isEmpty) + #expect(result.extractedGlobalFuncs.isEmpty) + #expect(result.extractedGlobalVariables.isEmpty) } } From d041c252393e5aa346271040b0f942c1503d2824 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 1 Jun 2026 16:54:17 +0900 Subject: [PATCH 05/13] SwiftExtract: move typealiases --- .../JavaSourceDependencies.swift | 4 - .../SymbolTable+Printing.swift | 83 +++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SymbolTable+Printing.swift diff --git a/Sources/JExtractSwiftLib/JavaSourceDependencies.swift b/Sources/JExtractSwiftLib/JavaSourceDependencies.swift index 55fb17e6c..a9c64567f 100644 --- a/Sources/JExtractSwiftLib/JavaSourceDependencies.swift +++ b/Sources/JExtractSwiftLib/JavaSourceDependencies.swift @@ -24,10 +24,6 @@ import FoundationEssentials import Foundation #endif -package typealias JavaClassName = String -package typealias JavaFullyQualifiedClassName = String -package typealias JavaPackageName = String - extension SourceDependencies { /// Inject synthetic `@JavaClass public class {}` stubs so the symbol /// table can resolve Java wrapper types referenced in the Swift API. diff --git a/Sources/JExtractSwiftLib/SymbolTable+Printing.swift b/Sources/JExtractSwiftLib/SymbolTable+Printing.swift new file mode 100644 index 000000000..0f28ebffb --- /dev/null +++ b/Sources/JExtractSwiftLib/SymbolTable+Printing.swift @@ -0,0 +1,83 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 CodePrinting +import SwiftExtract + +extension SwiftSymbolTable { + package func printImportedModules(_ printer: inout CodePrinter) { + let mainSymbolSourceModules = Set( + self.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName) + ) + + for module in self.importedModules.keys.sorted() { + guard module != "Swift" else { + continue + } + + // Synthetic stub modules (e.g. ) exist purely for + // symbol-table resolution; they are not real Swift modules and must + // not be emitted as `import` statements. + guard !self.syntheticImportedModuleNames.contains(module) else { + continue + } + + guard let alternativeModules = self.importedModules[module]?.alternativeModules else { + printer.print("import \(module)") + continue + } + + // Only the main source of symbols emits the conditional import block. + // Secondary modules (e.g. FoundationEssentials when Foundation is the main source) + // are skipped when their main source is already present, because the main source's + // block already covers the import. If no main source is present, fall back to a + // plain import so the module is still imported. + guard alternativeModules.isMainSourceOfSymbols else { + if mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) { + printer.print("import \(module)") + } + continue + } + + var importGroups: [String: [String]] = [:] + for name in alternativeModules.moduleNames { + guard let otherModule = self.importedModules[name] else { continue } + + let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName + importGroups[groupKey, default: []].append(otherModule.moduleName) + } + + for (index, group) in importGroups.keys.sorted().enumerated() { + if index > 0 && importGroups.keys.count > 1 { + printer.print("#elseif canImport(\(group))") + } else { + printer.print("#if canImport(\(group))") + } + + for groupModule in importGroups[group] ?? [] { + printer.print("import \(groupModule)") + } + } + + if importGroups.keys.isEmpty { + printer.print("import \(module)") + } else { + printer.print("#else") + printer.print("import \(module)") + printer.print("#endif") + } + } + printer.println() + } +} From a07aadf713796fef5f155f4a27b2ee4427f47b34 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 2 Jun 2026 11:29:28 +0900 Subject: [PATCH 06/13] SwiftExtract: decouple from Java config + optional operator extraction Introduce SwiftExtractConfiguration protocol (neutral AccessLevelMode / Logger.Level) so SwiftExtract no longer depends on SwiftJavaConfigurationShared; Configuration conforms via JExtractSwiftLib/Configuration+SwiftExtract.swift. Gate operator extraction behind config.extractsOperators (default off -> Java unchanged). Expose ExtractedNominalType.declAttributes / declGroupSyntax. All SwiftExtractTests + JExtractSwiftTests pass. --- Package.swift | 1 - .../Configuration+SwiftExtract.swift | 61 ++++++++++ .../FFM/FFMSwift2JavaGenerator.swift | 2 +- .../JNI/JNISwift2JavaGenerator.swift | 4 +- Sources/SwiftExtract/ExtractedDecls.swift | 14 +++ Sources/SwiftExtract/Logger.swift | 15 ++- .../SwiftExtract/SwiftAnalysisVisitor.swift | 19 +-- Sources/SwiftExtract/SwiftAnalyzer.swift | 17 ++- .../SwiftExtractConfiguration.swift | 114 ++++++++++++++++++ Sources/SwiftExtract/SwiftFileFilter.swift | 6 +- .../SwiftTypes/SwiftSymbolTable.swift | 3 +- .../SwiftJavaBaseAsyncParsableCommand.swift | 2 +- .../AnalysisResultTests.swift | 3 +- 13 files changed, 230 insertions(+), 31 deletions(-) create mode 100644 Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift create mode 100644 Sources/SwiftExtract/SwiftExtractConfiguration.swift diff --git a/Package.swift b/Package.swift index 6a1b0e28e..b830a610c 100644 --- a/Package.swift +++ b/Package.swift @@ -351,7 +351,6 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "Logging", package: "swift-log"), - "SwiftJavaConfigurationShared", ], path: "Sources/SwiftExtract", resources: [ diff --git a/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift b/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift new file mode 100644 index 000000000..eef586b89 --- /dev/null +++ b/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 SwiftExtract +import SwiftJavaConfigurationShared + +/// Bridges swift-java's `Configuration` onto the language-neutral +/// `SwiftExtractConfiguration` surface consumed by `SwiftExtract`. +/// +/// Most members are satisfied directly by `Configuration`'s own properties; only +/// the two enum-typed members need a mapping from swift-java's enums onto the +/// neutral `AccessLevelMode` / `Logger.Level`. +extension Configuration: SwiftExtractConfiguration { + public var swiftExtractAccessLevel: AccessLevelMode { + switch effectiveMinimumInputAccessLevelMode { + case .public: .public + case .package: .package + case .internal: .internal + } + } + + public var swiftExtractLogLevel: SwiftExtract.Logger.Level? { + guard let logLevel else { return nil } + switch logLevel { + case .trace: return .trace + case .debug: return .debug + case .info: return .info + case .notice: return .notice + case .warning: return .warning + case .error: return .error + case .critical: return .critical + } + } +} + +extension LogLevel { + /// Bridges from the analysis layer's neutral `Logger.Level` (used by the CLI's + /// `--log-level` option) onto swift-java's own `LogLevel`. + public init(_ level: SwiftExtract.Logger.Level) { + switch level { + case .trace: self = .trace + case .debug: self = .debug + case .info: self = .info + case .notice: self = .notice + case .warning: self = .warning + case .error: self = .error + case .critical: self = .critical + } + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index 5d22e3baa..4a9f899ce 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -86,7 +86,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { // If we are forced to write empty files, construct the expected outputs. // It is sufficient to use file names only, since SwiftPM requires names to be unique within a module anyway. - if translator.config.effectiveWriteEmptyFiles { + if config.effectiveWriteEmptyFiles { self.expectedOutputSwiftFileNames = Set( translator.inputs.compactMap { (input) -> String? in guard let fileName = input.path.split(separator: PATH_SEPARATOR).last else { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index 564a54c7f..b0379e2f4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -86,7 +86,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { // If we are forced to write empty files, construct the expected outputs. // It is sufficient to use file names only, since SwiftPM requires names to be unique within a module anyway. - if translator.config.effectiveWriteEmptyFiles { + if config.effectiveWriteEmptyFiles { self.expectedOutputSwiftFileNames = Set( translator.inputs.compactMap { (input) -> String? in guard let fileName = input.path.split(separator: PATH_SEPARATOR).last else { @@ -117,7 +117,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { self.expectedOutputSwiftFileNames = [] } - if translator.config.enableJavaCallbacks ?? false { + if config.enableJavaCallbacks ?? false { // We translate all the protocol wrappers // as we need them to know what protocols we can allow the user to implement themselves // in Java. diff --git a/Sources/SwiftExtract/ExtractedDecls.swift b/Sources/SwiftExtract/ExtractedDecls.swift index c6a322188..af5011124 100644 --- a/Sources/SwiftExtract/ExtractedDecls.swift +++ b/Sources/SwiftExtract/ExtractedDecls.swift @@ -151,6 +151,20 @@ public final class ExtractedNominalType: ExtractedSwiftDecl { self.swiftNominal.qualifiedName } + /// The attribute list on this type's declaration (e.g. `@resultBuilder`), + /// for language targets that key behavior off attributes. + /// Mirrors `ExtractedFunc.swiftDecl` being public for the function case. + public var declAttributes: AttributeListSyntax { + swiftNominal.syntax.attributes + } + + /// The declaration-group syntax for this type (protocol/struct/class/enum/ + /// actor), for language targets that need to inspect members or clauses the + /// neutral model doesn't surface (e.g. a protocol's primary associated types). + public var declGroupSyntax: any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax { + swiftNominal.syntax + } + /// The output generic clause, e.g. "" for generic base types, "" for specialized or non-generic public var outputGenericClause: String { if isSpecialization { diff --git a/Sources/SwiftExtract/Logger.swift b/Sources/SwiftExtract/Logger.swift index aae0148af..5a89412db 100644 --- a/Sources/SwiftExtract/Logger.swift +++ b/Sources/SwiftExtract/Logger.swift @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// import Foundation -import SwiftJavaConfigurationShared import SwiftSyntax // Placeholder for some better logger, we could depend on swift-log @@ -113,7 +112,19 @@ public struct Logger { } extension Logger { - public typealias Level = SwiftJavaConfigurationShared.LogLevel + /// Log verbosity levels for the analysis layer's lightweight logger. + /// + /// Language-neutral; language-specific configuration modules map their own + /// log-level enums onto this via `SwiftExtractConfiguration`. + public enum Level: String, Codable, Hashable, Sendable { + case trace + case debug + case info + case notice + case warning + case error + case critical + } } extension Logger.Level { diff --git a/Sources/SwiftExtract/SwiftAnalysisVisitor.swift b/Sources/SwiftExtract/SwiftAnalysisVisitor.swift index 81b311757..74bd6b2cf 100644 --- a/Sources/SwiftExtract/SwiftAnalysisVisitor.swift +++ b/Sources/SwiftExtract/SwiftAnalysisVisitor.swift @@ -15,13 +15,12 @@ import Foundation import Logging import SwiftIfConfig -import SwiftJavaConfigurationShared import SwiftParser import SwiftSyntax final class SwiftAnalysisVisitor { let translator: SwiftAnalyzer - var config: Configuration { + var config: any SwiftExtractConfiguration { self.translator.config } @@ -186,8 +185,14 @@ final class SwiftAnalysisVisitor { switch node.name.tokenKind { case .binaryOperator, .prefixOperator, .postfixOperator: - self.log.debug("Skip importing: '\(node.qualifiedNameForDebug)'; Operators are not supported.") - return + // Operators are extracted as ordinary `.function`s only when the target + // opts in (other language code generators may map them to language + // constructs in a post-pass). Most targets (e.g. Java) cannot express + // Swift operators and skip them. + guard config.extractsOperators else { + self.log.debug("Skip importing: '\(node.qualifiedNameForDebug)'; Operators are not supported.") + return + } default: break } @@ -720,13 +725,13 @@ extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyn /// the result on a per-decl basis (e.g. Java honors `@JavaExport` / /// `@JavaClass` here) func shouldExtract( - config: Configuration, + config: any SwiftExtractConfiguration, log: Logger, in parent: ExtractedNominalType?, decider: (any ExtractDecider)? ) -> Bool { let accessLevelPasses: Bool = - switch config.effectiveMinimumInputAccessLevelMode { + switch config.swiftExtractAccessLevel { case .public: self.isPublic(in: parent?.swiftNominal.syntax) case .package: self.isAtLeastPackage case .internal: self.isAtLeastInternal @@ -744,7 +749,7 @@ extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyn if !accessLevelPasses { log.debug( - "Skip import '\(self.qualifiedNameForDebug)': not at least \(config.effectiveMinimumInputAccessLevelMode)" + "Skip import '\(self.qualifiedNameForDebug)': not at least \(config.swiftExtractAccessLevel)" ) } return accessLevelPasses diff --git a/Sources/SwiftExtract/SwiftAnalyzer.swift b/Sources/SwiftExtract/SwiftAnalyzer.swift index 909468d53..dad00cd5e 100644 --- a/Sources/SwiftExtract/SwiftAnalyzer.swift +++ b/Sources/SwiftExtract/SwiftAnalyzer.swift @@ -15,7 +15,6 @@ import Foundation import Logging import SwiftIfConfig -import SwiftJavaConfigurationShared import SwiftParser import SwiftSyntax @@ -30,7 +29,7 @@ public final class SwiftAnalyzer { package var log: Logger - package let config: Configuration + package let config: any SwiftExtractConfiguration /// The build configuration used to resolve #if conditional compilation blocks. package let buildConfig: any BuildConfiguration @@ -76,13 +75,14 @@ public final class SwiftAnalyzer { package let extractDecider: (any ExtractDecider)? public init( - config: Configuration, + config: any SwiftExtractConfiguration, + moduleName: String? = nil, extractDecider: (any ExtractDecider)? = nil ) { - guard let swiftModule = config.swiftModule else { + guard let swiftModule = moduleName ?? 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.log = Logger(label: "translator", logLevel: config.swiftExtractLogLevel ?? .info) self.config = config self.swiftModuleName = swiftModule self.extractDecider = extractDecider @@ -150,13 +150,12 @@ extension SwiftAnalyzer { public static func analyze( sources: [(path: String, text: String)], moduleName: String, - config: Configuration? = nil, + config: (any SwiftExtractConfiguration)? = nil, sourceDependencies: SourceDependencies = SourceDependencies(), extractDecider: (any ExtractDecider)? = nil ) throws -> AnalysisResult { - var effectiveConfig = config ?? Configuration() - effectiveConfig.swiftModule = moduleName - let translator = SwiftAnalyzer(config: effectiveConfig, extractDecider: extractDecider) + let effectiveConfig = config ?? DefaultSwiftExtractConfiguration(swiftModule: moduleName) + let translator = SwiftAnalyzer(config: effectiveConfig, moduleName: moduleName, extractDecider: extractDecider) translator.sourceDependencies = sourceDependencies for source in sources { translator.add(filePath: source.path, text: source.text) diff --git a/Sources/SwiftExtract/SwiftExtractConfiguration.swift b/Sources/SwiftExtract/SwiftExtractConfiguration.swift new file mode 100644 index 000000000..b2bb25fd0 --- /dev/null +++ b/Sources/SwiftExtract/SwiftExtractConfiguration.swift @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 +// +//===----------------------------------------------------------------------===// + +/// Minimum access level a declaration must have to be considered for extraction. +/// +/// Language-neutral counterpart to a configuration's access-level setting. The +/// concrete `Configuration` types in language layers (e.g. swift-java, or other +/// language code generators) map their own enums onto this. +public enum AccessLevelMode: String, Sendable { + case `public` + case `package` + case `internal` +} + +/// The configuration surface required by the language-neutral `SwiftExtract` +/// analysis layer. +/// +/// `SwiftExtract` deliberately does NOT depend on any language-specific +/// configuration module. Instead, each language layer makes its own +/// `Configuration` type conform to this protocol, mapping its settings onto the +/// neutral surface below. This keeps the analysis layer reusable across targets +/// (e.g. Java/JNI/FFM, or other language code generators) without pulling +/// target-specific config types into `SwiftExtract`. +/// +/// The two enum-typed members use `swiftExtract`-prefixed names so a conforming +/// type can keep its own, differently-typed `logLevel` / +/// `effectiveMinimumInputAccessLevelMode` members without a name collision. +public protocol SwiftExtractConfiguration { + /// Name of the Swift module being analyzed. + var swiftModule: String? { get } + + /// Optional path to a JSON `StaticBuildConfiguration` used to resolve `#if`. + var staticBuildConfigurationFile: String? { get } + + /// Glob patterns selecting which Swift files/types to extract. + var swiftFilterInclude: [String]? { get } + + /// Glob patterns excluding Swift files/types from extraction. + var swiftFilterExclude: [String]? { get } + + /// Stub declarations for imported modules whose source is unavailable to the + /// analyzer. Keyed by module name; values are Swift declaration strings parsed + /// as if they belonged to that module. + var importedModuleStubs: [String: [String]]? { get } + + /// Minimum access level required for a declaration to be extracted. + var swiftExtractAccessLevel: AccessLevelMode { get } + + /// Whether operator declarations (e.g. `static func + (…)`) should be + /// extracted as ordinary `.function`s. Most targets (e.g. Java) cannot express + /// Swift operators and leave this `false`; other language code generators that + /// map operators to language constructs set it `true` and recognize the + /// operator functions in a post-analysis pass. + var extractsOperators: Bool { get } + + /// Verbosity for the analyzer's logger; `nil` falls back to `.info`. + var swiftExtractLogLevel: Logger.Level? { get } + + /// Whether the given module name has stub declarations configured. + func hasImportedModuleStub(moduleOfNominal moduleName: String) -> Bool +} + +extension SwiftExtractConfiguration { + public var extractsOperators: Bool { false } + + public func hasImportedModuleStub(moduleOfNominal moduleName: String) -> Bool { + importedModuleStubs?.keys.contains(moduleName) ?? false + } +} + +/// A minimal, self-contained `SwiftExtractConfiguration` for callers that only +/// need analysis (tests, tools) and don't have a richer language-specific +/// configuration to supply. +public struct DefaultSwiftExtractConfiguration: SwiftExtractConfiguration { + public var swiftModule: String? + public var staticBuildConfigurationFile: String? + public var swiftFilterInclude: [String]? + public var swiftFilterExclude: [String]? + public var importedModuleStubs: [String: [String]]? + public var swiftExtractAccessLevel: AccessLevelMode + public var swiftExtractLogLevel: Logger.Level? + public var extractsOperators: Bool + + public init( + swiftModule: String? = nil, + accessLevel: AccessLevelMode = .public, + logLevel: Logger.Level? = nil, + extractsOperators: Bool = false, + staticBuildConfigurationFile: String? = nil, + swiftFilterInclude: [String]? = nil, + swiftFilterExclude: [String]? = nil, + importedModuleStubs: [String: [String]]? = nil + ) { + self.swiftModule = swiftModule + self.swiftExtractAccessLevel = accessLevel + self.swiftExtractLogLevel = logLevel + self.extractsOperators = extractsOperators + self.staticBuildConfigurationFile = staticBuildConfigurationFile + self.swiftFilterInclude = swiftFilterInclude + self.swiftFilterExclude = swiftFilterExclude + self.importedModuleStubs = importedModuleStubs + } +} diff --git a/Sources/SwiftExtract/SwiftFileFilter.swift b/Sources/SwiftExtract/SwiftFileFilter.swift index 40c6ef6b3..e0e0e7741 100644 --- a/Sources/SwiftExtract/SwiftFileFilter.swift +++ b/Sources/SwiftExtract/SwiftFileFilter.swift @@ -12,8 +12,6 @@ // //===----------------------------------------------------------------------===// -import SwiftJavaConfigurationShared - // ==== ----------------------------------------------------------------------- // MARK: Swift filter pattern classification @@ -195,7 +193,7 @@ package func matchesTypeNameFilter(qualifiedName: String, pattern: String) -> Bo /// Only file-path patterns (containing `/`) and plain patterns (no `/` or `.`) /// are checked here. Type-name patterns are skipped — use /// `shouldExtractSwiftType` for those -package func shouldExtractSwiftFile(relativePath: String, config: Configuration) -> Bool { +package func shouldExtractSwiftFile(relativePath: String, config: any SwiftExtractConfiguration) -> Bool { if let includeFilters = config.swiftFilterInclude, !includeFilters.isEmpty { // Must match at least one file-level include pattern. // If all include patterns are type-name patterns, don't filter at file level @@ -246,7 +244,7 @@ private func matchesFilePattern(relativePath: String, pattern: String) -> Bool { /// Only type-name patterns (containing `.`) and plain patterns (no `/` or `.`) /// are checked here. File-path patterns are skipped — use `shouldExtractSwiftFile` /// for those -package func shouldExtractSwiftType(qualifiedName: String, config: Configuration) -> Bool { +package func shouldExtractSwiftType(qualifiedName: String, config: any SwiftExtractConfiguration) -> Bool { if let includeFilters = config.swiftFilterInclude, !includeFilters.isEmpty { let typePatterns = includeFilters.filter { classifyPattern($0) != .filePath } if !typePatterns.isEmpty { diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift b/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift index 5e38f43d6..45557b7ac 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift @@ -14,7 +14,6 @@ import Logging import SwiftIfConfig -import SwiftJavaConfigurationShared import SwiftParser import SwiftSyntax @@ -100,7 +99,7 @@ extension SwiftSymbolTable { moduleName: String, _ inputFiles: some Collection, additionalInputFiles: [SwiftInputFile] = [], - config: Configuration?, + config: (any SwiftExtractConfiguration)?, sourceDependencies: SourceDependencies, buildConfig: any BuildConfiguration = .swiftExtractDefault, log: Logger? = nil, diff --git a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift index 748114c30..95eaf7d0a 100644 --- a/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift +++ b/Sources/SwiftJavaTool/SwiftJavaBaseAsyncParsableCommand.swift @@ -183,7 +183,7 @@ extension SwiftJavaBaseAsyncParsableCommand { config = Configuration() } // override configuration with options from command line - config.logLevel = command.logLevel + config.logLevel = LogLevel(command.logLevel) return config } } diff --git a/Tests/SwiftExtractTests/AnalysisResultTests.swift b/Tests/SwiftExtractTests/AnalysisResultTests.swift index 420da6ec2..ad9cbec7c 100644 --- a/Tests/SwiftExtractTests/AnalysisResultTests.swift +++ b/Tests/SwiftExtractTests/AnalysisResultTests.swift @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// import SwiftExtract -import SwiftJavaConfigurationShared import Testing /// End-to-end tests that drive the analysis pipeline (Swift source → @@ -244,7 +243,7 @@ struct AnalysisResultSuite { // MARK: Filter include/exclude @Test func swiftFilterExcludeSkipsMatchingTypes() throws { - var config = Configuration() + var config = DefaultSwiftExtractConfiguration() config.swiftFilterExclude = ["Skip*"] let result = try SwiftAnalyzer.analyze( From e395b3bcbbd7e2ef250e7671a0ab454167b20121 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Tue, 2 Jun 2026 13:48:37 +0900 Subject: [PATCH 07/13] SwiftExtract: Include a few general purpose is... checks --- .../SwiftTypes/SwiftFunctionSignature.swift | 5 +++ .../SwiftTypes/SwiftParameter.swift | 38 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftFunctionSignature.swift b/Sources/SwiftExtract/SwiftTypes/SwiftFunctionSignature.swift index 9a0261c14..477fb5f2e 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftFunctionSignature.swift @@ -38,6 +38,11 @@ public struct SwiftFunctionSignature: Equatable { effectSpecifiers.contains(.throws) } + /// Whether any parameter is variadic (`T...`). + public var hasVariadicParams: Bool { + parameters.contains(where: \.isVariadic) + } + public init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift b/Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift index 90b694a42..71db093df 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift @@ -20,16 +20,44 @@ public struct SwiftParameter: Equatable { public var parameterName: String? public var type: SwiftType + /// Whether this is a variadic parameter (`T...`). + public var isVariadic: Bool + /// Whether the parameter declares a default value. + public var hasDefaultValue: Bool + /// The default-value expression source, if any (e.g. `42`, `[]`). + public var defaultValueExpression: String? + public init( convention: SwiftParameterConvention, argumentLabel: String? = nil, parameterName: String? = nil, - type: SwiftType + type: SwiftType, + isVariadic: Bool = false, + hasDefaultValue: Bool = false, + defaultValueExpression: String? = nil ) { self.convention = convention self.argumentLabel = argumentLabel self.parameterName = parameterName self.type = type + self.isVariadic = isVariadic + self.hasDefaultValue = hasDefaultValue + self.defaultValueExpression = defaultValueExpression + } + + /// The simple parameter name, falling back to the argument label. + public var name: String { + parameterName ?? argumentLabel ?? "_" + } + + /// The external argument label, falling back to the parameter name. + public var effectiveLabel: String { + argumentLabel ?? name + } + + /// Whether this parameter is passed `inout`. + public var isInout: Bool { + convention == .inout } } @@ -75,6 +103,9 @@ extension SwiftParameter { self.argumentLabel = nil self.parameterName = node.firstName?.identifier?.name self.argumentLabel = node.firstName?.identifier?.name + self.isVariadic = false + self.hasDefaultValue = node.defaultValue != nil + self.defaultValueExpression = node.defaultValue?.value.trimmedDescription } } @@ -113,6 +144,11 @@ extension SwiftParameter { // Determine the type. self.type = try SwiftType(type, lookupContext: lookupContext) + // Variadic / default-value information. + self.isVariadic = node.ellipsis != nil + self.hasDefaultValue = node.defaultValue != nil + self.defaultValueExpression = node.defaultValue?.value.trimmedDescription + // FIXME: swift-syntax itself should have these utilities based on identifiers. if let secondName = node.secondName { self.argumentLabel = node.firstName.identifier?.name From b849ab56e1441d45357ff48d94225fd1633cb590 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 3 Jun 2026 22:15:17 +0900 Subject: [PATCH 08/13] SwiftExtract: configurable importable modules + generic-type initializer extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two opt-in, language-neutral knobs on `SwiftExtractConfiguration` (both default to the prior behavior, so the Java path is unchanged): - `availableImportModules: Set` — module names treated as importable when resolving `#if canImport()`. The analyzer wraps its build configuration in an `ImportOverlayBuildConfiguration` so declarations guarded behind `#if canImport(MyModule)` are extracted (e.g. another language code generator may declare its runtime module importable here, even when the static build config doesn't otherwise know about it). - `extractsGenericTypeInitializers: Bool` — extract initializers of generic nominal types even when not specialized. swift-java skips these by default (an open generic isn't directly constructible); other language code generators that specialize generics in a post-analysis pass need the base type's initializers available to clone onto the specialization. --- .../SwiftExtract/SwiftAnalysisVisitor.swift | 4 +- Sources/SwiftExtract/SwiftAnalyzer.swift | 16 +++++++- .../SwiftExtractConfiguration.swift | 24 +++++++++++- ...wiftExtractDefaultBuildConfiguration.swift | 38 +++++++++++++++++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftExtract/SwiftAnalysisVisitor.swift b/Sources/SwiftExtract/SwiftAnalysisVisitor.swift index 74bd6b2cf..d7c0a43f1 100644 --- a/Sources/SwiftExtract/SwiftAnalysisVisitor.swift +++ b/Sources/SwiftExtract/SwiftAnalysisVisitor.swift @@ -338,7 +338,9 @@ final class SwiftAnalysisVisitor { return } - if typeContext.swiftNominal.isGeneric && !typeContext.isSpecialization { + if typeContext.swiftNominal.isGeneric && !typeContext.isSpecialization + && !config.extractsGenericTypeInitializers + { log.debug("Skip Importing generic type initializer \(node.kind) '\(node.qualifiedNameForDebug)'") return } diff --git a/Sources/SwiftExtract/SwiftAnalyzer.swift b/Sources/SwiftExtract/SwiftAnalyzer.swift index dad00cd5e..9c16013bb 100644 --- a/Sources/SwiftExtract/SwiftAnalyzer.swift +++ b/Sources/SwiftExtract/SwiftAnalyzer.swift @@ -91,15 +91,27 @@ public final class SwiftAnalyzer { do { let data = try Data(contentsOf: URL(fileURLWithPath: staticBuildConfigPath)) let decoder = JSONDecoder() - self.buildConfig = try decoder.decode(StaticBuildConfiguration.self, from: data) + let staticConfig = try decoder.decode(StaticBuildConfiguration.self, from: data) + self.buildConfig = SwiftAnalyzer.overlayingAvailableModules(staticConfig, config.availableImportModules) self.log.info("Using custom static build configuration from: \(staticBuildConfigPath)") } catch { fatalError("Failed to load static build configuration from '\(staticBuildConfigPath)': \(error)") } } else { - self.buildConfig = .swiftExtractDefault + self.buildConfig = SwiftAnalyzer.overlayingAvailableModules(.swiftExtractDefault, config.availableImportModules) } } + + /// Overlay the configured extra importable modules onto a base build config + /// (returns the base unchanged when none are configured). + private static func overlayingAvailableModules( + _ base: Base, + _ availableImportModules: Set + ) -> any BuildConfiguration { + availableImportModules.isEmpty + ? base + : ImportOverlayBuildConfiguration(base: base, availableImportModules: availableImportModules) + } } // ===== -------------------------------------------------------------------------------------------------------------- diff --git a/Sources/SwiftExtract/SwiftExtractConfiguration.swift b/Sources/SwiftExtract/SwiftExtractConfiguration.swift index b2bb25fd0..090912599 100644 --- a/Sources/SwiftExtract/SwiftExtractConfiguration.swift +++ b/Sources/SwiftExtract/SwiftExtractConfiguration.swift @@ -67,6 +67,21 @@ public protocol SwiftExtractConfiguration { /// Verbosity for the analyzer's logger; `nil` falls back to `.info`. var swiftExtractLogLevel: Logger.Level? { get } + /// Whether to extract initializers of *generic* nominal types even when they + /// are not (yet) specialized. swift-java skips these by default (an open + /// generic isn't directly constructible); other language code generators that + /// specialize generics in a post-analysis pass set this `true` so the base + /// type's initializers are available to clone onto the specialization. + /// Default: false. + var extractsGenericTypeInitializers: Bool { get } + + /// Module names that should be treated as importable when resolving + /// `#if canImport()` conditions, in addition to whatever the build + /// configuration already knows. Lets a target opt-in to extracting code + /// guarded behind `#if canImport(MyModule)` (e.g. another language code + /// generator can declare its runtime module importable here). Default: empty. + var availableImportModules: Set { get } + /// Whether the given module name has stub declarations configured. func hasImportedModuleStub(moduleOfNominal moduleName: String) -> Bool } @@ -74,6 +89,10 @@ public protocol SwiftExtractConfiguration { extension SwiftExtractConfiguration { public var extractsOperators: Bool { false } + public var extractsGenericTypeInitializers: Bool { false } + + public var availableImportModules: Set { [] } + public func hasImportedModuleStub(moduleOfNominal moduleName: String) -> Bool { importedModuleStubs?.keys.contains(moduleName) ?? false } @@ -91,6 +110,7 @@ public struct DefaultSwiftExtractConfiguration: SwiftExtractConfiguration { public var swiftExtractAccessLevel: AccessLevelMode public var swiftExtractLogLevel: Logger.Level? public var extractsOperators: Bool + public var availableImportModules: Set public init( swiftModule: String? = nil, @@ -100,7 +120,8 @@ public struct DefaultSwiftExtractConfiguration: SwiftExtractConfiguration { staticBuildConfigurationFile: String? = nil, swiftFilterInclude: [String]? = nil, swiftFilterExclude: [String]? = nil, - importedModuleStubs: [String: [String]]? = nil + importedModuleStubs: [String: [String]]? = nil, + availableImportModules: Set = [] ) { self.swiftModule = swiftModule self.swiftExtractAccessLevel = accessLevel @@ -110,5 +131,6 @@ public struct DefaultSwiftExtractConfiguration: SwiftExtractConfiguration { self.swiftFilterInclude = swiftFilterInclude self.swiftFilterExclude = swiftFilterExclude self.importedModuleStubs = importedModuleStubs + self.availableImportModules = availableImportModules } } diff --git a/Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift b/Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift index 41f588974..a11d76d73 100644 --- a/Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift +++ b/Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift @@ -101,3 +101,41 @@ extension BuildConfiguration where Self == SwiftExtractDefaultBuildConfiguration .shared } } + +/// Wraps any `BuildConfiguration` and additionally reports a configured set of +/// module names as importable. Used so a target can extract declarations guarded +/// behind `#if canImport()` for modules the static build configuration +/// does not otherwise know about (e.g. another language code generator can +/// declare its own runtime module importable for extraction). +package struct ImportOverlayBuildConfiguration: BuildConfiguration { + package var base: Base + package var availableImportModules: Set + + package init(base: Base, availableImportModules: Set) { + self.base = base + self.availableImportModules = availableImportModules + } + + package func canImport(importPath: [(TokenSyntax, String)], version: CanImportVersion) throws -> Bool { + // `importPath` is the dotted module path; the leading component is the module. + if let moduleName = importPath.first?.1, availableImportModules.contains(moduleName) { + return true + } + return try base.canImport(importPath: importPath, version: version) + } + + package func isCustomConditionSet(name: String) throws -> Bool { try base.isCustomConditionSet(name: name) } + package func hasFeature(name: String) throws -> Bool { try base.hasFeature(name: name) } + package func hasAttribute(name: String) throws -> Bool { try base.hasAttribute(name: name) } + package func isActiveTargetOS(name: String) throws -> Bool { try base.isActiveTargetOS(name: name) } + package func isActiveTargetArchitecture(name: String) throws -> Bool { try base.isActiveTargetArchitecture(name: name) } + package func isActiveTargetEnvironment(name: String) throws -> Bool { try base.isActiveTargetEnvironment(name: name) } + package func isActiveTargetRuntime(name: String) throws -> Bool { try base.isActiveTargetRuntime(name: name) } + package func isActiveTargetPointerAuthentication(name: String) throws -> Bool { try base.isActiveTargetPointerAuthentication(name: name) } + package func isActiveTargetObjectFormat(name: String) throws -> Bool { try base.isActiveTargetObjectFormat(name: name) } + package var targetPointerBitWidth: Int { base.targetPointerBitWidth } + package var targetAtomicBitWidths: [Int] { base.targetAtomicBitWidths } + package var endianness: Endianness { base.endianness } + package var languageVersion: VersionTuple { base.languageVersion } + package var compilerVersion: VersionTuple { base.compilerVersion } +} From 7b87b10b332c332d0ba42d8dacf1526493460868 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 3 Jun 2026 22:15:38 +0900 Subject: [PATCH 09/13] SwiftExtract: include URL in the Foundation known-module overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Foundation/FoundationEssentials known-module source files declared Data, Date, and UUID, but URL was missing — so any user code using URL (e.g. `func getHost(url: URL)`) failed to import with an unresolved-type warning, dropping the function silently. Other consumers of the language-neutral analyzer rely on URL for bridging tests; add it alongside the other Foundation built-ins, declaring just the failable `init(string:)` and `absoluteString` property the language-neutral analyzer needs to resolve uses. --- Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift b/Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift index 47e360146..689c24f61 100644 --- a/Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift @@ -130,6 +130,11 @@ private let foundationEssentialsSourceFile: SourceFileSyntax = """ } public struct UUID {} + + public struct URL { + public init?(string: String) + public var absoluteString: String { get } + } """ private var foundationSourceFile: SourceFileSyntax { From 1c12eb57fb7e62fc3f4bd42caa4805faaa481a9a Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 4 Jun 2026 08:50:02 +0900 Subject: [PATCH 10/13] SwiftExtract: cover extractsGenericTypeInitializers & availableImportModules `DefaultSwiftExtractConfiguration` exposed `extractsOperators` and `availableImportModules` as stored properties with init parameters but omitted `extractsGenericTypeInitializers`, so callers using the default config could not opt into it programmatically and silently fell back to the protocol-extension default of `false`. Add it as a stored property and init parameter alongside the other two. Also add four targeted tests in `AnalysisResultTests` exercising the non-default paths of the two analysis-shaping knobs: - `unspecializedGenericInitializersAreSkippedByDefault` and `extractsGenericTypeInitializersKeepsBaseInitializers` confirm the base `Tank` flips between 0 and 2 initializers as the knob toggles. - `canImportGuardedDeclsAreSkippedWhenModuleNotAvailable` and `availableImportModulesActivatesCanImportClause` confirm `#if canImport(MadeUpModule)`-guarded types are extracted only when the module is listed in `availableImportModules`. --- .../SwiftExtractConfiguration.swift | 3 + .../AnalysisResultTests.swift | 114 ++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/Sources/SwiftExtract/SwiftExtractConfiguration.swift b/Sources/SwiftExtract/SwiftExtractConfiguration.swift index 090912599..cb6840495 100644 --- a/Sources/SwiftExtract/SwiftExtractConfiguration.swift +++ b/Sources/SwiftExtract/SwiftExtractConfiguration.swift @@ -110,6 +110,7 @@ public struct DefaultSwiftExtractConfiguration: SwiftExtractConfiguration { public var swiftExtractAccessLevel: AccessLevelMode public var swiftExtractLogLevel: Logger.Level? public var extractsOperators: Bool + public var extractsGenericTypeInitializers: Bool public var availableImportModules: Set public init( @@ -117,6 +118,7 @@ public struct DefaultSwiftExtractConfiguration: SwiftExtractConfiguration { accessLevel: AccessLevelMode = .public, logLevel: Logger.Level? = nil, extractsOperators: Bool = false, + extractsGenericTypeInitializers: Bool = false, staticBuildConfigurationFile: String? = nil, swiftFilterInclude: [String]? = nil, swiftFilterExclude: [String]? = nil, @@ -127,6 +129,7 @@ public struct DefaultSwiftExtractConfiguration: SwiftExtractConfiguration { self.swiftExtractAccessLevel = accessLevel self.swiftExtractLogLevel = logLevel self.extractsOperators = extractsOperators + self.extractsGenericTypeInitializers = extractsGenericTypeInitializers self.staticBuildConfigurationFile = staticBuildConfigurationFile self.swiftFilterInclude = swiftFilterInclude self.swiftFilterExclude = swiftFilterExclude diff --git a/Tests/SwiftExtractTests/AnalysisResultTests.swift b/Tests/SwiftExtractTests/AnalysisResultTests.swift index ad9cbec7c..64ff1d3c1 100644 --- a/Tests/SwiftExtractTests/AnalysisResultTests.swift +++ b/Tests/SwiftExtractTests/AnalysisResultTests.swift @@ -313,4 +313,118 @@ struct AnalysisResultSuite { #expect(result.extractedGlobalFuncs.isEmpty) #expect(result.extractedGlobalVariables.isEmpty) } + + // ==== ----------------------------------------------------------------------- + // MARK: Configuration knobs + + /// By default, initializers on an unspecialized generic type are NOT + /// extracted: the open generic isn't directly constructible, so swift-java + /// (and any caller leaving the knob at its default `false`) drops them. + @Test func unspecializedGenericInitializersAreSkippedByDefault() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public struct Tank { + public init() {} + public init(capacity: Int) {} + } + """ + ) + ], + moduleName: "Aquarium" + ) + + let tank = try #require(result.extractedTypes["Tank"]) + #expect(tank.swiftNominal.isGeneric) + #expect(!tank.isSpecialization) + #expect(tank.initializers.isEmpty) + } + + /// Opting into `extractsGenericTypeInitializers` keeps the base type's + /// initializers in the analysis result so post-analysis specializers can + /// clone them onto a concrete `Tank`. + @Test func extractsGenericTypeInitializersKeepsBaseInitializers() throws { + var config = DefaultSwiftExtractConfiguration() + config.extractsGenericTypeInitializers = true + + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public struct Tank { + public init() {} + public init(capacity: Int) {} + } + """ + ) + ], + moduleName: "Aquarium", + config: config + ) + + let tank = try #require(result.extractedTypes["Tank"]) + #expect(tank.swiftNominal.isGeneric) + #expect(!tank.isSpecialization) + #expect(tank.initializers.count == 2) + } + + /// `#if canImport()` blocks are inactive by default for modules the + /// build configuration doesn't know about — the type guarded behind them + /// must not appear in the analysis result. + @Test func canImportGuardedDeclsAreSkippedWhenModuleNotAvailable() throws { + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public struct AlwaysHere { + public init() {} + } + #if canImport(MadeUpModule) + public struct OnlyWhenImportable { + public init() {} + } + #endif + """ + ) + ], + moduleName: "Aquarium" + ) + + #expect(result.extractedTypes["AlwaysHere"] != nil) + #expect(result.extractedTypes["OnlyWhenImportable"] == nil) + } + + /// Adding the module to `availableImportModules` activates the + /// `#if canImport()` clause so its declarations are extracted. + @Test func availableImportModulesActivatesCanImportClause() throws { + var config = DefaultSwiftExtractConfiguration() + config.availableImportModules = ["MadeUpModule"] + + let result = try SwiftAnalyzer.analyze( + sources: [ + ( + "/fake/Source.swift", + """ + public struct AlwaysHere { + public init() {} + } + #if canImport(MadeUpModule) + public struct OnlyWhenImportable { + public init() {} + } + #endif + """ + ) + ], + moduleName: "Aquarium", + config: config + ) + + #expect(result.extractedTypes["AlwaysHere"] != nil) + #expect(result.extractedTypes["OnlyWhenImportable"] != nil) + } } From 9964dcfccfc96dbb88856ef519b9a2cf7123dded Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 4 Jun 2026 08:50:17 +0900 Subject: [PATCH 11/13] SwiftExtract: document the dummy.json placeholder in Package.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `Resources/dummy.json` placeholder exists only so SwiftPM emits a `Bundle.module` for the SwiftExtract target — the real `static-build-config.json` is generated at build time by `_StaticBuildConfigPlugin`. Note that on the `.process("Resources")` line so a future reader doesn't try to delete the empty-looking file. --- Package.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Package.swift b/Package.swift index b830a610c..bcdf2dd54 100644 --- a/Package.swift +++ b/Package.swift @@ -354,6 +354,9 @@ let package = Package( ], path: "Sources/SwiftExtract", resources: [ + // Holds the `dummy.json` placeholder so SwiftPM emits a `Bundle.module` + // for this target. The real `static-build-config.json` is generated at + // build time by the `_StaticBuildConfigPlugin` build tool below. .process("Resources") ], swiftSettings: [ From 792a9ae623f21ac97c521e4ea3069d1d65b7e9c9 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 4 Jun 2026 12:02:30 +0900 Subject: [PATCH 12/13] SwiftExtract: doc cleanup, drop default knob impls, drop swift-5 mode on tests Three small review-driven cleanups: - `SwiftAnalyzer` doc: drop the parenthetical example in the lead-in and fix "language-neutral" to "output is language-neutral"; drop the "useful for tests / no code generation" sentence on the static `analyze` convenience. - `SwiftExtractConfiguration`: remove the protocol-extension defaults for `extractsOperators` and `extractsGenericTypeInitializers`. Both are semantic decisions about what the analysis layer should do for a given language target; making conformers state their position keeps a new language code generator from silently inheriting the Java-specific defaults. `Configuration` (swift-java) now declares both as `false` explicitly. - `SwiftExtractTests`: drop the `.swiftLanguageMode(.v5)` override; the target compiles and runs cleanly under the package's default Swift 6 mode. --- Package.swift | 3 --- Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift | 5 +++++ Sources/SwiftExtract/SwiftAnalyzer.swift | 7 +++---- Sources/SwiftExtract/SwiftExtractConfiguration.swift | 4 ---- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Package.swift b/Package.swift index bcdf2dd54..3bff5f029 100644 --- a/Package.swift +++ b/Package.swift @@ -475,9 +475,6 @@ let package = Package( "SwiftExtract", .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), - ], - swiftSettings: [ - .swiftLanguageMode(.v5) ] ), diff --git a/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift b/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift index eef586b89..50bf71225 100644 --- a/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift +++ b/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift @@ -42,6 +42,11 @@ extension Configuration: SwiftExtractConfiguration { case .critical: return .critical } } + + // swift-java targets Java, which cannot express Swift operators or + // construct open generic types directly: leave both knobs off + public var extractsOperators: Bool { false } + public var extractsGenericTypeInitializers: Bool { false } } extension LogLevel { diff --git a/Sources/SwiftExtract/SwiftAnalyzer.swift b/Sources/SwiftExtract/SwiftAnalyzer.swift index 9c16013bb..ccea5c0e4 100644 --- a/Sources/SwiftExtract/SwiftAnalyzer.swift +++ b/Sources/SwiftExtract/SwiftAnalyzer.swift @@ -19,9 +19,9 @@ import SwiftParser import SwiftSyntax /// Drives the analysis of Swift source code into an `AnalysisResult` that -/// downstream language generators (e.g. Java/JNI/FFM, others) can consume +/// downstream language generators can consume. /// -/// The analysis is language-neutral; language-specific extraction rules +/// The analysis output is language-neutral; language-specific extraction rules /// (such as honoring Java's `@JavaExport` or skipping `@JavaClass`-wrapped /// types) are layered in via an optional `ExtractDecider` public final class SwiftAnalyzer { @@ -157,8 +157,7 @@ extension SwiftAnalyzer { } /// Top-level convenience: run analysis on the given Swift sources and return - /// the resulting `AnalysisResult`. Useful for tests and for callers that only - /// need analysis (no code generation). + /// the resulting `AnalysisResult`. public static func analyze( sources: [(path: String, text: String)], moduleName: String, diff --git a/Sources/SwiftExtract/SwiftExtractConfiguration.swift b/Sources/SwiftExtract/SwiftExtractConfiguration.swift index cb6840495..03e768421 100644 --- a/Sources/SwiftExtract/SwiftExtractConfiguration.swift +++ b/Sources/SwiftExtract/SwiftExtractConfiguration.swift @@ -87,10 +87,6 @@ public protocol SwiftExtractConfiguration { } extension SwiftExtractConfiguration { - public var extractsOperators: Bool { false } - - public var extractsGenericTypeInitializers: Bool { false } - public var availableImportModules: Set { [] } public func hasImportedModuleStub(moduleOfNominal moduleName: String) -> Bool { From 9ff62865fee788c5d0773bc139aed3c3530c4aa4 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 4 Jun 2026 12:03:46 +0900 Subject: [PATCH 13/13] SwiftExtract: extract AccessLevelMode into SwiftExtractConfigurationShared MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer asked: "why does swift-java need to map at all, can it not use this exact enum?" — yes, after introducing a small shared target. `SwiftJavaConfigurationShared` is intentionally lightweight (stdlib + Foundation only): it must be symlinked into each plugin's source tree because SwiftPM plugins can't have target dependencies. Pulling SwiftSyntax in via `SwiftExtract` would balloon plugin builds. Instead, introduce a sibling `SwiftExtractConfigurationShared` target that holds nothing but the small `AccessLevelMode` enum. Both `SwiftExtract` and `SwiftJavaConfigurationShared` depend on it; the plugin symlink discipline (`Plugins/PluginsShared/SwiftExtractConfigurationShared`) mirrors the existing `SwiftJavaConfigurationShared` symlink. Effects: - `AccessLevelMode` is the single, shared enum. swift-java's `Configuration` uses it directly via `@_exported import`, retiring `JExtractMinimumAccessLevelMode` and the four-line mapping switch in `Configuration+SwiftExtract.swift` (now an identity passthrough). - `SwiftJavaConfigurationShared/Configuration.swift` guards the import with `#if canImport(SwiftExtractConfigurationShared)` so plugin builds (which inline the file alongside `AccessLevelMode.swift` via symlink rather than as a separate module) still compile. - `AccessLevelMode` gains `@nonexhaustive` (SE-0487, gated with `#if compiler(>=6.2)`) per reviewer request, so adding cases in the future is non-breaking. `Codable` conformance is added so it can replace `JExtractMinimumAccessLevelMode` in the on-disk `Configuration` JSON without changing the wire format. - The CLI's `@Option var minimumInputAccessLevelMode` and the `ExpressibleByArgument` conformance switch over to `AccessLevelMode` accordingly. --- Package.swift | 15 +++++++- .../SwiftExtractConfigurationShared | 1 + .../Configuration+SwiftExtract.swift | 14 ++++---- .../SwiftExtractConfiguration.swift | 22 +++++------- .../AccessLevelMode.swift | 34 +++++++++++++++++++ .../Configuration.swift | 11 ++++-- .../JExtractMinimumAccessLevelMode.swift | 26 -------------- .../Commands/JExtractCommand.swift | 4 +-- 8 files changed, 75 insertions(+), 52 deletions(-) create mode 120000 Plugins/PluginsShared/SwiftExtractConfigurationShared create mode 100644 Sources/SwiftExtractConfigurationShared/AccessLevelMode.swift delete mode 100644 Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift diff --git a/Package.swift b/Package.swift index 3bff5f029..22656a0ac 100644 --- a/Package.swift +++ b/Package.swift @@ -43,6 +43,11 @@ let package = Package( targets: ["SwiftJavaConfigurationShared"] ), + .library( + name: "SwiftExtractConfigurationShared", + targets: ["SwiftExtractConfigurationShared"] + ), + .library( name: "JavaUtil", targets: ["JavaUtil"] @@ -276,7 +281,14 @@ let package = Package( ), .target( - name: "SwiftJavaConfigurationShared" + name: "SwiftJavaConfigurationShared", + dependencies: [ + "SwiftExtractConfigurationShared" + ] + ), + + .target( + name: "SwiftExtractConfigurationShared" ), .target( @@ -351,6 +363,7 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "Logging", package: "swift-log"), + "SwiftExtractConfigurationShared", ], path: "Sources/SwiftExtract", resources: [ diff --git a/Plugins/PluginsShared/SwiftExtractConfigurationShared b/Plugins/PluginsShared/SwiftExtractConfigurationShared new file mode 120000 index 000000000..263f08269 --- /dev/null +++ b/Plugins/PluginsShared/SwiftExtractConfigurationShared @@ -0,0 +1 @@ +../../Sources/SwiftExtractConfigurationShared \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift b/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift index 50bf71225..d55e59675 100644 --- a/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift +++ b/Sources/JExtractSwiftLib/Configuration+SwiftExtract.swift @@ -18,16 +18,14 @@ import SwiftJavaConfigurationShared /// Bridges swift-java's `Configuration` onto the language-neutral /// `SwiftExtractConfiguration` surface consumed by `SwiftExtract`. /// -/// Most members are satisfied directly by `Configuration`'s own properties; only -/// the two enum-typed members need a mapping from swift-java's enums onto the -/// neutral `AccessLevelMode` / `Logger.Level`. +/// Most members are satisfied directly by `Configuration`'s own properties. +/// `Configuration` shares `AccessLevelMode` with the analyzer (both pull it in +/// from `SwiftExtractConfigurationShared`), so `swiftExtractAccessLevel` is a +/// straight passthrough. Only `swiftExtractLogLevel` needs a mapping from +/// swift-java's `LogLevel` onto the neutral `Logger.Level`. extension Configuration: SwiftExtractConfiguration { public var swiftExtractAccessLevel: AccessLevelMode { - switch effectiveMinimumInputAccessLevelMode { - case .public: .public - case .package: .package - case .internal: .internal - } + effectiveMinimumInputAccessLevelMode } public var swiftExtractLogLevel: SwiftExtract.Logger.Level? { diff --git a/Sources/SwiftExtract/SwiftExtractConfiguration.swift b/Sources/SwiftExtract/SwiftExtractConfiguration.swift index 03e768421..ba3e20557 100644 --- a/Sources/SwiftExtract/SwiftExtractConfiguration.swift +++ b/Sources/SwiftExtract/SwiftExtractConfiguration.swift @@ -12,16 +12,7 @@ // //===----------------------------------------------------------------------===// -/// Minimum access level a declaration must have to be considered for extraction. -/// -/// Language-neutral counterpart to a configuration's access-level setting. The -/// concrete `Configuration` types in language layers (e.g. swift-java, or other -/// language code generators) map their own enums onto this. -public enum AccessLevelMode: String, Sendable { - case `public` - case `package` - case `internal` -} +@_exported import SwiftExtractConfigurationShared /// The configuration surface required by the language-neutral `SwiftExtract` /// analysis layer. @@ -33,9 +24,14 @@ public enum AccessLevelMode: String, Sendable { /// (e.g. Java/JNI/FFM, or other language code generators) without pulling /// target-specific config types into `SwiftExtract`. /// -/// The two enum-typed members use `swiftExtract`-prefixed names so a conforming -/// type can keep its own, differently-typed `logLevel` / -/// `effectiveMinimumInputAccessLevelMode` members without a name collision. +/// `AccessLevelMode` lives in the small `SwiftExtractConfigurationShared` +/// target so language-specific configuration shared modules (e.g. +/// `SwiftJavaConfigurationShared`) can use the same enum directly without +/// taking a dependency on SwiftSyntax. +/// +/// The enum-typed `swiftExtractLogLevel` member uses a `swiftExtract`-prefixed +/// name so a conforming type can keep its own, differently-typed `logLevel` +/// member without a name collision. public protocol SwiftExtractConfiguration { /// Name of the Swift module being analyzed. var swiftModule: String? { get } diff --git a/Sources/SwiftExtractConfigurationShared/AccessLevelMode.swift b/Sources/SwiftExtractConfigurationShared/AccessLevelMode.swift new file mode 100644 index 000000000..89d4325b3 --- /dev/null +++ b/Sources/SwiftExtractConfigurationShared/AccessLevelMode.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2026 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 +// +//===----------------------------------------------------------------------===// + +/// Minimum access level a declaration must have to be considered for extraction. +/// +/// Lives in the small `SwiftExtractConfigurationShared` target so the analysis +/// layer and language-specific configuration layers (e.g. swift-java's +/// `SwiftJavaConfigurationShared`) can both depend on it without dragging +/// SwiftSyntax into the latter. +#if compiler(>=6.2) +@nonexhaustive +#endif +public enum AccessLevelMode: String, Codable, Sendable { + case `public` + case `package` + case `internal` +} + +extension AccessLevelMode { + public static var `default`: Self { + .public + } +} diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 1562b4bf6..61af95a33 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -14,6 +14,13 @@ import Foundation +// In a real module build this resolves to a separate target. In plugin builds +// the file is inlined (via symlink) alongside `AccessLevelMode.swift`, so the +// module isn't a discoverable import — guard with canImport. +#if canImport(SwiftExtractConfigurationShared) +@_exported import SwiftExtractConfigurationShared +#endif + //////////////////////////////////////////////////////////////////////////////// // This file is only supposed to be edited in `Shared/` and must be symlinked // // from everywhere else! We cannot share dependencies with or between plugins // @@ -62,8 +69,8 @@ public struct Configuration: Codable { writeEmptyFiles ?? false } - public var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? - public var effectiveMinimumInputAccessLevelMode: JExtractMinimumAccessLevelMode { + public var minimumInputAccessLevelMode: AccessLevelMode? + public var effectiveMinimumInputAccessLevelMode: AccessLevelMode { minimumInputAccessLevelMode ?? .default } diff --git a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift b/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift deleted file mode 100644 index 22fead577..000000000 --- a/Sources/SwiftJavaConfigurationShared/JExtract/JExtractMinimumAccessLevelMode.swift +++ /dev/null @@ -1,26 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 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 -// -//===----------------------------------------------------------------------===// - -/// The minimum access level which -public enum JExtractMinimumAccessLevelMode: String, Codable { - case `public` - case `package` - case `internal` -} - -extension JExtractMinimumAccessLevelMode { - public static var `default`: Self { - .public - } -} diff --git a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift index f901eb731..020ae8202 100644 --- a/Sources/SwiftJavaTool/Commands/JExtractCommand.swift +++ b/Sources/SwiftJavaTool/Commands/JExtractCommand.swift @@ -66,7 +66,7 @@ extension SwiftJava { var writeEmptyFiles: Bool? @Option(help: "The lowest access level of Swift declarations that should be extracted, defaults to 'public'.") - var minimumInputAccessLevelMode: JExtractMinimumAccessLevelMode? + var minimumInputAccessLevelMode: AccessLevelMode? @Option( help: @@ -222,6 +222,6 @@ struct IllegalModeCombinationError: Error { } extension JExtractGenerationMode: ExpressibleByArgument {} -extension JExtractMinimumAccessLevelMode: ExpressibleByArgument {} +extension AccessLevelMode: ExpressibleByArgument {} extension JExtractMemoryManagementMode: ExpressibleByArgument {} extension JExtractAsyncFuncMode: ExpressibleByArgument {}