diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 910f4de5c..f2b031c07 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,6 +106,24 @@ In the case of formatting issues, `git add` the formatting changes and attempt t Some documentation is generated from source and checked in, so CI can catch it drifting out of date. If you changed `Configuration.swift` or a CLI command's flags/help text, run `scripts/generate-docs.sh` and commit the generated changes. +### Developing against local dependency checkouts + +swift-java depends on [swift-java-jni-core](https://github.com/swiftlang/swift-java-jni-core) and [swift-binding-generator-core](https://github.com/swiftlang/swift-binding-generator-core) (home of the `SwiftExtract` library) from their `main` branches. To develop against local checkouts instead, clone the repositories next to your swift-java checkout and point the corresponding environment variables at them: + +```bash +export PROJECT_ROOT= # the directory containing your checkouts +cd $PROJECT_ROOT +ls +> swift-java +> swift-java-jni-core +> swift-binding-generator-core +> # ...and all the others here + +export SWIFT_JAVA_JNI_CORE_PATH=$PROJECT_ROOT/swift-java-jni-core +export SWIFT_BINDING_GENERATOR_CORE_PATH=$PROJECT_ROOT/swift-binding-generator-core +swift build +``` + ## How to contribute your work Please open a pull request at https://github.com/swiftlang/swift-java. Make sure the CI passes, and then wait for code review. diff --git a/Package.swift b/Package.swift index 14bd36aea..90f5b64a7 100644 --- a/Package.swift +++ b/Package.swift @@ -11,6 +11,13 @@ if let localPath = Context.environment["SWIFT_JAVA_JNI_CORE_PATH"] { swiftJavaJNICoreDep = .package(url: "https://github.com/swiftlang/swift-java-jni-core", branch: "main") } +let swiftBindingGeneratorCoreDep: Package.Dependency +if let localPath = Context.environment["SWIFT_BINDING_GENERATOR_CORE_PATH"] { + swiftBindingGeneratorCoreDep = .package(path: localPath) +} else { + swiftBindingGeneratorCoreDep = .package(url: "https://github.com/swiftlang/swift-binding-generator-core", branch: "main") +} + // Set SWIFTJAVA_DOCC_PLUGIN_INSTALL=1 to install the docc-plugin automatically, // or DOCC_PLUGIN_PATH= to point at a local checkout. // This is a workaround because swift-subprocess includes the plugin explicitly, @@ -59,11 +66,6 @@ let package = Package( targets: ["SwiftJavaConfigurationShared"] ), - .library( - name: "SwiftExtractConfigurationShared", - targets: ["SwiftExtractConfigurationShared"] - ), - .library( name: "JavaUtil", targets: ["JavaUtil"] @@ -129,11 +131,6 @@ let package = Package( targets: ["SwiftRuntimeFunctions"] ), - .library( - name: "SwiftExtract", - targets: ["SwiftExtract"] - ), - .library( name: "CodePrinting", targets: ["CodePrinting"] @@ -166,6 +163,7 @@ let package = Package( ], dependencies: [ swiftJavaJNICoreDep, + swiftBindingGeneratorCoreDep, .package(url: "https://github.com/swiftlang/swift-syntax", from: "603.0.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.8.0"), .package(url: "https://github.com/apple/swift-system", from: "1.4.0"), @@ -304,14 +302,10 @@ let package = Package( .target( name: "SwiftJavaConfigurationShared", dependencies: [ - "SwiftExtractConfigurationShared" + .product(name: "SwiftExtractConfigurationShared", package: "swift-binding-generator-core") ] ), - .target( - name: "SwiftExtractConfigurationShared" - ), - .target( name: "SwiftJavaShared" ), @@ -352,6 +346,7 @@ let package = Package( .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SystemPackage", package: "swift-system"), + .product(name: "SwiftExtract", package: "swift-binding-generator-core"), "SwiftJava", "JavaUtilJar", "JavaNet", @@ -375,32 +370,6 @@ 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"), - "SwiftExtractConfigurationShared", - ], - 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: [ - .swiftLanguageMode(.v5) - ], - plugins: [ - .plugin(name: "_StaticBuildConfigPlugin") - ] - ), - .target( name: "JExtractSwiftLib", dependencies: [ @@ -412,7 +381,7 @@ 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", + .product(name: "SwiftExtract", package: "swift-binding-generator-core"), "SwiftJavaShared", "SwiftJavaConfigurationShared", "CodePrinting", @@ -423,14 +392,6 @@ let package = Package( ] ), - .executableTarget( - name: "StaticBuildConfigPluginExecutable", - dependencies: [ - .product(name: "Subprocess", package: "swift-subprocess"), - .product(name: "SwiftIfConfig", package: "swift-syntax"), - ] - ), - // Dev-time tool: regenerates the "Supported configuration options" table in // `Sources/SwiftJavaDocumentation/Documentation.docc/SwiftJavaConfigFile.md` // straight from the doc comments on `Configuration.swift` and the enums it @@ -448,14 +409,6 @@ let package = Package( path: "Sources/GenerateConfigDocs" ), - .plugin( - name: "_StaticBuildConfigPlugin", - capability: .buildTool(), - dependencies: [ - "StaticBuildConfigPluginExecutable" - ] - ), - .plugin( name: "JExtractSwiftPlugin", capability: .buildTool(), @@ -512,7 +465,7 @@ let package = Package( name: "JExtractSwiftTests", dependencies: [ "JExtractSwiftLib", - "SwiftExtract", + .product(name: "SwiftExtract", package: "swift-binding-generator-core"), "CodePrinting", ], swiftSettings: [ @@ -520,15 +473,6 @@ let package = Package( ] ), - .testTarget( - name: "SwiftExtractTests", - dependencies: [ - "SwiftExtract", - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax"), - ] - ), - .testTarget( name: "CodePrintingTests", dependencies: [ diff --git a/Plugins/PluginsShared/SwiftExtractConfigurationShared b/Plugins/PluginsShared/SwiftExtractConfigurationShared deleted file mode 120000 index 263f08269..000000000 --- a/Plugins/PluginsShared/SwiftExtractConfigurationShared +++ /dev/null @@ -1 +0,0 @@ -../../Sources/SwiftExtractConfigurationShared \ No newline at end of file diff --git a/Sources/SwiftExtractConfigurationShared/AccessLevelMode.swift b/Plugins/PluginsShared/SwiftExtractConfigurationShared/AccessLevelMode.swift similarity index 100% rename from Sources/SwiftExtractConfigurationShared/AccessLevelMode.swift rename to Plugins/PluginsShared/SwiftExtractConfigurationShared/AccessLevelMode.swift diff --git a/Plugins/PluginsShared/SwiftExtractConfigurationShared/DO_NOT_EDIT.txt b/Plugins/PluginsShared/SwiftExtractConfigurationShared/DO_NOT_EDIT.txt new file mode 100644 index 000000000..af5089910 --- /dev/null +++ b/Plugins/PluginsShared/SwiftExtractConfigurationShared/DO_NOT_EDIT.txt @@ -0,0 +1,14 @@ +DO NOT EDIT THESE FILES IN THIS REPOSITORY. + +The files in this directory are vendored, byte-identical copies of: + + https://github.com/swiftlang/swift-binding-generator-core + Sources/SwiftExtractConfigurationShared/ + +They exist because SwiftPM package plugins cannot take sources from a package +dependency, so the plugins in this repository compile these files directly. + +Make any changes in swift-binding-generator-core instead, and then copy the +updated files here. The swift-java compatibility CI job in +swift-binding-generator-core diffs these copies against its own sources and +will flag any drift. diff --git a/Sources/SwiftExtractConfigurationShared/LogLevel.swift b/Plugins/PluginsShared/SwiftExtractConfigurationShared/LogLevel.swift similarity index 100% rename from Sources/SwiftExtractConfigurationShared/LogLevel.swift rename to Plugins/PluginsShared/SwiftExtractConfigurationShared/LogLevel.swift diff --git a/Plugins/_StaticBuildConfigPlugin/_StaticBuildConfigPlugin.swift b/Plugins/_StaticBuildConfigPlugin/_StaticBuildConfigPlugin.swift deleted file mode 100644 index 7feb83929..000000000 --- a/Plugins/_StaticBuildConfigPlugin/_StaticBuildConfigPlugin.swift +++ /dev/null @@ -1,34 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 Foundation -import PackagePlugin - -@main -struct _StaticBuildConfigPlugin: BuildToolPlugin { - func createBuildCommands(context: PluginContext, target: any Target) async throws -> [Command] { - let executable = try context.tool(named: "StaticBuildConfigPluginExecutable").url - let out = context.pluginWorkDirectoryURL.appending(path: "static-build-config.json") - return [ - .buildCommand( - displayName: "Run -print-static-build-config", - executable: executable, - arguments: [out.path(percentEncoded: false)], - environment: [:], - inputFiles: [], - outputFiles: [out] - ) - ] - } -} diff --git a/Sources/GenerateConfigDocs/GenerateConfigDocs.swift b/Sources/GenerateConfigDocs/GenerateConfigDocs.swift index b5c5b2bf9..a667172d2 100644 --- a/Sources/GenerateConfigDocs/GenerateConfigDocs.swift +++ b/Sources/GenerateConfigDocs/GenerateConfigDocs.swift @@ -49,13 +49,29 @@ struct GenerateConfigDocs: ParsableCommand { .appendingPathComponent("Documentation.docc", isDirectory: true) .appendingPathComponent("SwiftJavaConfigFile.md", isDirectory: false) + // `SwiftExtractConfigurationShared` lives in the swift-binding-generator-core + // package since `SwiftExtract` was split out of this repo. Scan every location + // it may be found in. let scanDirs: [URL] = [ repoRoot .appendingPathComponent("Sources", isDirectory: true) .appendingPathComponent("SwiftJavaConfigurationShared", isDirectory: true), + // Legacy in-repo location (pre-split layout). repoRoot .appendingPathComponent("Sources", isDirectory: true) .appendingPathComponent("SwiftExtractConfigurationShared", isDirectory: true), + // Resolved swift-binding-generator-core checkout. + repoRoot + .appendingPathComponent(".build", isDirectory: true) + .appendingPathComponent("checkouts", isDirectory: true) + .appendingPathComponent("swift-binding-generator-core", isDirectory: true) + .appendingPathComponent("Sources", isDirectory: true) + .appendingPathComponent("SwiftExtractConfigurationShared", isDirectory: true), + // Vendored copy shared with the build plugins. + repoRoot + .appendingPathComponent("Plugins", isDirectory: true) + .appendingPathComponent("PluginsShared", isDirectory: true) + .appendingPathComponent("SwiftExtractConfigurationShared", isDirectory: true), ] // Parse enums and structs referenced by Configuration. diff --git a/Sources/SwiftExtract/Convenience/Collection+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/Collection+SwiftExtract.swift similarity index 64% rename from Sources/SwiftExtract/Convenience/Collection+Extensions.swift rename to Sources/JExtractSwiftLib/Convenience/Collection+SwiftExtract.swift index e04341368..3b6b048a6 100644 --- a/Sources/SwiftExtract/Convenience/Collection+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/Collection+SwiftExtract.swift @@ -12,20 +12,6 @@ // //===----------------------------------------------------------------------===// -extension Dictionary { - /// Same values, corresponding to mapped keys. - public func mapKeys( - _ transform: (Key) throws -> Transformed - ) rethrows -> [Transformed: Value] { - .init( - uniqueKeysWithValues: try map { - (try transform($0.key), $0.value) - } - ) - } - -} - extension Collection { package typealias IsLastElement = Bool package var withIsLast: any Collection<(Element, IsLastElement)> { @@ -39,13 +25,3 @@ extension Collection { } } } - -extension Collection where Element == Int { - var sum: Int { - var s = 0 - for i in self { - s += i - } - return s - } -} diff --git a/Sources/SwiftExtract/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+SwiftExtract.swift similarity index 100% rename from Sources/SwiftExtract/Convenience/String+Extensions.swift rename to Sources/JExtractSwiftLib/Convenience/String+SwiftExtract.swift diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift new file mode 100644 index 000000000..2ba86938a --- /dev/null +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +extension DeclSyntaxProtocol { + /// Signature part of the declaration. I.e. without body or member block. + package var signatureString: String { + switch DeclSyntax(self.detached).as(DeclSyntaxEnum.self) { + case .functionDecl(let node): + node.with(\.body, nil).triviaSanitizedDescription + case .initializerDecl(let node): + node.with(\.body, nil).triviaSanitizedDescription + case .classDecl(let node): + node.with(\.memberBlock, "").triviaSanitizedDescription + case .structDecl(let node): + node.with(\.memberBlock, "").triviaSanitizedDescription + case .protocolDecl(let node): + node.with(\.memberBlock, "").triviaSanitizedDescription + case .accessorDecl(let node): + node.with(\.body, nil).triviaSanitizedDescription + case .subscriptDecl(let node): + node.with(\.accessorBlock, nil).triviaSanitizedDescription + case .variableDecl(let node): + node + .with( + \.bindings, + PatternBindingListSyntax( + node.bindings.map { + $0.detached + .with(\.accessorBlock, nil) + .with(\.initializer, nil) + } + ), + ) + .triviaSanitizedDescription + case .enumCaseDecl(let node): + node.triviaSanitizedDescription + default: + fatalError("unimplemented \(self.kind)") + } + } + + + /// Syntax text but without unnecessary trivia. + /// + /// Connective trivia are condensed into a single whitespace, but no space + /// after opening or before closing parentheses + var triviaSanitizedDescription: String { + let visitor = TriviaSanitizingDescriptionVisitor(viewMode: .sourceAccurate) + visitor.walk(self.trimmed) + return visitor.result + } +} + +class TriviaSanitizingDescriptionVisitor: SyntaxVisitor { + var result: String = "" + + var prevTokenKind: TokenKind = .endOfFile + var prevHadTrailingSpace: Bool = false + + override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { + let tokenKind = node.tokenKind + switch (prevTokenKind, tokenKind) { + case // No whitespace after open parentheses. + (.leftAngle, _), (.leftParen, _), (.leftSquare, _), (.endOfFile, _), + // No whitespace before close parentheses. + (_, .rightAngle), (_, .rightParen), (_, .rightSquare): + break + default: + if prevHadTrailingSpace || !node.leadingTrivia.isEmpty { + result += " " + } + } + result += node.text + prevTokenKind = tokenKind + prevHadTrailingSpace = !node.trailingTrivia.isEmpty + + return .skipChildren + } +} diff --git a/Sources/StaticBuildConfigPluginExecutable/StaticBuildConfigPluginExecutable.swift b/Sources/StaticBuildConfigPluginExecutable/StaticBuildConfigPluginExecutable.swift deleted file mode 100644 index 52a1d07be..000000000 --- a/Sources/StaticBuildConfigPluginExecutable/StaticBuildConfigPluginExecutable.swift +++ /dev/null @@ -1,60 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 Foundation -import Subprocess -import SwiftIfConfig - -@main struct StaticBuildConfigPluginExecutable { - static func main() async throws { - let args = CommandLine.arguments - guard args.count > 1 else { - print("Usage: \(args[0]) ") - return - } - let dst = URL(fileURLWithPath: args[1]) - - let data = try await loadStaticBuildConfig() - try data.write(to: dst, options: .atomic) - } - - static func loadStaticBuildConfig() async throws -> Data { - #if compiler(>=6.3) - let result = try await run( - .name("swift"), - arguments: ["frontend", "-print-static-build-config", "-target", "aarch64-unknown-linux-gnu"], - output: .data(limit: 65536), - error: .string(limit: 65536) - ) - if let error = result.standardError, !error.isEmpty { - fatalError(error) - } - return result.standardOutput - #else - #if compiler(>=6.2) - let configuration = StaticBuildConfiguration( - languageVersion: .init(components: [5, 10]), - compilerVersion: .init(components: [6, 2]) - ) - #else - let configuration = StaticBuildConfiguration( - languageVersion: .init(components: [5, 10]), - compilerVersion: .init(components: [6, 1]) - ) - #endif - let encoder = JSONEncoder() - return try encoder.encode(configuration) - #endif - } -} diff --git a/Sources/SwiftExtract/AnalysisResult.swift b/Sources/SwiftExtract/AnalysisResult.swift deleted file mode 100644 index 259882a96..000000000 --- a/Sources/SwiftExtract/AnalysisResult.swift +++ /dev/null @@ -1,203 +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 complete analysis result of the analyzed Swift inputs. -/// This is used as the primary input to source generators, which then act on the analyzed decls. -public struct AnalysisResult { - public var extractedTypes: [SwiftTypeName: ExtractedNominalType] - public var extractedGlobalVariables: [ExtractedFunc] - public var extractedGlobalFuncs: [ExtractedFunc] - - /// Extensions the analyzed module declares on nominal types from other modules. - /// - /// Separate from `extractedTypes`, since we're not declaring types here at all, just extensions. - public var crossModuleExtensions: CrossModuleExtensions - - public init( - extractedTypes: [SwiftTypeName: ExtractedNominalType], - extractedGlobalVariables: [ExtractedFunc], - extractedGlobalFuncs: [ExtractedFunc], - crossModuleExtensions: CrossModuleExtensions = CrossModuleExtensions() - ) { - self.extractedTypes = extractedTypes - self.extractedGlobalVariables = extractedGlobalVariables - self.extractedGlobalFuncs = extractedGlobalFuncs - self.crossModuleExtensions = crossModuleExtensions - } - - /// Expands variadic functions into distinct overloads. - public mutating func expandVariadicOverloads(maxOverloads: Int) { - self.extractedGlobalFuncs = self.extractedGlobalFuncs.flatMap { - $0.expandingVariadicOverloads(maxOverloads: maxOverloads) - } - - for type in self.extractedTypes.values { - type.methods = type.methods.flatMap { $0.expandingVariadicOverloads(maxOverloads: maxOverloads) } - type.initializers = type.initializers.flatMap { $0.expandingVariadicOverloads(maxOverloads: maxOverloads) } - } - } -} - -// ==== ----------------------------------------------------------------------- -// MARK: Conformance queries - -/// A conformance the analysis observed, either stated directly or inherited -public struct ObservedConformance { - /// How this conformance was derived - public enum Derivation: Equatable { - /// An inheritance clause named the protocol directly - case stated - /// Reached from a stated conformance by inheritance. - case inherited(via: [SwiftNominalIdentity]) - } - - /// The conforming nominal - public let type: SwiftNominalIdentity - - /// The protocol it conforms to - public let protocolType: SwiftNominalIdentity - - public let derivation: Derivation - - /// Requirements guarding the *stated* conformance this was derived from. - /// Empty when the conformance is unconditional. - public let requirements: [SwiftGenericRequirement] - - /// True when the guarding `where` clause held a requirement the analyzer could not - /// represent, so `requirements` is an incomplete picture. - /// - /// This can happen when a not-extracted type is used in a requirement. - public let hasUnrepresentableRequirements: Bool - - public init( - type: SwiftNominalIdentity, - protocolType: SwiftNominalIdentity, - derivation: Derivation, - requirements: [SwiftGenericRequirement], - hasUnrepresentableRequirements: Bool = false - ) { - self.type = type - self.protocolType = protocolType - self.derivation = derivation - self.requirements = requirements - self.hasUnrepresentableRequirements = hasUnrepresentableRequirements - } - - /// Whether this conformance applies only under a `where` clause - public var isConditional: Bool { - !requirements.isEmpty || hasUnrepresentableRequirements - } -} - -extension AnalysisResult { - - /// Every nominal the analysis observed to conform to `protocolType`. - public func typesConforming(to protocolType: SwiftNominalIdentity) -> [ObservedConformance] { - var results: [ObservedConformance] = [] - var refinementCache: [SwiftNominalIdentity: [SwiftNominalIdentity]?] = [:] - - /// The protocols walked from `stated` up to and including `protocolType`, or nil when - /// `stated` does not refine it. Empty when `stated` *is* `protocolType` - func refinementPath(from stated: SwiftNominalIdentity) -> [SwiftNominalIdentity]? { - if let cached = refinementCache[stated] { return cached } - // Seed the cache before recursing so a cyclic inheritance clause in malformed - // input cannot spin forever - refinementCache[stated] = .some(nil) - var path: [SwiftNominalIdentity]? = nil - if stated == protocolType { - path = [] - } else { - for parent in inheritedProtocolIdentities(of: stated) { - if let rest = refinementPath(from: parent) { - path = [parent] + rest - break - } - } - } - refinementCache[stated] = .some(path) - return path - } - - func consider( - conformer: SwiftNominalIdentity, - stated: SwiftNominalIdentity, - requirements: [SwiftGenericRequirement], - hasUnrepresentableRequirements: Bool - ) { - guard let path = refinementPath(from: stated) else { return } - // `path` ends at `protocolType`. The reportable chain starts at what source - // actually named and stops short of the protocol being asked about - let via = Array(([stated] + path).dropLast()) - results.append( - ObservedConformance( - type: conformer, - protocolType: protocolType, - derivation: path.isEmpty ? .stated : .inherited(via: via), - requirements: requirements, - hasUnrepresentableRequirements: hasUnrepresentableRequirements - ) - ) - } - - // Types this module declares, from their own inheritance clauses - for name in extractedTypes.keys.sorted() { - guard let type = extractedTypes[name], !type.swiftNominal.isProtocolKind else { continue } - let conformer = type.identity - for stated in protocolIdentities(in: type.inheritedTypes) { - consider( - conformer: conformer, - stated: stated, - requirements: [], - hasUnrepresentableRequirements: false - ) - } - } - - // Other module's types, from the extensions that gave them conformances - for record in crossModuleExtensions.all { - for stated in protocolIdentities(in: record.addedConformances) { - consider( - conformer: record.extendedType, - stated: stated, - requirements: record.requirements, - hasUnrepresentableRequirements: record.hasUnrepresentableRequirements - ) - } - } - - return results - } - - /// The protocols `protocolType` refines, as far as this analysis can see them. - /// - /// Resolved through `extractedTypes`. - private func inheritedProtocolIdentities(of protocolType: SwiftNominalIdentity) -> [SwiftNominalIdentity] { - guard let declared = extractedTypes[protocolType.qualifiedName] else { return [] } - return protocolIdentities(in: declared.inheritedTypes) - } - - private func protocolIdentities(in types: [SwiftType]) -> [SwiftNominalIdentity] { - types.compactMap { type in - guard let decl = type.asNominalTypeDeclaration, decl.isProtocolKind else { return nil } - return decl.identity - } - } -} - -extension SwiftNominalTypeDeclaration { - /// Whether this declaration is a protocol - var isProtocolKind: Bool { - kind == .protocol - } -} diff --git a/Sources/SwiftExtract/Convenience/SwiftSyntax+Extensions.swift b/Sources/SwiftExtract/Convenience/SwiftSyntax+Extensions.swift deleted file mode 100644 index 5c07c2018..000000000 --- a/Sources/SwiftExtract/Convenience/SwiftSyntax+Extensions.swift +++ /dev/null @@ -1,334 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SwiftDiagnostics -@_spi(ExperimentalLanguageFeatures) import SwiftSyntax - -extension WithModifiersSyntax { - var accessControlModifiers: DeclModifierListSyntax { - modifiers.filter { modifier in - modifier.isAccessControl - } - } -} - -extension ImplicitlyUnwrappedOptionalTypeSyntax { - var asOptionalTypeSyntax: any TypeSyntaxProtocol { - OptionalTypeSyntax( - leadingTrivia: leadingTrivia, - unexpectedBeforeWrappedType, - wrappedType: wrappedType, - self.unexpectedBetweenWrappedTypeAndExclamationMark, - self.unexpectedAfterExclamationMark, - trailingTrivia: self.trailingTrivia, - ) - } -} - -extension DeclModifierSyntax { - var isAccessControl: Bool { - switch self.name.tokenKind { - case .keyword(.private), .keyword(.fileprivate), .keyword(.internal), .keyword(.package), - .keyword(.public), .keyword(.open): - return true - default: - return false - } - } -} - -extension DeclModifierSyntax { - var isPublic: Bool { - switch self.name.tokenKind { - case .keyword(.private): false - case .keyword(.fileprivate): false - case .keyword(.internal): false - case .keyword(.package): false - case .keyword(.public): true - case .keyword(.open): true - default: false - } - } - - var isPackage: Bool { - switch self.name.tokenKind { - case .keyword(.private): false - case .keyword(.fileprivate): false - case .keyword(.internal): false - case .keyword(.package): true - case .keyword(.public): false - case .keyword(.open): false - default: false - } - } - - var isAtLeastPackage: Bool { - isPackage || isPublic - } - - var isInternal: Bool { - switch self.name.tokenKind { - case .keyword(.private): false - case .keyword(.fileprivate): false - case .keyword(.internal): true - case .keyword(.package): false - case .keyword(.public): false - case .keyword(.open): false - default: false - } - } - - var isAtLeastInternal: Bool { - isInternal || isPackage || isPublic - } -} - -extension AccessLevelMode { - package func matches(_ modifier: DeclModifierSyntax) -> Bool { - switch (self, modifier.name.tokenKind) { - case (.public, .keyword(.public)), - (.public, .keyword(.open)): - return true - case (.package, .keyword(.public)), - (.package, .keyword(.open)), - (.package, .keyword(.package)): - return true - case (.internal, .keyword(.public)), - (.internal, .keyword(.open)), - (.internal, .keyword(.package)), - (.internal, .keyword(.internal)): - return true - default: - return false - } - } -} - -extension WithModifiersSyntax { - package func isPublic(in type: NominalTypeDeclSyntaxNode?) -> Bool { - if let protocolDecl = type?.as(ProtocolDeclSyntax.self) { - return protocolDecl.isPublic(in: nil) - } - - return self.modifiers.contains { modifier in - modifier.isPublic - } - } - - package var isAtLeastPackage: Bool { - self.modifiers.contains { modifier in - modifier.isAtLeastPackage - } - } - - package var isAtLeastInternal: Bool { - if self.modifiers.isEmpty { - // we assume that default access level is internal - return true - } - - return self.modifiers.contains { modifier in - modifier.isAtLeastInternal - } - } - - /// Whether this declaration's access level is at least `mode`. The `parent` - /// is consulted only by the `.public` mode (so e.g. members of a public - /// protocol are themselves treated as public). - public func isAtLeast( - _ mode: AccessLevelMode, - in parent: ExtractedNominalType? - ) -> Bool { - self.isAtLeast(mode, in: parent?.swiftNominal.syntax) - } - - /// Lower-level overload taking the parent's syntax node directly. Used by - /// the analyzer; downstream `ExtractDecider`s use the `ExtractedNominalType` - /// overload above. - package func isAtLeast( - _ mode: AccessLevelMode, - in parent: NominalTypeDeclSyntaxNode? - ) -> Bool { - switch mode { - case .public: return self.isPublic(in: parent) - case .package: return self.isAtLeastPackage - case .internal: return self.isAtLeastInternal - } - } -} - -extension DeclSyntaxProtocol { - /// Find inner most "decl" node in ancestors. - public var ancestorDecl: DeclSyntax? { - var node: Syntax = Syntax(self) - while let parent = node.parent { - if let decl = parent.as(DeclSyntax.self) { - return decl - } - node = parent - } - return nil - } - - /// Declaration name primarily for debugging. - public var nameForDebug: String { - switch DeclSyntax(self).as(DeclSyntaxEnum.self) { - case .accessorDecl(let node): - node.accessorSpecifier.text - case .actorDecl(let node): - node.name.text - case .associatedTypeDecl(let node): - node.name.text - case .classDecl(let node): - node.name.text - case .deinitializerDecl(_): - "deinit" - case .editorPlaceholderDecl: - "" - case .enumCaseDecl(let node): - // FIXME: Handle multiple elements. - if let element = node.elements.first { - element.name.text - } else { - "case" - } - case .enumDecl(let node): - node.name.text - case .extensionDecl(let node): - node.extendedType.description - case .functionDecl(let node): - node.name.text + "(" + node.signature.parameterClause.parameters.map({ $0.firstName.text + ":" }).joined() + ")" - case .ifConfigDecl(_): - "#if" - case .importDecl(_): - "import" - case .initializerDecl(let node): - "init" + "(" + node.signature.parameterClause.parameters.map({ $0.firstName.text + ":" }).joined() + ")" - case .macroDecl(let node): - node.name.text - case .macroExpansionDecl(let node): - "#" + node.macroName.trimmedDescription - case .missingDecl(_): - "(missing)" - case .operatorDecl(let node): - node.name.text - case .poundSourceLocation(_): - "#sourceLocation" - case .precedenceGroupDecl(let node): - node.name.text - case .protocolDecl(let node): - node.name.text - case .structDecl(let node): - node.name.text - case .subscriptDecl(let node): - "subscript" + "(" + node.parameterClause.parameters.map({ $0.firstName.text + ":" }).joined() + ")" - case .typeAliasDecl(let node): - node.name.text - case .variableDecl(let node): - // FIXME: Handle multiple variables. - if let element = node.bindings.first { - element.pattern.trimmedDescription - } else { - "var" - } - case .unexpectedCodeDecl(let node): - node.trimmedDescription - case .usingDecl(let node): - node.trimmedDescription - } - } - - /// Qualified declaration name primarily for debugging. - public var qualifiedNameForDebug: String { - if let parent = ancestorDecl { - parent.qualifiedNameForDebug + "." + nameForDebug - } else { - nameForDebug - } - } - - /// Signature part of the declaration. I.e. without body or member block. - package var signatureString: String { - switch DeclSyntax(self.detached).as(DeclSyntaxEnum.self) { - case .functionDecl(let node): - node.with(\.body, nil).triviaSanitizedDescription - case .initializerDecl(let node): - node.with(\.body, nil).triviaSanitizedDescription - case .classDecl(let node): - node.with(\.memberBlock, "").triviaSanitizedDescription - case .structDecl(let node): - node.with(\.memberBlock, "").triviaSanitizedDescription - case .protocolDecl(let node): - node.with(\.memberBlock, "").triviaSanitizedDescription - case .accessorDecl(let node): - node.with(\.body, nil).triviaSanitizedDescription - case .subscriptDecl(let node): - node.with(\.accessorBlock, nil).triviaSanitizedDescription - case .variableDecl(let node): - node - .with( - \.bindings, - PatternBindingListSyntax( - node.bindings.map { - $0.detached - .with(\.accessorBlock, nil) - .with(\.initializer, nil) - } - ), - ) - .triviaSanitizedDescription - case .enumCaseDecl(let node): - node.triviaSanitizedDescription - default: - fatalError("unimplemented \(self.kind)") - } - } - - /// Syntax text but without unnecessary trivia. - /// - /// Connective trivia are condensed into a single whitespace, but no space - /// after opening or before closing parentheses - var triviaSanitizedDescription: String { - let visitor = TriviaSanitizingDescriptionVisitor(viewMode: .sourceAccurate) - visitor.walk(self.trimmed) - return visitor.result - } -} - -class TriviaSanitizingDescriptionVisitor: SyntaxVisitor { - var result: String = "" - - var prevTokenKind: TokenKind = .endOfFile - var prevHadTrailingSpace: Bool = false - - override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { - let tokenKind = node.tokenKind - switch (prevTokenKind, tokenKind) { - case // No whitespace after open parentheses. - (.leftAngle, _), (.leftParen, _), (.leftSquare, _), (.endOfFile, _), - // No whitespace before close parentheses. - (_, .rightAngle), (_, .rightParen), (_, .rightSquare): - break - default: - if prevHadTrailingSpace || !node.leadingTrivia.isEmpty { - result += " " - } - } - result += node.text - prevTokenKind = tokenKind - prevHadTrailingSpace = !node.trailingTrivia.isEmpty - - return .skipChildren - } -} diff --git a/Sources/SwiftExtract/CrossModuleExtensions.swift b/Sources/SwiftExtract/CrossModuleExtensions.swift deleted file mode 100644 index 3a1ade38e..000000000 --- a/Sources/SwiftExtract/CrossModuleExtensions.swift +++ /dev/null @@ -1,157 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 - -/// The members a single extension contributes to the type it extends -public struct CrossModuleExtensionMembers { - public var initializers: [ExtractedFunc] - public var methods: [ExtractedFunc] - public var variables: [ExtractedFunc] - - public init( - initializers: [ExtractedFunc] = [], - methods: [ExtractedFunc] = [], - variables: [ExtractedFunc] = [] - ) { - self.initializers = initializers - self.methods = methods - self.variables = variables - } - - public var isEmpty: Bool { - initializers.isEmpty && methods.isEmpty && variables.isEmpty - } -} - -// ==== ----------------------------------------------------------------------- -// MARK: A cross-module extension - -/// One `extension` the analyzed sources declare on a nominal type from another module -public struct CrossModuleExtension { - /// The other module's nominal being extended - public let extendedType: SwiftNominalIdentity - - /// The extension declaration syntax - public let syntax: ExtensionDeclSyntax - - /// Protocols this extension adds, resolved. - public let addedConformances: [SwiftType] - - /// Members this extension contributes - public let members: CrossModuleExtensionMembers - - /// The `where` clause requirements, resolved. - /// - /// Empty for an unconditional extension, unless `hasUnrepresentableRequirements` in which case - /// this collection is missing the unrepresentable requirements. - public let requirements: [SwiftGenericRequirement] - - /// True when the `where` clause held a requirement this analyzer cannot represent - /// (a layout requirement, or one whose types did not resolve). - public let hasUnrepresentableRequirements: Bool - - /// The extension's own attributes, including e.g. `@available` among them. - public let attributes: AttributeListSyntax - - public let sourceFilePath: String - - public init( - extendedType: SwiftNominalIdentity, - syntax: ExtensionDeclSyntax, - addedConformances: [SwiftType], - members: CrossModuleExtensionMembers, - requirements: [SwiftGenericRequirement], - hasUnrepresentableRequirements: Bool = false, - attributes: AttributeListSyntax, - sourceFilePath: String - ) { - self.extendedType = extendedType - self.syntax = syntax - self.addedConformances = addedConformances - self.members = members - self.requirements = requirements - self.hasUnrepresentableRequirements = hasUnrepresentableRequirements - self.attributes = attributes - self.sourceFilePath = sourceFilePath - } - - /// Whether the conformances this extension adds apply only under its `where` clause - public var isConditional: Bool { - !requirements.isEmpty || hasUnrepresentableRequirements - } -} - -/// An extension whose extended type could not be resolved at all. -/// -/// A diagnostics channel rather than a worklist. These were previously discarded with -/// no trace, which made an unresolvable import indistinguishable from an empty extension -public struct UnresolvedExtension { - /// The extended type as written, e.g. "SomeUnknownModule.Widget" - public let extendedTypeDescription: String - public let syntax: ExtensionDeclSyntax - public let sourceFilePath: String - - public init(extendedTypeDescription: String, syntax: ExtensionDeclSyntax, sourceFilePath: String) { - self.extendedTypeDescription = extendedTypeDescription - self.syntax = syntax - self.sourceFilePath = sourceFilePath - } -} - -/// The cross-module extensions found by one analysis. -/// -/// A module cannot declare a nominal type owned by another module, so an extension is -/// the only way the analyzed sources can add conformances or members to another module's -/// type. -public struct CrossModuleExtensions { - /// Every recorded cross-module extension, in the order encountered - public private(set) var all: [CrossModuleExtension] = [] - - /// Extensions whose extended type never resolved - public private(set) var unresolved: [UnresolvedExtension] = [] - - /// Index into `all`, keyed by extended type - private var byExtendedType: [SwiftNominalIdentity: [Int]] = [:] - - public init() {} - - public var isEmpty: Bool { - all.isEmpty && unresolved.isEmpty - } - - public mutating func record(_ extension: CrossModuleExtension) { - byExtendedType[`extension`.extendedType, default: []].append(all.count) - all.append(`extension`) - } - - public mutating func record(unresolved unresolvedExtension: UnresolvedExtension) { - unresolved.append(unresolvedExtension) - } - - /// Every cross-module extension on `type`, in the order encountered - public func extensions(of type: SwiftNominalIdentity) -> [CrossModuleExtension] { - (byExtendedType[type] ?? []).map { all[$0] } - } - - /// Every other module's nominal that some cross-module extension extends, in a stable order - public var extendedTypes: [SwiftNominalIdentity] { - var seen: Set = [] - var ordered: [SwiftNominalIdentity] = [] - for record in all where seen.insert(record.extendedType).inserted { - ordered.append(record.extendedType) - } - return ordered - } -} diff --git a/Sources/SwiftExtract/ExtractDecider.swift b/Sources/SwiftExtract/ExtractDecider.swift deleted file mode 100644 index 2e3a02302..000000000 --- a/Sources/SwiftExtract/ExtractDecider.swift +++ /dev/null @@ -1,67 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 - -/// Decides if a declaration should be extracted. This logic is specific for every output language and should handle things like access control as well as supported features when deciding of to import or skip a decl -/// -/// `SwiftExtract` itself is language-neutral and applies no extraction -/// policy of its own beyond resolving the declaration. The decider is the -/// single place that decides, for a given decl, whether the analyzer should -/// import it. That covers the access-level filter, attribute rules -/// (e.g. Java's `@JavaExport` / `@JavaClass` family), and target-specific -/// quirks (e.g. skipping Swift operators when the language can't render -/// them). When no decider is provided, the analyzer falls back to -/// `DefaultAccessLevelExtractDecider`, which only enforces the configured -/// access level -public protocol ExtractDecider { - /// Decide whether `decl` should be extracted. - /// - /// Implementations should emit a `.trace` line on every skip path (using - /// their own logger) so users can see why a decl was dropped. - /// - /// - Parameters: - /// - decl: the declaration being considered - /// - parent: the nominal type containing `decl`, when applicable - func shouldExtract( - decl: DeclSyntax, - in parent: ExtractedNominalType? - ) -> Bool -} - -/// Minimal `ExtractDecider` that enforces only the configured access-level -/// filter. Used by `SwiftAnalyzer` when no decider is supplied -public struct DefaultAccessLevelExtractDecider: ExtractDecider { - public let accessLevel: AccessLevelMode - let log: Logger - - public init(accessLevel: AccessLevelMode, logLevel: LogLevel = .info) { - self.accessLevel = accessLevel - self.log = Logger(label: "DefaultAccessLevelExtractDecider", logLevel: logLevel) - } - - public func shouldExtract( - decl: DeclSyntax, - in parent: ExtractedNominalType? - ) -> Bool { - guard let mod = decl.asProtocol((any WithModifiersSyntax).self) else { - return false - } - let ok = mod.isAtLeast(accessLevel, in: parent) - if !ok { - log.trace("Skip '\(decl.qualifiedNameForDebug)': not at least \(accessLevel)") - } - return ok - } -} diff --git a/Sources/SwiftExtract/ExtractedDecls+Attributes.swift b/Sources/SwiftExtract/ExtractedDecls+Attributes.swift deleted file mode 100644 index 8f28fb6e5..000000000 --- a/Sources/SwiftExtract/ExtractedDecls+Attributes.swift +++ /dev/null @@ -1,65 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 - -extension ExtractedNominalType { - public var attributeList: AttributeListSyntax { - swiftNominal.syntax.attributes - } - - public func attribute(named name: String) -> AttributeSyntax? { - Self.first(attribute: name, in: attributeList) - } - - static func first(attribute name: String, in attributes: AttributeListSyntax) -> AttributeSyntax? { - for element in attributes { - guard let attr = element.as(AttributeSyntax.self), - let attrName = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text, - attrName == name - else { - continue - } - return attr - } - return nil - } -} - -extension ExtractedFunc { - public var attributeList: AttributeListSyntax? { - if let n = swiftDecl.as(FunctionDeclSyntax.self) { return n.attributes } - if let n = swiftDecl.as(InitializerDeclSyntax.self) { return n.attributes } - if let n = swiftDecl.as(VariableDeclSyntax.self) { return n.attributes } - if let n = swiftDecl.as(SubscriptDeclSyntax.self) { return n.attributes } - if let n = swiftDecl.as(EnumCaseDeclSyntax.self) { return n.attributes } - return nil - } - - public func attribute(named name: String) -> AttributeSyntax? { - guard let list = attributeList else { return nil } - return ExtractedNominalType.first(attribute: name, in: list) - } -} - -extension ExtractedEnumCase { - public var attributeList: AttributeListSyntax? { - swiftDecl.as(EnumCaseDeclSyntax.self)?.attributes - } - - public func attribute(named name: String) -> AttributeSyntax? { - guard let list = attributeList else { return nil } - return ExtractedNominalType.first(attribute: name, in: list) - } -} diff --git a/Sources/SwiftExtract/ExtractedDecls.swift b/Sources/SwiftExtract/ExtractedDecls.swift deleted file mode 100644 index 6b821a815..000000000 --- a/Sources/SwiftExtract/ExtractedDecls.swift +++ /dev/null @@ -1,514 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -/// Any extracted Swift declaration -public protocol ExtractedSwiftDecl: AnyObject {} - -public enum SwiftAPIKind: Equatable { - case function - case initializer - case getter - case setter - case enumCase - case subscriptGetter - case subscriptSetter - case prefixOperator - case binaryOperator - case postfixOperator -} - -/// Describes a Swift nominal type (e.g., a class, struct, enum) that has been -/// 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 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: 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`. - public var sourceFilePath: String { - self.swiftNominal.sourceFilePath - } - - // Backing storage for member collections - public var initializers: [ExtractedFunc] = [] - public var methods: [ExtractedFunc] = [] - public var variables: [ExtractedFunc] = [] - public var cases: [ExtractedEnumCase] = [] - public var inheritedTypes: [SwiftType] - public var parent: SwiftNominalTypeDeclaration? - - /// The Swift base type name, e.g. "Box" — always the unparameterized name - public var baseTypeName: String { swiftNominal.qualifiedName } - - /// 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 - public var isSpecialization: Bool { specializationBaseType != nil } - - /// Generic parameter names (e.g. ["Element"] for Box). Empty for non-generic types - public var genericParameterNames: [String] { - swiftNominal.genericParameters.map(\.name) - } - - /// Generic parameter declarations (e.g. `[Element]` for `Box`). - /// Empty for non-generic types. - public var genericParameters: [SwiftGenericParameterDeclaration] { - swiftNominal.genericParameters - } - - /// Maps generic parameter -> concrete type argument. Empty for unspecialized types - /// e.g. {"Element": "Fish"} for FishBox - public var genericArguments: [String: String] = [:] - - /// True when all generic parameters have corresponding arguments - public var isFullySpecialized: Bool { - !genericParameterNames.isEmpty && genericParameterNames.allSatisfy { genericArguments.keys.contains($0) } - } - - public init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { - self.swiftNominal = swiftNominal - self.specializationBaseType = nil - self.inheritedTypes = - swiftNominal.inheritanceTypes?.compactMap { - try? SwiftType($0.type, lookupContext: lookupContext) - } ?? [] - self.parent = swiftNominal.parent - self.swiftType = swiftNominal.asSwiftType - } - - /// Init for creating a specialization - private init(base: ExtractedNominalType, specializedTypeName: String, genericArguments: [String: String]) { - self.swiftNominal = base.swiftNominal - self.specializationBaseType = base - - let selfType = SwiftType.nominal( - SwiftNominalType( - parent: swiftNominal.parent?.asSwiftNominalType, - nominalTypeDecl: SwiftNominalTypeDeclaration( - name: specializedTypeName, - sourceFilePath: swiftNominal.sourceFilePath, - moduleName: swiftNominal.moduleName, - parent: swiftNominal.parent, - node: swiftNominal.syntax - ), - genericArguments: [] - ) - ) - self.initializers = base.initializers.map { $0.clone(for: selfType) } - self.methods = base.methods.map { $0.clone(for: selfType) } - self.variables = base.variables.map { $0.clone(for: selfType) } - self.cases = base.cases.map { $0.clone(for: selfType) } - self.inheritedTypes = base.inheritedTypes - self.parent = base.parent - - self.specializedTypeName = specializedTypeName - self.genericArguments = genericArguments - self.swiftType = selfType - } - - public let swiftType: SwiftType - - /// Structured output-facing type name — "FishBox" for specialized, "Box" for base - public var effectiveOutputTypeName: SwiftQualifiedTypeName { - if let specializedTypeName { - return SwiftQualifiedTypeName(specializedTypeName) - } - return swiftNominal.qualifiedTypeName - } - - /// This type's module-qualified identity, keyed on the output-facing name so a - /// specialization identifies as itself ("FishBox") rather than as its base ("Box"). - /// - /// `identity.qualifiedName` is therefore `effectiveTypeName`, the key this type is - /// registered under in `AnalysisResult.extractedTypes` - public var identity: SwiftNominalIdentity { - SwiftNominalIdentity(moduleName: swiftNominal.moduleName, typeName: effectiveOutputTypeName) - } - - /// The effective Swift-side type name used as a registration key in the - /// analyzer's type table - "FishBox" for a specialization registered via - /// `typealias FishBox = Box`, the qualified base name (e.g. "Box") - /// for a non-specialized type. Output-language-facing names live on the - /// downstream code generator (e.g. JExtractSwiftLib's `effectiveJavaName`). - public var effectiveTypeName: String { - effectiveOutputTypeName.fullName - } - - /// The Swift type for thunk generation — "Box" for specialized, "Box" for base - /// Computed from baseTypeName + genericArguments - 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: ", "))>" - } - - public var qualifiedName: String { - 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 { - "" - } else if genericParameterNames.isEmpty { - "" - } else { - "<\(genericParameterNames.joined(separator: ", "))>" - } - } - - /// Create a specialized version of this generic type - public func specialize( - as specializedName: String, - with substitutions: [String: String], - ) throws -> ExtractedNominalType { - guard !genericParameterNames.isEmpty else { - throw SpecializationError( - message: "Unable to specialize non-generic type '\(baseTypeName)' as '\(specializedName)'" - ) - } - let missingParams = genericParameterNames.filter { substitutions[$0] == nil } - guard missingParams.isEmpty else { - throw SpecializationError( - message: "Missing type arguments for: \(missingParams) when specializing \(baseTypeName) as \(specializedName)" - ) - } - - if swiftNominal.kind == .enum { - throw SpecializationError( - message: "Specialization for enums are not yet supported: '\(baseTypeName)' as '\(specializedName)'" - ) - } - - return ExtractedNominalType( - base: self, - specializedTypeName: specializedName, - genericArguments: substitutions, - ) - } - - /// Checks if this type, or any of types it inherits from, conforms to the passed in protocol. - public func conformsTo(_ protocolName: String, in extractedTypes: [SwiftTypeName: ExtractedNominalType]) -> Bool { - var visited: Set = [] - 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 = extractedTypes[name], visited.insert(ObjectIdentifier(next)).inserted { - queue.append(next) - } - } - } - return false - } -} - -public struct SpecializationError: Error { - public let message: String -} - -public final class ExtractedEnumCase: ExtractedSwiftDecl, CustomStringConvertible { - /// The case name - public let name: String - - /// The enum parameters - public let parameters: [SwiftEnumCaseParameter] - - public let swiftDecl: any DeclSyntaxProtocol - - public let enumType: SwiftNominalType - - /// A function that represents the Swift static "initializer" for cases - public let caseFunction: ExtractedFunc - - public init( - name: String, - parameters: [SwiftEnumCaseParameter], - swiftDecl: any DeclSyntaxProtocol, - enumType: SwiftNominalType, - caseFunction: ExtractedFunc, - ) { - self.name = name - self.parameters = parameters - self.swiftDecl = swiftDecl - self.enumType = enumType - self.caseFunction = caseFunction - } - - public var description: String { - """ - ExtractedEnumCase { - name: \(name), - parameters: \(parameters), - swiftDecl: \(swiftDecl), - enumType: \(enumType), - caseFunction: \(caseFunction) - } - """ - } - - public func clone(for parent: SwiftType) -> ExtractedEnumCase { - ExtractedEnumCase( - name: name, - parameters: parameters, - swiftDecl: swiftDecl, - enumType: enumType, - caseFunction: caseFunction.clone(for: parent) - ) - } -} - -extension ExtractedEnumCase: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - public static func == (lhs: ExtractedEnumCase, rhs: ExtractedEnumCase) -> Bool { - lhs === rhs - } -} - -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 - - /// The function name. - /// e.g., "init" for an initializer or "foo" for "foo(a:b:)". - public let name: String - - public let swiftDecl: any DeclSyntaxProtocol - - public let apiKind: SwiftAPIKind - - public let functionSignature: SwiftFunctionSignature - - /// The short path from module root to the file in which this declaration was originally declared. - /// E.g. for `Sources/Example/My/Types.swift` it would be `My/Types.swift`. - public let sourceFilePath: String? - - public var signatureString: String { - self.swiftDecl.signatureString - } - - public var parentType: SwiftType? { - functionSignature.selfParameter?.selfType - } - - public var isStatic: Bool { - if case .staticMethod = functionSignature.selfParameter { - return true - } - return false - } - - public var isClass: Bool { - if case .classMethod = functionSignature.selfParameter { - return true - } - return false - } - - public var isInitializer: Bool { - if case .initializer = functionSignature.selfParameter { - return true - } - return false - } - - /// If this function/method is member of a class/struct/protocol, - /// this will contain that declaration's extracted name. - public var hasParent: Bool { functionSignature.selfParameter != nil } - - /// A display name to use to refer to the Swift declaration with its - /// enclosing type, if there is one. - public var displayName: String { - let prefix = - switch self.apiKind { - case .getter: "getter:" - case .setter: "setter:" - case .enumCase: "case:" - case .function, .initializer: "" - case .subscriptGetter: "subscriptGetter:" - case .subscriptSetter: "subscriptSetter:" - case .prefixOperator: "prefixOperator:" - case .binaryOperator: "binaryOperator:" - case .postfixOperator: "postfixOperator:" - } - - let context = - if let parentType { - "\(parentType)." - } else { - "" - } - - return prefix + context + self.name - } - - public var isThrowing: Bool { - self.functionSignature.effectSpecifiers.contains(.throws) || self.functionSignature.isImplicitlyThrowing - } - - public var isAsync: Bool { - self.functionSignature.isAsync - } - - public var isImplicitlyAsync: Bool { - self.functionSignature.isImplicitlyAsync - } - - public var isIsolated: Bool { - self.functionSignature.isIsolated - } - - public init( - module: String, - swiftDecl: any DeclSyntaxProtocol, - name: String, - apiKind: SwiftAPIKind, - functionSignature: SwiftFunctionSignature, - sourceFilePath: String? = nil, - ) { - self.module = module - self.name = name - self.swiftDecl = swiftDecl - self.apiKind = apiKind - self.functionSignature = functionSignature - self.sourceFilePath = sourceFilePath - } - - public var description: String { - """ - ExtractedFunc { - apiKind: \(apiKind) - module: \(module) - name: \(name) - signature: \(self.swiftDecl.signatureString) - } - """ - } - - public func clone(for parent: SwiftType) -> ExtractedFunc { - var functionSignature = functionSignature - assert(functionSignature.selfParameter?.selfType != nil) - functionSignature.selfParameter?.selfType = parent - return ExtractedFunc( - module: module, - swiftDecl: swiftDecl, - name: name, - apiKind: apiKind, - functionSignature: functionSignature, - sourceFilePath: sourceFilePath - ) - } - - /// Expands this function into `maxOverloads + 1` functions if it contains a variadic parameter. - /// Replaces the variadic parameter `T...` with `N` discrete parameters (`arg0: T`, `arg1: T`, etc.) - /// for `N` in `0...maxOverloads`. - /// Returns `[self]` if the function has no variadic parameters. - public func expandingVariadicOverloads(maxOverloads: Int) -> [ExtractedFunc] { - guard functionSignature.hasVariadicParams else { - return [self] - } - - var overloads: [ExtractedFunc] = [] - - // Find the index of the variadic parameter. Swift only allows one. - guard let variadicIndex = functionSignature.parameters.firstIndex(where: \.isVariadic) else { - return [self] - } - - let variadicParam = functionSignature.parameters[variadicIndex] - - for count in 0...maxOverloads { - var newParameters = functionSignature.parameters - newParameters.remove(at: variadicIndex) - - var expandedParams: [SwiftParameter] = [] - for i in 0.. Bool { - lhs === rhs - } -} - -extension ExtractedNominalType: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - public static func == (lhs: ExtractedNominalType, rhs: ExtractedNominalType) -> Bool { - lhs === rhs - } -} diff --git a/Sources/SwiftExtract/Logger.swift b/Sources/SwiftExtract/Logger.swift deleted file mode 100644 index c81974019..000000000 --- a/Sources/SwiftExtract/Logger.swift +++ /dev/null @@ -1,113 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 Foundation -@_exported import SwiftExtractConfigurationShared -import SwiftSyntax - -// Placeholder for some better logger, we could depend on swift-log -public struct Logger { - public var label: String - public var logLevel: LogLevel - - public init(label: String, logLevel: LogLevel) { - self.label = label - self.logLevel = logLevel - } - - public func error( - _ message: @autoclosure () -> String, - metadata: [String: Any] = [:], - file: String = #fileID, - line: UInt = #line, - function: String = #function - ) { - guard logLevel <= .error else { - return - } - - let metadataString: String = - if metadata.isEmpty { "" } else { "\(metadata)" } - - print("[error][\(file):\(line)](\(function)) \(message()) \(metadataString)") - } - - public func warning( - _ message: @autoclosure () -> String, - metadata: [String: Any] = [:], - file: String = #fileID, - line: UInt = #line, - function: String = #function - ) { - guard logLevel <= .warning else { - return - } - - let metadataString: String = - if metadata.isEmpty { "" } else { "\(metadata)" } - - print("[warning][\(file):\(line)](\(function)) \(message()) \(metadataString)") - } - - public func info( - _ message: @autoclosure () -> String, - metadata: [String: Any] = [:], - file: String = #fileID, - line: UInt = #line, - function: String = #function - ) { - guard logLevel <= .info else { - return - } - - let metadataString: String = - if metadata.isEmpty { "" } else { "\(metadata)" } - - print("[info][\(file):\(line)](\(function)) \(message()) \(metadataString)") - } - - public func debug( - _ message: @autoclosure () -> String, - metadata: [String: Any] = [:], - file: String = #fileID, - line: UInt = #line, - function: String = #function - ) { - guard logLevel <= .debug else { - return - } - - let metadataString: String = - if metadata.isEmpty { "" } else { "\(metadata)" } - - print("[debug][\(file):\(line)](\(function)) \(message()) \(metadataString)") - } - - public func trace( - _ message: @autoclosure () -> String, - metadata: [String: Any] = [:], - file: String = #fileID, - line: UInt = #line, - function: String = #function - ) { - guard logLevel <= .trace else { - return - } - - let metadataString: String = - metadata.isEmpty ? "" : "\(metadata)" - - print("[trace][\(file):\(line)](\(function)) \(message()) \(metadataString)") - } -} diff --git a/Sources/SwiftExtract/Resources/dummy.json b/Sources/SwiftExtract/Resources/dummy.json deleted file mode 100644 index 8a3735457..000000000 --- a/Sources/SwiftExtract/Resources/dummy.json +++ /dev/null @@ -1,2 +0,0 @@ -// The actual resources will be generated by the buildToolsPlugin. This file is necessary for SwiftPM to generate a module bundle. -{} diff --git a/Sources/SwiftExtract/SourceDependencies.swift b/Sources/SwiftExtract/SourceDependencies.swift deleted file mode 100644 index 6d97a9637..000000000 --- a/Sources/SwiftExtract/SourceDependencies.swift +++ /dev/null @@ -1,48 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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/SwiftExtract/SwiftAnalysisVisitor.swift b/Sources/SwiftExtract/SwiftAnalysisVisitor.swift deleted file mode 100644 index 8f9d003cc..000000000 --- a/Sources/SwiftExtract/SwiftAnalysisVisitor.swift +++ /dev/null @@ -1,949 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 Foundation -import SwiftIfConfig -import SwiftParser -import SwiftSyntax - -final class SwiftAnalysisVisitor { - let analyzer: SwiftAnalyzer - var config: any SwiftExtractConfiguration { - self.analyzer.config - } - - init(analyzer: SwiftAnalyzer) { - self.analyzer = analyzer - } - - var log: Logger { analyzer.log } - - /// Constrained extensions deferred until specializations are applied - private struct DeferredConstrainedExtension { - var node: ExtensionDeclSyntax - var sourceFilePath: String - var constraints: [ParsedWhereConstraint] - } - private var deferredConstrainedExtensions: [DeferredConstrainedExtension] = [] - - private static func operatorKind(_ node: FunctionDeclSyntax) -> SwiftAPIKind? { - switch node.name.tokenKind { - case .binaryOperator: - return .binaryOperator - case .prefixOperator: - return .prefixOperator - case .postfixOperator: - return .postfixOperator - default: - return nil - } - } - - func visit(inputFile: SwiftInputFile) { - let node = inputFile.syntax - for codeItem in node.statements { - if let declNode = codeItem.item.as(DeclSyntax.self) { - self.visit(decl: declNode, in: nil, sourceFilePath: inputFile.path) - } - } - } - - 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) - case .classDecl(let node): - self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .structDecl(let node): - self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .enumDecl(let node): - self.visit(enumDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .protocolDecl(let node): - self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .extensionDecl(let node): - self.visit(extensionDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .typeAliasDecl(let node): - self.visit(typeAliasDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .associatedTypeDecl: - break // TODO: Implement associated types - - case .initializerDecl(let node): - self.visit(initializerDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .functionDecl(let node): - self.visit(functionDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .variableDecl(let node): - self.visit(variableDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .subscriptDecl(let node): - self.visit(subscriptDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .enumCaseDecl(let node): - self.visit(enumCaseDecl: node, in: parent, sourceFilePath: sourceFilePath) - case .ifConfigDecl(let node): - self.visit(ifConfigDecl: node, in: parent, sourceFilePath: sourceFilePath) - default: - break - } - } - - func visit( - nominalDecl node: some DeclSyntaxProtocol & DeclGroupSyntax & NamedDeclSyntax - & WithAttributesSyntax & WithModifiersSyntax, - in parent: ExtractedNominalType?, - sourceFilePath: String, - ) { - guard let extractedNominalType = analyzer.extractedNominalType(node, parent: parent) else { - return - } - - // Check if there's a specialization entry for this type - applySpecialization(to: extractedNominalType) - - for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: extractedNominalType, sourceFilePath: sourceFilePath) - } - } - - func visit( - enumDecl node: EnumDeclSyntax, - in parent: ExtractedNominalType?, - sourceFilePath: String, - ) { - self.visit(nominalDecl: node, in: parent, sourceFilePath: sourceFilePath) - - self.synthesizeRawRepresentableConformance(enumDecl: node, in: parent) - } - - func visit( - extensionDecl node: ExtensionDeclSyntax, - in parent: ExtractedNominalType?, - sourceFilePath: String, - ) { - guard parent == nil else { - // 'extension' in a nominal type is invalid. Ignore - return - } - - let extractedNominalType: ExtractedNominalType - switch analyzer.resolveExtendedType(node.extendedType) { - case .extractable(let extracted): - extractedNominalType = extracted - - case .otherModule(let otherModuleDecl): - recordCrossModuleExtension(node, extending: otherModuleDecl, sourceFilePath: sourceFilePath) - return - - case .rejected: - return - - case .unresolved: - self.reportSkipped( - node, - name: "extension \(node.extendedType.trimmedDescription)", - sourceFilePath: sourceFilePath, - reason: "extended type '\(node.extendedType.trimmedDescription)' did not resolve" - ) - analyzer.crossModuleExtensions.record( - unresolved: UnresolvedExtension( - extendedTypeDescription: node.extendedType.trimmedDescription, - syntax: node, - sourceFilePath: sourceFilePath, - ) - ) - return - } - - guard let constraints = parseWhereConstraints(node.genericWhereClause) else { - log.debug( - "Skip importing constrained extension '\(node.extendedType.trimmedDescription)'; unsupported where-clause requirements: \(node.genericWhereClause?.trimmedDescription ?? "")" - ) - return - } - - guard !constraints.isEmpty else { - // The extension is unconstrained: add to the base type (visible through all specializations) - extractedNominalType.inheritedTypes += - node.inheritanceClause?.inheritedTypes.compactMap { - try? SwiftType($0.type, lookupContext: analyzer.lookupContext) - } ?? [] - for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: extractedNominalType, sourceFilePath: sourceFilePath) - } - return - } - - let hasConformanceConstraint = constraints.contains { if case .conformance = $0 { true } else { false } } - - // Conformance requirements depend on inheritedTypes that may be populated - // by an `extension Fish: Animal {}` later in the same file: always defer. - if hasConformanceConstraint { - deferredConstrainedExtensions.append( - .init(node: node, sourceFilePath: sourceFilePath, constraints: constraints) - ) - return - } - - let matchingSpecializations = findMatchingSpecializations( - extendedType: extractedNominalType, - whereConstraints: constraints, - ) - if matchingSpecializations.isEmpty { - // Specializations may not exist yet: defer for later - deferredConstrainedExtensions.append( - .init(node: node, sourceFilePath: sourceFilePath, constraints: constraints) - ) - return - } - - // Visit members in each matching specialization, not the base type - for specialized in matchingSpecializations { - for memberItem in node.memberBlock.members { - self.visit(decl: memberItem.decl, in: specialized, sourceFilePath: sourceFilePath) - } - } - } - - // ==== ----------------------------------------------------------------------- - // MARK: Cross-module extensions - - /// Record an extension on a nominal owned by another module. - private func recordCrossModuleExtension( - _ node: ExtensionDeclSyntax, - extending otherModuleDecl: SwiftNominalTypeDeclaration, - sourceFilePath: String, - ) { - let addedConformances = - node.inheritanceClause?.inheritedTypes.compactMap { - try? SwiftType($0.type, lookupContext: analyzer.lookupContext) - } ?? [] - let (requirements, hasUnrepresentable) = resolveExtensionRequirements( - node.genericWhereClause, - of: "\(otherModuleDecl.moduleName).\(otherModuleDecl.qualifiedName)" - ) - - log.debug( - "Record cross-module extension of '\(otherModuleDecl.moduleName).\(otherModuleDecl.qualifiedName)'" - + " adding [\(addedConformances.map(\.description).joined(separator: ", "))]" - ) - - analyzer.crossModuleExtensions.record( - CrossModuleExtension( - extendedType: otherModuleDecl.identity, - syntax: node, - addedConformances: addedConformances, - members: captureCrossModuleMembers(of: node, extending: otherModuleDecl, sourceFilePath: sourceFilePath), - requirements: requirements, - hasUnrepresentableRequirements: hasUnrepresentable, - attributes: node.attributes, - sourceFilePath: sourceFilePath, - ) - ) - } - - /// Collect the members a cross-module extension adds - private func captureCrossModuleMembers( - of node: ExtensionDeclSyntax, - extending otherModuleDecl: SwiftNominalTypeDeclaration, - sourceFilePath: String, - ) -> CrossModuleExtensionMembers { - guard - let scratch = try? ExtractedNominalType( - swiftNominal: otherModuleDecl, - lookupContext: analyzer.lookupContext - ) - else { - return CrossModuleExtensionMembers() - } - - for memberItem in node.memberBlock.members { - switch memberItem.decl.as(DeclSyntaxEnum.self) { - case .functionDecl(let functionNode): - self.visit(functionDecl: functionNode, in: scratch, sourceFilePath: sourceFilePath) - case .variableDecl(let variableNode): - self.visit(variableDecl: variableNode, in: scratch, sourceFilePath: sourceFilePath) - case .initializerDecl(let initializerNode): - self.visit(initializerDecl: initializerNode, in: scratch, sourceFilePath: sourceFilePath) - case .subscriptDecl(let subscriptNode): - self.visit(subscriptDecl: subscriptNode, in: scratch, sourceFilePath: sourceFilePath) - default: - log.debug("Skip member of cross-module extension of '\(otherModuleDecl.qualifiedName)': \(memberItem.decl.kind)") - } - } - - return CrossModuleExtensionMembers( - initializers: scratch.initializers, - methods: scratch.methods, - variables: scratch.variables, - ) - } - - /// Resolve an extension's `where` clause into the requirements. - private func resolveExtensionRequirements( - _ whereClause: GenericWhereClauseSyntax?, - of extendedTypeDescription: String - ) -> (requirements: [SwiftGenericRequirement], hasUnrepresentable: Bool) { - guard let whereClause else { return ([], false) } - - var requirements: [SwiftGenericRequirement] = [] - var hasUnrepresentable = false - - /// A requirement we cannot represent makes `requirements` an incomplete picture of - /// what guards the conformance, so say which one and why - func reportUnrepresentable(_ requirementNode: some SyntaxProtocol, reason: String, hint: Bool) { - let message = - "Unrepresentable requirement '\(requirementNode.trimmedDescription)'" - + " in extension of '\(extendedTypeDescription)'; \(reason)" - self.log.warning(hint ? self.makeMissingTypeMessage(message) : message) - hasUnrepresentable = true - } - - for requirementNode in whereClause.requirements { - switch requirementNode.requirement { - case .conformanceRequirement(let conformance): - if let lhs = try? SwiftType(conformance.leftType, lookupContext: analyzer.lookupContext), - let rhs = try? SwiftType(conformance.rightType, lookupContext: analyzer.lookupContext) - { - requirements.append(.inherits(lhs, rhs)) - } else { - reportUnrepresentable(conformance, reason: "a type in it did not resolve", hint: true) - } - - case .sameTypeRequirement(let sameType): - if let leftNode = sameType.leftType.as(TypeSyntax.self), - let rightNode = sameType.rightType.as(TypeSyntax.self), - let lhs = try? SwiftType(leftNode, lookupContext: analyzer.lookupContext), - let rhs = try? SwiftType(rightNode, lookupContext: analyzer.lookupContext) - { - requirements.append(.equals(lhs, rhs)) - } else { - reportUnrepresentable(sameType, reason: "a type in it did not resolve", hint: true) - } - - case .layoutRequirement(let layout): - // The shared requirement vocabulary has no layout case - reportUnrepresentable(layout, reason: "layout requirements are not supported", hint: false) - } - } - return (requirements, hasUnrepresentable) - } - - func visit( - functionDecl node: FunctionDeclSyntax, - in typeContext: ExtractedNominalType?, - sourceFilePath: String, - ) { - guard node.shouldExtract(config: config, in: typeContext, decider: analyzer.extractDecider) else { - return - } - - self.log.debug("Import function: '\(node.qualifiedNameForDebug)'") - - let signature: SwiftFunctionSignature - do { - signature = try SwiftFunctionSignature( - node, - enclosingType: typeContext?.swiftType, - lookupContext: analyzer.lookupContext, - ) - } catch { - self.reportSkipped( - node, - name: "'\(node.qualifiedNameForDebug)'", - sourceFilePath: sourceFilePath, - error: error - ) - return - } - - let apiKind: SwiftAPIKind = - if typeContext != nil && Self.operatorKind(node) != nil { - Self.operatorKind(node)! - } else { - .function - } - - let extracted = ExtractedFunc( - module: analyzer.swiftModuleName, - swiftDecl: node, - name: node.name.text.unescapedSwiftName, - apiKind: apiKind, - functionSignature: signature, - sourceFilePath: sourceFilePath, - ) - - log.debug("Record extracted method \(node.qualifiedNameForDebug)") - if let typeContext { - typeContext.methods.append(extracted) - } else { - analyzer.extractedGlobalFuncs.append(extracted) - } - } - - func visit( - enumCaseDecl node: EnumCaseDeclSyntax, - in typeContext: ExtractedNominalType?, - sourceFilePath: String, - ) { - guard let typeContext else { - self.log.info("Enum case must be within a current type; \(node)") - return - } - - do { - for caseElement in node.elements { - self.log.debug("Import case \(caseElement.name) of enum \(node.qualifiedNameForDebug)") - - let parameters = try caseElement.parameterClause?.parameters.map { - try SwiftEnumCaseParameter($0, lookupContext: analyzer.lookupContext) - } - - let signature = try SwiftFunctionSignature( - caseElement, - enclosingType: typeContext.swiftType, - lookupContext: analyzer.lookupContext, - ) - - let caseName = caseElement.name.text.unescapedSwiftName - let caseFunction = ExtractedFunc( - module: analyzer.swiftModuleName, - swiftDecl: node, - name: caseName, - apiKind: .enumCase, - functionSignature: signature, - sourceFilePath: sourceFilePath, - ) - - let extractedCase = ExtractedEnumCase( - name: caseName, - parameters: parameters ?? [], - swiftDecl: node, - enumType: SwiftNominalType(nominalTypeDecl: typeContext.swiftNominal), - caseFunction: caseFunction, - ) - - typeContext.cases.append(extractedCase) - } - } catch { - self.reportSkipped( - node, - name: "\(node.qualifiedNameForDebug)", - sourceFilePath: sourceFilePath, - error: error - ) - } - } - - func visit( - variableDecl node: VariableDeclSyntax, - in typeContext: ExtractedNominalType?, - sourceFilePath: String, - ) { - guard node.shouldExtract(config: config, in: typeContext, decider: analyzer.extractDecider) else { - return - } - - guard let binding = node.bindings.first else { - return - } - - let varName = "\(binding.pattern.trimmed)" - - self.log.debug("Import variable: \(node.kind) '\(node.qualifiedNameForDebug)'") - - do { - let supportedAccessors = node.supportedAccessorKinds( - binding: binding, - minimumAccessLevel: config.effectiveMinimumInputAccessLevelMode, - ) - if supportedAccessors.contains(.get) { - try importAccessor( - from: DeclSyntax(node), - in: typeContext, - kind: .getter, - name: varName, - sourceFilePath: sourceFilePath, - ) - } - if supportedAccessors.contains(.set) { - try importAccessor( - from: DeclSyntax(node), - in: typeContext, - kind: .setter, - name: varName, - sourceFilePath: sourceFilePath, - ) - } - } catch { - self.reportSkipped( - node, - name: "\(node.qualifiedNameForDebug)", - sourceFilePath: sourceFilePath, - error: error - ) - } - } - - func visit( - initializerDecl node: InitializerDeclSyntax, - in typeContext: ExtractedNominalType?, - sourceFilePath: String, - ) { - guard let typeContext else { - self.log.info("Initializer must be within a current type; \(node)") - return - } - guard node.shouldExtract(config: config, in: typeContext, decider: analyzer.extractDecider) else { - return - } - - self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'") - - let signature: SwiftFunctionSignature - do { - signature = try SwiftFunctionSignature( - node, - enclosingType: typeContext.swiftType, - lookupContext: analyzer.lookupContext, - ) - } catch { - self.reportSkipped( - node, - name: "\(node.qualifiedNameForDebug)", - sourceFilePath: sourceFilePath, - error: error - ) - return - } - let extracted = ExtractedFunc( - module: analyzer.swiftModuleName, - swiftDecl: node, - name: "init", - apiKind: .initializer, - functionSignature: signature, - sourceFilePath: sourceFilePath, - ) - - typeContext.initializers.append(extracted) - } - - private func visit( - subscriptDecl node: SubscriptDeclSyntax, - in typeContext: ExtractedNominalType?, - sourceFilePath: String, - ) { - guard node.shouldExtract(config: config, in: typeContext, decider: analyzer.extractDecider) else { - return - } - - guard let accessorBlock = node.accessorBlock else { - return - } - - let name = "subscript" - let accessors = accessorBlock.supportedAccessorKinds() - - do { - if accessors.contains(.get) { - try importAccessor( - from: DeclSyntax(node), - in: typeContext, - kind: .subscriptGetter, - name: name, - sourceFilePath: sourceFilePath, - ) - } - if accessors.contains(.set) { - try importAccessor( - from: DeclSyntax(node), - in: typeContext, - kind: .subscriptSetter, - name: name, - sourceFilePath: sourceFilePath, - ) - } - } catch { - self.reportSkipped( - node, - name: "\(node.qualifiedNameForDebug)", - sourceFilePath: sourceFilePath, - error: error - ) - } - } - - private func visit( - ifConfigDecl node: IfConfigDeclSyntax, - in parent: ExtractedNominalType?, - sourceFilePath: String - ) { - let (clause, _) = node.activeClause(in: analyzer.buildConfig) - if let clause, let elements = clause.elements { - switch elements { - case .statements(let codeBlock): - for codeItem in codeBlock { - if let declNode = codeItem.item.as(DeclSyntax.self) { - self.visit(decl: declNode, in: parent, sourceFilePath: sourceFilePath) - } - } - case .decls(let memberBlock): - for memberItem in memberBlock { - self.visit(decl: memberItem.decl, in: parent, sourceFilePath: sourceFilePath) - } - default: - break - } - } - } - - private func importAccessor( - from node: DeclSyntax, - in typeContext: ExtractedNominalType?, - kind: SwiftAPIKind, - name: String, - sourceFilePath: String, - ) throws { - let signature: SwiftFunctionSignature - - switch node.as(DeclSyntaxEnum.self) { - case .variableDecl(let varNode): - signature = try SwiftFunctionSignature( - varNode, - isSet: kind == .setter, - enclosingType: typeContext?.swiftType, - lookupContext: analyzer.lookupContext, - ) - case .subscriptDecl(let subscriptNode): - signature = try SwiftFunctionSignature( - subscriptNode, - isSet: kind == .subscriptSetter, - enclosingType: typeContext?.swiftType, - lookupContext: analyzer.lookupContext, - ) - default: - log.warning("Not supported declaration type \(node.kind) while calling importAccessor!") - return - } - - let extracted = ExtractedFunc( - module: analyzer.swiftModuleName, - swiftDecl: node, - name: name, - apiKind: kind, - functionSignature: signature, - sourceFilePath: sourceFilePath, - ) - - log.debug( - "Record extracted variable accessor \(kind == .getter || kind == .subscriptGetter ? "getter" : "setter"):\(node.qualifiedNameForDebug)" - ) - if let typeContext { - typeContext.variables.append(extracted) - } else { - analyzer.extractedGlobalVariables.append(extracted) - } - } - - private func synthesizeRawRepresentableConformance( - enumDecl node: EnumDeclSyntax, - in parent: ExtractedNominalType?, - ) { - guard let extracted = analyzer.extractedNominalType(node, parent: parent) else { - return - } - - if let firstInheritanceType = extracted.swiftNominal.firstInheritanceType, - let inheritanceType = try? SwiftType( - firstInheritanceType, - lookupContext: analyzer.lookupContext, - ), - inheritanceType.isRawTypeCompatible - { - if !extracted.variables.contains(where: { - $0.name == "rawValue" && $0.functionSignature.result.type == inheritanceType - }) { - let decl: DeclSyntax = "public var rawValue: \(raw: inheritanceType.description) { get }" - self.visit(decl: decl, in: extracted, sourceFilePath: extracted.sourceFilePath) - } - - if !extracted.initializers.contains(where: { - $0.functionSignature.parameters.count == 1 - && $0.functionSignature.parameters.first?.parameterName == "rawValue" - && $0.functionSignature.parameters.first?.type == inheritanceType - }) { - let decl: DeclSyntax = "public init?(rawValue: \(raw: inheritanceType))" - self.visit(decl: decl, in: extracted, sourceFilePath: extracted.sourceFilePath) - } - } - } - - // ==== ----------------------------------------------------------------------- - // MARK: Typealias declarations - - func visit( - typeAliasDecl node: TypeAliasDeclSyntax, - in typeContext: ExtractedNominalType?, - sourceFilePath: String, - ) { - guard node.shouldExtract(config: config, in: typeContext, decider: analyzer.extractDecider) else { - return - } - - let outputName = node.name.text - let rhsType = node.initializer.value - - let genericArgs: [String] - if let identType = rhsType.as(IdentifierTypeSyntax.self) { - genericArgs = identType.genericArgumentClause?.arguments.compactMap { $0.argument.trimmedDescription } ?? [] - } else if let memberType = rhsType.as(MemberTypeSyntax.self) { - genericArgs = memberType.genericArgumentClause?.arguments.compactMap { $0.argument.trimmedDescription } ?? [] - } else { - return - } - - // Only register as specialization if the RHS has generic arguments - guard !genericArgs.isEmpty else { return } - - // Resolve the base type through the symbol table - guard let baseType = analyzer.extractedNominalType(rhsType) else { - log.debug("Could not resolve base type for specialization: \(rhsType.trimmedDescription)") - return - } - - registerSpecialization( - outputName: outputName, - baseType: baseType, - genericArgs: genericArgs, - rhsDescription: rhsType.trimmedDescription, - ) - } - - /// Register a specialization from a typealias that specializes a generic type - private func registerSpecialization( - outputName: String, - baseType: ExtractedNominalType, - genericArgs: [String], - rhsDescription: String, - ) { - // Build substitutions dict from the generic parameters - var substitutions: [String: String] = [:] - if baseType.swiftNominal.isGeneric { - let genericParams = baseType.swiftNominal.genericParameters.map { $0.name } - for (i, param) in genericParams.enumerated() { - if i < genericArgs.count { - substitutions[param] = genericArgs[i] - } - } - } - - let specialized: ExtractedNominalType - do { - specialized = try baseType.specialize(as: outputName, with: substitutions) - } catch { - log.warning("Failed to specialize \(baseType.baseTypeName) as \(outputName): \(error)") - return - } - analyzer.specializations[baseType, default: []].insert(specialized) - log.info("Registered specialization: \(outputName) = \(rhsDescription)") - } - - // ==== ----------------------------------------------------------------------- - // MARK: Specialization support - - /// Apply specializations to a type if matching entries exist - func applySpecialization(to extractedType: ExtractedNominalType) { - guard let specializations = analyzer.specializations[extractedType] else { - return - } - - for specialized in specializations { - analyzer.extractedTypes[specialized.effectiveTypeName] = specialized - log.info("Applied specialization: \(specialized.effectiveTypeName) -> \(specialized.effectiveSwiftTypeName)") - } - } - - /// Apply specializations that were registered after their target types were visited, - /// then process any deferred constrained extensions - func applyPendingSpecializations() { - for (_, specializations) in analyzer.specializations { - for specialized in specializations { - if analyzer.extractedTypes[specialized.effectiveTypeName] != nil { - continue - } - analyzer.extractedTypes[specialized.effectiveTypeName] = specialized - log.info("Applied pending specialization: \(specialized.effectiveTypeName) -> \(specialized.effectiveSwiftTypeName)") - } - } - - // Process constrained extensions that were deferred - for deferred in deferredConstrainedExtensions { - guard let baseType = analyzer.extractedNominalType(deferred.node.extendedType) else { - continue - } - let matchingSpecializations = findMatchingSpecializations( - extendedType: baseType, - whereConstraints: deferred.constraints, - ) - guard !matchingSpecializations.isEmpty else { - log.debug("Skipping deferred constrained extension of \(deferred.node.extendedType.trimmedDescription) — no matching specialization") - continue - } - for specialized in matchingSpecializations { - for memberItem in deferred.node.memberBlock.members { - self.visit(decl: memberItem.decl, in: specialized, sourceFilePath: deferred.sourceFilePath) - } - } - } - deferredConstrainedExtensions.removeAll() - } - - // ==== ----------------------------------------------------------------------- - // MARK: Constrained extension merging - - private enum ParsedWhereConstraint { - case sameType(first: String, second: String) - case conformance(typeParam: String, proto: String) - } - - /// Returns list of where requirements -- empty if unconstrained; or nil if failed to parse/handle the constraints. - private func parseWhereConstraints(_ whereClause: GenericWhereClauseSyntax?) -> [ParsedWhereConstraint]? { - guard let whereClause else { return [] } - var constraints: [ParsedWhereConstraint] = [] - for requirement in whereClause.requirements { - switch requirement.requirement { - case .sameTypeRequirement(let sameType): - let first = sameType.leftType.trimmedDescription - let second = sameType.rightType.trimmedDescription - constraints.append(.sameType(first: first, second: second)) - - case .conformanceRequirement(let conformance): - let typeParam = conformance.leftType.trimmedDescription - if let composition = conformance.rightType.as(CompositionTypeSyntax.self) { - for element in composition.elements { - constraints.append( - .conformance(typeParam: typeParam, proto: element.type.trimmedDescription) - ) - } - } else { - constraints.append( - .conformance(typeParam: typeParam, proto: conformance.rightType.trimmedDescription) - ) - } - - case .layoutRequirement: - return nil - } - } - return constraints - } - - /// Find specializations whose type args match the given where-clause constraints - private func findMatchingSpecializations( - extendedType: ExtractedNominalType, - whereConstraints: [ParsedWhereConstraint], - ) -> [ExtractedNominalType] { - guard let specializations = analyzer.specializations[extendedType] else { - return [] - } - return specializations.filter { specialized in - constraintsMatchSpecialization(whereConstraints, specialized: specialized) - } - } - - /// Check if where clause constraints match a specialization's generic arguments. - /// Where-clauses are conjunctive: every constraint must hold. - private func constraintsMatchSpecialization( - _ constraints: [ParsedWhereConstraint], - specialized: ExtractedNominalType, - ) -> Bool { - for constraint in constraints { - switch constraint { - case .sameType(let first, let second): - if specialized.genericArguments[first] == second { continue } - if specialized.genericArguments[second] == first { continue } - return false - - case .conformance(let typeParam, let proto): - guard let concreteName = specialized.genericArguments[typeParam] else { - return false - } - guard let concreteType = analyzer.extractedTypes[concreteName] else { - return false - } - guard concreteType.conformsTo(proto, in: analyzer.extractedTypes) else { - return false - } - } - } - return true - } - - func makeMissingTypeMessage(_ message: String) -> String { - guard let hint = config.unresolvedTypeHint else { - return message - } - return "\(message). \(hint)" - } - - /// Log a skipped declaration (as before) and report it to the configured - /// diagnostics sink, if any. - func reportSkipped( - _ node: some SyntaxProtocol, - name: String, - sourceFilePath: String, - error: any Error - ) { - self.reportSkipped( - node, - name: name, - sourceFilePath: sourceFilePath, - reason: "\(error)", - error: error - ) - } - - /// Same, for skips that are not driven by a thrown error - func reportSkipped( - _ node: some SyntaxProtocol, - name: String, - sourceFilePath: String, - reason: String, - error: (any Error)? = nil - ) { - let message = "Failed to import: \(name) in module '\(analyzer.swiftModuleName)'; \(reason)" - self.log.warning(self.makeMissingTypeMessage(message)) - analyzer.diagnosticsSink?.emit( - SwiftExtractDiagnostic( - kind: .skippedDeclaration, - declarationName: name, - moduleName: analyzer.swiftModuleName, - message: message, - node: Syntax(node), - sourceFilePath: sourceFilePath, - underlyingError: error - ) - ) - } -} - -extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyntax { - /// Decide whether this declaration should be extracted. - /// - /// Delegates entirely to the supplied `decider`; all per-decl policy - /// lives there — see `ExtractDecider`. - func shouldExtract( - config: any SwiftExtractConfiguration, - in parent: ExtractedNominalType?, - decider: any ExtractDecider - ) -> Bool { - decider.shouldExtract(decl: DeclSyntax(self), in: parent) - } -} diff --git a/Sources/SwiftExtract/SwiftAnalyzer+Foundation.swift b/Sources/SwiftExtract/SwiftAnalyzer+Foundation.swift deleted file mode 100644 index c9f23cfc4..000000000 --- a/Sources/SwiftExtract/SwiftAnalyzer+Foundation.swift +++ /dev/null @@ -1,89 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 SwiftSyntax - -extension SwiftAnalyzer { - 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 - // Data also emits DataProtocol, etc. - struct FoundationTypeGroup { - let candidates: [SwiftKnownTypeDeclKind] - let fakeSourceFilePath: String - } - let groups: [[FoundationTypeGroup]] = [ - [ - .init( - candidates: [.foundationData, .essentialsData], - fakeSourceFilePath: "Foundation/FAKE_FOUNDATION_DATA.swift", - ), - .init( - candidates: [.foundationDataProtocol, .essentialsDataProtocol], - fakeSourceFilePath: "Foundation/FAKE_FOUNDATION_DATAPROTOCOL.swift", - ), - ], - [ - .init( - candidates: [.foundationDate, .essentialsDate], - fakeSourceFilePath: "Foundation/FAKE_FOUNDATION_DATE.swift", - ) - ], - [ - .init( - candidates: [.foundationUUID, .essentialsUUID], - fakeSourceFilePath: "Foundation/FAKE_FOUNDATION_UUID.swift", - ) - ], - [ - .init( - candidates: [.foundationURL, .essentialsURL], - fakeSourceFilePath: "Foundation/FAKE_FOUNDATION_URL.swift", - ) - ], - ] - - for group in groups { - let resolved: [(primary: SwiftNominalTypeDeclaration, source: String, candidates: [SwiftNominalTypeDeclaration])] = - group.compactMap { type in - let candidates = type.candidates.compactMap { self.symbolTable[$0] } - guard let primary = candidates.first else { - return nil - } - return (primary, type.fakeSourceFilePath, candidates) - } - guard !resolved.isEmpty else { - continue - } - - let allCandidates = resolved.flatMap(\.candidates) - let isReferenced = self.isUsing(where: { decl in - allCandidates.contains(where: { $0 === decl }) - }) - guard isReferenced else { - continue - } - - // Visit the fake source files, and register the types. - for entry in resolved { - visitor.visit( - nominalDecl: entry.primary.syntax.asNominal!, - in: nil, - sourceFilePath: entry.source, - ) - } - } - } -} diff --git a/Sources/SwiftExtract/SwiftAnalyzer.swift b/Sources/SwiftExtract/SwiftAnalyzer.swift deleted file mode 100644 index 5e73c7cb3..000000000 --- a/Sources/SwiftExtract/SwiftAnalyzer.swift +++ /dev/null @@ -1,491 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 Foundation -import SwiftIfConfig -import SwiftParser -import SwiftSyntax - -/// Drives the analysis of Swift source code into an `AnalysisResult` that -/// downstream language generators can consume. -public final class SwiftAnalyzer { - static let SWIFT_INTERFACE_SUFFIX = ".swiftinterface" - - package var log: Logger - - package let config: any SwiftExtractConfiguration - - /// The build configuration used to resolve #if conditional compilation blocks. - package let buildConfig: any BuildConfiguration - - /// The name of the Swift module being translated. - package let swiftModuleName: String - - // ==== Input - - 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 - 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. - /// Populated by `SwiftToJava.run` before `analyze()` runs. - package var sourceDependencies = SourceDependencies() - - // ==== Output state - - package var extractedGlobalVariables: [ExtractedFunc] = [] - - package var extractedGlobalFuncs: [ExtractedFunc] = [] - - /// A mapping from Swift type names (e.g., A.B) over to the extracted nominal - /// type representation. - package var extractedTypes: [SwiftTypeName: ExtractedNominalType] = [:] - - /// Extensions these sources declare on nominal types owned by other modules. - /// - /// Kept apart from `extractedTypes` on purpose: recording another module's type here says - /// what the analysis learned about it, never that a generator should emit it - package var crossModuleExtensions: CrossModuleExtensions = CrossModuleExtensions() - - /// Specializations of generic types that will get their concrete Java declarations, "as if" they were independent types - package var specializations: [ExtractedNominalType: Set] = [:] - - package var lookupContext: SwiftTypeLookupContext! = nil - - package var symbolTable: SwiftSymbolTable! { - lookupContext?.symbolTable - } - - /// Language-specific per-decl extraction policy. Every language target - /// must supply one — pass `DefaultAccessLevelExtractDecider` for the - /// access-level-only baseline. - package let extractDecider: any ExtractDecider - - /// Receives an event for every declaration the analyzer skips; `nil` - /// keeps the default log-and-drop behavior only. - package let diagnosticsSink: (any SwiftExtractDiagnosticsSink)? - - public init( - config: any SwiftExtractConfiguration, - moduleName: String? = nil, - extractDecider: any ExtractDecider, - diagnosticsSink: (any SwiftExtractDiagnosticsSink)? = nil - ) { - 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: "analyzer", logLevel: config.logLevel ?? .info) - self.config = config - self.swiftModuleName = swiftModule - self.extractDecider = extractDecider - self.diagnosticsSink = diagnosticsSink - - if let staticBuildConfigPath = config.staticBuildConfigurationFile { - do { - let data = try Data(contentsOf: URL(fileURLWithPath: staticBuildConfigPath)) - let decoder = JSONDecoder() - 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 = 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) - } -} - -// ===== -------------------------------------------------------------------------------------------------------------- -// MARK: Analysis - -extension SwiftAnalyzer { - /// Snapshot of the analysis state as a value-typed `AnalysisResult`. - public var result: AnalysisResult { - AnalysisResult( - extractedTypes: self.extractedTypes, - extractedGlobalVariables: self.extractedGlobalVariables, - extractedGlobalFuncs: self.extractedGlobalFuncs, - crossModuleExtensions: self.crossModuleExtensions, - ) - } - - public func add(filePath: String, text: String) { - log.debug("Adding: \(filePath)") - let sourceFileSyntax = Parser.parse(source: text) - self.inputs.append(SwiftInputFile(syntax: sourceFileSyntax, path: filePath)) - } - - /// Convenient method for analyzing single file. - package func analyze(path: String, text: String) throws { - self.add(filePath: path, text: text) - try self.analyze() - } - - /// Analyze registered inputs, optionally with a hook that fires after the - /// per-source walk has populated the specialization registry from any - /// in-source `typealias Alias = Base` declarations, but before the - /// deferred constrained-extension queue is processed against those - /// specializations. - /// - /// Downstream language code generators that drive specialization from a - /// configuration source other than Swift typealiases (e.g. a `specialize:` - /// config entry that names a base type by qualified name) can use the hook - /// to call `registerSpecialization(_:outputName:typeArgs:)` so their - /// specializations participate in `findMatchingSpecializations` alongside - /// the analyzer's natively-registered ones. Without the hook, those - /// specializations are registered too late and any constrained extension - /// (`extension Box where T == ConcreteT { … }`) is silently dropped. - public func analyze( - beforeProcessingDeferredExtensions hook: (SwiftAnalyzer) throws -> Void = { _ in } - ) throws { - prepareForTranslation() - - let visitor = SwiftAnalysisVisitor(analyzer: self) - - for input in self.inputs { - log.trace("Analyzing \(input.path)") - visitor.visit(inputFile: input) - } - - try hook(self) - - // Apply any specializations registered after their target types were visited - visitor.applyPendingSpecializations() - - self.visitFoundationDeclsIfNeeded(with: visitor) - } - - /// Register a specialization of a generic type, producing a concrete - /// extracted type as if an in-source `typealias \(outputName) = \(baseQualifiedName)<…>` - /// had been declared. Intended for use from the - /// `analyze(beforeProcessingDeferredExtensions:)` hook so deferred - /// constrained extensions can match against the registered specialization. - /// - /// The base type is resolved via the parsed module's symbol table by its - /// qualified name (e.g. `"Box"`, `"ATHM.Server"`); returns `nil` if the - /// base can't be found, isn't generic, or specialization fails. - /// - /// Note: this lower-cases over `ExtractedNominalType.specialize`, but unlike - /// the visitor's typealias path it accepts already-substituted argument - /// names (so callers that consume external configuration can pass the - /// concrete type names directly without going through TypeSyntax parsing). - @discardableResult - public func registerSpecialization( - baseQualifiedName: String, - outputName: String, - typeArgs: [String: String] - ) -> ExtractedNominalType? { - guard let base = resolveBaseForSpecialization(baseQualifiedName: baseQualifiedName) else { - return nil - } - let specialized: ExtractedNominalType - do { - specialized = try base.specialize(as: outputName, with: typeArgs) - } catch { - log.warning("Failed to specialize \(base.baseTypeName) as \(outputName): \(error)") - return nil - } - self.specializations[base, default: []].insert(specialized) - log.info("Registered specialization (external): \(outputName) = \(base.baseTypeName)<\(typeArgs.values.joined(separator: ", "))>") - return specialized - } - - /// Like `registerSpecialization(baseQualifiedName:outputName:typeArgs:)`, - /// but accepts positional generic arguments (matched in order to the - /// base's generic parameters). Convenient for callers that scrape - /// `typealias Foo = Bar` from source syntax and don't already - /// know the parameter names. - @discardableResult - public func registerSpecializationByPosition( - baseQualifiedName: String, - outputName: String, - positionalArgs: [String] - ) -> ExtractedNominalType? { - guard let base = resolveBaseForSpecialization(baseQualifiedName: baseQualifiedName) else { - return nil - } - var typeArgs: [String: String] = [:] - for (i, name) in base.genericParameterNames.enumerated() where i < positionalArgs.count { - typeArgs[name] = positionalArgs[i] - } - return registerSpecialization( - baseQualifiedName: baseQualifiedName, - outputName: outputName, - typeArgs: typeArgs - ) - } - - private func resolveBaseForSpecialization(baseQualifiedName: String) -> ExtractedNominalType? { - let parts = baseQualifiedName.split(separator: ".").map(String.init) - var resolvedBase: SwiftNominalTypeDeclaration? = nil - var parent: SwiftNominalTypeDeclaration? = nil - for part in parts { - let next = symbolTable.lookupType(part, parent: parent) - guard let next else { return nil } - parent = next - resolvedBase = next - } - guard let baseDecl = resolvedBase else { return nil } - let base = self.extractedNominalType(baseDecl) - guard let base, !base.genericParameterNames.isEmpty else { return nil } - return base - } - - /// The set of effective Swift-side type names of every specialization - /// currently registered with the analyzer (across all base types). Useful - /// for callers driving `registerSpecialization` from a hook to skip names - /// already registered by the analyzer's own typealias-decl visitor and - /// avoid double-registering distinct `ExtractedNominalType` instances - /// with the same effective name. - public var registeredSpecializationNames: Set { - Set(self.specializations.values.flatMap { $0 }.map(\.effectiveTypeName)) - } - - /// Top-level convenience: run analysis on the given Swift sources and return - /// the resulting `AnalysisResult`. Optionally accepts a hook fired after - /// the per-source walk and before deferred-constrained-extension - /// processing — see - /// `SwiftAnalyzer.analyze(beforeProcessingDeferredExtensions:)` for the - /// hook semantics. Use to drive specialization registration from - /// downstream configuration sources (e.g. `specialize:` config entries) - /// so deferred constrained extensions match against those specializations - /// before they're dropped. - public static func analyze( - sources: [(path: String, text: String)], - moduleName: String, - extractDecider: any ExtractDecider, - config: (any SwiftExtractConfiguration)? = nil, - sourceDependencies: SourceDependencies = SourceDependencies(), - diagnosticsSink: (any SwiftExtractDiagnosticsSink)? = nil, - beforeProcessingDeferredExtensions hook: (SwiftAnalyzer) throws -> Void = { _ in } - ) throws -> AnalysisResult { - let effectiveConfig = config ?? DefaultSwiftExtractConfiguration(swiftModule: moduleName) - let analyzer = SwiftAnalyzer( - config: effectiveConfig, - moduleName: moduleName, - extractDecider: extractDecider, - diagnosticsSink: diagnosticsSink - ) - analyzer.sourceDependencies = sourceDependencies - for source in sources { - analyzer.add(filePath: source.path, text: source.text) - } - try analyzer.analyze(beforeProcessingDeferredExtensions: hook) - return analyzer.result - } - - /// Resolve a type reference found outside of a declaration signature - /// (e.g. inside an attribute argument such as `@JavaExport(as: Foo.self)`) - /// against the analyzed module's symbol tables. Only valid after - /// `analyze()` has run. - public func resolveType(_ syntax: TypeSyntax) throws -> SwiftType { - precondition(lookupContext != nil, "resolveType may only be called after analyze()") - return try SwiftType(syntax, lookupContext: lookupContext) - } - - package func prepareForTranslation() { - let symbolTable = SwiftSymbolTable.setup( - moduleName: self.swiftModuleName, - inputs, - config: self.config, - sourceDependencies: self.sourceDependencies, - buildConfig: self.buildConfig, - ) - self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable) - self.lookupContext.allowUnresolvedTypeReferences = - self.config.allowUnresolvedTypeReferences - } - - /// Check if any of the extracted decls uses a nominal declaration that satisfies - /// the given predicate. - func isUsing(where predicate: (SwiftNominalTypeDeclaration) -> Bool) -> Bool { - func check(_ type: SwiftType) -> Bool { - switch type { - case .nominal(let nominal): - if nominal.genericArguments.contains(where: check) { - return true - } - return predicate(nominal.nominalTypeDecl) - case .tuple(let tuple): - return tuple.contains(where: { check($0.type) }) - case .function(let fn): - return check(fn.resultType) || fn.parameters.contains(where: { check($0.type) }) - case .metatype(let ty): - return check(ty) - case .existential(let ty), .opaque(let ty): - return check(ty) - case .composite(let types): - return types.contains(where: check) - case .inlineArray(_, let element): - return check(element) - case .genericParameter: - return false - } - } - - func check(_ fn: ExtractedFunc) -> Bool { - if check(fn.functionSignature.result.type) { - return true - } - if fn.functionSignature.parameters.contains(where: { check($0.type) }) { - return true - } - return false - } - - if self.extractedGlobalFuncs.contains(where: check) { - return true - } - if self.extractedGlobalVariables.contains(where: check) { - return true - } - for extractedType in self.extractedTypes.values { - if extractedType.initializers.contains(where: check) { - return true - } - if extractedType.methods.contains(where: check) { - return true - } - if extractedType.variables.contains(where: check) { - return true - } - } - return false - } -} - -// ==== ---------------------------------------------------------------------------------------------------------------- -// MARK: Type translation -extension SwiftAnalyzer { - /// Try to resolve the given nominal declaration node into its extracted representation. - func extractedNominalType( - _ nominalNode: some DeclGroupSyntax & NamedDeclSyntax & WithModifiersSyntax & WithAttributesSyntax, - parent: ExtractedNominalType?, - ) -> ExtractedNominalType? { - if !nominalNode.shouldExtract(config: config, in: parent, decider: extractDecider) { - return nil - } - - guard let nominal = symbolTable.lookupType(nominalNode.name.text, parent: parent?.swiftNominal) else { - return nil - } - return self.extractedNominalType(nominal) - } - - /// The outcome of resolving the type an `extension` extends. - enum ExtendedTypeResolution { - /// Resolved, owned by this analysis, and passed every filter - case extractable(ExtractedNominalType) - /// Resolved, but owned by another module. - case otherModule(SwiftNominalTypeDeclaration) - /// Resolved, but either filtered out explicitly - case rejected - /// Failed to resolve, the type is not known - case unresolved - } - - /// Whether this analysis owns `decl` for the purpose of emitting bindings for it. - func ownsForEmission(_ decl: SwiftNominalTypeDeclaration) -> Bool { - let isFromThisModule = decl.moduleName == self.swiftModuleName - let isFromStubbedModule = config.hasImportedModuleStub(moduleOfNominal: decl.moduleName) - let isFromDependencyModule = sourceDependencies.swiftModuleNames.contains(decl.moduleName) - return isFromThisModule || isFromStubbedModule || isFromDependencyModule - } - - /// Resolve the type an extension extends, reporting *why* resolution ended where it did - func resolveExtendedType(_ typeNode: TypeSyntax) -> ExtendedTypeResolution { - guard let swiftType = try? SwiftType(typeNode, lookupContext: lookupContext), - let swiftNominalDecl = swiftType.asNominalTypeDeclaration - else { - return .unresolved - } - - guard ownsForEmission(swiftNominalDecl) else { - return .otherModule(swiftNominalDecl) - } - - guard - swiftNominalDecl.syntax.shouldExtract( - config: config, - in: nil as ExtractedNominalType?, - decider: extractDecider - ) - else { - return .rejected - } - - guard let extracted = extractedNominalType(swiftNominalDecl) else { - return .rejected - } - return .extractable(extracted) - } - - /// Try to resolve the given nominal type node into its extracted representation. - /// - /// Yields a type only when this analysis both owns it and intends to emit it, which is - /// what callers outside the extension path require - func extractedNominalType( - _ typeNode: TypeSyntax - ) -> ExtractedNominalType? { - guard case .extractable(let extracted) = resolveExtendedType(typeNode) else { - return nil - } - return extracted - } - - func extractedNominalType(_ nominal: SwiftNominalTypeDeclaration) -> ExtractedNominalType? { - let fullName = nominal.qualifiedName - - guard shouldExtractSwiftType(qualifiedName: fullName, config: config) else { - log.debug("Skip import '\(fullName)': filtered by swiftFilterInclude/swiftFilterExclude") - return nil - } - - if let alreadyExtracted = extractedTypes[fullName] { - return alreadyExtracted - } - - let extractedNominal = try? ExtractedNominalType(swiftNominal: nominal, lookupContext: lookupContext) - - extractedTypes[fullName] = extractedNominal - return extractedNominal - } -} - -// ==== ----------------------------------------------------------------------- -// MARK: Errors - -public struct SwiftAnalyzerError: Error { - let message: String - - public init(message: String) { - self.message = message - } -} diff --git a/Sources/SwiftExtract/SwiftExtractConfiguration.swift b/Sources/SwiftExtract/SwiftExtractConfiguration.swift deleted file mode 100644 index fa82c43c5..000000000 --- a/Sources/SwiftExtract/SwiftExtractConfiguration.swift +++ /dev/null @@ -1,138 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -@_exported import SwiftExtractConfigurationShared - -/// 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`. -/// -/// `AccessLevelMode` and `LogLevel` live in the small -/// `SwiftExtractConfigurationShared` target so language-specific configuration -/// shared modules (e.g. `SwiftJavaConfigurationShared`) can use the same -/// enums directly without taking a dependency on SwiftSyntax. -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 effectiveMinimumInputAccessLevelMode: AccessLevelMode { get } - - /// Verbosity for the analyzer's logger; `nil` falls back to `.info`. - var logLevel: LogLevel? { 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 } - - /// Consumer-specific guidance appended to "unresolved type" log messages, - /// e.g. pointing the user at the consumer's mechanism for declaring - /// cross-module dependencies. `nil` appends nothing. - var unresolvedTypeHint: String? { get } - - /// Whether type lookups that can't resolve a name should fall back to a - /// synthetic, unresolved nominal reference instead of throwing - /// `TypeTranslationError.unknown`. - /// - /// `SwiftExtract` defaults to a strict policy: when a parameter, return - /// type, property type, etc. references a name the symbol table can't - /// resolve, the enclosing declaration is silently dropped (the analyzer - /// emits a `[warning] Failed to import: …` log line). That's correct for - /// Java/JNI, where the generator can't render code referencing an - /// unresolved Swift type. - /// - /// Code generators that resolve names later than analysis time - /// (lazy specializations) can opt-in by setting this `true`. Unresolved - /// names then become synthetic nominal types stamped with - /// `isUnresolvedTypePlaceholder == true` so downstream passes can substitute - /// or recognize them. See - /// `SwiftNominalTypeDeclaration.isUnresolvedTypePlaceholder` for the long - /// form. Default: false. - var allowUnresolvedTypeReferences: Bool { get } - - /// Whether the given module name has stub declarations configured. - func hasImportedModuleStub(moduleOfNominal moduleName: String) -> Bool -} - -extension SwiftExtractConfiguration { - public var availableImportModules: Set { [] } - - public var allowUnresolvedTypeReferences: Bool { false } - - public var unresolvedTypeHint: String? { nil } - - 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 effectiveMinimumInputAccessLevelMode: AccessLevelMode - public var logLevel: LogLevel? - public var availableImportModules: Set - public var allowUnresolvedTypeReferences: Bool - - public init( - swiftModule: String? = nil, - accessLevel: AccessLevelMode = .public, - logLevel: LogLevel? = nil, - staticBuildConfigurationFile: String? = nil, - swiftFilterInclude: [String]? = nil, - swiftFilterExclude: [String]? = nil, - importedModuleStubs: [String: [String]]? = nil, - availableImportModules: Set = [], - allowUnresolvedTypeReferences: Bool = false - ) { - self.swiftModule = swiftModule - self.effectiveMinimumInputAccessLevelMode = accessLevel - self.logLevel = logLevel - self.staticBuildConfigurationFile = staticBuildConfigurationFile - self.swiftFilterInclude = swiftFilterInclude - self.swiftFilterExclude = swiftFilterExclude - self.importedModuleStubs = importedModuleStubs - self.availableImportModules = availableImportModules - self.allowUnresolvedTypeReferences = allowUnresolvedTypeReferences - } -} diff --git a/Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift b/Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift deleted file mode 100644 index a11d76d73..000000000 --- a/Sources/SwiftExtract/SwiftExtractDefaultBuildConfiguration.swift +++ /dev/null @@ -1,141 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 Foundation -import SwiftIfConfig -import SwiftSyntax - -/// A default, fixed build configuration during static analysis for interface extraction. -package struct SwiftExtractDefaultBuildConfiguration: BuildConfiguration { - package static let shared = SwiftExtractDefaultBuildConfiguration() - - private var base: StaticBuildConfiguration - - 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") - } - do { - let data = try Data(contentsOf: url) - let decoder = JSONDecoder() - base = try decoder.decode(StaticBuildConfiguration.self, from: data) - } catch { - fatalError("\(error)") - } - } - - package func isCustomConditionSet(name: String) throws -> Bool { - base.isCustomConditionSet(name: name) - } - - package func hasFeature(name: String) throws -> Bool { - base.hasFeature(name: name) - } - - package func hasAttribute(name: String) throws -> Bool { - base.hasAttribute(name: name) - } - - package func canImport(importPath: [(TokenSyntax, String)], version: CanImportVersion) throws -> Bool { - try base.canImport(importPath: importPath, version: version) - } - - package func isActiveTargetOS(name: String) throws -> Bool { - true - } - - package func isActiveTargetArchitecture(name: String) throws -> Bool { - true - } - - package func isActiveTargetEnvironment(name: String) throws -> Bool { - true - } - - package func isActiveTargetRuntime(name: String) throws -> Bool { - true - } - - package func isActiveTargetPointerAuthentication(name: String) throws -> Bool { - true - } - - package func isActiveTargetObjectFormat(name: String) throws -> Bool { - true - } - - 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 - } -} - -extension BuildConfiguration where Self == SwiftExtractDefaultBuildConfiguration { - package static var swiftExtractDefault: 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 } -} diff --git a/Sources/SwiftExtract/SwiftExtractDiagnostics.swift b/Sources/SwiftExtract/SwiftExtractDiagnostics.swift deleted file mode 100644 index 7979961e9..000000000 --- a/Sources/SwiftExtract/SwiftExtractDiagnostics.swift +++ /dev/null @@ -1,55 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 diagnostic reported by the analyzer. -public struct SwiftExtractDiagnostic { - public enum Kind { - /// The declaration was skipped entirely and is absent from the - /// `AnalysisResult`. - case skippedDeclaration - } - - public let kind: Kind - - /// Qualified name of the affected declaration, formatted for human-readable - /// output (e.g. `Greeter.greet(name:)`). - public let declarationName: String - - /// Name of the module being analyzed. - public let moduleName: String - - /// Neutral, consumer-independent description of what went wrong. Does not - /// include `SwiftExtractConfiguration.unresolvedTypeHint`, which is only - /// appended to the analyzer's own log output. - public let message: String - - /// The syntax node the event is anchored to. Consumers can derive precise - /// source locations from it: its root tree is the parsed source file. - public let node: Syntax - - /// Path of the source file containing `node`, as supplied to the analyzer. - public let sourceFilePath: String - - /// The error that caused the declaration to be diagnosed, when one was thrown. - public let underlyingError: (any Error)? -} - -/// Receives diagnostic events during analysis. -/// -/// Supplying a sink does not suppress the analyzer's log output. -public protocol SwiftExtractDiagnosticsSink { - func emit(_ diagnostic: SwiftExtractDiagnostic) -} diff --git a/Sources/SwiftExtract/SwiftFileFilter.swift b/Sources/SwiftExtract/SwiftFileFilter.swift deleted file mode 100644 index e0e0e7741..000000000 --- a/Sources/SwiftExtract/SwiftFileFilter.swift +++ /dev/null @@ -1,288 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -// ==== ----------------------------------------------------------------------- -// MARK: Swift filter pattern classification - -/// 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 -package enum SwiftFilterPatternKind { - /// Pattern contains `/`, ends in a Swift source extension, or is a bare `**` - /// glob — matches against relative file paths - case filePath - /// Pattern uses `.` to separate components — matches against qualified type - /// names (e.g. `Outer.Inner`, `**.User`, `Outer.**`) - case typeName - /// Plain name with no separators — matches against both - case plain -} - -package func classifyPattern(_ pattern: String) -> SwiftFilterPatternKind { - // Directory separator is the strongest signal for a file path - if pattern.contains("/") { - return .filePath - } - // A pattern ending in a Swift source extension is a filename pattern, even - // though it contains `.`, e.g. `MyType.swift`, `User.swiftinterface` - if pattern.hasSuffix(".swift") || pattern.hasSuffix(".swiftinterface") { - return .filePath - } - // Dotted patterns are nested type names. `**` segments are valid wildcards - // within dotted paths (e.g. `**.Internal`, `Outer.**`) - if pattern.contains(".") { - return .typeName - } - // `**` with no `.` or `/` is rare but we treat it as "all files" basically - if pattern.contains("**") { - return .filePath - } - return .plain -} - -// ==== ----------------------------------------------------------------------- -// MARK: Glob-like matching - -/// Match a value split by `separator` against a glob-like pattern split by the -/// same separator. Supports `**` (zero or more segments) and trailing `*` -/// within a segment -private func matchesGlob( - value: String, - pattern: String, - separator: Character -) -> Bool { - let valueParts = value.split(separator: separator, omittingEmptySubsequences: true) - let patternParts = pattern.split(separator: separator, omittingEmptySubsequences: true) - - return matchParts( - valueParts: Array(valueParts), - valueIdx: 0, - patternParts: Array(patternParts), - patternIdx: 0 - ) -} - -/// Walk `valueParts` and `patternParts` in lockstep starting from the given -/// positions. Literal segments must match one-to-one; a `**` segment can -/// consume zero or more value segments (resolved by trying every possible -/// skip length recursively) -private func matchParts( - valueParts: [Substring], - valueIdx: Int, - patternParts: [Substring], - patternIdx: Int -) -> Bool { - var valueIdx = valueIdx - var patternIdx = patternIdx - - while patternIdx < patternParts.count { - let currentPattern = patternParts[patternIdx] - - if currentPattern == "**" { - return matchDoubleStarWildcard( - valueParts: valueParts, - valueIdx: valueIdx, - patternParts: patternParts, - doubleStarIdx: patternIdx - ) - } - - // Pattern still has literal parts but value is exhausted — no match - guard valueIdx < valueParts.count else { - return false - } - - guard matchSegment(String(valueParts[valueIdx]), against: String(currentPattern)) else { - return false - } - valueIdx += 1 - patternIdx += 1 - } - - // Full match only when both sides are exhausted - return valueIdx == valueParts.count -} - -/// Handle a `**` wildcard at `doubleStarPos` in the pattern. -/// `**` matches zero or more consecutive value segments, so we try every -/// possible number of skipped segments and recurse on the remainder -private func matchDoubleStarWildcard( - valueParts: [Substring], - valueIdx: Int, - patternParts: [Substring], - doubleStarIdx: Int -) -> Bool { - // `**` at the end of the pattern matches everything remaining - if doubleStarIdx == patternParts.count - 1 { - return true - } - - // Try consuming 0, 1, 2, ... value segments with the `**` - for skipCount in valueIdx...valueParts.count { - if matchParts( - valueParts: valueParts, - valueIdx: skipCount, - patternParts: patternParts, - patternIdx: doubleStarIdx + 1 - ) { - return true - } - } - return false -} - -/// Match a single segment against a pattern segment. -/// Supports trailing `*` wildcard (e.g. `Us*` matches `User`) -private func matchSegment(_ segment: String, against pattern: String) -> Bool { - if pattern == "*" { - return true - } - if pattern.hasSuffix("*") { - let prefix = String(pattern.dropLast()) - return segment.hasPrefix(prefix) - } - return segment == pattern -} - -// ==== ----------------------------------------------------------------------- -// MARK: File-path matching - -/// Check whether `relativePath` (including `.swift` extension, using `/` separators) -/// matches the given glob-like `pattern`. -/// -/// Supported pattern syntax: -/// - `**` 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 -package func matchesFilePathFilter(relativePath: String, pattern: String) -> Bool { - matchesGlob(value: relativePath, pattern: pattern, separator: "/") -} - -// ==== ----------------------------------------------------------------------- -// MARK: Type-name matching - -/// Check whether a qualified type name (e.g. `Something.Other`) matches a -/// dot-separated pattern. -/// -/// Supported pattern syntax: -/// - `**` matches zero or more name components -/// - `*` at the end of a component matches any suffix -/// - exact component match otherwise -package func matchesTypeNameFilter(qualifiedName: String, pattern: String) -> Bool { - matchesGlob(value: qualifiedName, pattern: pattern, separator: ".") -} - -// ==== ----------------------------------------------------------------------- -// MARK: Combined filter application - -/// Determine whether a file at the given `relativePath` (including `.swift` -/// 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 -/// `shouldExtractSwiftType` for those -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 - let filePatterns = includeFilters.filter { classifyPattern($0) != .typeName } - if !filePatterns.isEmpty { - let included = filePatterns.contains { pattern in - matchesFilePattern(relativePath: relativePath, pattern: pattern) - } - guard included else { - return false - } - } - } - - if let excludeFilters = config.swiftFilterExclude, !excludeFilters.isEmpty { - let filePatterns = excludeFilters.filter { classifyPattern($0) != .typeName } - let excluded = filePatterns.contains { pattern in - matchesFilePattern(relativePath: relativePath, pattern: pattern) - } - if excluded { - return false - } - } - - return true -} - -/// Match a file pattern against a relative path. Plain patterns (no `/` or `.`) -/// are matched against the filename without the `.swift` extension; file-path -/// patterns are matched against the full relative path as-is -private func matchesFilePattern(relativePath: String, pattern: String) -> Bool { - switch classifyPattern(pattern) { - case .plain: - // Plain pattern like "MyType" — match against just the filename sans .swift - let fileName = relativePath.split(separator: "/").last.map(String.init) ?? relativePath - return matchSegment(fileName.dropSwiftFileSuffix(), against: pattern) - case .filePath: - return matchesFilePathFilter(relativePath: relativePath, pattern: pattern) - case .typeName: - return false - } -} - -/// Determine whether a type with the given `qualifiedName` (e.g. `MyClass` or -/// `Outer.Inner`) should be extracted, based on the include/exclude filters in -/// `config`. -/// -/// 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: any SwiftExtractConfiguration) -> Bool { - if let includeFilters = config.swiftFilterInclude, !includeFilters.isEmpty { - let typePatterns = includeFilters.filter { classifyPattern($0) != .filePath } - if !typePatterns.isEmpty { - let included = typePatterns.contains { pattern in - let kind = classifyPattern(pattern) - switch kind { - case .typeName: - return matchesTypeNameFilter(qualifiedName: qualifiedName, pattern: pattern) - case .plain: - // Plain pattern: match against the top-level name - return matchSegment(qualifiedName.split(separator: ".").first.map(String.init) ?? qualifiedName, against: pattern) - case .filePath: - return false - } - } - guard included else { - return false - } - } - } - - if let excludeFilters = config.swiftFilterExclude, !excludeFilters.isEmpty { - let typePatterns = excludeFilters.filter { classifyPattern($0) != .filePath } - let excluded = typePatterns.contains { pattern in - let kind = classifyPattern(pattern) - switch kind { - case .typeName: - return matchesTypeNameFilter(qualifiedName: qualifiedName, pattern: pattern) - case .plain: - return matchSegment(qualifiedName.split(separator: ".").first.map(String.init) ?? qualifiedName, against: pattern) - case .filePath: - return false - } - } - if excluded { - return false - } - } - - return true -} diff --git a/Sources/SwiftExtract/SwiftTypes/ExtractedSwiftModule.swift b/Sources/SwiftExtract/SwiftTypes/ExtractedSwiftModule.swift deleted file mode 100644 index f8c1a6d6f..000000000 --- a/Sources/SwiftExtract/SwiftTypes/ExtractedSwiftModule.swift +++ /dev/null @@ -1,40 +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 -// -//===----------------------------------------------------------------------===// - -public struct ExtractedSwiftModule: Hashable { - public let name: String - public let availableWithModuleName: String? - public var alternativeModuleNames: Set - public var isMainSourceOfSymbols: Bool - - public init( - name: String, - availableWithModuleName: String? = nil, - alternativeModuleNames: Set = [], - isMainSourceOfSymbols: Bool = false - ) { - self.name = name - self.availableWithModuleName = availableWithModuleName - self.alternativeModuleNames = alternativeModuleNames - self.isMainSourceOfSymbols = isMainSourceOfSymbols - } - - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.name == rhs.name - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(name) - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift b/Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift deleted file mode 100644 index ed20722e6..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftDependencyScanner.swift +++ /dev/null @@ -1,96 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -/// Scan importing modules. -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( - ExtractedSwiftModule(name: moduleName, availableWithModuleName: nil, alternativeModuleNames: []) - ) - } else if let ifConfigDecl = item.item.as(IfConfigDeclSyntax.self) { - importingModuleNames.append(contentsOf: modules(from: ifConfigDecl)) - } - } - return importingModuleNames -} - -private func modules(from ifConfigDecl: IfConfigDeclSyntax) -> [ExtractedSwiftModule] { - guard - let firstClause = ifConfigDecl.clauses.first, - let calledExpression = firstClause.condition?.as(FunctionCallExprSyntax.self)?.calledExpression.as( - DeclReferenceExprSyntax.self - ), - calledExpression.baseName.text == "canImport" - else { - return [] - } - - var modules: [ExtractedSwiftModule] = [] - modules.reserveCapacity(ifConfigDecl.clauses.count) - - for (index, clause) in ifConfigDecl.clauses.enumerated() { - let importedModuleNames = - clause.elements?.as(CodeBlockItemListSyntax.self)? - .compactMap { CodeBlockItemSyntax($0) } - .compactMap { $0.item.as(ImportDeclSyntax.self) } - .compactMap { $0.path.first?.name.text } ?? [] - - let importModuleName: String? = - if let funcCallExpr = clause.condition?.as(FunctionCallExprSyntax.self), - let calledDeclReference = funcCallExpr.calledExpression.as(DeclReferenceExprSyntax.self), - calledDeclReference.baseName.text == "canImport", - let moduleNameSyntax = funcCallExpr.arguments.first?.expression.as(DeclReferenceExprSyntax.self) - { - moduleNameSyntax.baseName.text - } else { - nil - } - - let clauseModules = importedModuleNames.map { - ExtractedSwiftModule( - name: $0, - availableWithModuleName: importModuleName, - alternativeModuleNames: [] - ) - } - - // Assume single import from #else statement is fallback and use it as main source of symbols - if clauseModules.count == 1 && index == (ifConfigDecl.clauses.count - 1) - && clause.poundKeyword.tokenKind == .poundElse - { - var fallbackModule: ExtractedSwiftModule = clauseModules[0] - var moduleNames: [String] = [] - moduleNames.reserveCapacity(modules.count) - - for i in 0.. (parameters: [SwiftGenericParameterDeclaration], requirements: [SwiftGenericRequirement]) { - var params: [SwiftGenericParameterDeclaration] = [] - var requirements: [SwiftGenericRequirement] = [] - - // Parameter clause - if let parameterClause { - for parameterNode in parameterClause.parameters { - guard parameterNode.specifier == nil else { - throw SwiftFunctionTranslationError.genericParameterSpecifier(parameterNode) - } - let param = - try lookupContext.typeDeclaration(for: parameterNode, sourceFilePath: "FIXME_HAS_NO_PATH.swift") - as! SwiftGenericParameterDeclaration - params.append(param) - if let inheritedNode = parameterNode.inheritedType { - let inherited = try SwiftType(inheritedNode, lookupContext: lookupContext) - requirements.append(.inherits(.genericParameter(param), inherited)) - } - } - } - - // Where clause - if let whereClause { - for requirementNode in whereClause.requirements { - let requirement: SwiftGenericRequirement - switch requirementNode.requirement { - case .conformanceRequirement(let conformance): - requirement = .inherits( - try SwiftType(conformance.leftType, lookupContext: lookupContext), - try SwiftType(conformance.rightType, lookupContext: lookupContext) - ) - case .sameTypeRequirement(let sameType): - guard let leftType = sameType.leftType.as(TypeSyntax.self) else { - throw SwiftFunctionTranslationError.expressionInGenericRequirement(requirementNode) - } - guard let rightType = sameType.rightType.as(TypeSyntax.self) else { - throw SwiftFunctionTranslationError.expressionInGenericRequirement(requirementNode) - } - requirement = .equals( - try SwiftType(leftType, lookupContext: lookupContext), - try SwiftType(rightType, lookupContext: lookupContext) - ) - case .layoutRequirement: - throw SwiftFunctionTranslationError.layoutRequirement(requirementNode) - } - requirements.append(requirement) - } - } - - return (params, requirements) - } - - /// Translate the function signature, returning the list of translated - /// parameters, its effect specifiers, and the error type of a typed - /// `throws(E)` clause if present. - public static func translateFunctionSignature( - _ signature: FunctionSignatureSyntax, - lookupContext: SwiftTypeLookupContext - ) throws -> ( - parameters: [SwiftParameter], - effectSpecifiers: [SwiftEffectSpecifier], - thrownTypedError: SwiftType? - ) { - var effectSpecifiers = [SwiftEffectSpecifier]() - if signature.effectSpecifiers?.throwsClause != nil { - effectSpecifiers.append(.throws) - } - if signature.effectSpecifiers?.asyncSpecifier != nil { - effectSpecifiers.append(.async) - } - - let thrownTypedError = SwiftType.thrownTypedError( - from: signature.effectSpecifiers?.throwsClause, - lookupContext: lookupContext - ) - - let parameters = try signature.parameterClause.parameters.map { param in - try SwiftParameter(param, lookupContext: lookupContext) - } - - return (parameters, effectSpecifiers, thrownTypedError) - } - - public init( - _ varNode: VariableDeclSyntax, - isSet: Bool, - enclosingType: SwiftType?, - lookupContext: SwiftTypeLookupContext - ) throws { - - self.selfParameter = try Self.variableSelfParameter( - for: DeclSyntax(varNode), - enclosingType: enclosingType, - isSet: isSet - ) - - guard let binding = varNode.bindings.first, varNode.bindings.count == 1 else { - throw SwiftFunctionTranslationError.multipleBindings(varNode) - } - - guard let varTypeNode = binding.typeAnnotation?.type else { - throw SwiftFunctionTranslationError.missingTypeAnnotation(varNode) - } - let valueType = try SwiftType(varTypeNode, lookupContext: lookupContext) - - var accessorEffects: AccessorEffects? = nil - switch binding.accessorBlock?.accessors { - case .getter(let getter): - if let getter = getter.as(AccessorDeclSyntax.self) { - accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext) - } - case .accessors(let accessors): - if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) { - accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext) - } - default: - break - } - - self.effectSpecifiers = accessorEffects?.effectSpecifiers ?? [] - self.thrownTypedError = accessorEffects?.thrownTypedError - - if isSet { - self.parameters = [ - SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType) - ] - self.result = .void - } else { - self.parameters = [] - self.result = .init(convention: .direct, type: valueType) - } - self.genericParameters = [] - self.genericRequirements = [] - } - - public init( - _ subscriptNode: SubscriptDeclSyntax, - isSet: Bool, - enclosingType: SwiftType?, - lookupContext: SwiftTypeLookupContext - ) throws { - self.selfParameter = try Self.variableSelfParameter( - for: DeclSyntax(subscriptNode), - enclosingType: enclosingType, - isSet: isSet - ) - - let valueType: SwiftType = try SwiftType(subscriptNode.returnClause.type, lookupContext: lookupContext) - var nodeParameters = try subscriptNode.parameterClause.parameters.map { param -> SwiftParameter in - var p = try SwiftParameter(param, lookupContext: lookupContext) - // Subscript parameters have no external argument label by default. A - // single-identifier subscript parameter `subscript(index: Int)` is - // called as `obj[5]`, not `obj[index: 5]` — only the explicit - // `subscript(label name: Int)` form (i.e. a `secondName` exists) - // surfaces an external label. SwiftParameter.init treats single- - // identifier params like function params (label == name); strip the - // label here for the subscript-specific call shape. - if param.secondName == nil { - p.argumentLabel = nil - } - return p - } - - var accessorEffects: AccessorEffects? = nil - switch subscriptNode.accessorBlock?.accessors { - case .getter(let getter): - if let getter = getter.as(AccessorDeclSyntax.self) { - accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext) - } - case .accessors(let accessors): - if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) { - accessorEffects = Self.translateEffectSpecifiers(from: getter, lookupContext: lookupContext) - } - default: - break - } - - self.effectSpecifiers = accessorEffects?.effectSpecifiers ?? [] - self.thrownTypedError = accessorEffects?.thrownTypedError - - if isSet { - nodeParameters.append(SwiftParameter(convention: .byValue, parameterName: "newValue", type: valueType)) - self.result = .void - } else { - self.result = .init(convention: .direct, type: valueType) - } - - self.parameters = nodeParameters - self.genericParameters = [] - self.genericRequirements = [] - } - - struct AccessorEffects { - var effectSpecifiers: [SwiftEffectSpecifier] = [] - var thrownTypedError: SwiftType? = nil - } - - private static func translateEffectSpecifiers( - from decl: AccessorDeclSyntax, - lookupContext: SwiftTypeLookupContext - ) -> AccessorEffects { - var effectSpecifiers = [SwiftEffectSpecifier]() - if decl.effectSpecifiers?.throwsClause != nil { - effectSpecifiers.append(.throws) - } - if decl.effectSpecifiers?.asyncSpecifier != nil { - effectSpecifiers.append(.async) - } - - let thrownTypedError = SwiftType.thrownTypedError( - from: decl.effectSpecifiers?.throwsClause, - lookupContext: lookupContext - ) - - return AccessorEffects(effectSpecifiers: effectSpecifiers, thrownTypedError: thrownTypedError) - } - - private static func variableSelfParameter( - for decl: DeclSyntax, - enclosingType: SwiftType?, - isSet: Bool - ) throws -> SwiftSelfParameter? { - let modifiers: DeclModifierListSyntax? = - switch decl.as(DeclSyntaxEnum.self) { - case .variableDecl(let varDecl): varDecl.modifiers - case .subscriptDecl(let subscriptDecl): subscriptDecl.modifiers - default: nil - } - - guard let modifiers else { - return nil - } - - // If this is a member of a type, so we will have a self parameter. Figure out the - // type and convention for the self parameter. - if let enclosingType { - var isStatic = false - var isClass = false - for modifier in modifiers { - switch modifier.name.tokenKind { - case .keyword(.static): isStatic = true - case .keyword(.class): isClass = true - default: break - } - } - - if isStatic { - return .staticMethod(enclosingType) - } else if isClass { - return .classMethod(enclosingType) - } else { - return .instance( - convention: isSet && !enclosingType.isReferenceType ? .inout : .byValue, - swiftType: enclosingType - ) - } - } else { - return nil - } - } -} - -extension VariableDeclSyntax { - /// Determine what operations (i.e. get and/or set) supported in this `VariableDeclSyntax` - /// - /// - Parameters: - /// - binding: the pattern binding in this declaration. - /// - minimumAccessLevel: the minimum access level being extracted - public func supportedAccessorKinds( - binding: PatternBindingSyntax, - minimumAccessLevel: AccessLevelMode - ) -> AccessorBlockSyntax.SupportedAccessorKinds { - if self.bindingSpecifier.tokenKind == .keyword(.let) { - return [.get] - } - - // Account for private(set) and similar modifiers. - for modifier in self.modifiers where modifier.detail?.detail.text == "set" { - if !minimumAccessLevel.matches(modifier) { - return [.get] - } - } - - if let accessorBlock = binding.accessorBlock { - return accessorBlock.supportedAccessorKinds() - } - - return [.get, .set] - } -} - -extension AccessorBlockSyntax { - public struct SupportedAccessorKinds: OptionSet { - public var rawValue: UInt8 - - 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` - public func supportedAccessorKinds() -> SupportedAccessorKinds { - switch self.accessors { - case .getter: - return [.get] - case .accessors(let accessors): - for accessor in accessors { - switch accessor.accessorSpecifier.tokenKind { - // Existence of any write accessor or observer implies this supports read/write. - case .keyword(.set), .keyword(._modify), .keyword(.unsafeMutableAddress), - .keyword(.willSet), .keyword(.didSet): - return [.get, .set] - default: // Ignore willSet/didSet and unknown accessors. - break - } - } - return [.get] - } - } -} - -public enum SwiftFunctionTranslationError: Error { - case `throws`(ThrowsClauseSyntax) - case async(TokenSyntax) - case missingEnclosingType(InitializerDeclSyntax) - case failableInitializer(InitializerDeclSyntax) - case multipleBindings(VariableDeclSyntax) - case missingTypeAnnotation(VariableDeclSyntax) - case unsupportedAccessor(AccessorDeclSyntax) - case genericParameterSpecifier(GenericParameterSyntax) - case expressionInGenericRequirement(GenericRequirementSyntax) - case layoutRequirement(GenericRequirementSyntax) -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftFunctionType.swift b/Sources/SwiftExtract/SwiftTypes/SwiftFunctionType.swift deleted file mode 100644 index a7e8f43c5..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftFunctionType.swift +++ /dev/null @@ -1,105 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -public struct SwiftFunctionType: Equatable { - public enum Convention: Equatable { - case swift - case c - } - - public var convention: Convention - public var parameters: [SwiftParameter] - public var resultType: SwiftType - public var isEscaping: Bool = false - - public var effectSpecifiers: [SwiftEffectSpecifier] = [] - - public var isAsync: Bool { effectSpecifiers.contains(.async) } - - public var isThrowing: Bool { effectSpecifiers.contains(.throws) } - public var isTypedThrowing: Bool { thrownTypedError != nil } - public var thrownTypedError: SwiftType? = nil - - public init( - convention: Convention, - parameters: [SwiftParameter], - resultType: SwiftType, - isEscaping: Bool = false, - effectSpecifiers: [SwiftEffectSpecifier] = [], - thrownTypedError: SwiftType? = nil - ) { - self.convention = convention - self.parameters = parameters - self.resultType = resultType - self.isEscaping = isEscaping - self.effectSpecifiers = effectSpecifiers - self.thrownTypedError = thrownTypedError - } -} - -extension SwiftFunctionType: CustomStringConvertible { - public var description: String { - let parameterString = parameters.map { $0.descriptionInType }.joined(separator: ", ") - let conventionPrefix = - switch convention { - case .c: "@convention(c) " - case .swift: "" - } - let escapingPrefix = isEscaping ? "@escaping " : "" - let throwsString = - switch (isThrowing, thrownTypedError) { - case (true, .some(let errorType)): " throws(\(errorType.description))" - case (true, .none): " throws" - case (false, _): "" - } - let effectsSuffix = (isAsync ? " async" : "") + throwsString - return "\(escapingPrefix)\(conventionPrefix)(\(parameterString))\(effectsSuffix) -> \(resultType.description)" - } -} - -extension SwiftFunctionType { - public init( - _ node: FunctionTypeSyntax, - convention: Convention, - isEscaping: Bool = false, - lookupContext: SwiftTypeLookupContext - ) throws { - self.convention = convention - self.isEscaping = isEscaping - self.parameters = try node.parameters.map { param in - let isInout = param.inoutKeyword != nil - return SwiftParameter( - convention: isInout ? .inout : .byValue, - type: try SwiftType(param.type, lookupContext: lookupContext) - ) - } - - self.resultType = try SwiftType(node.returnClause.type, lookupContext: lookupContext) - - var effectSpecifiers: [SwiftEffectSpecifier] = [] - if node.effectSpecifiers?.asyncSpecifier != nil { - effectSpecifiers.append(.async) - } - if let throwsClause = node.effectSpecifiers?.throwsClause { - effectSpecifiers.append(.throws) - self.thrownTypedError = SwiftType.thrownTypedError( - from: throwsClause, - lookupContext: lookupContext - ) - } - self.effectSpecifiers = effectSpecifiers - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift b/Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift deleted file mode 100644 index d3cf9ffa8..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftKnownModules.swift +++ /dev/null @@ -1,152 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax -import SwiftSyntaxBuilder - -public enum SwiftKnownModule: String { - case swift = "Swift" - case foundation = "Foundation" - case foundationEssentials = "FoundationEssentials" - - public var name: String { - self.rawValue - } - - public var symbolTable: SwiftModuleSymbolTable { - switch self { - case .swift: swiftSymbolTable - case .foundation: foundationSymbolTable - case .foundationEssentials: foundationEssentialsSymbolTable - } - } - - public var sourceFile: SourceFileSyntax { - switch self { - case .swift: swiftSourceFile - case .foundation: foundationEssentialsSourceFile - case .foundationEssentials: foundationEssentialsSourceFile - } - } -} - -private var swiftSymbolTable: SwiftModuleSymbolTable { - var builder = SwiftParsedModuleSymbolTableBuilder(moduleName: "Swift", importedModules: [:]) - builder.handle(sourceFile: swiftSourceFile, sourceFilePath: "SwiftStdlib.swift") // FIXME: missing path here - return builder.finalize() -} - -private var foundationEssentialsSymbolTable: SwiftModuleSymbolTable { - var builder = SwiftParsedModuleSymbolTableBuilder( - moduleName: "FoundationEssentials", - requiredAvailablityOfModuleWithName: "FoundationEssentials", - alternativeModules: .init(isMainSourceOfSymbols: false, moduleNames: ["Foundation"]), - importedModules: ["Swift": swiftSymbolTable] - ) - builder.handle(sourceFile: foundationEssentialsSourceFile, sourceFilePath: "FakeFoundation.swift") - return builder.finalize() -} - -private var foundationSymbolTable: SwiftModuleSymbolTable { - var builder = SwiftParsedModuleSymbolTableBuilder( - moduleName: "Foundation", - alternativeModules: .init(isMainSourceOfSymbols: true, moduleNames: ["FoundationEssentials"]), - importedModules: ["Swift": swiftSymbolTable] - ) - builder.handle(sourceFile: foundationSourceFile, sourceFilePath: "Foundation.swift") - return builder.finalize() -} - -private let swiftSourceFile: SourceFileSyntax = """ - public struct Bool {} - public struct Int {} - public struct UInt {} - public struct Int8 {} - public struct UInt8 {} - public struct Int16 {} - public struct UInt16 {} - public struct Int32 {} - public struct UInt32 {} - public struct Int64 {} - public struct UInt64 {} - public struct Float {} - public struct Double {} - - public struct UnsafeRawPointer {} - public struct UnsafeMutableRawPointer {} - public struct UnsafeRawBufferPointer {} - public struct UnsafeMutableRawBufferPointer {} - - public struct UnsafePointer {} - public struct UnsafeMutablePointer {} - - public struct UnsafeBufferPointer {} - public struct UnsafeMutableBufferPointer {} - - public struct OpaquePointer {} - - public struct Optional {} - - public struct Array {} - - public struct Dictionary {} - - public struct Set {} - - public protocol Error {} - - // FIXME: Support 'typealias Void = ()' - public struct Void {} - - public struct String { - public init(cString: UnsafePointer) - public func withCString(_ body: (UnsafePointer) -> Void) - } - """ - -private let foundationEssentialsSourceFile: SourceFileSyntax = """ - public protocol DataProtocol {} - - public protocol LocalizedError: Error {} - - public struct Data: DataProtocol { - public init(bytes: UnsafeRawPointer, count: Int) - public init(_ bytes: [UInt8]) - public var count: Int { get } - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) - } - - public struct Date { - /// The interval between the date object and 00:00:00 UTC on 1 January 1970. - public var timeIntervalSince1970: Double { get } - - /// Returns a `Date` initialized relative to 00:00:00 UTC on 1 January 1970 by a given number of seconds. - public init(timeIntervalSince1970: Double) - } - - public typealias TimeInterval = Double - - public struct UUID {} - - public struct URL { - public init?(string: String) - public var absoluteString: String { get } - } - """ - -private var foundationSourceFile: SourceFileSyntax { - // On platforms other than Darwin, imports such as FoundationEssentials, FoundationNetworking, etc. are used, - // so this file should be created by combining the files of the aforementioned modules. - foundationEssentialsSourceFile -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift deleted file mode 100644 index bcc167f03..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftKnownTypeDecls.swift +++ /dev/null @@ -1,222 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -public enum SwiftKnownType: Equatable { - case bool - case int - case uint - case int8 - case uint8 - case int16 - case uint16 - case int32 - case uint32 - case int64 - case uint64 - case float - case double - case unsafeRawPointer - case unsafeRawBufferPointer - case unsafeMutableRawPointer - case unsafeMutableRawBufferPointer - case unsafePointer(_ pointee: SwiftType) - case unsafeMutablePointer(_ pointee: SwiftType) - case unsafeBufferPointer(_ element: SwiftType) - case unsafeMutableBufferPointer(_ element: SwiftType) - case opaquePointer - case optional(_ wrapped: SwiftType) - case void - case string - case array(_ element: SwiftType) - case dictionary(_ key: SwiftType, _ value: SwiftType) - case set(_ element: SwiftType) - - // Foundation - case foundationDataProtocol - case essentialsDataProtocol - case foundationData - case essentialsData - case foundationDate - case essentialsDate - case foundationUUID - case essentialsUUID - case foundationURL - case essentialsURL - - public init?(kind: SwiftKnownTypeDeclKind, genericArguments: [SwiftType]?) { - switch kind { - case .bool: self = .bool - case .int: self = .int - case .uint: self = .uint - case .int8: self = .int8 - case .uint8: self = .uint8 - case .int16: self = .int16 - case .uint16: self = .uint16 - case .int32: self = .int32 - case .uint32: self = .uint32 - case .int64: self = .int64 - case .uint64: self = .uint64 - case .float: self = .float - case .double: self = .double - case .unsafeRawPointer: self = .unsafeRawPointer - case .unsafeRawBufferPointer: self = .unsafeRawBufferPointer - case .unsafeMutableRawPointer: self = .unsafeMutableRawPointer - case .unsafeMutableRawBufferPointer: self = .unsafeMutableRawBufferPointer - case .unsafePointer: - guard let arg = genericArguments?.first else { return nil } - self = .unsafePointer(arg) - case .unsafeMutablePointer: - guard let arg = genericArguments?.first else { return nil } - self = .unsafeMutablePointer(arg) - case .unsafeBufferPointer: - guard let arg = genericArguments?.first else { return nil } - self = .unsafeBufferPointer(arg) - case .unsafeMutableBufferPointer: - guard let arg = genericArguments?.first else { return nil } - self = .unsafeMutableBufferPointer(arg) - case .opaquePointer: self = .opaquePointer - case .optional: - guard let arg = genericArguments?.first else { return nil } - self = .optional(arg) - case .void: self = .void - case .string: self = .string - case .array: - guard let arg = genericArguments?.first else { return nil } - self = .array(arg) - case .dictionary: - guard let key = genericArguments?.first, let value = genericArguments?.dropFirst().first else { return nil } - self = .dictionary(key, value) - case .set: - guard let arg = genericArguments?.first else { return nil } - self = .set(arg) - case .foundationDataProtocol: self = .foundationDataProtocol - case .essentialsDataProtocol: self = .essentialsDataProtocol - case .foundationData: self = .foundationData - case .essentialsData: self = .essentialsData - case .foundationDate: self = .foundationDate - case .essentialsDate: self = .essentialsDate - case .foundationUUID: self = .foundationUUID - case .essentialsUUID: self = .essentialsUUID - case .foundationURL: self = .foundationURL - case .essentialsURL: self = .essentialsURL - } - } - - public var kind: SwiftKnownTypeDeclKind { - switch self { - case .bool: .bool - case .int: .int - case .uint: .uint - case .int8: .int8 - case .uint8: .uint8 - case .int16: .int16 - case .uint16: .uint16 - case .int32: .int32 - case .uint32: .uint32 - case .int64: .int64 - case .uint64: .uint64 - case .float: .float - case .double: .double - case .unsafeRawPointer: .unsafeRawPointer - case .unsafeRawBufferPointer: .unsafeRawBufferPointer - case .unsafeMutableRawPointer: .unsafeMutableRawPointer - case .unsafeMutableRawBufferPointer: .unsafeMutableRawBufferPointer - case .unsafePointer: .unsafePointer - case .unsafeMutablePointer: .unsafeMutablePointer - case .unsafeBufferPointer: .unsafeBufferPointer - case .unsafeMutableBufferPointer: .unsafeMutableBufferPointer - case .opaquePointer: .opaquePointer - case .optional: .optional - case .void: .void - case .string: .string - case .array: .array - case .dictionary: .dictionary - case .set: .set - case .foundationDataProtocol: .foundationDataProtocol - case .essentialsDataProtocol: .essentialsDataProtocol - case .foundationData: .foundationData - case .essentialsData: .essentialsData - case .foundationDate: .foundationDate - case .essentialsDate: .essentialsDate - case .foundationUUID: .foundationUUID - case .essentialsUUID: .essentialsUUID - case .foundationURL: .foundationURL - case .essentialsURL: .essentialsURL - } - } -} - -public enum SwiftKnownTypeDeclKind: String, Hashable { - // Swift - case bool = "Swift.Bool" - case int = "Swift.Int" - case uint = "Swift.UInt" - case int8 = "Swift.Int8" - case uint8 = "Swift.UInt8" - case int16 = "Swift.Int16" - case uint16 = "Swift.UInt16" - case int32 = "Swift.Int32" - case uint32 = "Swift.UInt32" - case int64 = "Swift.Int64" - case uint64 = "Swift.UInt64" - case float = "Swift.Float" - case double = "Swift.Double" - case unsafeRawPointer = "Swift.UnsafeRawPointer" - case unsafeRawBufferPointer = "Swift.UnsafeRawBufferPointer" - case unsafeMutableRawPointer = "Swift.UnsafeMutableRawPointer" - case unsafeMutableRawBufferPointer = "Swift.UnsafeMutableRawBufferPointer" - case unsafePointer = "Swift.UnsafePointer" - case unsafeMutablePointer = "Swift.UnsafeMutablePointer" - case unsafeBufferPointer = "Swift.UnsafeBufferPointer" - case unsafeMutableBufferPointer = "Swift.UnsafeMutableBufferPointer" - case opaquePointer = "Swift.OpaquePointer" - case optional = "Swift.Optional" - case void = "Swift.Void" - case string = "Swift.String" - case array = "Swift.Array" - case dictionary = "Swift.Dictionary" - case set = "Swift.Set" - - // Foundation - case foundationDataProtocol = "Foundation.DataProtocol" - case essentialsDataProtocol = "FoundationEssentials.DataProtocol" - case foundationData = "Foundation.Data" - case essentialsData = "FoundationEssentials.Data" - case foundationDate = "Foundation.Date" - case essentialsDate = "FoundationEssentials.Date" - case foundationUUID = "Foundation.UUID" - case essentialsUUID = "FoundationEssentials.UUID" - case foundationURL = "Foundation.URL" - case essentialsURL = "FoundationEssentials.URL" - - public var moduleAndName: (module: String, name: String) { - let qualified = self.rawValue - let period = qualified.firstIndex(of: ".")! - return ( - module: String(qualified[.. ()` function type. - /// - /// Commonly used to initialize a buffer using the passed bytes and length. - public var functionInitializeByteBuffer: SwiftType { - .function( - SwiftFunctionType( - convention: .c, - parameters: [ - SwiftParameter(convention: .byValue, parameterName: nil, type: self.unsafeRawPointer), // array base pointer - SwiftParameter(convention: .byValue, parameterName: nil, type: self.int), // array length - ], - resultType: .void - ) - ) - } - - public func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { - .nominal( - SwiftNominalType( - nominalTypeDecl: symbolTable[.unsafePointer], - genericArguments: [pointeeType] - ) - ) - } - - public func unsafeMutablePointer(_ pointeeType: SwiftType) -> SwiftType { - .nominal( - SwiftNominalType( - nominalTypeDecl: symbolTable[.unsafeMutablePointer], - genericArguments: [pointeeType] - ) - ) - } - - public func unsafeBufferPointer(_ elementType: SwiftType) -> SwiftType { - .nominal( - SwiftNominalType( - nominalTypeDecl: symbolTable[.unsafeBufferPointer], - genericArguments: [elementType] - ) - ) - } - - public func unsafeMutableBufferPointer(_ elementType: SwiftType) -> SwiftType { - .nominal( - SwiftNominalType( - nominalTypeDecl: symbolTable[.unsafeMutableBufferPointer], - genericArguments: [elementType] - ) - ) - } - - public func optionalSugar(_ wrappedType: SwiftType) -> SwiftType { - .nominal( - SwiftNominalType( - sugarName: .optional, - nominalTypeDecl: symbolTable[.optional], - genericArguments: [wrappedType] - ) - ) - } - - public func arraySugar(_ elementType: SwiftType) -> SwiftType { - .nominal( - SwiftNominalType( - sugarName: .array, - nominalTypeDecl: symbolTable[.array], - genericArguments: [elementType] - ) - ) - } - - public func dictionarySugar(_ keyType: SwiftType, _ valueType: SwiftType) -> SwiftType { - .nominal( - SwiftNominalType( - sugarName: .dictionary, - nominalTypeDecl: symbolTable[.dictionary], - genericArguments: [keyType, valueType] - ) - ) - } - - public func set(_ elementType: SwiftType) -> SwiftType { - .nominal( - SwiftNominalType( - nominalTypeDecl: symbolTable[.set], - genericArguments: [elementType] - ) - ) - } - - /// Returns the known representative concrete type if there is one for the - /// given protocol kind. E.g. `Data` for `DataProtocol` - 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 - public static func representativeType(of knownProtocol: SwiftKnownTypeDeclKind) -> SwiftKnownTypeDeclKind? { - switch knownProtocol { - case .foundationDataProtocol: return .foundationData - case .essentialsDataProtocol: return .essentialsData - default: return nil - } - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/SwiftExtract/SwiftTypes/SwiftModuleSymbolTable.swift deleted file mode 100644 index 5e4f5ba6a..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftModuleSymbolTable.swift +++ /dev/null @@ -1,77 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax -import SwiftSyntaxBuilder - -public struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { - /// The name of this module. - public let moduleName: String - - /// The name of module required to be imported and checked via canImport statement. - public let requiredAvailablityOfModuleWithName: String? - - /// Data about alternative modules which provides desired symbos e.g. FoundationEssentials is non-Darwin platform alternative for Foundation - public let alternativeModules: AlternativeModuleNamesData? - - /// The top-level nominal types, found by name. - public var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:] - - /// The top-level typealias declarations, found by name. - 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. - 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. - public var nestedTypeAliases: [SwiftNominalTypeDeclaration: [String: SwiftTypeAliasDeclaration]] = [:] - - /// Look for a top-level type with the given name. - public func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { - topLevelTypes[name] - } - - /// Look for a top-level typealias with the given name. - public func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration? { - topLevelTypeAliases[name] - } - - // Look for a nested type with the given name. - public func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { - nestedTypes[parent]?[name] - } - - // Look for a nested typealias with the given name. - public func lookupNestedTypealias(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftTypeAliasDeclaration? { - nestedTypeAliases[parent]?[name] - } - - public func isAlternative(for moduleName: String) -> Bool { - alternativeModules.flatMap { $0.moduleNames.contains(moduleName) } ?? false - } -} - -extension SwiftModuleSymbolTable { - public struct AlternativeModuleNamesData { - /// Flag indicating module should be used as source of symbols to avoid duplication of symbols. - public let isMainSourceOfSymbols: Bool - - /// Names of modules which are alternative for currently checked module. - public let moduleNames: Set - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/SwiftExtract/SwiftTypes/SwiftNominalTypeDeclaration.swift deleted file mode 100644 index 0f9e81074..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ /dev/null @@ -1,328 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -// ==== ----------------------------------------------------------------------- -// MARK: Nominal identity - -/// A module-qualified identity for a nominal type. -public struct SwiftNominalIdentity: Hashable, Sendable, CustomStringConvertible { - /// The module that declares this nominal, e.g. "Swift" - public let moduleName: String - - /// The parent-chained name within that module, e.g. `GeneratedContent.Kind` - public let typeName: SwiftQualifiedTypeName - - public init(moduleName: String, typeName: SwiftQualifiedTypeName) { - self.moduleName = moduleName - self.typeName = typeName - } - - /// e.g. "Swift.String", "MyLib.GeneratedContent.Kind" - public var fullyQualifiedName: String { - "\(moduleName).\(typeName.fullName)" - } - - /// Parent-chained but module-unqualified: the spelling `extractedTypes` is keyed by - public var qualifiedName: String { - typeName.fullName - } - - public var leafName: String { - typeName.leafName - } - - public var description: String { fullyQualifiedName } -} - -/// A syntax node for a nominal type declaration. -public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax - & WithModifiersSyntax - -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`. - 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. - public let moduleName: String - - /// The name of this nominal type, e.g. 'MyCollection'. - public let name: String - - public init(sourceFilePath: String, moduleName: String, name: String) { - self.sourceFilePath = sourceFilePath - self.moduleName = moduleName - self.name = name - } -} - -/// A syntax node paired with a simple file path -public struct SwiftInputFile { - public let syntax: SourceFileSyntax - /// Simple file path of the file from which the syntax node was parsed. - public let path: String - public init(syntax: SourceFileSyntax, path: String) { - self.syntax = syntax - self.path = path - } -} - -/// 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. -public class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { - public enum Kind { - case actor - case `class` - case `enum` - case `protocol` - case `struct` - } - - /// The syntax node this declaration is derived from. - public let syntax: NominalTypeDeclSyntaxNode - - /// The kind of nominal type. - public let kind: Kind - - /// The parent nominal type when this nominal type is nested inside another type, e.g., - /// MyCollection.Iterator. - public let parent: SwiftNominalTypeDeclaration? - - /// The generic parameters of this nominal type. - public let genericParameters: [SwiftGenericParameterDeclaration] - - /// True when this declaration is a placeholder synthesized by - /// `SwiftSyntheticTypes.unresolvedNominal(_:)` because the symbol table - /// couldn't resolve the name. - /// - /// Exists to support **lazy specializations** — code generators that - /// resolve names later than analysis time. Without lenient mode the - /// analyzer would drop entire declarations the moment any unresolved name - /// appeared in their signature; lenient mode keeps the decl and stamps the - /// unknown name with this flag, letting a downstream substitution pass - /// recognize and replace it. - /// - /// Canonical case: an associated type in a protocol requirement. For - /// - /// protocol Container { - /// associatedtype Element - /// func first() -> Element - /// } - /// - /// the symbol table has no top-level `Element` when walking `first()` — it - /// resolves later when a conforming type fixes a carrier - /// (`MyCollection: Container where Element == Int`). Strict mode would - /// drop `first()` from the analysis result; lenient mode keeps it with - /// `Element` as a placeholder a later substitution pass replaces with the - /// carrier's real type. Generic-parameter references in the body of a - /// generic type and externally-bridged simple-name types follow the same - /// shape. - public let isUnresolvedTypePlaceholder: Bool - - /// Identify this nominal declaration as one of the known standard library - /// types, like 'Swift.Int[. - public private(set) lazy var knownTypeKind: SwiftKnownTypeDeclKind? = { - self.computeKnownStandardLibraryType() - }() - - /// Create a nominal type declaration from the syntax node for a nominal type - /// declaration. - public init( - name: String, - sourceFilePath: String, - moduleName: String, - parent: SwiftNominalTypeDeclaration?, - node: NominalTypeDeclSyntaxNode, - isUnresolvedTypePlaceholder: Bool = false, - ) { - self.parent = parent - self.syntax = node - self.isUnresolvedTypePlaceholder = isUnresolvedTypePlaceholder - self.genericParameters = - node.asProtocol(WithGenericParametersSyntax.self)?.genericParameterClause?.parameters.map { - SwiftGenericParameterDeclaration(sourceFilePath: sourceFilePath, moduleName: moduleName, node: $0) - } ?? [] - - // Determine the kind from the syntax node. - switch Syntax(node).as(SyntaxEnum.self) { - case .actorDecl: self.kind = .actor - case .classDecl: self.kind = .class - case .enumDecl: self.kind = .enum - case .protocolDecl: self.kind = .protocol - case .structDecl: self.kind = .struct - default: fatalError("Not a nominal type declaration") - } - super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: name) - } - - public private(set) lazy var firstInheritanceType: TypeSyntax? = { - guard let firstInheritanceType = self.syntax.inheritanceClause?.inheritedTypes.first else { - return nil - } - - return firstInheritanceType.type - }() - - public var inheritanceTypes: InheritedTypeListSyntax? { - self.syntax.inheritanceClause?.inheritedTypes - } - - public var genericWhereClause: GenericWhereClauseSyntax? { - self.syntax.asProtocol(WithGenericParametersSyntax.self)?.genericWhereClause - } - - /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". - public private(set) lazy var isSendable: Bool = { - // Check if Sendable is in the inheritance list - guard let inheritanceClause = self.syntax.inheritanceClause else { - return false - } - - for inheritedType in inheritanceClause.inheritedTypes { - if inheritedType.type.trimmedDescription == "Sendable" { - return true - } - } - - return false - }() - - /// Determine the known standard library type for this nominal type - /// declaration. - private func computeKnownStandardLibraryType() -> SwiftKnownTypeDeclKind? { - if parent != nil { - return nil - } - - return SwiftKnownTypeDeclKind(rawValue: "\(moduleName).\(name)") - } - - /// Structured qualified type name built from the parent chain - public var qualifiedTypeName: SwiftQualifiedTypeName { - if let parent = self.parent { - return SwiftQualifiedTypeName(parent.qualifiedTypeName.components + [name]) - } else { - return SwiftQualifiedTypeName(name) - } - } - - /// This declaration's module-qualified identity. - /// - /// `qualifiedName` alone cannot distinguish `Swift.String` from a locally declared - /// `String`, which is why anything indexing types across module boundaries keys on this - public var identity: SwiftNominalIdentity { - SwiftNominalIdentity(moduleName: moduleName, typeName: qualifiedTypeName) - } - - public var qualifiedName: String { - qualifiedTypeName.fullName - } - - /// Like `qualifiedName` but with dots replaced by underscores, suitable for - /// use in C symbol names and Java identifiers - public var flatName: String { - qualifiedTypeName.fullFlatName - } - - public var isReferenceType: Bool { - switch kind { - case .actor, .class: - return true - case .enum, .struct, .protocol: - return false - } - } - - public var isGeneric: Bool { - !genericParameters.isEmpty - } -} - -extension SwiftNominalTypeDeclaration: CustomStringConvertible { - public var description: String { - if isGeneric { - "\(qualifiedName)<\(genericParameters.map(\.name).joined(separator: ", "))>" - } else { - qualifiedName - } - } -} - -public class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { - public let syntax: GenericParameterSyntax - - public init( - sourceFilePath: String, - moduleName: String, - node: GenericParameterSyntax - ) { - self.syntax = node - super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) - } - - public var hasEach: Bool { - syntax.specifier?.tokenKind == .keyword(.each) - } - - public var packReferenceName: String { - if hasEach { - "each \(name)" - } else { - name - } - } - - public var packExpansionName: String { - if hasEach { - "repeat each \(name)" - } else { - name - } - } -} - -/// A plain typealias will resolve as the right hand type in generated code. -/// -/// 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`. -public final class SwiftTypeAliasDeclaration: SwiftTypeDeclaration { - public let syntax: TypeAliasDeclSyntax - - public init( - sourceFilePath: String, - moduleName: String, - node: TypeAliasDeclSyntax - ) { - self.syntax = node - super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) - } -} - -extension SwiftTypeDeclaration: Equatable { - public static func == (lhs: SwiftTypeDeclaration, rhs: SwiftTypeDeclaration) -> Bool { - lhs === rhs - } -} - -extension SwiftTypeDeclaration: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift b/Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift deleted file mode 100644 index 896449e0a..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftParameter.swift +++ /dev/null @@ -1,170 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -public struct SwiftParameter: Equatable { - public var convention: SwiftParameterConvention - public var argumentLabel: String? - 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? - /// Whether the parameter is marked `isolated`. - public var isIsolated: Bool - - public init( - convention: SwiftParameterConvention, - argumentLabel: String? = nil, - parameterName: String? = nil, - type: SwiftType, - isVariadic: Bool = false, - hasDefaultValue: Bool = false, - defaultValueExpression: String? = nil, - isIsolated: Bool = false - ) { - self.convention = convention - self.argumentLabel = argumentLabel - self.parameterName = parameterName - self.type = type - self.isVariadic = isVariadic - self.hasDefaultValue = hasDefaultValue - self.defaultValueExpression = defaultValueExpression - self.isIsolated = isIsolated - } - - /// 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 - } -} - -extension SwiftParameter: CustomStringConvertible { - public var description: String { - let argumentLabel = self.argumentLabel ?? "_" - let parameterName = self.parameterName ?? "_" - - return "\(argumentLabel) \(parameterName): \(descriptionInType)" - } - - public var descriptionInType: String { - let conventionString: String - switch convention { - case .byValue: - conventionString = "" - - case .consuming: - conventionString = "consuming " - - case .inout: - conventionString = "inout " - } - - return conventionString + type.description - } -} - -/// Describes how a parameter is passed. -public enum SwiftParameterConvention: Equatable { - /// The parameter is passed by-value or borrowed. - case byValue - /// The parameter is passed by-value but consumed. - case consuming - /// The parameter is passed indirectly via inout. - case `inout` -} - -extension SwiftParameter { - public init(_ node: EnumCaseParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { - self.convention = .byValue - self.type = try SwiftType(node.type, lookupContext: lookupContext) - 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 - self.isIsolated = false - } -} - -extension SwiftParameter { - 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 - var convention = SwiftParameterConvention.byValue - var isIsolated = false - if let attributedType = type.as(AttributedTypeSyntax.self) { - var sawUnknownSpecifier = false - for specifier in attributedType.specifiers { - guard case .simpleTypeSpecifier(let simple) = specifier else { - sawUnknownSpecifier = true - continue - } - - switch simple.specifier.tokenKind { - case .keyword(.consuming), .keyword(.__consuming), .keyword(.__owned): - convention = .consuming - case .keyword(.inout): - convention = .inout - case .keyword(.isolated): - isIsolated = true - default: - sawUnknownSpecifier = true - break - } - } - - // Ignore anything else in the attributed type. - if !sawUnknownSpecifier && attributedType.attributes.isEmpty { - type = attributedType.baseType - } - } - self.convention = convention - self.isIsolated = isIsolated - - // 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 - self.parameterName = secondName.identifier?.name - } else { - self.argumentLabel = node.firstName.identifier?.name - self.parameterName = self.argumentLabel - } - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift deleted file mode 100644 index 773b7b00f..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ /dev/null @@ -1,311 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftIfConfig -import SwiftSyntax - -package struct SwiftParsedModuleSymbolTableBuilder { - package let log: Logger? - - /// The symbol table being built. - package var symbolTable: SwiftModuleSymbolTable - - /// Imported modules to resolve type syntax. - package let importedModules: [String: SwiftModuleSymbolTable] - - /// The build configuration used to resolve #if conditional compilation blocks. - package let buildConfig: any BuildConfiguration - - /// An extension decl whose extended type hasn't been resolved yet, paired with - /// the path of the source file it was declared in. - package struct UnresolvedExtension { - package var node: ExtensionDeclSyntax - package var sourceFilePath: String - } - - /// Extension decls their extended type hasn't been resolved, paired with - /// the path of the source file they were declared in. - package var unresolvedExtensions: [UnresolvedExtension] - - package init( - moduleName: String, - requiredAvailablityOfModuleWithName: String? = nil, - alternativeModules: SwiftModuleSymbolTable.AlternativeModuleNamesData? = nil, - importedModules: [String: SwiftModuleSymbolTable], - buildConfig: any BuildConfiguration = .swiftExtractDefault, - log: Logger? = nil - ) { - self.log = log - self.symbolTable = .init( - moduleName: moduleName, - requiredAvailablityOfModuleWithName: requiredAvailablityOfModuleWithName, - alternativeModules: alternativeModules - ) - self.importedModules = importedModules - self.buildConfig = buildConfig - self.unresolvedExtensions = [] - } - - package var moduleName: String { - symbolTable.moduleName - } -} - -extension SwiftParsedModuleSymbolTableBuilder { - - package mutating func handle( - sourceFile: SourceFileSyntax, - sourceFilePath: String - ) { - // Find top-level type declarations. - for statement in sourceFile.statements { - self.handle(codeBlockItem: statement.item, sourceFilePath: sourceFilePath) - } - } - - package mutating func handle( - codeBlockItem node: CodeBlockItemSyntax.Item, - sourceFilePath: String - ) { - // We only care about declarations. - guard case .decl(let decl) = node else { - return - } - - if let nominalTypeNode = decl.asNominal { - self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalTypeNode, parent: nil) - } else if let typeAliasNode = decl.as(TypeAliasDeclSyntax.self) { - self.handle(sourceFilePath: sourceFilePath, typeAliasDecl: typeAliasNode, parent: nil) - } else if let extensionNode = decl.as(ExtensionDeclSyntax.self) { - self.handle(extensionDecl: extensionNode, sourceFilePath: sourceFilePath) - } else if let ifConfigNode = decl.as(IfConfigDeclSyntax.self) { - self.handle(ifConfig: ifConfigNode, sourceFilePath: sourceFilePath) - } else if let typeAliasNode = decl.as(TypeAliasDeclSyntax.self) { - self.handle(typeAliasDecl: typeAliasNode, sourceFilePath: sourceFilePath) - } - } - - package mutating func handle( - typeAliasDecl node: TypeAliasDeclSyntax, - sourceFilePath: String - ) { - let name = node.name.text - if symbolTable.topLevelTypeAliases[name] != nil - || symbolTable.lookupTopLevelNominalType(name) != nil - { - log?.debug("Failed to add a typealias into symbol table: redeclaration; \(name)") - return - } - symbolTable.topLevelTypeAliases[name] = SwiftTypeAliasDeclaration( - sourceFilePath: sourceFilePath, - moduleName: moduleName, - node: node - ) - } - - /// Add a nominal type declaration and all of the nested types within it to the symbol - /// table. - package mutating func handle( - sourceFilePath: String, - nominalTypeDecl node: NominalTypeDeclSyntaxNode, - parent: SwiftNominalTypeDeclaration? - ) { - // If we have already recorded a nominal type with the name in this module, - // it's an invalid redeclaration. - 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)") - return - } - - // Otherwise, create the nominal type declaration. - let nominalTypeDecl = SwiftNominalTypeDeclaration( - name: node.name.text, - sourceFilePath: sourceFilePath, - moduleName: moduleName, - parent: parent, - node: node - ) - - if let parent { - // For nested types, make them discoverable from the parent type. - symbolTable.nestedTypes[parent, default: [:]][nominalTypeDecl.name] = nominalTypeDecl - } else { - // For top-level types, make them discoverable by name. - symbolTable.topLevelTypes[nominalTypeDecl.name] = nominalTypeDecl - } - - self.handle(sourceFilePath: sourceFilePath, memberBlock: node.memberBlock, parent: nominalTypeDecl) - } - - package mutating func handle( - sourceFilePath: String, - typeAliasDecl node: TypeAliasDeclSyntax, - parent: SwiftNominalTypeDeclaration? - ) { - 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)") - return - } - - let typeAliasDecl = SwiftTypeAliasDeclaration( - sourceFilePath: sourceFilePath, - moduleName: moduleName, - node: node - ) - - if let parent { - symbolTable.nestedTypeAliases[parent, default: [:]][typeAliasDecl.name] = typeAliasDecl - } else { - symbolTable.topLevelTypeAliases[typeAliasDecl.name] = typeAliasDecl - } - } - - package mutating func handle( - sourceFilePath: String, - memberBlock node: MemberBlockSyntax, - parent: SwiftNominalTypeDeclaration - ) { - for member in node.members { - self.handle(sourceFilePath: sourceFilePath, memberDecl: member.decl, parent: parent) - } - } - - /// Add a nested type declaration to the symbol table. - package mutating func handle( - sourceFilePath: String, - memberDecl decl: DeclSyntax, - parent: SwiftNominalTypeDeclaration - ) { - if let nominalMember = decl.asNominal { - self.handle(sourceFilePath: sourceFilePath, nominalTypeDecl: nominalMember, parent: parent) - } else if let typeAliasMember = decl.as(TypeAliasDeclSyntax.self) { - self.handle(sourceFilePath: sourceFilePath, typeAliasDecl: typeAliasMember, parent: parent) - } else if let ifConfigNode = decl.as(IfConfigDeclSyntax.self) { - let (clause, _) = ifConfigNode.activeClause(in: buildConfig) - if let clause, let elements = clause.elements { - switch elements { - case .decls(let memberBlock): - for memberItem in memberBlock { - self.handle(sourceFilePath: sourceFilePath, memberDecl: memberItem.decl, parent: parent) - } - case .statements(let codeBlock): - for codeItem in codeBlock { - if let declNode = codeItem.item.as(DeclSyntax.self) { - self.handle(sourceFilePath: sourceFilePath, memberDecl: declNode, parent: parent) - } - } - default: - break - } - } - } - } - - package mutating func handle( - extensionDecl node: ExtensionDeclSyntax, - sourceFilePath: String - ) { - if !self.tryHandle(extension: node, sourceFilePath: sourceFilePath) { - self.unresolvedExtensions.append(UnresolvedExtension(node: node, sourceFilePath: sourceFilePath)) - } - } - - /// Add any nested types within the given extension to the symbol table. - /// If the extended nominal type can't be resolved, returns false. - package mutating func tryHandle( - extension node: ExtensionDeclSyntax, - sourceFilePath: String - ) -> Bool { - // Try to resolve the type referenced by this extension declaration. - // If it fails, we'll try again later. - let table = SwiftSymbolTable( - parsedModule: symbolTable, - importedModules: importedModules - ) - let lookupContext = SwiftTypeLookupContext(symbolTable: table) - guard let extendedType = try? SwiftType(node.extendedType, lookupContext: lookupContext) else { - return false - } - guard let extendedNominal = extendedType.asNominalTypeDeclaration else { - // Extending type was not a nominal type. Ignore it. - return true - } - - // Find any nested types within this extension and add them. - self.handle(sourceFilePath: sourceFilePath, memberBlock: node.memberBlock, parent: extendedNominal) - return true - } - - package mutating func handle( - ifConfig node: IfConfigDeclSyntax, - sourceFilePath: String - ) { - let (clause, _) = node.activeClause(in: buildConfig) - if let clause, let elements = clause.elements { - switch elements { - case .statements(let codeBlock): - for codeItem in codeBlock { - self.handle(codeBlockItem: codeItem.item, sourceFilePath: sourceFilePath) - } - default: - break - } - } - } - - /// Finalize the symbol table and return it. - 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.: - // - // extension Outer.Inner { struct Deeper {} } - // extension Outer { struct Inner {} } - // struct Outer {} - // - while !unresolvedExtensions.isEmpty { - var extensions = self.unresolvedExtensions - extensions.removeAll(where: { - self.tryHandle(extension: $0.node, sourceFilePath: $0.sourceFilePath) - }) - - // If we didn't resolve anything, we're done. - if extensions.count == unresolvedExtensions.count { - break - } - - assert(extensions.count < unresolvedExtensions.count) - self.unresolvedExtensions = extensions - } - - return symbolTable - } -} - -extension DeclSyntaxProtocol { - package var asNominal: NominalTypeDeclSyntaxNode? { - switch DeclSyntax(self).as(DeclSyntaxEnum.self) { - case .actorDecl(let actorDecl): actorDecl - case .classDecl(let classDecl): classDecl - case .enumDecl(let enumDecl): enumDecl - case .protocolDecl(let protocolDecl): protocolDecl - case .structDecl(let structDecl): structDecl - default: nil - } - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftQualifiedTypeName.swift b/Sources/SwiftExtract/SwiftTypes/SwiftQualifiedTypeName.swift deleted file mode 100644 index 81a5c4419..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftQualifiedTypeName.swift +++ /dev/null @@ -1,47 +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 -// -//===----------------------------------------------------------------------===// - -/// A structured representation of a Swift qualified type name such as -/// `Logger.Message`, providing self-documenting conversions to the various -/// string forms needed across the codebase: -/// - **qualifiedName** (`Logger.Message`) - for Swift source -/// - **flatName** (`Logger_Message`) - for C symbols / `@_cdecl` and Java identifiers -/// - **leafName** (`Message`) - innermost component only -public struct SwiftQualifiedTypeName: Hashable, Sendable, CustomStringConvertible { - /// Name components from outermost to innermost, e.g. ["Logger", "Message"] - public let components: [String] - - public init(_ components: [String]) { - precondition(!components.isEmpty) - self.components = components - } - - public init(_ leafName: String) { - self.components = [leafName] - } - - /// Leaf name (innermost), e.g. "Message" - public var leafName: String { components.last! } - - /// Dot-separated for Swift source, e.g. "Logger.Message" - public var fullName: String { components.joined(separator: ".") } - - /// Underscore-separated for C symbols and Java identifiers, e.g. "Logger_Message" - public var fullFlatName: String { components.joined(separator: "_") } - - /// CustomStringConvertible - uses fullName - public var description: String { fullName } - - public var isNested: Bool { components.count > 1 } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftResult.swift b/Sources/SwiftExtract/SwiftTypes/SwiftResult.swift deleted file mode 100644 index 87fb57ea6..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftResult.swift +++ /dev/null @@ -1,36 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -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 - } -} - -public enum SwiftResultConvention: Equatable { - case direct - case indirect -} - -extension SwiftResult { - public static var void: Self { - Self(convention: .direct, type: .void) - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift b/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift deleted file mode 100644 index d29271b69..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftSymbolTable.swift +++ /dev/null @@ -1,296 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftIfConfig -import SwiftParser -import SwiftSyntax - -public protocol SwiftSymbolTableProtocol { - /// The module name that this symbol table describes. - var moduleName: String { get } - - /// Look for a top-level nominal type with the given name. This should only - /// return nominal types within this module. - func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? - - /// Look for a top-level typealias with the given name. - func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration? - - // Look for a nested type with the given name. - func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? - - // Look for a nested typealias with the given name. - func lookupNestedTypealias(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftTypeAliasDeclaration? -} - -extension SwiftSymbolTableProtocol { - /// Look for a type - public func lookupType(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftNominalTypeDeclaration? { - if let parent { - return lookupNestedType(name, parent: parent) - } - - return lookupTopLevelNominalType(name) - } - - public func lookupTypealias(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftTypeAliasDeclaration? { - if let parent { - return lookupNestedTypealias(name, parent: parent) - } - - return lookupTopLevelTypealias(name) - } -} - -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] { - // Ordering with source of symbols preference: - // - main-source-of-symbols modules come first (alphabetical among themselves), - // - then the rest (alphabetical). - importedModules.values.sorted(by: { lhs, rhs in - let lhsIsMain = lhs.alternativeModules?.isMainSourceOfSymbols ?? false - let rhsIsMain = rhs.alternativeModules?.isMainSourceOfSymbols ?? false - if lhsIsMain != rhsIsMain { return lhsIsMain } - return lhs.moduleName < rhs.moduleName - }) - } - - public init( - parsedModule: SwiftModuleSymbolTable, - importedModules: [String: SwiftModuleSymbolTable], - syntheticImportedModuleNames: Set = [] - ) { - self.parsedModule = parsedModule - self.importedModules = importedModules - self.syntheticImportedModuleNames = syntheticImportedModuleNames - } - - public func isModuleName(_ name: String) -> Bool { - if name == moduleName { - return true - } - return importedModules.keys.contains(name) - } -} - -extension SwiftSymbolTable { - package static func setup( - moduleName: String, - _ inputFiles: some Collection, - additionalInputFiles: [SwiftInputFile] = [], - config: (any SwiftExtractConfiguration)?, - sourceDependencies: SourceDependencies, - buildConfig: any BuildConfiguration = .swiftExtractDefault, - log: Logger? = nil, - ) -> SwiftSymbolTable { - - // Prepare imported modules. - // FIXME: Support arbitrary dependencies. - var modules: Set = [] - for inputFile in inputFiles { - let importedModules = importingModules(sourceFile: inputFile.syntax) - modules.formUnion(importedModules) - } - var importedModules: [String: SwiftModuleSymbolTable] = [:] - importedModules[SwiftKnownModule.swift.name] = SwiftKnownModule.swift.symbolTable - for module in modules { - // We don't need duplicates of symbols, first known definition is enough to parse module - // e.g Data from FoundationEssentials and Foundation collide and lead to different results due to random order of keys in Swift's Dictionary - // guard module.isMainSourceOfSymbols || !importedModules.contains(where: { $0.value.isAlternative(for: String)}) else { continue } - - if importedModules[module.name] == nil, - let knownModule = SwiftKnownModule(rawValue: module.name) - { - importedModules[module.name] = knownModule.symbolTable - } - } - - for dependencyModuleName in sourceDependencies.swiftModuleNames { - // The module may already have been loaded as a known/built-in module - // (e.g. Swift, Foundation) above - guard importedModules[dependencyModuleName] == nil else { - continue - } - 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 - // (it can shadow names) and too narrow (it misses modules the dep - // imports but the primary doesn't). - var dependencyModuleBuilder = SwiftParsedModuleSymbolTableBuilder( - moduleName: dependencyModuleName, - importedModules: importedModules, - buildConfig: buildConfig, - ) - for input in dependencyInputs { - dependencyModuleBuilder.handle(sourceFile: input.syntax, sourceFilePath: input.path) - } - 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())" - ) - } - - // Load stub type declarations for imported modules from config. - // This enables types from external modules (e.g. extension targets) to be - // resolved in the symbol table without scanning their actual source. - if let stubs = config?.importedModuleStubs { - for (stubModuleName, declarations) in stubs { - if importedModules[stubModuleName] == nil { - let source = declarations.joined(separator: "\n") - let sourceFile = Parser.parse(source: source) - var stubBuilder = SwiftParsedModuleSymbolTableBuilder( - moduleName: stubModuleName, - importedModules: importedModules, - buildConfig: buildConfig, - ) - 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())") - } else { - log?.info("Module '\(stubModuleName)' already known, skipping stub") - } - } - } else { - log?.debug("No importedModuleStubs in config") - } - - // FIXME: Support granular lookup context (file, type context). - - var builder = SwiftParsedModuleSymbolTableBuilder( - moduleName: moduleName, - importedModules: importedModules, - buildConfig: buildConfig, - log: log, - ) - // First, register top-level and nested nominal types to the symbol table. - for sourceFile in inputFiles { - builder.handle(sourceFile: sourceFile.syntax, sourceFilePath: sourceFile.path) - } - for sourceFile in additionalInputFiles { - builder.handle(sourceFile: sourceFile.syntax, sourceFilePath: sourceFile.path) - } - let parsedModule = builder.finalize() - return SwiftSymbolTable( - parsedModule: parsedModule, - importedModules: importedModules, - syntheticImportedModuleNames: sourceDependencies.syntheticModuleNames, - ) - } -} - -extension SwiftSymbolTable: SwiftSymbolTableProtocol { - 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. - public func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { - if let parsedResult = parsedModule.lookupTopLevelNominalType(name) { - return parsedResult - } - - for importedModule in prioritySortedImportedModules { - if let result = importedModule.lookupTopLevelNominalType(name) { - return result - } - } - - return nil - } - - /// Look for a top-level nominal type in a specific module by name - public func lookupTopLevelNominalType(_ name: String, inModule moduleName: String) -> SwiftNominalTypeDeclaration? { - if moduleName == self.moduleName { - return parsedModule.lookupTopLevelNominalType(name) - } - return importedModules[moduleName]?.lookupTopLevelNominalType(name) - } - - /// Look for a top-level typealias with the given name. - public func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration? { - if let parsedResult = parsedModule.lookupTopLevelTypealias(name) { - return parsedResult - } - - for importedModule in prioritySortedImportedModules { - if let result = importedModule.lookupTopLevelTypealias(name) { - return result - } - } - - return nil - } - - // Look for a nested type with the given name. - public func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { - if let parsedResult = parsedModule.lookupNestedType(name, parent: parent) { - return parsedResult - } - - for importedModule in importedModules.values { - if let result = importedModule.lookupNestedType(name, parent: parent) { - return result - } - } - - return nil - } - - // Look for a nested typealias with the given name. - public func lookupNestedTypealias(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftTypeAliasDeclaration? { - if let parsedResult = parsedModule.lookupNestedTypealias(name, parent: parent) { - return parsedResult - } - - for importedModule in importedModules.values { - if let result = importedModule.lookupNestedTypealias(name, parent: parent) { - return result - } - } - - return nil - } -} - -extension SwiftSymbolTable { - /// Map 'SwiftKnownTypeDeclKind' to the declaration. - public subscript(knownType: SwiftKnownTypeDeclKind) -> SwiftNominalTypeDeclaration! { - if let known = knownTypeToNominal[knownType] { - return known - } - - let (module, name) = knownType.moduleAndName - guard let moduleTable = importedModules[module] else { - return nil - } - - let found = moduleTable.lookupTopLevelNominalType(name) - knownTypeToNominal[knownType] = found - return found - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftSyntheticTypes.swift b/Sources/SwiftExtract/SwiftTypes/SwiftSyntheticTypes.swift deleted file mode 100644 index 90fd7991d..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftSyntheticTypes.swift +++ /dev/null @@ -1,59 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 SwiftParser -import SwiftSyntax - -// ==== ----------------------------------------------------------------------- -// MARK: SwiftSyntheticTypes -// -// `SwiftType` is normally only constructable from a real source declaration -// the symbol table can resolve. A few callers — primarily downstream language -// code generators that treat unresolved names symbolically (see -// `SwiftNominalTypeDeclaration.isUnresolvedTypePlaceholder` for the -// lazy-specialization motivation) — need to build an unresolved nominal -// reference from a bare type name. This builds one by parsing a throwaway -// `struct {}` to obtain the syntax node the -// `SwiftNominalTypeDeclaration` initializer requires. - -public enum SwiftSyntheticTypes { - /// Build an unresolved nominal `SwiftType` for the given simple type name. - /// The result has `isUnresolvedTypePlaceholder == true` so a downstream pass - /// can recognize and substitute it. See - /// `SwiftNominalTypeDeclaration.isUnresolvedTypePlaceholder` for usage. - public static func unresolvedNominal( - _ name: String - ) -> SwiftType { - let source = Parser.parse(source: "struct \(name) {}") - // Fall back gracefully if `name` isn't a simple identifier (the parsed - // declaration list will be empty; reuse a placeholder syntax node and - // record the requested name on the declaration itself). - let structSyntax: StructDeclSyntax - if let s = source.statements.first?.item.as(StructDeclSyntax.self) { - structSyntax = s - } else { - structSyntax = Parser.parse(source: "struct __Synthesized {}") - .statements.first!.item.cast(StructDeclSyntax.self) - } - let decl = SwiftNominalTypeDeclaration( - name: name, - sourceFilePath: "", - moduleName: "", - parent: nil, - node: structSyntax, - isUnresolvedTypePlaceholder: true - ) - return .nominal(SwiftNominalType(nominalTypeDecl: decl)) - } -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftType+GenericTypes.swift b/Sources/SwiftExtract/SwiftTypes/SwiftType+GenericTypes.swift deleted file mode 100644 index 7949bf489..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftType+GenericTypes.swift +++ /dev/null @@ -1,97 +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 -// -//===----------------------------------------------------------------------===// - -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. - package func representativeConcreteTypeIn( - knownTypes: SwiftKnownTypes, - genericParameters: [SwiftGenericParameterDeclaration], - genericRequirements: [SwiftGenericRequirement] - ) -> SwiftType? { - representativeConcreteType( - self, - knownTypes: knownTypes, - genericParameters: genericParameters, - genericRequirements: genericRequirements - ) - } - - /// Returns the protocol type if this is a generic parameter in the list - package func typeIn( - genericParameters: [SwiftGenericParameterDeclaration], - genericRequirements: [SwiftGenericRequirement] - ) -> SwiftType? { - switch self { - case .genericParameter(let genericParam): - if genericParameters.contains(genericParam) { - let types: [SwiftType] = genericRequirements.compactMap { - guard case .inherits(let left, let right) = $0, left == self else { - return nil - } - return right - } - - if types.isEmpty { - // TODO: Any?? - return nil - } else if types.count == 1 { - return types.first! - } else { - return .composite(types) - } - } - - return nil - - default: - return nil - } - } -} - -private func representativeConcreteType( - _ type: SwiftType, - knownTypes: SwiftKnownTypes, - genericParameters: [SwiftGenericParameterDeclaration], - genericRequirements: [SwiftGenericRequirement] -) -> SwiftType? { - var maybeProto: SwiftType? = nil - switch type { - case .existential(let proto), .opaque(let proto): - maybeProto = proto - case .genericParameter(let genericParam): - // If the type is a generic parameter declared in this function and - // conforms to a protocol with representative concrete type, use it. - if genericParameters.contains(genericParam) { - for requirement in genericRequirements { - if case .inherits(let left, let right) = requirement, left == type { - guard maybeProto == nil else { - // multiple requirements on the generic parameter. - return nil - } - maybeProto = right - break - } - } - } - default: - return nil - } - - if let knownProtocol = maybeProto?.asNominalTypeDeclaration?.knownTypeKind { - return knownTypes.representativeType(of: knownProtocol) - } - return nil -} diff --git a/Sources/SwiftExtract/SwiftTypes/SwiftType.swift b/Sources/SwiftExtract/SwiftTypes/SwiftType.swift deleted file mode 100644 index 6a9a89141..000000000 --- a/Sources/SwiftExtract/SwiftTypes/SwiftType.swift +++ /dev/null @@ -1,784 +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 -// -//===----------------------------------------------------------------------===// - -import SwiftSyntax - -/// An element of a Swift tuple type, preserving the optional label. -public struct SwiftTupleElement: Equatable, CustomStringConvertible { - public var label: String? - public var type: SwiftType - - public init(label: String? = nil, type: SwiftType) { - self.label = label - self.type = type - } - - public var description: String { - if let label { - return "\(label): \(type)" - } - return "\(type)" - } -} - -/// Describes a type in the Swift type system. -public enum SwiftType: Equatable { - case nominal(SwiftNominalType) - - case genericParameter(SwiftGenericParameterDeclaration) - - indirect case function(SwiftFunctionType) - - /// `.Type` - indirect case metatype(SwiftType) - - /// `(