Skip to content

[Swiftify] Handle anonymous parameters #81384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,19 @@ protocol ParamInfo: CustomStringConvertible {
) -> BoundsCheckedThunkBuilder
}

func getParamName(_ param: FunctionParameterSyntax, _ paramIndex: Int) -> TokenSyntax {
let name = param.secondName ?? param.firstName
if name.trimmed.text == "_" {
return "_param\(raw: paramIndex)"
}
return name
}

func tryGetParamName(_ funcDecl: FunctionDeclSyntax, _ expr: SwiftifyExpr) -> TokenSyntax? {
switch expr {
case .param(let i):
let funcParam = getParam(funcDecl, i - 1)
return funcParam.secondName ?? funcParam.firstName
return getParamName(funcParam, i - 1)
case .`self`:
return .keyword(.self)
default: return nil
Expand Down Expand Up @@ -419,7 +427,12 @@ struct FunctionCallBuilder: BoundsCheckedThunkBuilder {
// filter out deleted parameters, i.e. ones where argTypes[i] _contains_ nil
return type == nil || type! != nil
}.map { (i: Int, e: FunctionParameterSyntax) in
e.with(\.type, (argTypes[i] ?? e.type)!)
let param = e.with(\.type, (argTypes[i] ?? e.type)!)
let name = param.secondName ?? param.firstName
if name.trimmed.text == "_" {
Comment on lines +430 to +432
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the redundant logic to extract the parameter name and check for _?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good point!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah this is needed for general swift syntax, e.g. if you have func foo(bar: T). If you just blindly do .with(\.secondName, getParamName(param, i)), then you end up with func foo(bar bar: T)). Which I guess is not actually a problem, just redundant. But I also don't think we can end up with that syntax from an imported C function, so that's fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait no that redundancy is actually an error:

   | `- note: in expansion of macro '_SwiftifyImport' on global function 'ptrNamed(ptr:_:)' here
 6 | func ptrNamed(ptr: UnsafePointer<CInt>, _ len: CInt) {
 7 | }
   +--- macro expansion @_SwiftifyImport -------------------------------
   |1 | @_alwaysEmitIntoClient
   |2 | func ptrNamed(ptr ptr: UnsafeBufferPointer<CInt>) {
   |  |               `- error: extraneous duplicate parameter name; 'ptr' already has an argument label
   |3 |     return unsafe ptrNamed(ptr: ptr.baseAddress!, CInt(exactly: ptr.count)!)
   |4 | }
   +--------------------------------------------------------------------

I don't know of any way a C function can import to that syntax, but it doesn't hurt to handle it correctly.

return param.with(\.secondName, getParamName(param, i))
}
return param
}
if let last = newParams.popLast() {
newParams.append(last.with(\.trailingComma, nil))
Expand All @@ -437,7 +450,7 @@ struct FunctionCallBuilder: BoundsCheckedThunkBuilder {
let functionRef = DeclReferenceExprSyntax(baseName: base.name)
let args: [ExprSyntax] = base.signature.parameterClause.parameters.enumerated()
.map { (i: Int, param: FunctionParameterSyntax) in
let name = param.secondName ?? param.firstName
let name = getParamName(param, i)
let declref = DeclReferenceExprSyntax(baseName: name)
return pointerArgs[i] ?? ExprSyntax(declref)
}
Expand Down Expand Up @@ -647,7 +660,7 @@ extension ParamBoundsThunkBuilder {
}

var name: TokenSyntax {
return param.secondName ?? param.firstName
getParamName(param, index)
}
}

Expand Down Expand Up @@ -828,8 +841,7 @@ struct CountedOrSizedPointerThunkBuilder: ParamBoundsThunkBuilder, PointerBounds
}

