diff --git a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift index 292b7182..190f9993 100644 --- a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift +++ b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift @@ -13,11 +13,12 @@ //===----------------------------------------------------------------------===// import Foundation +import JavaTypes /// Represents a value of a `java.lang.foreign.Self` that we want to render in generated Java code. /// /// This type may gain further methods for adjusting target layout, byte order, names etc. -public struct ForeignValueLayout: CustomStringConvertible { +public struct ForeignValueLayout: CustomStringConvertible, Equatable { var inlineComment: String? var value: String @@ -35,6 +36,20 @@ public struct ForeignValueLayout: CustomStringConvertible { self.needsMemoryLayoutCall = true } + public init?(javaType: JavaType) { + switch javaType { + case .boolean: self = .SwiftBool + case .byte: self = .SwiftInt8 + case .char: self = .SwiftUInt16 + case .short: self = .SwiftInt16 + case .int: self = .SwiftInt32 + case .long: self = .SwiftInt64 + case .float: self = .SwiftFloat + case .double: self = .SwiftDouble + case .array, .class, .void: return nil + } + } + public var description: String { var result = "" @@ -68,4 +83,9 @@ extension ForeignValueLayout { public static let SwiftFloat = Self(javaConstant: "SWIFT_FLOAT") public static let SwiftDouble = Self(javaConstant: "SWIFT_DOUBLE") + + var isPrimitive: Bool { + // FIXME: This is a hack, we need an enum to better describe this! + value != "SWIFT_POINTER" + } } diff --git a/Sources/JExtractSwift/NominalTypeResolution.swift b/Sources/JExtractSwift/NominalTypeResolution.swift index e3cc18a1..d2421a24 100644 --- a/Sources/JExtractSwift/NominalTypeResolution.swift +++ b/Sources/JExtractSwift/NominalTypeResolution.swift @@ -25,13 +25,13 @@ public class NominalTypeResolution { /// Mapping from extension declarations to the type declaration that they /// extend. - private var resolvedExtensions: [ExtensionDeclSyntax: NominalTypeDeclSyntaxNode] = [:] + var resolvedExtensions: [ExtensionDeclSyntax: NominalTypeDeclSyntaxNode] = [:] /// Extensions that have been encountered but not yet resolved to private var unresolvedExtensions: [ExtensionDeclSyntax] = [] /// Mapping from qualified nominal type names to their syntax nodes. - private var topLevelNominalTypes: [String: NominalTypeDeclSyntaxNode] = [:] + var topLevelNominalTypes: [String: NominalTypeDeclSyntaxNode] = [:] @_spi(Testing) public init() { } } diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift new file mode 100644 index 00000000..6b49774f --- /dev/null +++ b/Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift @@ -0,0 +1,377 @@ +//===----------------------------------------------------------------------===// +// +// 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 JavaTypes +import SwiftSyntax + +extension Swift2JavaTranslator { + @_spi(Testing) + public func lowerFunctionSignature( + _ decl: FunctionDeclSyntax, + enclosingType: TypeSyntax? = nil + ) throws -> LoweredFunctionSignature { + let signature = try SwiftFunctionSignature( + decl, + enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, + symbolTable: symbolTable + ) + + return try lowerFunctionSignature(signature) + } + + /// Lower the given Swift function signature to a Swift @_cdecl function signature, + /// which is C compatible, and the corresponding Java method signature. + /// + /// Throws an error if this function cannot be lowered for any reason. + func lowerFunctionSignature( + _ signature: SwiftFunctionSignature + ) throws -> LoweredFunctionSignature { + // Lower all of the parameters. + let loweredSelf = try signature.selfParameter.map { selfParameter in + try lowerParameter( + selfParameter.type, + convention: selfParameter.convention, parameterName: "self" + ) + } + + let loweredParameters = try signature.parameters.enumerated().map { (index, param) in + try lowerParameter( + param.type, + convention: param.convention, + parameterName: param.parameterName ?? "_\(index)" + ) + } + + // Lower the result. + var loweredResult = try lowerParameter( + signature.result.type, + convention: .byValue, + parameterName: "_result" + ) + + // If the result type doesn't lower to either empty (void) or a single + // primitive result, make it indirect. + let indirectResult: Bool + if !(loweredResult.javaFFMParameters.count == 0 || + (loweredResult.javaFFMParameters.count == 1 && + loweredResult.javaFFMParameters[0].isPrimitive)) { + loweredResult = try lowerParameter( + signature.result.type, + convention: .inout, + parameterName: "_result" + ) + indirectResult = true + } else { + indirectResult = false + } + + // Collect all of the lowered parameters for the @_cdecl function. + var allLoweredParameters: [LoweredParameters] = [] + var cdeclLoweredParameters: [SwiftParameter] = [] + if let loweredSelf { + allLoweredParameters.append(loweredSelf) + cdeclLoweredParameters.append(contentsOf: loweredSelf.cdeclParameters) + } + allLoweredParameters.append(contentsOf: loweredParameters) + cdeclLoweredParameters.append( + contentsOf: loweredParameters.flatMap { $0.cdeclParameters } + ) + + let cdeclResult: SwiftResult + if indirectResult { + cdeclLoweredParameters.append( + contentsOf: loweredResult.cdeclParameters + ) + cdeclResult = .init(convention: .direct, type: .tuple([])) + } else if loweredResult.cdeclParameters.count == 1, + let primitiveResult = loweredResult.cdeclParameters.first { + cdeclResult = .init(convention: .direct, type: primitiveResult.type) + } else if loweredResult.cdeclParameters.count == 0 { + cdeclResult = .init(convention: .direct, type: .tuple([])) + } else { + fatalError("Improper lowering of result for \(signature)") + } + + let cdeclSignature = SwiftFunctionSignature( + isStaticOrClass: false, + selfParameter: nil, + parameters: cdeclLoweredParameters, + result: cdeclResult + ) + + return LoweredFunctionSignature( + original: signature, + cdecl: cdeclSignature, + parameters: allLoweredParameters, + result: loweredResult + ) + } + + func lowerParameter( + _ type: SwiftType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> LoweredParameters { + switch type { + case .function, .metatype, .optional: + throw LoweringError.unhandledType(type) + + case .nominal(let nominal): + // Types from the Swift standard library that we know about. + if nominal.nominalTypeDecl.moduleName == "Swift", + nominal.nominalTypeDecl.parent == nil { + // Primitive types + if let loweredPrimitive = try lowerParameterPrimitive(nominal, convention: convention, parameterName: parameterName) { + return loweredPrimitive + } + + // Swift pointer types. + if let loweredPointers = try lowerParameterPointers(nominal, convention: convention, parameterName: parameterName) { + return loweredPointers + } + } + + let mutable = (convention == .inout) + let loweringStep: LoweringStep + switch nominal.nominalTypeDecl.kind { + case .actor, .class: loweringStep = .passDirectly(parameterName) + case .enum, .struct, .protocol: loweringStep = .passIndirectly(parameterName) + } + + return LoweredParameters( + cdeclToOriginal: loweringStep, + cdeclParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: parameterName, + type: .nominal( + SwiftNominalType( + nominalTypeDecl: mutable + ? swiftStdlibTypes.unsafeMutableRawPointerDecl + : swiftStdlibTypes.unsafeRawPointerDecl + ) + ) + ) + ], + javaFFMParameters: [.SwiftPointer] + ) + + case .tuple(let tuple): + let parameterNames = tuple.indices.map { "\(parameterName)_\($0)" } + let loweredElements: [LoweredParameters] = try zip(tuple, parameterNames).map { element, name in + try lowerParameter(element, convention: convention, parameterName: name) + } + return LoweredParameters( + cdeclToOriginal: .tuplify(parameterNames.map { .passDirectly($0) }), + cdeclParameters: loweredElements.flatMap { $0.cdeclParameters }, + javaFFMParameters: loweredElements.flatMap { $0.javaFFMParameters } + ) + } + } + + func lowerParameterPrimitive( + _ nominal: SwiftNominalType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> LoweredParameters? { + let nominalName = nominal.nominalTypeDecl.name + let type = SwiftType.nominal(nominal) + + // Swift types that map directly to Java primitive types. + if let primitiveType = JavaType(swiftTypeName: nominalName) { + // We cannot handle inout on primitive types. + if convention == .inout { + throw LoweringError.inoutNotSupported(type) + } + + return LoweredParameters( + cdeclToOriginal: .passDirectly(parameterName), + cdeclParameters: [ + SwiftParameter( + convention: convention, + parameterName: parameterName, + type: type + ) + ], + javaFFMParameters: [ + ForeignValueLayout(javaType: primitiveType)! + ] + ) + } + + // The Swift "Int" type, which maps to whatever the pointer-sized primitive + // integer type is in Java (int for 32-bit, long for 64-bit). + if nominalName == "Int" { + // We cannot handle inout on primitive types. + if convention == .inout { + throw LoweringError.inoutNotSupported(type) + } + + return LoweredParameters( + cdeclToOriginal: .passDirectly(parameterName), + cdeclParameters: [ + SwiftParameter( + convention: convention, + parameterName: parameterName, + type: type + ) + ], + javaFFMParameters: [ + .SwiftInt + ] + ) + } + + return nil + } + + func lowerParameterPointers( + _ nominal: SwiftNominalType, + convention: SwiftParameterConvention, + parameterName: String + ) throws -> LoweredParameters? { + let nominalName = nominal.nominalTypeDecl.name + let type = SwiftType.nominal(nominal) + + guard let (requiresArgument, mutable, hasCount) = nominalName.isNameOfSwiftPointerType else { + return nil + } + + // At the @_cdecl level, make everything a raw pointer. + let cdeclPointerType = mutable + ? swiftStdlibTypes.unsafeMutableRawPointerDecl + : swiftStdlibTypes.unsafeRawPointerDecl + var cdeclToOriginal: LoweringStep + switch (requiresArgument, hasCount) { + case (false, false): + cdeclToOriginal = .passDirectly(parameterName) + + case (true, false): + // FIXME: Generic arguments, ugh + cdeclToOriginal = .suffixed( + .passDirectly(parameterName), + ".assumingMemoryBound(to: \(nominal.genericArguments![0]).self)" + ) + + case (false, true): + cdeclToOriginal = .initialize(type, arguments: [ + LabeledArgument(label: "start", argument: .passDirectly(parameterName + "_pointer")), + LabeledArgument(label: "count", argument: .passDirectly(parameterName + "_count")) + ]) + + case (true, true): + cdeclToOriginal = .initialize( + type, + arguments: [ + LabeledArgument(label: "start", + argument: .suffixed( + .passDirectly(parameterName + "_pointer"), + ".assumingMemoryBound(to: \(nominal.genericArguments![0]).self")), + LabeledArgument(label: "count", + argument: .passDirectly(parameterName + "_count")) + ] + ) + } + + let lowered: [(SwiftParameter, ForeignValueLayout)] + if hasCount { + lowered = [ + ( + SwiftParameter( + convention: convention, + parameterName: parameterName + "_pointer", + type: SwiftType.nominal( + SwiftNominalType(nominalTypeDecl: cdeclPointerType) + ) + ), + .SwiftPointer + ), + ( + SwiftParameter( + convention: convention, + parameterName: parameterName + "_count", + type: SwiftType.nominal( + SwiftNominalType(nominalTypeDecl: swiftStdlibTypes.intDecl) + ) + ), + .SwiftInt + ) + ] + } else { + lowered = [ + ( + SwiftParameter( + convention: convention, + parameterName: parameterName + "_pointer", + type: SwiftType.nominal( + SwiftNominalType(nominalTypeDecl: cdeclPointerType) + ) + ), + .SwiftPointer + ), + ] + } + + return LoweredParameters( + cdeclToOriginal: cdeclToOriginal, + cdeclParameters: lowered.map(\.0), + javaFFMParameters: lowered.map(\.1) + ) + } +} + +struct LabeledArgument { + var label: String? + var argument: Element +} + +extension LabeledArgument: Equatable where Element: Equatable { } + +/// How to lower the Swift parameter +enum LoweringStep: Equatable { + case passDirectly(String) + case passIndirectly(String) + indirect case suffixed(LoweringStep, String) + case initialize(SwiftType, arguments: [LabeledArgument]) + case tuplify([LoweringStep]) +} + +struct LoweredParameters: Equatable { + /// The steps needed to get from the @_cdecl parameter to the original function + /// parameter. + var cdeclToOriginal: LoweringStep + + /// The lowering of the parameters at the C level in Swift. + var cdeclParameters: [SwiftParameter] + + /// The lowerung of the parmaeters at the C level as expressed for Java's + /// foreign function and memory interface. + /// + /// The elements in this array match up with those of 'cdeclParameters'. + var javaFFMParameters: [ForeignValueLayout] +} + +enum LoweringError: Error { + case inoutNotSupported(SwiftType) + case unhandledType(SwiftType) +} + +@_spi(Testing) +public struct LoweredFunctionSignature: Equatable { + var original: SwiftFunctionSignature + public var cdecl: SwiftFunctionSignature + + var parameters: [LoweredParameters] + var result: LoweredParameters +} diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index 9894244b..bf1d72a2 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -24,9 +24,6 @@ public final class Swift2JavaTranslator { package var log = Logger(label: "translator", logLevel: .info) - // ==== Input configuration - let swiftModuleName: String - // ==== Output configuration let javaPackage: String @@ -42,16 +39,29 @@ public final class Swift2JavaTranslator { /// type representation. package var importedTypes: [String: ImportedNominalType] = [:] + var swiftStdlibTypes: SwiftStandardLibraryTypes + + let symbolTable: SwiftSymbolTable let nominalResolution: NominalTypeResolution = NominalTypeResolution() var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry() + /// The name of the Swift module being translated. + var swiftModuleName: String { + symbolTable.moduleName + } + public init( javaPackage: String, swiftModuleName: String ) { self.javaPackage = javaPackage - self.swiftModuleName = swiftModuleName + self.symbolTable = SwiftSymbolTable(parsedModuleName: swiftModuleName) + + // Create a mock of the Swift standard library. + var parsedSwiftModule = SwiftParsedModuleSymbolTable(moduleName: "Swift") + self.swiftStdlibTypes = SwiftStandardLibraryTypes(into: &parsedSwiftModule) + self.symbolTable.importedModules.append(parsedSwiftModule.symbolTable) } } @@ -87,9 +97,8 @@ extension Swift2JavaTranslator { package func analyzeSwiftInterface(interfaceFilePath: String, text: String) throws { let sourceFileSyntax = Parser.parse(source: text) - // Find all of the types and extensions, then bind the extensions. - nominalResolution.addSourceFile(sourceFileSyntax) - nominalResolution.bindExtensions() + addSourceFile(sourceFileSyntax) + prepareForTranslation() let visitor = Swift2JavaVisitor( moduleName: self.swiftModuleName, @@ -99,6 +108,25 @@ extension Swift2JavaTranslator { visitor.walk(sourceFileSyntax) } + package func addSourceFile(_ sourceFile: SourceFileSyntax) { + nominalResolution.addSourceFile(sourceFile) + } + + package func prepareForTranslation() { + nominalResolution.bindExtensions() + + for (_, node) in nominalResolution.topLevelNominalTypes { + symbolTable.parsedModule.addNominalTypeDeclaration(node, parent: nil) + } + + for (ext, nominalNode) in nominalResolution.resolvedExtensions { + guard let nominalDecl = symbolTable.parsedModule.lookup(nominalNode) else { + continue + } + + symbolTable.parsedModule.addExtension(ext, extending: nominalDecl) + } + } } // ===== -------------------------------------------------------------------------------------------------------------- diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift new file mode 100644 index 00000000..187c18a6 --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift @@ -0,0 +1,118 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +/// Provides a complete signature for a Swift function, which includes its +/// parameters and return type. +@_spi(Testing) +public struct SwiftFunctionSignature: Equatable { + var isStaticOrClass: Bool + var selfParameter: SwiftParameter? + var parameters: [SwiftParameter] + var result: SwiftResult +} + +extension SwiftFunctionSignature { + /// Create a function declaration with the given name that has this + /// signature. + package func createFunctionDecl(_ name: String) -> FunctionDeclSyntax { + let parametersStr = parameters.map(\.description).joined(separator: ", ") + let resultStr = result.type.description + let decl: DeclSyntax = """ + func \(raw: name)(\(raw: parametersStr)) -> \(raw: resultStr) { + // implementation + } + """ + return decl.cast(FunctionDeclSyntax.self) + } +} + +extension SwiftFunctionSignature { + init( + _ node: FunctionDeclSyntax, + enclosingType: SwiftType?, + symbolTable: SwiftSymbolTable + ) throws { + // 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 isMutating = false + var isConsuming = false + var isStaticOrClass = false + for modifier in node.modifiers { + switch modifier.name { + case .keyword(.mutating): isMutating = true + case .keyword(.static), .keyword(.class): isStaticOrClass = true + case .keyword(.consuming): isConsuming = true + default: break + } + } + + if isStaticOrClass { + self.selfParameter = SwiftParameter( + convention: .byValue, + type: .metatype( + enclosingType + ) + ) + } else { + self.selfParameter = SwiftParameter( + convention: isMutating ? .inout : isConsuming ? .consuming : .byValue, + type: enclosingType + ) + } + + self.isStaticOrClass = isStaticOrClass + } else { + self.selfParameter = nil + self.isStaticOrClass = false + } + + // Translate the parameters. + self.parameters = try node.signature.parameterClause.parameters.map { param in + try SwiftParameter(param, symbolTable: symbolTable) + } + + // Translate the result type. + if let resultType = node.signature.returnClause?.type { + self.result = try SwiftResult( + convention: .direct, + type: SwiftType(resultType, symbolTable: symbolTable) + ) + } else { + self.result = SwiftResult(convention: .direct, type: .tuple([])) + } + + // FIXME: Prohibit effects for now. + if let throwsClause = node.signature.effectSpecifiers?.throwsClause { + throw SwiftFunctionTranslationError.throws(throwsClause) + } + if let asyncSpecifier = node.signature.effectSpecifiers?.asyncSpecifier { + throw SwiftFunctionTranslationError.async(asyncSpecifier) + } + + // Prohibit generics for now. + if let generics = node.genericParameterClause { + throw SwiftFunctionTranslationError.generic(generics) + } + } +} + +enum SwiftFunctionTranslationError: Error { + case `throws`(ThrowsClauseSyntax) + case async(TokenSyntax) + case generic(GenericParameterClauseSyntax) +} diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift new file mode 100644 index 00000000..1e5637e3 --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftSyntax + +struct SwiftFunctionType: Equatable { + enum Convention: Equatable { + case swift + case c + } + + var convention: Convention + var parameters: [SwiftParameter] + var resultType: SwiftType +} + +extension SwiftFunctionType: CustomStringConvertible { + var description: String { + return "(\(parameters.map { $0.descriptionInType } )) -> \(resultType.description)" + } +} + +extension SwiftFunctionType { + init( + _ node: FunctionTypeSyntax, + convention: Convention, + symbolTable: SwiftSymbolTable + ) throws { + self.convention = convention + self.parameters = try node.parameters.map { param in + let isInout = param.inoutKeyword != nil + return SwiftParameter( + convention: isInout ? .inout : .byValue, + type: try SwiftType(param.type, symbolTable: symbolTable) + ) + } + + self.resultType = try SwiftType(node.returnClause.type, symbolTable: symbolTable) + + // check for effect specifiers + if let throwsClause = node.effectSpecifiers?.throwsClause { + throw SwiftFunctionTranslationError.throws(throwsClause) + } + if let asyncSpecifier = node.effectSpecifiers?.asyncSpecifier { + throw SwiftFunctionTranslationError.async(asyncSpecifier) + } + } +} diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift b/Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift new file mode 100644 index 00000000..f1bbaa12 --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol { + /// The name of this module. + let moduleName: String + + /// The top-level nominal types, found by name. + var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:] + + /// The nested types defined within this module. The map itself is indexed by the + /// identifier of the nominal type declaration, and each entry is a map from the nested + /// type name to the nominal type declaration. + var nestedTypes: [SwiftNominalTypeDeclaration: [String: SwiftNominalTypeDeclaration]] = [:] + + /// Look for a top-level type with the given name. + func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { + topLevelTypes[name] + } + + // Look for a nested type with the given name. + func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { + nestedTypes[parent]?[name] + } +} diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift new file mode 100644 index 00000000..cf017f98 --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +/// 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. +class SwiftNominalTypeDeclaration { + enum Kind { + case actor + case `class` + case `enum` + case `protocol` + case `struct` + } + + /// The kind of nominal type. + var kind: Kind + + /// The parent nominal type when this nominal type is nested inside another type, e.g., + /// MyCollection.Iterator. + var parent: SwiftNominalTypeDeclaration? + + /// 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. + var moduleName: String + + /// The name of this nominal type, e.g., 'MyCollection'. + var name: String + + // TODO: Generic parameters. + + /// Create a nominal type declaration from the syntax node for a nominal type + /// declaration. + init( + moduleName: String, + parent: SwiftNominalTypeDeclaration?, + node: NominalTypeDeclSyntaxNode + ) { + self.moduleName = moduleName + self.parent = parent + self.name = node.name.text + + // 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") + } + } +} + +extension SwiftNominalTypeDeclaration: Equatable { + static func ==(lhs: SwiftNominalTypeDeclaration, rhs: SwiftNominalTypeDeclaration) -> Bool { + lhs === rhs + } +} + +extension SwiftNominalTypeDeclaration: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } +} diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift new file mode 100644 index 00000000..b7bc28fb --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +struct SwiftParameter: Equatable { + var convention: SwiftParameterConvention + var argumentLabel: String? + var parameterName: String? + var type: SwiftType +} + +extension SwiftParameter: CustomStringConvertible { + var description: String { + let argumentLabel = self.argumentLabel ?? "_" + let parameterName = self.parameterName ?? "_" + + return "\(argumentLabel) \(parameterName): \(descriptionInType)" + } + + 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. +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 { + init(_ node: FunctionParameterSyntax, symbolTable: SwiftSymbolTable) throws { + // Determine the convention. The default is by-value, but modifiers can alter + // this. + var convention = SwiftParameterConvention.byValue + for modifier in node.modifiers { + switch modifier.name { + case .keyword(.consuming), .keyword(.__consuming), .keyword(.__owned): + convention = .consuming + case .keyword(.inout): + convention = .inout + default: + break + } + } + self.convention = convention + + // Determine the type. + self.type = try SwiftType(node.type, symbolTable: symbolTable) + + // 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/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift b/Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift new file mode 100644 index 00000000..1eb37eac --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift @@ -0,0 +1,120 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +struct SwiftParsedModuleSymbolTable { + var symbolTable: SwiftModuleSymbolTable + + /// The nominal type declarations, indexed by the nominal type declaration syntax node. + var nominalTypeDeclarations: [SyntaxIdentifier: SwiftNominalTypeDeclaration] = [:] + + /// Mapping from the nominal type declarations in this module back to the syntax + /// node. This is the reverse mapping of 'nominalTypeDeclarations'. + var nominalTypeSyntaxNodes: [SwiftNominalTypeDeclaration: NominalTypeDeclSyntaxNode] = [:] + + init(moduleName: String) { + symbolTable = .init(moduleName: moduleName) + } +} + +extension SwiftParsedModuleSymbolTable: SwiftSymbolTableProtocol { + var moduleName: String { + symbolTable.moduleName + } + + func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { + symbolTable.lookupTopLevelNominalType(name) + } + + func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { + symbolTable.lookupNestedType(name, parent: parent) + } +} + +extension SwiftParsedModuleSymbolTable { + /// Look up a nominal type declaration based on its syntax node. + func lookup(_ node: NominalTypeDeclSyntaxNode) -> SwiftNominalTypeDeclaration? { + nominalTypeDeclarations[node.id] + } + + /// Add a nominal type declaration and all of the nested types within it to the symbol + /// table. + @discardableResult + mutating func addNominalTypeDeclaration( + _ node: NominalTypeDeclSyntaxNode, + parent: SwiftNominalTypeDeclaration? + ) -> SwiftNominalTypeDeclaration { + // If we have already recorded this nominal type declaration, we're done. + if let existingNominal = nominalTypeDeclarations[node.id] { + return existingNominal + } + + // Otherwise, create the nominal type declaration. + let nominalTypeDecl = SwiftNominalTypeDeclaration( + moduleName: moduleName, + parent: parent, + node: node + ) + + // Ensure that we can find this nominal type declaration again based on the syntax + // node, and vice versa. + nominalTypeDeclarations[node.id] = nominalTypeDecl + nominalTypeSyntaxNodes[nominalTypeDecl] = node + + if let parent { + // For nested types, make them discoverable from the parent type. + symbolTable.nestedTypes[parent, default: [:]][node.name.text] = nominalTypeDecl + } else { + // For top-level types, make them discoverable by name. + symbolTable.topLevelTypes[node.name.text] = nominalTypeDecl + } + + // Find any nested types within this nominal type and add them. + for member in node.memberBlock.members { + if let nominalMember = member.decl.asNominal { + addNominalTypeDeclaration(nominalMember, parent: nominalTypeDecl) + } + } + + return nominalTypeDecl + } + + /// Add any nested types within the given extension (with known extended nominal type + /// declaration) to the symbol table. + mutating func addExtension( + _ extensionNode: ExtensionDeclSyntax, + extending nominalTypeDecl: SwiftNominalTypeDeclaration + ) { + // Find any nested types within this extension and add them. + for member in extensionNode.memberBlock.members { + if let nominalMember = member.decl.asNominal { + addNominalTypeDeclaration(nominalMember, parent: nominalTypeDecl) + } + } + } +} + +extension DeclSyntaxProtocol { + 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/JExtractSwift/SwiftTypes/SwiftResult.swift b/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift new file mode 100644 index 00000000..4ca14815 --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftResult.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +struct SwiftResult: Equatable { + var convention: SwiftResultConvention + var type: SwiftType +} + +enum SwiftResultConvention: Equatable { + case direct + case indirect +} diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift new file mode 100644 index 00000000..57a5865f --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift @@ -0,0 +1,208 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +/// Captures many types from the Swift standard library in their most basic +/// forms, so that the translator can reason about them in source code. +struct SwiftStandardLibraryTypes { + /// Swift.UnsafeRawPointer + var unsafeRawPointerDecl: SwiftNominalTypeDeclaration + + /// Swift.UnsafeMutableRawPointer + var unsafeMutableRawPointerDecl: SwiftNominalTypeDeclaration + + // Swift.UnsafePointer + var unsafePointerDecl: SwiftNominalTypeDeclaration + + // Swift.UnsafeMutablePointer + var unsafeMutablePointerDecl: SwiftNominalTypeDeclaration + + // Swift.UnsafeBufferPointer + var unsafeBufferPointerDecl: SwiftNominalTypeDeclaration + + // Swift.UnsafeMutableBufferPointer + var unsafeMutableBufferPointerDecl: SwiftNominalTypeDeclaration + + /// Swift.Bool + var boolDecl: SwiftNominalTypeDeclaration + + /// Swift.Int8 + var int8Decl: SwiftNominalTypeDeclaration + + /// Swift.Int16 + var int16Decl: SwiftNominalTypeDeclaration + + /// Swift.UInt16 + var uint16Decl: SwiftNominalTypeDeclaration + + /// Swift.Int32 + var int32Decl: SwiftNominalTypeDeclaration + + /// Swift.Int64 + var int64Decl: SwiftNominalTypeDeclaration + + /// Swift.Int + var intDecl: SwiftNominalTypeDeclaration + + /// Swift.Float + var floatDecl: SwiftNominalTypeDeclaration + + /// Swift.Double + var doubleDecl: SwiftNominalTypeDeclaration + + /// Swift.String + var stringDecl: SwiftNominalTypeDeclaration + + init(into parsedModule: inout SwiftParsedModuleSymbolTable) { + // Pointer types + self.unsafeRawPointerDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("UnsafeRawPointer"), + memberBlock: .init(members: []) + ), + parent: nil + ) + + self.unsafeMutableRawPointerDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("UnsafeMutableRawPointer"), + memberBlock: .init(members: []) + ), + parent: nil + ) + + self.unsafePointerDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("UnsafePointer"), + genericParameterClause: .init( + parameters: [GenericParameterSyntax(name: .identifier("Element"))] + ), + memberBlock: .init(members: []) + ), + parent: nil + ) + + self.unsafeMutablePointerDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("UnsafeMutablePointer"), + genericParameterClause: .init( + parameters: [GenericParameterSyntax(name: .identifier("Element"))] + ), + memberBlock: .init(members: []) + ), + parent: nil + ) + + self.unsafeBufferPointerDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("UnsafeBufferPointer"), + genericParameterClause: .init( + parameters: [GenericParameterSyntax(name: .identifier("Element"))] + ), + memberBlock: .init(members: []) + ), + parent: nil + ) + + self.unsafeMutableBufferPointerDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("UnsafeMutableBufferPointer"), + genericParameterClause: .init( + parameters: [GenericParameterSyntax(name: .identifier("Element"))] + ), + memberBlock: .init(members: []) + ), + parent: nil + ) + + self.boolDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("Bool"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.intDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("Int"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.int8Decl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("Int8"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.int16Decl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("Int16"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.uint16Decl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("UInt16"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.int32Decl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("Int32"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.int64Decl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("Int64"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.floatDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("Float"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.doubleDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("Double"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.intDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("Int"), + memberBlock: .init(members: []) + ), + parent: nil + ) + self.stringDecl = parsedModule.addNominalTypeDeclaration( + StructDeclSyntax( + name: .identifier("String"), + memberBlock: .init(members: []) + ), + parent: nil + ) + } +} diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift new file mode 100644 index 00000000..bb3c2f5f --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift @@ -0,0 +1,117 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +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 nested type with the given name. + func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? +} + +extension SwiftSymbolTableProtocol { + /// Look for a type + func lookupType(_ name: String, parent: SwiftNominalTypeDeclaration?) -> SwiftNominalTypeDeclaration? { + if let parent { + return lookupNestedType(name, parent: parent) + } + + return lookupTopLevelNominalType(name) + } +} + +class SwiftSymbolTable { + var importedModules: [SwiftModuleSymbolTable] = [] + var parsedModule: SwiftParsedModuleSymbolTable + + init(parsedModuleName: String) { + self.parsedModule = SwiftParsedModuleSymbolTable(moduleName: parsedModuleName) + } + + func addImportedModule(symbolTable: SwiftModuleSymbolTable) { + importedModules.append(symbolTable) + } + + func addTopLevelNominalTypeDeclarations(_ sourceFile: SourceFileSyntax) { + // Find top-level nominal type declarations. + for statement in sourceFile.statements { + // We only care about declarations. + guard case .decl(let decl) = statement.item, + let nominalTypeNode = decl.asNominal else { + continue + } + + parsedModule.addNominalTypeDeclaration(nominalTypeNode, parent: nil) + } + } + + func addExtensions( + _ sourceFile: SourceFileSyntax, + nominalResolution: NominalTypeResolution + ) { + // Find extensions. + for statement in sourceFile.statements { + // We only care about declarations. + guard case .decl(let decl) = statement.item, + let extNode = decl.as(ExtensionDeclSyntax.self), + let extendedTypeNode = nominalResolution.extendedType(of: extNode), + let extendedTypeDecl = parsedModule.nominalTypeDeclarations[extendedTypeNode.id] else { + continue + } + + parsedModule.addExtension(extNode, extending: extendedTypeDecl) + } + } +} + +extension SwiftSymbolTable: SwiftSymbolTableProtocol { + 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. + func lookupTopLevelNominalType(_ name: String) -> SwiftNominalTypeDeclaration? { + if let parsedResult = parsedModule.lookupTopLevelNominalType(name) { + return parsedResult + } + + for importedModule in importedModules { + if let result = importedModule.lookupTopLevelNominalType(name) { + return result + } + } + + return nil + } + + // Look for a nested type with the given name. + func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? { + if let parsedResult = parsedModule.lookupNestedType(name, parent: parent) { + return parsedResult + } + + for importedModule in importedModules { + if let result = importedModule.lookupNestedType(name, parent: parent) { + return result + } + } + + return nil + } +} diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift new file mode 100644 index 00000000..4eef607a --- /dev/null +++ b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift @@ -0,0 +1,216 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +/// Describes a type in the Swift type system. +enum SwiftType: Equatable { + indirect case function(SwiftFunctionType) + indirect case metatype(SwiftType) + case nominal(SwiftNominalType) + indirect case optional(SwiftType) + case tuple([SwiftType]) + + var asNominalType: SwiftNominalType? { + switch self { + case .nominal(let nominal): nominal + case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil + case .function, .metatype, .optional: nil + } + } + + var asNominalTypeDeclaration: SwiftNominalTypeDeclaration? { + asNominalType?.nominalTypeDecl + } +} + +extension SwiftType: CustomStringConvertible { + var description: String { + switch self { + case .nominal(let nominal): return nominal.description + case .function(let functionType): return functionType.description + case .metatype(let instanceType): + return "(\(instanceType.description)).Type" + case .optional(let wrappedType): + return "\(wrappedType.description)?" + case .tuple(let elements): + return "(\(elements.map(\.description).joined(separator: ", ")))" + } + } +} + +struct SwiftNominalType: Equatable { + enum Parent: Equatable { + indirect case nominal(SwiftNominalType) + } + + private var storedParent: Parent? + var nominalTypeDecl: SwiftNominalTypeDeclaration + var genericArguments: [SwiftType]? + + init( + parent: SwiftNominalType? = nil, + nominalTypeDecl: SwiftNominalTypeDeclaration, + genericArguments: [SwiftType]? = nil + ) { + self.storedParent = parent.map { .nominal($0) } + self.nominalTypeDecl = nominalTypeDecl + self.genericArguments = genericArguments + } + + var parent: SwiftNominalType? { + if case .nominal(let parent) = storedParent ?? .none { + return parent + } + + return nil + } +} + +extension SwiftNominalType: CustomStringConvertible { + var description: String { + var resultString: String + if let parent { + resultString = parent.description + "." + } else { + resultString = "" + } + + resultString += nominalTypeDecl.name + + if let genericArguments { + resultString += "<\(genericArguments.map(\.description).joined(separator: ", "))>" + } + + return resultString + } +} + +extension SwiftType { + init(_ type: TypeSyntax, symbolTable: SwiftSymbolTable) throws { + switch type.as(TypeSyntaxEnum.self) { + case .arrayType, .classRestrictionType, .compositionType, + .dictionaryType, .missingType, .namedOpaqueReturnType, + .packElementType, .packExpansionType, .someOrAnyType, + .suppressedType: + throw TypeTranslationError.unimplementedType(type) + + case .attributedType(let attributedType): + // Only recognize the "@convention(c)" and "@convention(swift)" attributes, and + // then only on function types. + // FIXME: This string matching is a horrible hack. + switch attributedType.trimmedDescription { + case "@convention(c)", "@convention(swift)": + let innerType = try SwiftType(attributedType.baseType, symbolTable: symbolTable) + switch innerType { + case .function(var functionType): + let isConventionC = attributedType.trimmedDescription == "@convention(c)" + let convention: SwiftFunctionType.Convention = isConventionC ? .c : .swift + functionType.convention = convention + self = .function(functionType) + default: + throw TypeTranslationError.unimplementedType(type) + } + default: + throw TypeTranslationError.unimplementedType(type) + } + + case .functionType(let functionType): + self = .function( + try SwiftFunctionType(functionType, convention: .swift, symbolTable: symbolTable) + ) + + case .identifierType(let identifierType): + // Translate the generic arguments. + let genericArgs = try identifierType.genericArgumentClause.map { genericArgumentClause in + try genericArgumentClause.arguments.map { argument in + try SwiftType(argument.argument, symbolTable: symbolTable) + } + } + + // Resolve the type by name. + self = try SwiftType( + originalType: type, + parent: nil, + name: identifierType.name.text, + genericArguments: genericArgs, + symbolTable: symbolTable + ) + + case .implicitlyUnwrappedOptionalType(let optionalType): + self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable)) + + case .memberType(let memberType): + // If the parent type isn't a known module, translate it. + // FIXME: Need a more reasonable notion of which names are module names + // for this to work. What can we query for this information? + let parentType: SwiftType? + if memberType.baseType.trimmedDescription == "Swift" { + parentType = nil + } else { + parentType = try SwiftType(memberType.baseType, symbolTable: symbolTable) + } + + // Translate the generic arguments. + let genericArgs = try memberType.genericArgumentClause.map { genericArgumentClause in + try genericArgumentClause.arguments.map { argument in + try SwiftType(argument.argument, symbolTable: symbolTable) + } + } + + self = try SwiftType( + originalType: type, + parent: parentType, + name: memberType.name.text, + genericArguments: genericArgs, + symbolTable: symbolTable + ) + + case .metatypeType(let metatypeType): + self = .metatype(try SwiftType(metatypeType.baseType, symbolTable: symbolTable)) + + case .optionalType(let optionalType): + self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable)) + + case .tupleType(let tupleType): + self = try .tuple(tupleType.elements.map { element in + try SwiftType(element.type, symbolTable: symbolTable) + }) + } + } + + init( + originalType: TypeSyntax, + parent: SwiftType?, + name: String, + genericArguments: [SwiftType]?, + symbolTable: SwiftSymbolTable + ) throws { + // Look up the imported types by name to resolve it to a nominal type. + guard let nominalTypeDecl = symbolTable.lookupType( + name, + parent: parent?.asNominalTypeDeclaration + ) else { + throw TypeTranslationError.unknown(originalType) + } + + self = .nominal( + SwiftNominalType( + parent: parent?.asNominalType, + nominalTypeDecl: nominalTypeDecl, + genericArguments: genericArguments + ) + ) + } +} diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index eef4140d..be0be1b8 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -176,7 +176,7 @@ extension String { /// 2. Whether the memory referenced by the pointer is mutable. /// 3. Whether the pointer type has a `count` property describing how /// many elements it points to. - fileprivate var isNameOfSwiftPointerType: (requiresArgument: Bool, mutable: Bool, hasCount: Bool)? { + var isNameOfSwiftPointerType: (requiresArgument: Bool, mutable: Bool, hasCount: Bool)? { switch self { case "COpaquePointer", "UnsafeRawPointer": return (requiresArgument: false, mutable: true, hasCount: false) @@ -280,17 +280,7 @@ extension TranslatedType { var foreignValueLayout: ForeignValueLayout { switch cCompatibleJavaMemoryLayout { case .primitive(let javaType): - switch javaType { - case .boolean: return .SwiftBool - case .byte: return .SwiftInt8 - case .char: return .SwiftUInt16 - case .short: return .SwiftInt16 - case .int: return .SwiftInt32 - case .long: return .SwiftInt64 - case .float: return .SwiftFloat - case .double: return .SwiftDouble - case .array, .class, .void: fatalError("Not a primitive type: \(cCompatibleJavaMemoryLayout) in \(self)") - } + return ForeignValueLayout(javaType: javaType)! case .int: return .SwiftInt diff --git a/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift new file mode 100644 index 00000000..ca101133 --- /dev/null +++ b/Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +@_spi(Testing) import JExtractSwift +import SwiftSyntax +import Testing + +/// Assert that the lowering of the function function declaration to a @_cdecl +/// entrypoint matches the expected form. +func assertLoweredFunction( + _ inputDecl: DeclSyntax, + javaPackage: String = "org.swift.mypackage", + swiftModuleName: String = "MyModule", + sourceFile: SourceFileSyntax? = nil, + enclosingType: TypeSyntax? = nil, + expectedCDecl: DeclSyntax, + fileID: String = #fileID, + filePath: String = #filePath, + line: Int = #line, + column: Int = #column +) throws { + let translator = Swift2JavaTranslator( + javaPackage: javaPackage, + swiftModuleName: swiftModuleName + ) + + if let sourceFile { + translator.addSourceFile(sourceFile) + } + + translator.prepareForTranslation() + + let inputFunction = inputDecl.cast(FunctionDeclSyntax.self) + let loweredFunction = try translator.lowerFunctionSignature( + inputFunction, + enclosingType: enclosingType + ) + let loweredCDecl = loweredFunction.cdecl.createFunctionDecl(inputFunction.name.text) + #expect( + loweredCDecl.description == expectedCDecl.description, + sourceLocation: Testing.SourceLocation( + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + ) +} diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift new file mode 100644 index 00000000..fd909565 --- /dev/null +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// 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 JExtractSwift +import SwiftSyntax +import SwiftSyntaxBuilder +import Testing + +final class FunctionLoweringTests { + @Test("Lowering buffer pointers") + func loweringBufferPointers() throws { + try assertLoweredFunction(""" + func f(x: Int, y: Swift.Float, z: UnsafeBufferPointer) { } + """, + expectedCDecl: """ + func f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) -> () { + // implementation + } + """ + ) + } + + @Test("Lowering tuples") + func loweringTuples() throws { + try assertLoweredFunction(""" + func f(t: (Int, (Float, Double)), z: UnsafePointer) { } + """, + expectedCDecl: """ + func f(_ t_0: Int, _ t_1_0: Float, _ t_1_1: Double, _ z_pointer: UnsafeRawPointer) -> () { + // implementation + } + """ + ) + } + + @Test("Lowering methods") + func loweringMethods() throws { + try assertLoweredFunction(""" + func shifted(by delta: (Double, Double)) -> Point { } + """, + sourceFile: """ + struct Point { } + """, + enclosingType: "Point", + expectedCDecl: """ + func shifted(_ self: UnsafeRawPointer, _ delta_0: Double, _ delta_1: Double, _ _result: UnsafeMutableRawPointer) -> () { + // implementation + } + """ + ) + } + + @Test("Lowering metatypes", .disabled("Metatypes are not yet lowered")) + func lowerMetatype() throws { + try assertLoweredFunction(""" + func f(t: Int.Type) { } + """, + expectedCDecl: """ + func f(t: RawPointerType) -> () { + // implementation + } + """ + ) + } +} +