Skip to content

Commit cebc48e

Browse files
committed
Lower initializers and static methods to C declarations
1 parent a2982f9 commit cebc48e

File tree

4 files changed

+230
-54
lines changed

4 files changed

+230
-54
lines changed

Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ extension Swift2JavaTranslator {
3333
return try lowerFunctionSignature(signature)
3434
}
3535

36+
/// Lower the given initializer to a C-compatible entrypoint,
37+
/// providing all of the mappings between the parameter and result types
38+
/// of the original function and its `@_cdecl` counterpart.
39+
@_spi(Testing)
40+
public func lowerFunctionSignature(
41+
_ decl: InitializerDeclSyntax,
42+
enclosingType: TypeSyntax? = nil
43+
) throws -> LoweredFunctionSignature {
44+
let signature = try SwiftFunctionSignature(
45+
decl,
46+
enclosingType: try enclosingType.map { try SwiftType($0, symbolTable: symbolTable) },
47+
symbolTable: symbolTable
48+
)
49+
50+
return try lowerFunctionSignature(signature)
51+
}
3652
/// Lower the given Swift function signature to a Swift @_cdecl function signature,
3753
/// which is C compatible, and the corresponding Java method signature.
3854
///
@@ -75,12 +91,18 @@ extension Swift2JavaTranslator {
7591
indirectResult = true
7692
}
7793

