From 4f6f7b500cea88c08be49dae1019b4fe59e71fdb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 22 Jan 2025 23:42:30 -0800 Subject: [PATCH] Initial implementation of a new lowering from a Swift func to `@_cdecl` func Start implementing a more complete and formal lowering from an arbitrary Swift function to a `@_cdecl`-compatible thunk that calls that function. This is set up in stages more akin to what you'd see in a compiler: 1. Resolve the syntax trees for the function into a more semantic representation, for example resolving type names to nominal type declarations. This includes a simplistic implementation of a symbol table so we can resolve arbitrary type names. 2. Lower the semantic representation of each function parameter (including self). How we do this varies based on type: * Value types (struct / enum) are passed indirectly via Unsafe(Mutable)RawPointer, using a mutable pointer when the corresponding parameter is inout. * Class / actor types are passed directly via UnsafeRawPointer. * Swift types that map to primitive types (like Swift.Int32) in passed directly, no translation required. * Tuple types are recursively "exploded" into multiple parameters. * Unsafe*BufferPointer types are "exploded" into a pointer and count. * Typed unsafe pointers types are passed via their raw versions. 3. Lower returns similarly to parameters, using indirect mutable parameters for the return when we can't return directly. 4. Render the lowered declaration into a FunctionDeclSyntax node, which can be modified by the client. At present, we aren't rendering the bodies of these thunks, which need to effectively undo the transformations described in (3) and (4). For example, reconstituting a tuple from disparate arguments, appropriately re-typing and loading from indirectly-passed value types, and so on. The description of the lowered signature is intended to provide sufficient information for doing so, but will likely require tweaking. At present, this new code is not integrated in the main code path for jextract-swift. Once it hits feature parity, we'll enable it. --- .../JavaConstants/ForeignValueLayouts.swift | 22 +- .../JExtractSwift/NominalTypeResolution.swift | 4 +- ...wift2JavaTranslator+FunctionLowering.swift | 377 ++++++++++++++++++ .../JExtractSwift/Swift2JavaTranslator.swift | 42 +- .../SwiftTypes/SwiftFunctionSignature.swift | 118 ++++++ .../SwiftTypes/SwiftFunctionType.swift | 59 +++ .../SwiftTypes/SwiftModuleSymbolTable.swift | 39 ++ .../SwiftNominalTypeDeclaration.swift | 78 ++++ .../SwiftTypes/SwiftParameter.swift | 88 ++++ .../SwiftParsedModuleSymbolTable.swift | 120 ++++++ .../SwiftTypes/SwiftResult.swift | 25 ++ .../SwiftStandardLibraryTypes.swift | 208 ++++++++++ .../SwiftTypes/SwiftSymbolTable.swift | 117 ++++++ .../JExtractSwift/SwiftTypes/SwiftType.swift | 216 ++++++++++ Sources/JExtractSwift/TranslatedType.swift | 14 +- .../Asserts/LoweringAssertions.swift | 59 +++ .../FunctionLoweringTests.swift | 77 ++++ 17 files changed, 1641 insertions(+), 22 deletions(-) create mode 100644 Sources/JExtractSwift/Swift2JavaTranslator+FunctionLowering.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftFunctionType.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftModuleSymbolTable.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftNominalTypeDeclaration.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftParameter.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftParsedModuleSymbolTable.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftResult.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftStandardLibraryTypes.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftSymbolTable.swift create mode 100644 Sources/JExtractSwift/SwiftTypes/SwiftType.swift create mode 100644 Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift create mode 100644 Tests/JExtractSwiftTests/FunctionLoweringTests.swift 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 + } + """ + ) + } +} +