Skip to content

Commit 16e71da

Browse files
authored
Merge pull request #84832 from hnrklssn/swiftify-extract-core
[Swiftify] Extract core of _SwiftifyImport (NFCI)
2 parents bae041c + e15f429 commit 16e71da

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
@@ -1247,11 +1247,10 @@ func parseCxxSpansInSignature(
12471247
}
12481248

12491249
func parseMacroParam(
1250-
_ paramAST: LabeledExprSyntax, _ signature: FunctionSignatureSyntax, _ rewriter: CountExprRewriter,
1250+
_ paramExpr: ExprSyntax, _ signature: FunctionSignatureSyntax, _ rewriter: CountExprRewriter,
12511251
nonescapingPointers: inout Set<Int>,
12521252
lifetimeDependencies: inout [SwiftifyExpr: [LifetimeDependence]]
12531253
) throws -> ParamInfo? {
1254-
let paramExpr = paramAST.expression
12551254
guard let enumConstructorExpr = paramExpr.as(FunctionCallExprSyntax.self) else {
12561255
throw DiagnosticError(
12571256
"expected _SwiftifyInfo enum literal as argument, got '\(paramExpr)'", node: paramExpr)
@@ -1577,6 +1576,121 @@ func deconstructFunction(_ declaration: some DeclSyntaxProtocol) throws -> Funct
15771576
throw DiagnosticError("@_SwiftifyImport only works on functions and initializers", node: declaration)
15781577
}
15791578

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

0 commit comments

Comments
 (0)