func castPointerToOpaquePointer(_ baseAddress: ExprSyntax) throws -> ExprSyntax {
let i = try getParameterIndexForParamName(signature.parameterClause.parameters, name)
let type = peelOptionalType(getParam(signature, i).type)
let type = peelOptionalType(getParam(signature, index).type)
if type.canRepresentBasicType(type: OpaquePointer.self) {
return ExprSyntax("OpaquePointer(\(baseAddress))")
}
Expand Down
2 changes: 2 additions & 0 deletions test/Interop/C/swiftify-import/Inputs/counted-by-noescape.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ void nullable(int len, int * __counted_by(len) _Nullable p __noescape);
int * __counted_by(len) __noescape returnPointer(int len);

int * __counted_by(len1) returnLifetimeBound(int len1, int len2, int * __counted_by(len2) p __lifetimebound);

void anonymous(int len, int * __counted_by(len) _Nullable __noescape);
9 changes: 9 additions & 0 deletions test/Interop/C/swiftify-import/counted-by-noescape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import CountedByNoEscapeClang

// CHECK: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(_param1: copy _param1)
// CHECK-NEXT: @_alwaysEmitIntoClient public func anonymous(_ _param1: inout MutableSpan<Int32>?)
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
// CHECK-NEXT: @lifetime(p: copy p)
// CHECK-NEXT: @_alwaysEmitIntoClient public func complexExpr(_ len: Int32, _ offset: Int32, _ p: inout MutableSpan<Int32>)
// CHECK-NEXT: @available(visionOS 1.1, tvOS 12.2, watchOS 5.2, iOS 12.2, macOS 10.14.4, *)
Expand Down Expand Up @@ -102,3 +105,9 @@ public func callSimple(_ p: inout MutableSpan<CInt>) {
public func callSwiftAttr(_ p: inout MutableSpan<CInt>) {
swiftAttr(&p)
}

@lifetime(p: copy p)
@inlinable
public func callAnonymous(_ p: inout MutableSpan<CInt>?) {
anonymous(&p)
}
43 changes: 43 additions & 0 deletions test/Macros/SwiftifyImport/CountedBy/Anonymous.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// REQUIRES: swift_swift_parser

// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -typecheck -plugin-path %swift-plugin-dir -strict-memory-safety -warnings-as-errors -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s

@_SwiftifyImport(.countedBy(pointer: .param(1), count: "len"))
public func myFunc(_: UnsafePointer<CInt>, _ len: CInt) {
}

@_SwiftifyImport(.countedBy(pointer: .param(1), count: "len"))
public func myFunc2(_ p: UnsafePointer<CInt>, _ len: CInt, _: CInt) {
}

@_SwiftifyImport(.countedBy(pointer: .param(1), count: "len"), .nonescaping(pointer: .param(1)))
public func myFunc3(_: UnsafePointer<CInt>, _ len: CInt) {
}

@_SwiftifyImport(.countedBy(pointer: .param(1), count: "len"), .nonescaping(pointer: .param(1)))
public func myFunc4(_: UnsafeMutablePointer<CInt>, _ len: CInt) {
}

// CHECK: @_alwaysEmitIntoClient
// CHECK-NEXT: public func myFunc(_ _param0: UnsafeBufferPointer<CInt>) {
// CHECK-NEXT: return unsafe myFunc(_param0.baseAddress!, CInt(exactly: _param0.count)!)
// CHECK-NEXT: }

// CHECK: @_alwaysEmitIntoClient
// CHECK-NEXT: public func myFunc2(_ p: UnsafeBufferPointer<CInt>, _ _param2: CInt) {
// CHECK-NEXT: return unsafe myFunc2(p.baseAddress!, CInt(exactly: p.count)!, _param2)
// CHECK-NEXT: }

// CHECK: @_alwaysEmitIntoClient
// CHECK-NEXT: public func myFunc3(_ _param0: Span<CInt>) {
// CHECK-NEXT: return unsafe _param0.withUnsafeBufferPointer { __param0Ptr in
// CHECK-NEXT: return unsafe myFunc3(__param0Ptr.baseAddress!, CInt(exactly: __param0Ptr.count)!)
// CHECK-NEXT: }
// CHECK-NEXT: }

// CHECK: @_alwaysEmitIntoClient @lifetime(_param0: copy _param0)
// CHECK-NEXT: public func myFunc4(_ _param0: inout MutableSpan<CInt>) {
// CHECK-NEXT: return unsafe _param0.withUnsafeMutableBufferPointer { __param0Ptr in
// CHECK-NEXT: return unsafe myFunc4(__param0Ptr.baseAddress!, CInt(exactly: __param0Ptr.count)!)
// CHECK-NEXT: }
// CHECK-NEXT: }