78-
let loweredSelf = try signature.selfParameter.map { selfParameter in
79-
try lowerParameter(
80-
selfParameter.type,
81-
convention: selfParameter.convention,
82-
parameterName: selfParameter.parameterName ?? "self"
83-
)
94+
// Lower the self parameter.
95+
let loweredSelf = try signature.selfParameter.flatMap { selfParameter in
96+
switch selfParameter {
97+
case .instance(let selfParameter):
98+
try lowerParameter(
99+
selfParameter.type,
100+
convention: selfParameter.convention,
101+
parameterName: selfParameter.parameterName ?? "self"
102+
)
103+
case .initializer, .staticMethod:
104+
nil
105+
}
84106
}
85107

86108
// Collect all of the lowered parameters for the @_cdecl function.
@@ -112,7 +134,6 @@ extension Swift2JavaTranslator {
112134
}
113135

114136
let cdeclSignature = SwiftFunctionSignature(
115-
isStaticOrClass: false,
116137
selfParameter: nil,
117138
parameters: cdeclLoweredParameters,
118139
result: cdeclResult
@@ -301,7 +322,7 @@ extension LoweredFunctionSignature {
301322
@_spi(Testing)
302323
public func cdeclThunk(
303324
cName: String,
304-
inputFunction: FunctionDeclSyntax,
325+
swiftFunctionName: String,
305326
stdlibTypes: SwiftStandardLibraryTypes
306327
) -> FunctionDeclSyntax {
307328
var loweredCDecl = cdecl.createFunctionDecl(cName)
@@ -315,14 +336,33 @@ extension LoweredFunctionSignature {
315336
// Lower "self", if there is one.
316337
let parametersToLower: ArraySlice<LoweredParameters>
317338
let cdeclToOriginalSelf: ExprSyntax?
318-
if let originalSelfParam = original.selfParameter {
319-
cdeclToOriginalSelf = try! ConversionStep(
320-
cdeclToSwift: originalSelfParam.type
321-
).asExprSyntax(
322-
isSelf: true,
323-
placeholder: originalSelfParam.parameterName ?? "self"
324-
)
325-
parametersToLower = parameters.dropLast()
339+
var initializerType: SwiftType? = nil
340+
if let originalSelf = original.selfParameter {
341+
switch originalSelf {
342+
case .instance(let originalSelfParam):
343+
// The instance was provided to the cdecl thunk, so convert it to
344+
// its Swift representation.
345+
cdeclToOriginalSelf = try! ConversionStep(
346+
cdeclToSwift: originalSelfParam.type
347+
).asExprSyntax(
348+
isSelf: true,
349+
placeholder: originalSelfParam.parameterName ?? "self"
350+
)
351+
parametersToLower = parameters.dropLast()
352+
353+
case .staticMethod(let selfType):
354+
// Static methods use the Swift type as "self", but there is no
355+
// corresponding cdecl parameter.
356+
cdeclToOriginalSelf = "\(raw: selfType.description)"
357+
parametersToLower = parameters[...]
358+
359+
case .initializer(let selfType):
360+
// Initializers use the Swift type to create the instance. Save it
361+
// for later. There is no corresponding cdecl parameter.
362+
initializerType = selfType
363+
cdeclToOriginalSelf = nil
364+
parametersToLower = parameters[...]
365+
}
326366
} else {
327367
cdeclToOriginalSelf = nil
328368
parametersToLower = parameters[...]
@@ -346,9 +386,14 @@ extension LoweredFunctionSignature {
346386
}
347387

348388
// Form the call expression.
349-
var callExpression: ExprSyntax = "\(inputFunction.name)(\(raw: cdeclToOriginalArguments.joined(separator: ", ")))"
350-
if let cdeclToOriginalSelf {
351-
callExpression = "\(cdeclToOriginalSelf).\(callExpression)"
389+
let callArguments: ExprSyntax = "(\(raw: cdeclToOriginalArguments.joined(separator: ", ")))"
390+
let callExpression: ExprSyntax
391+
if let initializerType {
392+
callExpression = "\(raw: initializerType.description)\(callArguments)"
393+
} else if let cdeclToOriginalSelf {
394+
callExpression = "\(cdeclToOriginalSelf).\(raw: swiftFunctionName)\(callArguments)"
395+
} else {
396+
callExpression = "\(raw: swiftFunctionName)\(callArguments)"
352397
}
353398

354399
// Handle the return.

Sources/JExtractSwift/SwiftTypes/SwiftFunctionSignature.swift

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,25 @@ import SwiftSyntaxBuilder
1919
/// parameters and return type.
2020
@_spi(Testing)
2121
public struct SwiftFunctionSignature: Equatable {
22-
// FIXME: isStaticOrClass probably shouldn't be here?
23-
var isStaticOrClass: Bool
24-
var selfParameter: SwiftParameter?
22+
var selfParameter: SwiftSelfParameter?
2523
var parameters: [SwiftParameter]
2624
var result: SwiftResult
2725
}
2826

27+
/// Describes the "self" parameter of a Swift function signature.
28+
enum SwiftSelfParameter: Equatable {
29+
/// 'self' is an instance parameter.
30+
case instance(SwiftParameter)
31+
32+
/// 'self' is a metatype for a static method. We only need the type to
33+
/// form the call.
34+
case staticMethod(SwiftType)
35+
36+
/// 'self' is the type for a call to an initializer. We only need the type
37+
/// to form the call.
38+
case initializer(SwiftType)
39+
}
40+
2941
extension SwiftFunctionSignature {
3042
/// Create a function declaration with the given name that has this
3143
/// signature.
@@ -49,6 +61,33 @@ extension SwiftFunctionSignature {
4961
}
5062

5163
extension SwiftFunctionSignature {
64+
init(
65+
_ node: InitializerDeclSyntax,
66+
enclosingType: SwiftType?,
67+
symbolTable: SwiftSymbolTable
68+
) throws {
69+
guard let enclosingType else {
70+
throw SwiftFunctionTranslationError.missingEnclosingType(node)
71+
}
72+
73+
// We do not yet support failable initializers.
74+
if node.optionalMark != nil {
75+
throw SwiftFunctionTranslationError.failableInitializer(node)
76+
}
77+
78+
// Prohibit generics for now.
79+
if let generics = node.genericParameterClause {
80+
throw SwiftFunctionTranslationError.generic(generics)
81+
}
82+
83+
self.selfParameter = .initializer(enclosingType)
84+
self.result = SwiftResult(convention: .direct, type: enclosingType)
85+
self.parameters = try Self.translateFunctionSignature(
86+
node.signature,
87+
symbolTable: symbolTable
88+
)
89+
}
90+
5291
init(
5392
_ node: FunctionDeclSyntax,
5493
enclosingType: SwiftType?,
@@ -59,40 +98,36 @@ extension SwiftFunctionSignature {
5998
if let enclosingType {
6099
var isMutating = false
61100
var isConsuming = false
62-
var isStaticOrClass = false
101+
var isStatic = false
63102
for modifier in node.modifiers {
64103
switch modifier.name.tokenKind {
65104
case .keyword(.mutating): isMutating = true
66-
case .keyword(.static), .keyword(.class): isStaticOrClass = true
105+
case .keyword(.static): isStatic = true
67106
case .keyword(.consuming): isConsuming = true
107+
case .keyword(.class): throw SwiftFunctionTranslationError.classMethod(modifier.name)
68108
default: break
69109
}
70110
}
71111

72-
if isStaticOrClass {
73-
self.selfParameter = SwiftParameter(
74-
convention: .byValue,
75-
type: .metatype(
76-
enclosingType
77-
)
78-
)
112+
if isStatic {
113+
self.selfParameter = .staticMethod(enclosingType)
79114
} else {
80-
self.selfParameter = SwiftParameter(
81-
convention: isMutating ? .inout : isConsuming ? .consuming : .byValue,
82-
type: enclosingType
115+
self.selfParameter = .instance(
116+
SwiftParameter(
117+
convention: isMutating ? .inout : isConsuming ? .consuming : .byValue,
118+
type: enclosingType
119+
)
83120
)
84121
}
85-
86-
self.isStaticOrClass = isStaticOrClass
87122
} else {
88123
self.selfParameter = nil
89-
self.isStaticOrClass = false
90124
}
91125

92126
// Translate the parameters.
93-
self.parameters = try node.signature.parameterClause.parameters.map { param in
94-
try SwiftParameter(param, symbolTable: symbolTable)
95-
}
127+
self.parameters = try Self.translateFunctionSignature(
128+
node.signature,
129+
symbolTable: symbolTable
130+
)
96131

97132
// Translate the result type.
98133
if let resultType = node.signature.returnClause?.type {
@@ -104,17 +139,28 @@ extension SwiftFunctionSignature {
104139
self.result = SwiftResult(convention: .direct, type: .tuple([]))
105140
}
106141

142+
// Prohibit generics for now.
143+
if let generics = node.genericParameterClause {
144+
throw SwiftFunctionTranslationError.generic(generics)
145+
}
146+
}
147+
148+
/// Translate the function signature, returning the list of translated
149+
/// parameters.
150+
static func translateFunctionSignature(
151+
_ signature: FunctionSignatureSyntax,
152+
symbolTable: SwiftSymbolTable
153+
) throws -> [SwiftParameter] {
107154
// FIXME: Prohibit effects for now.
108-
if let throwsClause = node.signature.effectSpecifiers?.throwsClause {
155+
if let throwsClause = signature.effectSpecifiers?.throwsClause {
109156
throw SwiftFunctionTranslationError.throws(throwsClause)
110157
}
111-
if let asyncSpecifier = node.signature.effectSpecifiers?.asyncSpecifier {
158+
if let asyncSpecifier = signature.effectSpecifiers?.asyncSpecifier {
112159
throw SwiftFunctionTranslationError.async(asyncSpecifier)
113160
}
114161

115-
// Prohibit generics for now.
116-
if let generics = node.genericParameterClause {
117-
throw SwiftFunctionTranslationError.generic(generics)
162+
return try signature.parameterClause.parameters.map { param in
163+
try SwiftParameter(param, symbolTable: symbolTable)
118164
}
119165
}
120166
}
@@ -123,4 +169,7 @@ enum SwiftFunctionTranslationError: Error {
123169
case `throws`(ThrowsClauseSyntax)
124170
case async(TokenSyntax)
125171
case generic(GenericParameterClauseSyntax)
172+
case classMethod(TokenSyntax)
173+
case missingEnclosingType(InitializerDeclSyntax)
174+
case failableInitializer(InitializerDeclSyntax)
126175
}

Tests/JExtractSwiftTests/Asserts/LoweringAssertions.swift

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,27 @@ func assertLoweredFunction(
4242

4343
translator.prepareForTranslation()
4444

45-
let inputFunction = inputDecl.cast(FunctionDeclSyntax.self)
46-
let loweredFunction = try translator.lowerFunctionSignature(
47-
inputFunction,
48-
enclosingType: enclosingType
49-
)
45+
let swiftFunctionName: String
46+
let loweredFunction: LoweredFunctionSignature
47+
if let inputFunction = inputDecl.as(FunctionDeclSyntax.self) {
48+
loweredFunction = try translator.lowerFunctionSignature(
49+
inputFunction,
50+
enclosingType: enclosingType
51+
)
52+
swiftFunctionName = inputFunction.name.text
53+
} else if let inputInitializer = inputDecl.as(InitializerDeclSyntax.self) {
54+
loweredFunction = try translator.lowerFunctionSignature(
55+
inputInitializer,
56+
enclosingType: enclosingType
57+
)
58+
swiftFunctionName = "init"
59+
} else {
60+
fatalError("Unhandling declaration kind for lowering")
61+
}
62+
5063
let loweredCDecl = loweredFunction.cdeclThunk(
51-
cName: "c_\(inputFunction.name.text)",
52-
inputFunction: inputFunction,
64+
cName: "c_\(swiftFunctionName)",
65+
swiftFunctionName: swiftFunctionName,
5366
stdlibTypes: translator.swiftStdlibTypes
5467
)
5568

@@ -65,7 +78,7 @@ func assertLoweredFunction(
6578

6679
let cFunction = translator.cdeclToCFunctionLowering(
6780
loweredFunction.cdecl,
68-
cName: "c_\(inputFunction.name.text)"
81+
cName: "c_\(swiftFunctionName)"
6982
)
7083
#expect(
7184
cFunction.description == expectedCFunction,

0 commit comments

Comments
 (0)