Skip to content

Commit e15f429

Browse files
committed
[Swiftify] Extract core of _SwiftifyImport (NFCI)
This refactors _SwiftifyImport in preparation of _SwiftifyImportProtocol, so that they can share a common core implementation.
1 parent d653b0c commit e15f429

File tree

1 file changed

+122
-106
lines changed

1 file changed

+122
-106
lines changed

lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift

Lines changed: 122 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,11 +1237,10 @@ func parseCxxSpansInSignature(
12371237
}
12381238

12391239
func parseMacroParam(
1240-
_ paramAST: LabeledExprSyntax, _ signature: FunctionSignatureSyntax, _ rewriter: CountExprRewriter,
1240+
_ paramExpr: ExprSyntax, _ signature: FunctionSignatureSyntax, _ rewriter: CountExprRewriter,
12411241
nonescapingPointers: inout Set<Int>,
12421242
lifetimeDependencies: inout [SwiftifyExpr: [LifetimeDependence]]
12431243
) throws -> ParamInfo? {
1244-
let paramExpr = paramAST.expression
12451244
guard let enumConstructorExpr = paramExpr.as(FunctionCallExprSyntax.self) else {
12461245
throw DiagnosticError(
12471246
"expected _SwiftifyInfo enum literal as argument, got '\(paramExpr)'", node: paramExpr)
@@ -1567,6 +1566,121 @@ func deconstructFunction(_ declaration: some DeclSyntaxProtocol) throws -> Funct
15671566
throw DiagnosticError("@_SwiftifyImport only works on functions and initializers", node: declaration)
15681567
}
15691568

1569+
func constructOverloadFunction(forDecl declaration: some DeclSyntaxProtocol, leadingTrivia: Trivia,
1570+
args arguments: [ExprSyntax], spanAvailability: String?,
1571+
typeMappings: [String: String]?) throws -> DeclSyntax {
1572+
let origFuncComponents = try deconstructFunction(declaration)
1573+
let (funcComponents, rewriter) = renameParameterNamesIfNeeded(origFuncComponents)
1574+
1575+
var nonescapingPointers = Set<Int>()
1576+
var lifetimeDependencies: [SwiftifyExpr: [LifetimeDependence]] = [:]
1577+
var parsedArgs = try arguments.compactMap {
1578+
try parseMacroParam(
1579+
$0, funcComponents.signature, rewriter, nonescapingPointers: &nonescapingPointers,
1580+
lifetimeDependencies: &lifetimeDependencies)
1581+
}
1582+
parsedArgs.append(
1583+
contentsOf: try parseCxxSpansInSignature(funcComponents.signature, typeMappings))
1584+
setNonescapingPointers(&parsedArgs, nonescapingPointers)
1585+
setLifetimeDependencies(&parsedArgs, lifetimeDependencies)
1586+
// We only transform non-escaping spans.
1587+
parsedArgs = parsedArgs.filter {
1588+
if let cxxSpanArg = $0 as? CxxSpan {
1589+
return cxxSpanArg.nonescaping || cxxSpanArg.pointerIndex == .return
1590+
} else {
1591+
return true
1592+
}
1593+
}
1594+
try checkArgs(parsedArgs, funcComponents)
1595+
parsedArgs.sort { a, b in
1596+
// make sure return value cast to Span happens last so that withUnsafeBufferPointer
1597+
// doesn't return a ~Escapable type
1598+
if a.pointerIndex != .return && b.pointerIndex == .return {
1599+
return true
1600+
}
1601+
if a.pointerIndex == .return && b.pointerIndex != .return {
1602+
return false
1603+
}
1604+
return paramOrReturnIndex(a.pointerIndex) < paramOrReturnIndex(b.pointerIndex)
1605+
}
1606+
let baseBuilder = FunctionCallBuilder(funcComponents)
1607+
1608+
let builder: BoundsCheckedThunkBuilder = parsedArgs.reduce(
1609+
baseBuilder,
1610+
{ (prev, parsedArg) in
1611+
parsedArg.getBoundsCheckedThunkBuilder(prev, funcComponents)
1612+
})
1613+
let newSignature = try builder.buildFunctionSignature([:], nil)
1614+
var eliminatedArgs = Set<Int>()
1615+
let basicChecks = try builder.buildBasicBoundsChecks(&eliminatedArgs)
1616+
let compoundChecks = try builder.buildCompoundBoundsChecks()
1617+
let checks = (basicChecks + compoundChecks).map { e in
1618+
CodeBlockItemSyntax(leadingTrivia: "\n", item: e)
1619+
}
1620+
let call: CodeBlockItemSyntax =
1621+
if declaration.is(InitializerDeclSyntax.self) {
1622+
CodeBlockItemSyntax(
1623+
item: CodeBlockItemSyntax.Item(
1624+
try builder.buildFunctionCall([:])))
1625+
} else {
1626+
CodeBlockItemSyntax(
1627+
item: CodeBlockItemSyntax.Item(
1628+
ReturnStmtSyntax(
1629+
returnKeyword: .keyword(.return, trailingTrivia: " "),
1630+
expression: try builder.buildFunctionCall([:]))))
1631+
}
1632+
let body = CodeBlockSyntax(statements: CodeBlockItemListSyntax(checks + [call]))
1633+
let returnLifetimeAttribute = getReturnLifetimeAttribute(funcComponents, lifetimeDependencies)
1634+
let lifetimeAttrs =
1635+
returnLifetimeAttribute + paramLifetimeAttributes(newSignature, funcComponents.attributes)
1636+
let availabilityAttr = try getAvailability(newSignature, spanAvailability)
1637+
let disfavoredOverload: [AttributeListSyntax.Element] =
1638+
[
1639+
.attribute(
1640+
AttributeSyntax(
1641+
atSign: .atSignToken(),
1642+
attributeName: IdentifierTypeSyntax(name: "_disfavoredOverload")))
1643+
]
1644+
let attributes =
1645+
funcComponents.attributes.filter { e in
1646+
switch e {
1647+
case .attribute(let attr):
1648+
// don't apply this macro recursively, and avoid dupe _alwaysEmitIntoClient
1649+
let name = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text
1650+
return name == nil || (name != "_SwiftifyImport" && name != "_alwaysEmitIntoClient")
1651+
default: return true
1652+
}
1653+
} + [
1654+
.attribute(
1655+
AttributeSyntax(
1656+
atSign: .atSignToken(),
1657+
attributeName: IdentifierTypeSyntax(name: "_alwaysEmitIntoClient")))
1658+
]
1659+
+ availabilityAttr
1660+
+ lifetimeAttrs
1661+
+ disfavoredOverload
1662+
let trivia =
1663+
leadingTrivia + .docLineComment("/// This is an auto-generated wrapper for safer interop\n")
1664+
if let origFuncDecl = declaration.as(FunctionDeclSyntax.self) {
1665+
return DeclSyntax(
1666+
origFuncDecl
1667+
.with(\.signature, newSignature)
1668+
.with(\.body, body)
1669+
.with(\.attributes, AttributeListSyntax(attributes))
1670+
.with(\.leadingTrivia, trivia))
1671+
}
1672+
if let origInitDecl = declaration.as(InitializerDeclSyntax.self) {
1673+
return DeclSyntax(
1674+
origInitDecl
1675+
.with(\.signature, newSignature)
1676+
.with(\.body, body)
1677+
.with(\.attributes, AttributeListSyntax(attributes))
1678+
.with(\.leadingTrivia, trivia))
1679+
}
1680+
throw DiagnosticError(
1681+
"Expected function decl or initializer decl, found: \(declaration.kind)", node: declaration)
1682+
}
1683+
15701684
/// A macro that adds safe(r) wrappers for functions with unsafe pointer types.
15711685
/// Depends on bounds, escapability and lifetime information for each pointer.
15721686
/// Intended to map to C attributes like __counted_by, __ended_by and __no_escape,
@@ -1580,9 +1694,6 @@ public struct SwiftifyImportMacro: PeerMacro {
15801694
in context: some MacroExpansionContext
15811695
) throws -> [DeclSyntax] {
15821696
do {
1583-
let origFuncComponents = try deconstructFunction(declaration)
1584-
let (funcComponents, rewriter) = renameParameterNamesIfNeeded(origFuncComponents)
1585-
15861697
let argumentList = node.arguments!.as(LabeledExprListSyntax.self)!
15871698
var arguments = [LabeledExprSyntax](argumentList)
15881699
let typeMappings = try parseTypeMappingParam(arguments.last)
@@ -1593,107 +1704,12 @@ public struct SwiftifyImportMacro: PeerMacro {
15931704
if spanAvailability != nil {
15941705
arguments = arguments.dropLast()
15951706
}
1596-
var nonescapingPointers = Set<Int>()
1597-
var lifetimeDependencies: [SwiftifyExpr: [LifetimeDependence]] = [:]
1598-
var parsedArgs = try arguments.compactMap {
1599-
try parseMacroParam(
1600-
$0, funcComponents.signature, rewriter, nonescapingPointers: &nonescapingPointers,
1601-
lifetimeDependencies: &lifetimeDependencies)
1602-
}
1603-
parsedArgs.append(contentsOf: try parseCxxSpansInSignature(funcComponents.signature, typeMappings))
1604-
setNonescapingPointers(&parsedArgs, nonescapingPointers)
1605-
setLifetimeDependencies(&parsedArgs, lifetimeDependencies)
1606-
// We only transform non-escaping spans.
1607-
parsedArgs = parsedArgs.filter {
1608-
if let cxxSpanArg = $0 as? CxxSpan {
1609-
return cxxSpanArg.nonescaping || cxxSpanArg.pointerIndex == .return
1610-
} else {
1611-
return true
1612-
}
1613-
}
1614-
try checkArgs(parsedArgs, funcComponents)
1615-
parsedArgs.sort { a, b in
1616-
// make sure return value cast to Span happens last so that withUnsafeBufferPointer
1617-
// doesn't return a ~Escapable type
1618-
if a.pointerIndex != .return && b.pointerIndex == .return {
1619-
return true
1620-
}
1621-
if a.pointerIndex == .return && b.pointerIndex != .return {
1622-
return false
1623-
}
1624-
return paramOrReturnIndex(a.pointerIndex) < paramOrReturnIndex(b.pointerIndex)
1625-
}
1626-
let baseBuilder = FunctionCallBuilder(funcComponents)
1627-
1628-
let builder: BoundsCheckedThunkBuilder = parsedArgs.reduce(
1629-
baseBuilder,
1630-
{ (prev, parsedArg) in
1631-
parsedArg.getBoundsCheckedThunkBuilder(prev, funcComponents)
1632-
})
1633-
let newSignature = try builder.buildFunctionSignature([:], nil)
1634-
var eliminatedArgs = Set<Int>()
1635-
let basicChecks = try builder.buildBasicBoundsChecks(&eliminatedArgs)
1636-
let compoundChecks = try builder.buildCompoundBoundsChecks()
1637-
let checks = (basicChecks + compoundChecks).map { e in
1638-
CodeBlockItemSyntax(leadingTrivia: "\n", item: e)
1639-
}
1640-
var call : CodeBlockItemSyntax
1641-
if declaration.is(InitializerDeclSyntax.self) {
1642-
call = CodeBlockItemSyntax(
1643-
item: CodeBlockItemSyntax.Item(
1644-
try builder.buildFunctionCall([:])))
1645-
} else {
1646-
call = CodeBlockItemSyntax(
1647-
item: CodeBlockItemSyntax.Item(
1648-
ReturnStmtSyntax(
1649-
returnKeyword: .keyword(.return, trailingTrivia: " "),
1650-
expression: try builder.buildFunctionCall([:]))))
1651-
}
1652-
let body = CodeBlockSyntax(statements: CodeBlockItemListSyntax(checks + [call]))
1653-
let returnLifetimeAttribute = getReturnLifetimeAttribute(funcComponents, lifetimeDependencies)
1654-
let lifetimeAttrs =
1655-
returnLifetimeAttribute + paramLifetimeAttributes(newSignature, funcComponents.attributes)
1656-
let availabilityAttr = try getAvailability(newSignature, spanAvailability)
1657-
let disfavoredOverload: [AttributeListSyntax.Element] =
1658-
[
1659-
.attribute(
1660-
AttributeSyntax(
1661-
atSign: .atSignToken(),
1662-
attributeName: IdentifierTypeSyntax(name: "_disfavoredOverload")))
1663-
]
1664-
let attributes = funcComponents.attributes.filter { e in
1665-
switch e {
1666-
case .attribute(let attr):
1667-
// don't apply this macro recursively, and avoid dupe _alwaysEmitIntoClient
1668-
let name = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text
1669-
return name == nil || (name != "_SwiftifyImport" && name != "_alwaysEmitIntoClient")
1670-
default: return true
1671-
}
1672-
} + [
1673-
.attribute(
1674-
AttributeSyntax(
1675-
atSign: .atSignToken(),
1676-
attributeName: IdentifierTypeSyntax(name: "_alwaysEmitIntoClient")))
1677-
]
1678-
+ availabilityAttr
1679-
+ lifetimeAttrs
1680-
+ disfavoredOverload
1681-
let trivia = node.leadingTrivia + .docLineComment("/// This is an auto-generated wrapper for safer interop\n")
1682-
if let origFuncDecl = declaration.as(FunctionDeclSyntax.self) {
1683-
return [DeclSyntax(origFuncDecl
1684-
.with(\.signature, newSignature)
1685-
.with(\.body, body)
1686-
.with(\.attributes, AttributeListSyntax(attributes))
1687-
.with(\.leadingTrivia, trivia))]
1688-
}
1689-
if let origInitDecl = declaration.as(InitializerDeclSyntax.self) {
1690-
return [DeclSyntax(origInitDecl
1691-
.with(\.signature, newSignature)
1692-
.with(\.body, body)
1693-
.with(\.attributes, AttributeListSyntax(attributes))
1694-
.with(\.leadingTrivia, trivia))]
1695-
}
1696-
return []
1707+
let args = arguments.map { $0.expression }
1708+
return [
1709+
try constructOverloadFunction(
1710+
forDecl: declaration, leadingTrivia: node.leadingTrivia, args: args,
1711+
spanAvailability: spanAvailability,
1712+
typeMappings: typeMappings)]
16971713
} catch let error as DiagnosticError {
16981714
context.diagnose(
16991715
Diagnostic(

0 commit comments

Comments
 (0)