diff --git a/Package.swift b/Package.swift index 84b348c5..51608c31 100644 --- a/Package.swift +++ b/Package.swift @@ -422,6 +422,7 @@ let package = Package( name: "JExtractSwiftLib", dependencies: [ .product(name: "SwiftBasicFormat", package: "swift-syntax"), + .product(name: "SwiftLexicalLookup", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "ArgumentParser", package: "swift-argument-parser"), diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index c2bb53ab..d2a71def 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -68,7 +68,7 @@ extension CType { case .optional(let wrapped) where wrapped.isPointer: try self.init(cdeclType: wrapped) - case .metatype, .optional, .tuple, .opaque, .existential: + case .genericParameter, .metatype, .optional, .tuple, .opaque, .existential: throw CDeclToCLoweringError.invalidCDeclType(cdeclType) } } @@ -98,7 +98,7 @@ extension CFunction { enum CDeclToCLoweringError: Error { case invalidCDeclType(SwiftType) - case invalidNominalType(SwiftNominalTypeDeclaration) + case invalidNominalType(SwiftTypeDeclaration) case invalidFunctionConvention(SwiftFunctionType) } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index bb994164..085a6331 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -26,10 +26,10 @@ extension FFMSwift2JavaGenerator { ) throws -> LoweredFunctionSignature { let signature = try SwiftFunctionSignature( decl, - enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, - symbolTable: symbolTable + enclosingType: try enclosingType.map { try SwiftType($0, lookupContext: lookupContext) }, + lookupContext: lookupContext ) - return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: lookupContext.symbolTable).lowerFunctionSignature(signature) } /// Lower the given initializer to a C-compatible entrypoint, @@ -42,11 +42,11 @@ extension FFMSwift2JavaGenerator { ) throws -> LoweredFunctionSignature { let signature = try SwiftFunctionSignature( decl, - enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, - symbolTable: symbolTable + enclosingType: try enclosingType.map { try SwiftType($0, lookupContext: lookupContext) }, + lookupContext: lookupContext ) - return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: lookupContext.symbolTable).lowerFunctionSignature(signature) } /// Lower the given variable decl to a C-compatible entrypoint, @@ -66,10 +66,10 @@ extension FFMSwift2JavaGenerator { let signature = try SwiftFunctionSignature( decl, isSet: isSet, - enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) }, - symbolTable: symbolTable + enclosingType: try enclosingType.map { try SwiftType($0, lookupContext: lookupContext) }, + lookupContext: lookupContext ) - return try CdeclLowering(symbolTable: symbolTable).lowerFunctionSignature(signature) + return try CdeclLowering(symbolTable: lookupContext.symbolTable).lowerFunctionSignature(signature) } } @@ -98,7 +98,9 @@ struct CdeclLowering { try lowerParameter( selfParameter.type, convention: selfParameter.convention, - parameterName: selfParameter.parameterName ?? "self" + parameterName: selfParameter.parameterName ?? "self", + genericParameters: signature.genericParameters, + genericRequirements: signature.genericRequirements ) case nil, .initializer(_), .staticMethod(_): nil @@ -106,10 +108,12 @@ struct CdeclLowering { // Lower all of the parameters. let loweredParameters = try signature.parameters.enumerated().map { (index, param) in - try lowerParameter( + return try lowerParameter( param.type, convention: param.convention, - parameterName: param.parameterName ?? "_\(index)" + parameterName: param.parameterName ?? "_\(index)", + genericParameters: signature.genericParameters, + genericRequirements: signature.genericRequirements ) } @@ -142,7 +146,9 @@ struct CdeclLowering { func lowerParameter( _ type: SwiftType, convention: SwiftParameterConvention, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> LoweredParameter { // If there is a 1:1 mapping between this Swift type and a C type, we just // return it. @@ -257,7 +263,7 @@ struct CdeclLowering { guard let genericArgs = nominal.genericArguments, genericArgs.count == 1 else { throw LoweringError.unhandledType(type) } - return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName) + return try lowerOptionalParameter(genericArgs[0], convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) case .string: // 'String' is passed in by C string. i.e. 'UnsafePointer' ('const uint8_t *') @@ -300,7 +306,7 @@ struct CdeclLowering { case .tuple(let tuple): if tuple.count == 1 { - return try lowerParameter(tuple[0], convention: convention, parameterName: parameterName) + return try lowerParameter(tuple[0], convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } if convention == .inout { throw LoweringError.inoutNotSupported(type) @@ -310,7 +316,7 @@ struct CdeclLowering { for (idx, element) in tuple.enumerated() { // FIXME: Use tuple element label. let cdeclName = "\(parameterName)_\(idx)" - let lowered = try lowerParameter(element, convention: convention, parameterName: cdeclName) + let lowered = try lowerParameter(element, convention: convention, parameterName: cdeclName, genericParameters: genericParameters, genericRequirements: genericRequirements) parameters.append(contentsOf: lowered.cdeclParameters) conversions.append(lowered.conversion) @@ -330,20 +336,14 @@ struct CdeclLowering { conversion: conversion ) - case .opaque(let proto), .existential(let proto): - // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` - // Translate it as the concrete type. - // NOTE: This is a temporary workaround until we add support for generics. - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { - return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName) + case .opaque, .existential, .genericParameter: + if let concreteTy = type.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try lowerParameter(concreteTy, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } throw LoweringError.unhandledType(type) case .optional(let wrapped): - return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName) + return try lowerOptionalParameter(wrapped, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } } @@ -354,7 +354,9 @@ struct CdeclLowering { func lowerOptionalParameter( _ wrappedType: SwiftType, convention: SwiftParameterConvention, - parameterName: String + parameterName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> LoweredParameter { // If there is a 1:1 mapping between this Swift type and a C type, lower it to 'UnsafePointer?' if let _ = try? CType(cdeclType: wrappedType) { @@ -398,18 +400,15 @@ struct CdeclLowering { conversion: .pointee(.typedPointer(.optionalChain(.placeholder), swiftType: wrappedType)) ) - case .existential(let proto), .opaque(let proto): - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { - return try lowerOptionalParameter(concreteTy, convention: convention, parameterName: parameterName) + case .existential, .opaque, .genericParameter: + if let concreteTy = wrappedType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { + return try lowerOptionalParameter(concreteTy, convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } throw LoweringError.unhandledType(.optional(wrappedType)) case .tuple(let tuple): if tuple.count == 1 { - return try lowerOptionalParameter(tuple[0], convention: convention, parameterName: parameterName) + return try lowerOptionalParameter(tuple[0], convention: convention, parameterName: parameterName, genericParameters: genericParameters, genericRequirements: genericRequirements) } throw LoweringError.unhandledType(.optional(wrappedType)) @@ -514,7 +513,7 @@ struct CdeclLowering { // Custom types are not supported yet. throw LoweringError.unhandledType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque: + case .genericParameter, .function, .metatype, .optional, .tuple, .existential, .opaque: // TODO: Implement throw LoweringError.unhandledType(type) } @@ -668,7 +667,7 @@ struct CdeclLowering { conversion: .tupleExplode(conversions, name: outParameterName) ) - case .function, .optional, .existential, .opaque: + case .genericParameter, .function, .optional, .existential, .opaque: throw LoweringError.unhandledType(type) } } @@ -754,7 +753,9 @@ public struct LoweredFunctionSignature: Equatable { selfParameter: nil, parameters: allLoweredParameters, result: SwiftResult(convention: .direct, type: result.cdeclResultType), - effectSpecifiers: [] + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] ) } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 9f658188..b29ca5d9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -24,7 +24,7 @@ extension FFMSwift2JavaGenerator { let translated: TranslatedFunctionDecl? do { - let translation = JavaTranslation(symbolTable: self.symbolTable) + let translation = JavaTranslation(knownTypes: SwiftKnownTypes(symbolTable: lookupContext.symbolTable)) translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") @@ -115,8 +115,8 @@ extension FFMSwift2JavaGenerator { struct JavaTranslation { var knownTypes: SwiftKnownTypes - init(symbolTable: SwiftSymbolTable) { - self.knownTypes = SwiftKnownTypes(symbolTable: symbolTable) + init(knownTypes: SwiftKnownTypes) { + self.knownTypes = knownTypes } func translate( @@ -256,7 +256,9 @@ extension FFMSwift2JavaGenerator { convention: swiftSelf.convention, parameterName: swiftSelf.parameterName ?? "self", loweredParam: loweredFunctionSignature.selfParameter!, - methodName: methodName + methodName: methodName, + genericParameters: swiftSignature.genericParameters, + genericRequirements: swiftSignature.genericRequirements ) } else { selfParameter = nil @@ -272,7 +274,9 @@ extension FFMSwift2JavaGenerator { convention: swiftParam.convention, parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName + methodName: methodName, + genericParameters: swiftSignature.genericParameters, + genericRequirements: swiftSignature.genericRequirements ) } @@ -295,7 +299,9 @@ extension FFMSwift2JavaGenerator { convention: SwiftParameterConvention, parameterName: String, loweredParam: LoweredParameter, - methodName: String + methodName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { // If there is a 1:1 mapping between this Swift type and a C type, that can @@ -352,7 +358,15 @@ extension FFMSwift2JavaGenerator { guard let genericArgs = swiftNominalType.genericArguments, genericArgs.count == 1 else { throw JavaTranslationError.unhandledType(swiftType) } - return try translateOptionalParameter(wrappedType: genericArgs[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + return try translateOptionalParameter( + wrappedType: genericArgs[0], + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) case .string: return TranslatedParameter( @@ -399,20 +413,16 @@ extension FFMSwift2JavaGenerator { conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) ) - case .existential(let proto), .opaque(let proto): - // If the protocol has a known representative implementation, e.g. `String` for `StringProtocol` - // Translate it as the concrete type. - // NOTE: This is a temporary workaround until we add support for generics. - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { + case .existential, .opaque, .genericParameter: + if let concreteTy = swiftType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { return try translateParameter( type: concreteTy, convention: convention, parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) } @@ -420,7 +430,15 @@ extension FFMSwift2JavaGenerator { throw JavaTranslationError.unhandledType(swiftType) case .optional(let wrapped): - return try translateOptionalParameter(wrappedType: wrapped, convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + return try translateOptionalParameter( + wrappedType: wrapped, + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) } } @@ -430,12 +448,14 @@ extension FFMSwift2JavaGenerator { convention: SwiftParameterConvention, parameterName: String, loweredParam: LoweredParameter, - methodName: String + methodName: String, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { - var (translatedClass, lowerFunc) = switch cType.javaType { + let (translatedClass, lowerFunc) = switch cType.javaType { case .int: ("OptionalInt", "toOptionalSegmentInt") case .long: ("OptionalLong", "toOptionalSegmentLong") case .double: ("OptionalDouble", "toOptionalSegmentDouble") @@ -473,23 +493,30 @@ extension FFMSwift2JavaGenerator { ], conversion: .call(.placeholder, function: "SwiftRuntime.toOptionalSegmentInstance", withArena: false) ) - case .existential(let proto), .opaque(let proto): - if - let knownProtocol = proto.asNominalTypeDeclaration?.knownTypeKind, - let concreteTy = knownTypes.representativeType(of: knownProtocol) - { + case .existential, .opaque, .genericParameter: + if let concreteTy = swiftType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { return try translateOptionalParameter( wrappedType: concreteTy, convention: convention, parameterName: parameterName, loweredParam: loweredParam, - methodName: methodName + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements ) } throw JavaTranslationError.unhandledType(.optional(swiftType)) case .tuple(let tuple): if tuple.count == 1 { - return try translateOptionalParameter(wrappedType: tuple[0], convention: convention, parameterName: parameterName, loweredParam: loweredParam, methodName: methodName) + return try translateOptionalParameter( + wrappedType: tuple[0], + convention: convention, + parameterName: parameterName, + loweredParam: loweredParam, + methodName: methodName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) } throw JavaTranslationError.unhandledType(.optional(swiftType)) default: @@ -580,7 +607,7 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .optional, .function, .existential, .opaque: + case .genericParameter, .optional, .function, .existential, .opaque: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 10dab4b9..1e32d1e5 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -125,7 +125,7 @@ extension FFMSwift2JavaGenerator { } func printSwiftThunkImports(_ printer: inout CodePrinter) { - for module in self.symbolTable.importedModules.keys.sorted() { + for module in self.lookupContext.symbolTable.importedModules.keys.sorted() { guard module != "Swift" else { continue } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index a24dabb1..d30d3e74 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -24,7 +24,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { let javaPackage: String let swiftOutputDirectory: String let javaOutputDirectory: String - let symbolTable: SwiftSymbolTable + let lookupContext: SwiftTypeLookupContext var javaPackagePath: String { javaPackage.replacingOccurrences(of: ".", with: "/") @@ -51,7 +51,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { self.javaPackage = javaPackage self.swiftOutputDirectory = swiftOutputDirectory self.javaOutputDirectory = javaOutputDirectory - self.symbolTable = translator.symbolTable + self.lookupContext = translator.lookupContext // If we are forced to write empty files, construct the expected outputs if translator.config.writeEmptyFiles ?? false { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 57ea15b2..8d18e6ae 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -216,7 +216,7 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .existential, .opaque: + case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftType) } } @@ -251,7 +251,7 @@ extension JNISwift2JavaGenerator { case .tuple([]): return TranslatedResult(javaType: .void, conversion: .placeholder) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 5ccefadb..48a6e8d4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -129,7 +129,7 @@ extension JNISwift2JavaGenerator { ) ) - case .metatype, .optional, .tuple, .existential, .opaque: + case .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftParameter.type) } } @@ -160,7 +160,7 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .function, .metatype, .optional, .tuple, .existential, .opaque: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -187,7 +187,7 @@ extension JNISwift2JavaGenerator { // Custom types are not supported yet. throw JavaTranslationError.unsupportedSwiftType(type) - case .function, .metatype, .optional, .tuple, .existential, .opaque: + case .function, .metatype, .optional, .tuple, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(type) } } @@ -223,7 +223,7 @@ extension JNISwift2JavaGenerator { conversion: .placeholder ) - case .metatype, .optional, .tuple, .function, .existential, .opaque: + case .metatype, .optional, .tuple, .function, .existential, .opaque, .genericParameter: throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index d1141e5f..3e2359f2 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -23,7 +23,7 @@ import SwiftSyntax public final class Swift2JavaTranslator { static let SWIFT_INTERFACE_SUFFIX = ".swiftinterface" - package var log = Logger(label: "translator", logLevel: .info) + package var log: Logger let config: Configuration @@ -52,7 +52,11 @@ public final class Swift2JavaTranslator { /// type representation. package var importedTypes: [String: ImportedNominalType] = [:] - package var symbolTable: SwiftSymbolTable! = nil + var lookupContext: SwiftTypeLookupContext! = nil + + var symbolTable: SwiftSymbolTable! { + return lookupContext?.symbolTable + } public init( config: Configuration @@ -60,6 +64,7 @@ public final class Swift2JavaTranslator { guard let swiftModule = config.swiftModule else { fatalError("Missing 'swiftModule' name.") // FIXME: can we make it required in config? but we shared config for many cases } + self.log = Logger(label: "translator", logLevel: config.logLevel ?? .info) self.config = config self.swiftModuleName = swiftModule } @@ -116,11 +121,12 @@ extension Swift2JavaTranslator { package func prepareForTranslation() { let dependenciesSource = self.buildDependencyClassesSourceFile() - self.symbolTable = SwiftSymbolTable.setup( + let symbolTable = SwiftSymbolTable.setup( moduleName: self.swiftModuleName, inputs.map({ $0.syntax }) + [dependenciesSource], log: self.log ) + self.lookupContext = SwiftTypeLookupContext(symbolTable: symbolTable) } /// Check if any of the imported decls uses a nominal declaration that satisfies @@ -140,6 +146,8 @@ extension Swift2JavaTranslator { return check(ty) case .existential(let ty), .opaque(let ty): return check(ty) + case .genericParameter: + return false } } @@ -206,7 +214,7 @@ extension Swift2JavaTranslator { func importedNominalType( _ typeNode: TypeSyntax ) -> ImportedNominalType? { - guard let swiftType = try? SwiftType(typeNode, symbolTable: self.symbolTable) else { + guard let swiftType = try? SwiftType(typeNode, lookupContext: lookupContext) else { return nil } guard let swiftNominalDecl = swiftType.asNominalTypeDeclaration else { diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 7ce982a2..90181757 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -104,7 +104,7 @@ final class Swift2JavaVisitor { signature = try SwiftFunctionSignature( node, enclosingType: typeContext?.swiftType, - symbolTable: self.translator.symbolTable + lookupContext: translator.lookupContext ) } catch { self.log.debug("Failed to import: '\(node.qualifiedNameForDebug)'; \(error)") @@ -145,7 +145,7 @@ final class Swift2JavaVisitor { node, isSet: kind == .setter, enclosingType: typeContext?.swiftType, - symbolTable: self.translator.symbolTable + lookupContext: translator.lookupContext ) let imported = ImportedFunc( @@ -193,7 +193,7 @@ final class Swift2JavaVisitor { signature = try SwiftFunctionSignature( node, enclosingType: typeContext.swiftType, - symbolTable: self.translator.symbolTable + lookupContext: translator.lookupContext ) } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 5cca79bf..85b96110 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -15,6 +15,11 @@ import SwiftSyntax import SwiftSyntaxBuilder +enum SwiftGenericRequirement: Equatable { + case inherits(SwiftType, SwiftType) + case equals(SwiftType, SwiftType) +} + /// Provides a complete signature for a Swift function, which includes its /// parameters and return type. public struct SwiftFunctionSignature: Equatable { @@ -22,17 +27,23 @@ public struct SwiftFunctionSignature: Equatable { var parameters: [SwiftParameter] var result: SwiftResult var effectSpecifiers: [SwiftEffectSpecifier] + var genericParameters: [SwiftGenericParameterDeclaration] + var genericRequirements: [SwiftGenericRequirement] init( selfParameter: SwiftSelfParameter? = nil, parameters: [SwiftParameter], result: SwiftResult, - effectSpecifiers: [SwiftEffectSpecifier] + effectSpecifiers: [SwiftEffectSpecifier], + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] ) { self.selfParameter = selfParameter self.parameters = parameters self.result = result self.effectSpecifiers = effectSpecifiers + self.genericParameters = genericParameters + self.genericRequirements = genericRequirements } } @@ -54,13 +65,8 @@ extension SwiftFunctionSignature { init( _ node: InitializerDeclSyntax, enclosingType: SwiftType?, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws { - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } - guard let enclosingType else { throw SwiftFunctionTranslationError.missingEnclosingType(node) } @@ -70,33 +76,36 @@ extension SwiftFunctionSignature { throw SwiftFunctionTranslationError.failableInitializer(node) } - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } - + let (genericParams, genericRequirements) = try Self.translateGenericParameters( + parameterClause: node.genericParameterClause, + whereClause: node.genericWhereClause, + lookupContext: lookupContext + ) let (parameters, effectSpecifiers) = try Self.translateFunctionSignature( node.signature, - symbolTable: symbolTable + lookupContext: lookupContext ) self.init( selfParameter: .initializer(enclosingType), parameters: parameters, result: SwiftResult(convention: .direct, type: enclosingType), - effectSpecifiers: effectSpecifiers + effectSpecifiers: effectSpecifiers, + genericParameters: genericParams, + genericRequirements: genericRequirements ) } init( _ node: FunctionDeclSyntax, enclosingType: SwiftType?, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws { - // Prohibit generics for now. - if let generics = node.genericParameterClause { - throw SwiftFunctionTranslationError.generic(generics) - } + let (genericParams, genericRequirements) = try Self.translateGenericParameters( + parameterClause: node.genericParameterClause, + whereClause: node.genericWhereClause, + lookupContext: lookupContext + ) // 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. @@ -132,7 +141,7 @@ extension SwiftFunctionSignature { // Translate the parameters. let (parameters, effectSpecifiers) = try Self.translateFunctionSignature( node.signature, - symbolTable: symbolTable + lookupContext: lookupContext ) // Translate the result type. @@ -140,20 +149,81 @@ extension SwiftFunctionSignature { if let resultType = node.signature.returnClause?.type { result = try SwiftResult( convention: .direct, - type: SwiftType(resultType, symbolTable: symbolTable) + type: SwiftType(resultType, lookupContext: lookupContext) ) } else { result = .void } - self.init(selfParameter: selfParameter, parameters: parameters, result: result, effectSpecifiers: effectSpecifiers) + self.init( + selfParameter: selfParameter, + parameters: parameters, + result: result, + effectSpecifiers: effectSpecifiers, + genericParameters: genericParams, + genericRequirements: genericRequirements + ) + } + + static func translateGenericParameters( + parameterClause: GenericParameterClauseSyntax?, + whereClause: GenericWhereClauseSyntax?, + lookupContext: SwiftTypeLookupContext + ) throws -> (parameters: [SwiftGenericParameterDeclaration], requirements: [SwiftGenericRequirement]) { + var params: [SwiftGenericParameterDeclaration] = [] + var requirements: [SwiftGenericRequirement] = [] + + // Parameter clause + if let parameterClause { + for parameterNode in parameterClause.parameters { + guard parameterNode.specifier == nil else { + throw SwiftFunctionTranslationError.genericParameterSpecifier(parameterNode) + } + let param = try lookupContext.typeDeclaration(for: parameterNode) as! SwiftGenericParameterDeclaration + params.append(param) + if let inheritedNode = parameterNode.inheritedType { + let inherited = try SwiftType(inheritedNode, lookupContext: lookupContext) + requirements.append(.inherits(.genericParameter(param), inherited)) + } + } + } + + // Where clause + if let whereClause { + for requirementNode in whereClause.requirements { + let requirement: SwiftGenericRequirement + switch requirementNode.requirement { + case .conformanceRequirement(let conformance): + requirement = .inherits( + try SwiftType(conformance.leftType, lookupContext: lookupContext), + try SwiftType(conformance.rightType, lookupContext: lookupContext) + ) + case .sameTypeRequirement(let sameType): + guard let leftType = sameType.leftType.as(TypeSyntax.self) else { + throw SwiftFunctionTranslationError.expressionInGenericRequirement(requirementNode) + } + guard let rightType = sameType.rightType.as(TypeSyntax.self) else { + throw SwiftFunctionTranslationError.expressionInGenericRequirement(requirementNode) + } + requirement = .equals( + try SwiftType(leftType, lookupContext: lookupContext), + try SwiftType(rightType, lookupContext: lookupContext) + ) + case .layoutRequirement: + throw SwiftFunctionTranslationError.layoutRequirement(requirementNode) + } + requirements.append(requirement) + } + } + + return (params, requirements) } /// Translate the function signature, returning the list of translated /// parameters and effect specifiers. static func translateFunctionSignature( _ signature: FunctionSignatureSyntax, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) throws -> ([SwiftParameter], [SwiftEffectSpecifier]) { var effectSpecifiers = [SwiftEffectSpecifier]() if signature.effectSpecifiers?.throwsClause != nil { @@ -164,13 +234,18 @@ extension SwiftFunctionSignature { } let parameters = try signature.parameterClause.parameters.map { param in - try SwiftParameter(param, symbolTable: symbolTable) + try SwiftParameter(param, lookupContext: lookupContext) } return (parameters, effectSpecifiers) } - init(_ varNode: VariableDeclSyntax, isSet: Bool, enclosingType: SwiftType?, symbolTable: SwiftSymbolTable) throws { + init( + _ varNode: VariableDeclSyntax, + isSet: Bool, + enclosingType: SwiftType?, + lookupContext: SwiftTypeLookupContext + ) 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. @@ -205,7 +280,7 @@ extension SwiftFunctionSignature { guard let varTypeNode = binding.typeAnnotation?.type else { throw SwiftFunctionTranslationError.missingTypeAnnotation(varNode) } - let valueType = try SwiftType(varTypeNode, symbolTable: symbolTable) + let valueType = try SwiftType(varTypeNode, lookupContext: lookupContext) var effectSpecifiers: [SwiftEffectSpecifier]? = nil switch binding.accessorBlock?.accessors { @@ -230,6 +305,8 @@ extension SwiftFunctionSignature { self.parameters = [] self.result = .init(convention: .direct, type: valueType) } + self.genericParameters = [] + self.genericRequirements = [] } private static func effectSpecifiers(from decl: AccessorDeclSyntax) throws -> [SwiftEffectSpecifier] { @@ -287,11 +364,13 @@ extension VariableDeclSyntax { enum SwiftFunctionTranslationError: Error { case `throws`(ThrowsClauseSyntax) case async(TokenSyntax) - case generic(GenericParameterClauseSyntax) case classMethod(TokenSyntax) case missingEnclosingType(InitializerDeclSyntax) case failableInitializer(InitializerDeclSyntax) case multipleBindings(VariableDeclSyntax) case missingTypeAnnotation(VariableDeclSyntax) case unsupportedAccessor(AccessorDeclSyntax) + case genericParameterSpecifier(GenericParameterSyntax) + case expressionInGenericRequirement(GenericRequirementSyntax) + case layoutRequirement(GenericRequirementSyntax) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift index 565a24c3..da6fd2a2 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionType.swift @@ -40,18 +40,18 @@ extension SwiftFunctionType { init( _ node: FunctionTypeSyntax, convention: Convention, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) 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) + type: try SwiftType(param.type, lookupContext: lookupContext) ) } - self.resultType = try SwiftType(node.returnClause.type, symbolTable: symbolTable) + self.resultType = try SwiftType(node.returnClause.type, lookupContext: lookupContext) // check for effect specifiers if let throwsClause = node.effectSpecifiers?.throwsClause { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 8be645c8..b377fd85 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -18,9 +18,24 @@ import SwiftSyntax @_spi(Testing) public typealias NominalTypeDeclSyntaxNode = any DeclGroupSyntax & NamedDeclSyntax & WithAttributesSyntax & WithModifiersSyntax +package class SwiftTypeDeclaration { + /// The module in which this nominal type is defined. If this is a nested type, the + /// module might be different from that of the parent type, if this nominal type + /// is defined in an extension within another module. + let moduleName: String + + /// The name of this nominal type, e.g., 'MyCollection'. + let name: String + + init(moduleName: String, name: String) { + self.moduleName = moduleName + self.name = name + } +} + /// Describes a nominal type declaration, which can be of any kind (class, struct, etc.) /// and has a name, parent type (if nested), and owning module. -package class SwiftNominalTypeDeclaration { +package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { enum Kind { case actor case `class` @@ -40,14 +55,6 @@ package class SwiftNominalTypeDeclaration { /// MyCollection.Iterator. let 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. - let moduleName: String - - /// The name of this nominal type, e.g., 'MyCollection'. - let name: String - // TODO: Generic parameters. /// Identify this nominal declaration as one of the known standard library @@ -63,9 +70,7 @@ package class SwiftNominalTypeDeclaration { parent: SwiftNominalTypeDeclaration?, node: NominalTypeDeclSyntaxNode ) { - self.moduleName = moduleName self.parent = parent - self.name = node.name.text self.syntax = node // Determine the kind from the syntax node. @@ -77,6 +82,7 @@ package class SwiftNominalTypeDeclaration { case .structDecl: self.kind = .struct default: fatalError("Not a nominal type declaration") } + super.init(moduleName: moduleName, name: node.name.text) } /// Determine the known standard library type for this nominal type @@ -107,13 +113,25 @@ package class SwiftNominalTypeDeclaration { } } -extension SwiftNominalTypeDeclaration: Equatable { - package static func ==(lhs: SwiftNominalTypeDeclaration, rhs: SwiftNominalTypeDeclaration) -> Bool { +package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration { + let syntax: GenericParameterSyntax + + init( + moduleName: String, + node: GenericParameterSyntax + ) { + self.syntax = node + super.init(moduleName: moduleName, name: node.name.text) + } +} + +extension SwiftTypeDeclaration: Equatable { + package static func ==(lhs: SwiftTypeDeclaration, rhs: SwiftTypeDeclaration) -> Bool { lhs === rhs } } -extension SwiftNominalTypeDeclaration: Hashable { +extension SwiftTypeDeclaration: Hashable { package func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift index 24b3d8b4..75d165e9 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParameter.swift @@ -58,7 +58,7 @@ enum SwiftParameterConvention: Equatable { } extension SwiftParameter { - init(_ node: FunctionParameterSyntax, symbolTable: SwiftSymbolTable) throws { + init(_ node: FunctionParameterSyntax, lookupContext: SwiftTypeLookupContext) throws { // Determine the convention. The default is by-value, but there are // specifiers on the type for other conventions (like `inout`). var type = node.type @@ -90,7 +90,7 @@ extension SwiftParameter { self.convention = convention // Determine the type. - self.type = try SwiftType(type, symbolTable: symbolTable) + self.type = try SwiftType(type, lookupContext: lookupContext) // FIXME: swift-syntax itself should have these utilities based on identifiers. if let secondName = node.secondName { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 4026e624..9b8ef236 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -122,7 +122,8 @@ extension SwiftParsedModuleSymbolTableBuilder { parsedModule: symbolTable, importedModules: importedModules ) - guard let extendedType = try? SwiftType(node.extendedType, symbolTable: table) else { + let lookupContext = SwiftTypeLookupContext(symbolTable: table) + guard let extendedType = try? SwiftType(node.extendedType, lookupContext: lookupContext) else { return false } guard let extendedNominal = extendedType.asNominalTypeDeclaration else { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift new file mode 100644 index 00000000..ce668485 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType+RepresentativeConcreteeType.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +extension SwiftType { + /// Returns a concrete type if this is a generic parameter in the list and it + /// conforms to a protocol with representative concrete type. + func representativeConcreteTypeIn( + knownTypes: SwiftKnownTypes, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] + ) -> SwiftType? { + return representativeConcreteType( + self, + knownTypes: knownTypes, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + } +} + +private func representativeConcreteType( + _ type: SwiftType, + knownTypes: SwiftKnownTypes, + genericParameters: [SwiftGenericParameterDeclaration], + genericRequirements: [SwiftGenericRequirement] +) -> SwiftType? { + var maybeProto: SwiftType? = nil + switch type { + case .existential(let proto), .opaque(let proto): + maybeProto = proto + case .genericParameter(let genericParam): + // If the type is a generic parameter declared in this function and + // conforms to a protocol with representative concrete type, use it. + if genericParameters.contains(genericParam) { + for requirement in genericRequirements { + if case .inherits(let left, let right) = requirement, left == type { + guard maybeProto == nil else { + // multiple requirements on the generic parameter. + return nil + } + maybeProto = right + break + } + } + } + default: + return nil + } + + if let knownProtocol = maybeProto?.asNominalTypeDeclaration?.knownTypeKind { + return knownTypes.representativeType(of: knownProtocol) + } + return nil +} diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index d0f1f98d..da738d39 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -18,6 +18,8 @@ import SwiftSyntax enum SwiftType: Equatable { case nominal(SwiftNominalType) + case genericParameter(SwiftGenericParameterDeclaration) + indirect case function(SwiftFunctionType) /// `.Type` @@ -43,7 +45,7 @@ enum SwiftType: Equatable { switch self { case .nominal(let nominal): nominal case .tuple(let elements): elements.count == 1 ? elements[0].asNominalType : nil - case .function, .metatype, .optional, .existential, .opaque: nil + case .genericParameter, .function, .metatype, .optional, .existential, .opaque: nil } } @@ -86,7 +88,7 @@ enum SwiftType: Equatable { return nominal.nominalTypeDecl.isReferenceType case .metatype, .function: return true - case .optional, .tuple, .existential, .opaque: + case .genericParameter, .optional, .tuple, .existential, .opaque: return false } } @@ -98,13 +100,14 @@ extension SwiftType: CustomStringConvertible { private var postfixRequiresParentheses: Bool { switch self { case .function, .existential, .opaque: true - case .metatype, .nominal, .optional, .tuple: false + case .genericParameter, .metatype, .nominal, .optional, .tuple: false } } var description: String { switch self { case .nominal(let nominal): return nominal.description + case .genericParameter(let genericParam): return genericParam.name case .function(let functionType): return functionType.description case .metatype(let instanceType): var instanceTypeStr = instanceType.description @@ -179,7 +182,7 @@ extension SwiftNominalType { } extension SwiftType { - init(_ type: TypeSyntax, symbolTable: SwiftSymbolTable) throws { + init(_ type: TypeSyntax, lookupContext: SwiftTypeLookupContext) throws { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .classRestrictionType, .compositionType, .dictionaryType, .missingType, .namedOpaqueReturnType, @@ -192,7 +195,7 @@ extension SwiftType { // FIXME: This string matching is a horrible hack. switch attributedType.attributes.trimmedDescription { case "@convention(c)", "@convention(swift)": - let innerType = try SwiftType(attributedType.baseType, symbolTable: symbolTable) + let innerType = try SwiftType(attributedType.baseType, lookupContext: lookupContext) switch innerType { case .function(var functionType): let isConventionC = attributedType.attributes.trimmedDescription == "@convention(c)" @@ -208,7 +211,7 @@ extension SwiftType { case .functionType(let functionType): self = .function( - try SwiftFunctionType(functionType, convention: .swift, symbolTable: symbolTable) + try SwiftFunctionType(functionType, convention: .swift, lookupContext: lookupContext) ) case .identifierType(let identifierType): @@ -217,7 +220,7 @@ extension SwiftType { try genericArgumentClause.arguments.map { argument in switch argument.argument { case .type(let argumentTy): - try SwiftType(argumentTy, symbolTable: symbolTable) + try SwiftType(argumentTy, lookupContext: lookupContext) default: throw TypeTranslationError.unimplementedType(type) } @@ -228,13 +231,13 @@ extension SwiftType { self = try SwiftType( originalType: type, parent: nil, - name: identifierType.name.text, + name: identifierType.name, genericArguments: genericArgs, - symbolTable: symbolTable + lookupContext: lookupContext ) case .implicitlyUnwrappedOptionalType(let optionalType): - self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable)) + self = .optional(try SwiftType(optionalType.wrappedType, lookupContext: lookupContext)) case .memberType(let memberType): // If the parent type isn't a known module, translate it. @@ -244,7 +247,7 @@ extension SwiftType { if memberType.baseType.trimmedDescription == "Swift" { parentType = nil } else { - parentType = try SwiftType(memberType.baseType, symbolTable: symbolTable) + parentType = try SwiftType(memberType.baseType, lookupContext: lookupContext) } // Translate the generic arguments. @@ -252,7 +255,7 @@ extension SwiftType { try genericArgumentClause.arguments.map { argument in switch argument.argument { case .type(let argumentTy): - try SwiftType(argumentTy, symbolTable: symbolTable) + try SwiftType(argumentTy, lookupContext: lookupContext) default: throw TypeTranslationError.unimplementedType(type) } @@ -262,27 +265,27 @@ extension SwiftType { self = try SwiftType( originalType: type, parent: parentType, - name: memberType.name.text, + name: memberType.name, genericArguments: genericArgs, - symbolTable: symbolTable + lookupContext: lookupContext ) case .metatypeType(let metatypeType): - self = .metatype(try SwiftType(metatypeType.baseType, symbolTable: symbolTable)) + self = .metatype(try SwiftType(metatypeType.baseType, lookupContext: lookupContext)) case .optionalType(let optionalType): - self = .optional(try SwiftType(optionalType.wrappedType, symbolTable: symbolTable)) + self = .optional(try SwiftType(optionalType.wrappedType, lookupContext: lookupContext)) case .tupleType(let tupleType): self = try .tuple(tupleType.elements.map { element in - try SwiftType(element.type, symbolTable: symbolTable) + try SwiftType(element.type, lookupContext: lookupContext) }) case .someOrAnyType(let someOrAntType): if someOrAntType.someOrAnySpecifier.tokenKind == .keyword(.some) { - self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + self = .opaque(try SwiftType(someOrAntType.constraint, lookupContext: lookupContext)) } else { - self = .opaque(try SwiftType(someOrAntType.constraint, symbolTable: symbolTable)) + self = .opaque(try SwiftType(someOrAntType.constraint, lookupContext: lookupContext)) } } } @@ -290,25 +293,37 @@ extension SwiftType { init( originalType: TypeSyntax, parent: SwiftType?, - name: String, + name: TokenSyntax, genericArguments: [SwiftType]?, - symbolTable: SwiftSymbolTable + lookupContext: SwiftTypeLookupContext ) 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) + let typeDecl: SwiftTypeDeclaration? + if let parent { + guard let parentDecl = parent.asNominalTypeDeclaration else { + throw TypeTranslationError.unknown(originalType) + } + typeDecl = lookupContext.symbolTable.lookupNestedType(name.text, parent: parentDecl) + } else { + typeDecl = try lookupContext.unqualifiedLookup(name: Identifier(name)!, from: name) + } + guard let typeDecl else { + throw TypeTranslationError.unknown(originalType) } - self = .nominal( - SwiftNominalType( - parent: parent?.asNominalType, - nominalTypeDecl: nominalTypeDecl, - genericArguments: genericArguments + if let nominalDecl = typeDecl as? SwiftNominalTypeDeclaration { + self = .nominal( + SwiftNominalType( + parent: parent?.asNominalType, + nominalTypeDecl: nominalDecl, + genericArguments: genericArguments + ) ) - ) + } else if let genericParamDecl = typeDecl as? SwiftGenericParameterDeclaration { + self = .genericParameter(genericParamDecl) + } else { + fatalError("unknown SwiftTypeDeclaration: \(type(of: typeDecl))") + } } init?( @@ -345,11 +360,11 @@ extension SwiftType { enum TypeTranslationError: Error { /// We haven't yet implemented support for this type. - case unimplementedType(TypeSyntax) + case unimplementedType(TypeSyntax, file: StaticString = #file, line: Int = #line) /// Missing generic arguments. - case missingGenericArguments(TypeSyntax) + case missingGenericArguments(TypeSyntax, file: StaticString = #file, line: Int = #line) /// Unknown nominal type. - case unknown(TypeSyntax) + case unknown(TypeSyntax, file: StaticString = #file, line: Int = #line) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift new file mode 100644 index 00000000..9ede2b1b --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -0,0 +1,151 @@ +//===----------------------------------------------------------------------===// +// +// 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 +@_spi(Experimental) import SwiftLexicalLookup + +/// Unqualified type lookup manager. +/// All unqualified lookup should be done via this instance. This caches the +/// association of `Syntax.ID` to `SwiftTypeDeclaration`, and guarantees that +/// there's only one `SwiftTypeDeclaration` per declaration `Syntax`. +class SwiftTypeLookupContext { + var symbolTable: SwiftSymbolTable + + private var typeDecls: [Syntax.ID: SwiftTypeDeclaration] = [:] + + init(symbolTable: SwiftSymbolTable) { + self.symbolTable = symbolTable + } + + /// Perform unqualified type lookup. + /// + /// - Parameters: + /// - name: name to lookup + /// - node: `Syntax` node the lookup happened + func unqualifiedLookup(name: Identifier, from node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + + for result in node.lookup(name) { + switch result { + case .fromScope(_, let names): + if !names.isEmpty { + return typeDeclaration(for: names) + } + + case .fromFileScope(_, let names): + if !names.isEmpty { + return typeDeclaration(for: names) + } + + case .lookInMembers(let scopeNode): + if let nominalDecl = try typeDeclaration(for: scopeNode) { + if let found = symbolTable.lookupNestedType(name.name, parent: nominalDecl as! SwiftNominalTypeDeclaration) { + return found + } + } + + case .lookInGenericParametersOfExtendedType(let extensionNode): + // TODO: Implement + _ = extensionNode + break + + case .mightIntroduceDollarIdentifiers: + // Dollar identifier can't be a type, ignore. + break + } + } + + // Fallback to global symbol table lookup. + return symbolTable.lookupTopLevelNominalType(name.name) + } + + /// Find the first type declaration in the `LookupName` results. + private func typeDeclaration(for names: [LookupName]) -> SwiftTypeDeclaration? { + for name in names { + switch name { + case .identifier(let identifiableSyntax, _): + return try? typeDeclaration(for: identifiableSyntax) + case .declaration(let namedDeclSyntax): + return try? typeDeclaration(for: namedDeclSyntax) + case .implicit(let implicitDecl): + // TODO: Implement + _ = implicitDecl + break + case .dollarIdentifier: + break + } + } + return nil + } + + /// Returns the type declaration object associated with the `Syntax` node. + /// If there's no declaration created, create an instance on demand, and cache it. + func typeDeclaration(for node: some SyntaxProtocol) throws -> SwiftTypeDeclaration? { + if let found = typeDecls[node.id] { + return found + } + + let typeDecl: SwiftTypeDeclaration + switch Syntax(node).as(SyntaxEnum.self) { + case .genericParameter(let node): + typeDecl = SwiftGenericParameterDeclaration(moduleName: symbolTable.moduleName, node: node) + case .classDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .actorDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .structDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .enumDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .protocolDecl(let node): + typeDecl = try nominalTypeDeclaration(for: node) + case .typeAliasDecl: + fatalError("typealias not implemented") + case .associatedTypeDecl: + fatalError("associatedtype not implemented") + default: + throw TypeLookupError.notType(Syntax(node)) + } + + typeDecls[node.id] = typeDecl + return typeDecl + } + + /// Create a nominal type declaration instance for the specified syntax node. + private func nominalTypeDeclaration(for node: NominalTypeDeclSyntaxNode) throws -> SwiftNominalTypeDeclaration { + SwiftNominalTypeDeclaration( + moduleName: self.symbolTable.moduleName, + parent: try parentTypeDecl(for: node), + node: node + ) + } + + /// Find a parent nominal type declaration of the specified syntax node. + private func parentTypeDecl(for node: some DeclSyntaxProtocol) throws -> SwiftNominalTypeDeclaration? { + var node: DeclSyntax = DeclSyntax(node) + while let parentDecl = node.ancestorDecl { + switch parentDecl.as(DeclSyntaxEnum.self) { + case .structDecl, .classDecl, .actorDecl, .enumDecl, .protocolDecl: + return (try typeDeclaration(for: parentDecl) as! SwiftNominalTypeDeclaration) + default: + node = parentDecl + continue + } + } + return nil + } +} + +enum TypeLookupError: Error { + case notType(Syntax) +} diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 4b0162f3..ffefe907 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -37,6 +37,7 @@ func assertOutput( column: Int = #column ) throws { var config = Configuration() + config.logLevel = .trace config.swiftModule = swiftModuleName let translator = Swift2JavaTranslator(config: config) translator.dependenciesClasses = Array(javaClassLookupTable.keys) diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index af71c157..7416779d 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -28,7 +28,7 @@ final class DataImportTests { """ import Foundation - public func receiveDataProtocol(dat: some DataProtocol) + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) """ @@ -342,9 +342,9 @@ final class DataImportTests { import Foundation """, """ - @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat") - public func swiftjava_SwiftModule_receiveDataProtocol_dat(_ dat: UnsafeRawPointer) { - receiveDataProtocol(dat: dat.assumingMemoryBound(to: Data.self).pointee) + @_cdecl("swiftjava_SwiftModule_receiveDataProtocol_dat_dat2") + public func swiftjava_SwiftModule_receiveDataProtocol_dat_dat2(_ dat: UnsafeRawPointer, _ dat2: UnsafeRawPointer?) { + receiveDataProtocol(dat: dat.assumingMemoryBound(to: Data.self).pointee, dat2: dat2?.assumingMemoryBound(to: Data.self).pointee) } """, @@ -365,22 +365,23 @@ final class DataImportTests { """ /** * {@snippet lang=c : - * void swiftjava_SwiftModule_receiveDataProtocol_dat(const void *dat) + * void swiftjava_SwiftModule_receiveDataProtocol_dat_dat2(const void *dat, const void *dat2) * } */ - private static class swiftjava_SwiftModule_receiveDataProtocol_dat { + private static class swiftjava_SwiftModule_receiveDataProtocol_dat_dat2 { private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* dat: */SwiftValueLayout.SWIFT_POINTER + /* dat: */SwiftValueLayout.SWIFT_POINTER, + /* dat2: */SwiftValueLayout.SWIFT_POINTER ); private static final MemorySegment ADDR = - SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveDataProtocol_dat"); + SwiftModule.findOrThrow("swiftjava_SwiftModule_receiveDataProtocol_dat_dat2"); private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); - public static void call(java.lang.foreign.MemorySegment dat) { + public static void call(java.lang.foreign.MemorySegment dat, java.lang.foreign.MemorySegment dat2) { try { if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall(dat); + CallTraces.traceDowncall(dat, dat2); } - HANDLE.invokeExact(dat); + HANDLE.invokeExact(dat, dat2); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } @@ -392,11 +393,11 @@ final class DataImportTests { /** * Downcall to Swift: * {@snippet lang=swift : - * public func receiveDataProtocol(dat: some DataProtocol) + * public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) * } */ - public static void receiveDataProtocol(Data dat) { - swiftjava_SwiftModule_receiveDataProtocol_dat.call(dat.$memorySegment()); + public static void receiveDataProtocol(Data dat, Optional dat2) { + swiftjava_SwiftModule_receiveDataProtocol_dat_dat2.call(dat.$memorySegment(), SwiftRuntime.toOptionalSegmentInstance(dat2)); } """, diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index 8419ec61..ba1aad06 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -404,6 +404,27 @@ final class FunctionLoweringTests { """) } + @Test("Lowering generic parameters") + func genericParam() throws { + try assertLoweredFunction( + """ + func fn(x: T, y: U?) where T: DataProtocol + """, + sourceFile: """ + import Foundation + """, + expectedCDecl: """ + @_cdecl("c_fn") + public func c_fn(_ x: UnsafeRawPointer, _ y: UnsafeRawPointer?) { + fn(x: x.assumingMemoryBound(to: Data.self).pointee, y: y?.assumingMemoryBound(to: Data.self).pointee) + } + """, + expectedCFunction: """ + void c_fn(const void *x, const void *y) + """ + ) + } + @Test("Lowering read accessor") func lowerGlobalReadAccessor() throws { try assertLoweredVariableAccessor(