- Pitch: Variadic #withUnsafePointer(to:body:) macro

Sometimes, when using legacy C APIs, you need many UnsafePointers to various values; especially when working with CoreFoundation style APIs. Currently, you might write something like this.

withUnsafePointer(to: foo) { foo in
  withUnsafePointer(to: bar) { bar in
    withUnsafePointer(to: baz) { baz in
      fooBar(foo, bar, baz)
    }
  }
}

This amount of nesting and indentation is ugly and can be difficult to work with. A variadic macro could expand to the above code, but look like this.

#withUnsafePointer(to: foo, bar, baz) { foo, bar, baz in 
  fooBar(foo, bar, baz)
}
2 Likes

This is a sample implementation I made.

public struct WithUnsafePointerMacro: ExpressionMacro {
    public enum Diagnostics: String, Error, DiagnosticMessage {
        public var message: String {
            switch self {
                case .didNotProvideTrailingClosure:
                    return "#withUnsafePointer(to:body:) requires a trailing closure"
                case .closureNeedsExplicitInput:
                    return "#withUnsafePointer(to:body:) requires an explicit closure parameter name for each closure parameter."
            }
        }
        
        public var diagnosticID: SwiftDiagnostics.MessageID {
            MessageID(domain: "WithUnsafePointerMacro", id: rawValue)
        }
        
        public var severity: DiagnosticSeverity { .error }
        
        case didNotProvideTrailingClosure
        case closureNeedsExplicitInput
        
        func error(in context: some MacroExpansionContext, node: Syntax) -> Self {
            context.diagnose(Diagnostic(node: node, message: self))
            return self
        }
    }
    
    public static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax {
        guard let closure = node.trailingClosure else {
            throw Diagnostics.didNotProvideTrailingClosure.error(in: context, node: Syntax(node))
        }
        
        guard let input = closure.signature?.input?.as(ClosureParamListSyntax.self) else {
            if node.argumentList.isEmpty {
                return "\(closure)()"
            } else {
                throw Diagnostics.closureNeedsExplicitInput.error(in: context, node: Syntax(closure))
            }
        }
        
        var parameterNames = Array(input.map { $0.name }.reversed())
        var parameterValues = Array(node.argumentList.map { $0.expression }.reversed())
        
        let firstName = parameterNames.removeFirst()
        let firstValue = parameterValues.removeFirst()
        
        var returnExpr: ExprSyntax = """
        withUnsafePointer(to: \(firstValue)) { \(firstName.trimmed) in
            \(closure.statements.trimmed)
        }
        """
        
        for (name, value) in zip(parameterNames, parameterValues) {
            returnExpr = """
            withUnsafePointer(to: \(value)) { \(name.trimmed) in
                \(returnExpr)
            }
            """
        }
        
        return returnExpr
    }
}
1 Like

Alternatively, we could just wait until variadic generics are ironed out, and write this as a function instead.

func withUnsafePointer<each T, Output>(to pointee: repeat each T, body: (repeat UnsafePointer<each T>) throws -> Output) rethrows -> Output {
  let pointer = (repeat UnsafeMutablePointer<(each T)>.allocate(capacity: 1))
  defer { repeat ((each pointer).deallocate()) }

  repeat ((each pointer).initialize(to: (each pointee)))
  return try body(repeat each pointer)
}

However, currently this does not compile.
You could also make this better using a tuple and getting a pointer to that, but it's still not ideal.

withUnsafePointer(to: (foo, bar, baz)) { pointer in
  let foo = pointer.pointer(to: \.0)
  let bar = pointer.pointer(to: \.1)
  let baz = pointer.pointer(to: \.2)
  
  return fooBar(foo, bar, baz)
}
5 Likes

As a practical workaround maybe just these overloads until a better solution is available:

typealias UP = UnsafePointer

func withUnsafePointer<R, T1, T2>(to v1: T1, _ v2: T2, _ body: (UP<T1>, UP<T2>) throws -> R) rethrows -> R {
    try withUnsafePointer(to: v1) { p1 in
        try withUnsafePointer(to: v2) { p2 in
            try body(p1, p2)
        }
    }
}

func withUnsafePointer<R, T1, T2, T3>(to v1: T1, _ v2: T2, _ v3: T3, _ body: (UP<T1>, UP<T2>, UP<T3>) throws -> R) rethrows -> R {
    .....
}
// etc

Like how @ViewBuilder has 10 overloads?

3 Likes

I believe that lack of compilation might be a bug? This seems to type-check correctly, but crashes the compiler.

Crashing code
func withUnsafePointers<each T, R>(
  to values: repeat each T,
  operation: (repeat (UnsafePointer<each T>)) throws -> R
) rethrows -> R {
  func makePointer<Value>(to value: Value) -> UnsafePointer<Value> {
    let pointer = UnsafeMutablePointer<Value>.allocate(capacity: 1)
    pointer.initialize(to: value)
    return UnsafePointer(pointer)
  }

  let pointers = repeat makePointer(to: each values)
  defer { repeat (each pointers).deallocate() }
  return try operation(repeat each pointers)
}

let x = withUnsafePointers(to: 4, "hello", 9.8 as Float) { intptr, strptr, fltptr in
  makeThingWithPointers(int: intptr, str: strptr, flt: fltptr)
}

func makeThingWithPointers(
  int: UnsafePointer<Int>,
  str: UnsafePointer<String>,
  flt: UnsafePointer<Float>
) -> String {
  ""
}
Crash log
SwiftCompile normal arm64 Compiling\ VariadicPointerTest.swift /Users/jbash/repos/Playgrounds/VariadicPointerTest/Sources/VariadicPointerTest/VariadicPointerTest.swift (in target 'VariadicPointerTest' from project 'VariadicPointerTest')
    cd /Users/jbash/repos/Playgrounds/VariadicPointerTest
    builtin-swiftTaskExecution -- /Applications/Xcode15-beta1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-frontend -frontend -c -primary-file /Users/jbash/repos/Playgrounds/VariadicPointerTest/Sources/VariadicPointerTest/VariadicPointerTest.swift -emit-dependencies-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.d -emit-const-values-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.swiftconstvalues -emit-reference-dependencies-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.swiftdeps -serialize-diagnostics-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.dia -target arm64-apple-ios12.0 -Xllvm -aarch64-use-tbi -enable-objc-interop -sdk /Applications/Xcode15-beta1.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk -I /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Products/Debug-iphoneos -I /Applications/Xcode15-beta1.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/lib -F /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Products/Debug-iphoneos -F /Applications/Xcode15-beta1.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Frameworks -F /Applications/Xcode15-beta1.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/Developer/Library/Frameworks -no-color-diagnostics -enable-testing -g -module-cache-path /Users/jbash/Library/Developer/Xcode/DerivedData/ModuleCache.noindex -swift-version 5 -enforce-exclusivity\=checked -Onone -D SWIFT_PACKAGE -D DEBUG -D Xcode -serialize-debugging-options -const-gather-protocols-file /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest_const_extract_protocols.json -empty-abi-descriptor -plugin-path /Applications/Xcode15-beta1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/plugins -plugin-path /Applications/Xcode15-beta1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/local/lib/swift/host/plugins -validate-clang-modules-once -clang-build-session-file /Users/jbash/Library/Developer/Xcode/DerivedData/ModuleCache.noindex/Session.modulevalidation -Xcc -working-directory -Xcc /Users/jbash/repos/Playgrounds/VariadicPointerTest -resource-dir /Applications/Xcode15-beta1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift -enable-anonymous-context-mangled-names -Xcc -ivfsstatcache -Xcc /Users/jbash/Library/Developer/Xcode/DerivedData/SDKStatCaches.noindex/iphoneos17.0-21A5248u-b23d71c8e10368373ae3270184a0e6b4.sdkstatcache -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/swift-overrides.hmap -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Products/Debug-iphoneos/include -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/DerivedSources-normal/arm64 -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/DerivedSources/arm64 -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/DerivedSources -Xcc -DSWIFT_PACKAGE -Xcc -DDEBUG\=1 -module-name VariadicPointerTest -frontend-parseable-output -disable-clang-spi -target-sdk-version 17.0 -target-sdk-name iphoneos17.0 -parse-as-library -o /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.o -index-unit-output-path /VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.o -index-store-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Index.noindex/DataStore -index-system-modules

Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Stack dump:
0.	Program arguments: /Applications/Xcode15-beta1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-frontend -frontend -c -primary-file /Users/jbash/repos/Playgrounds/VariadicPointerTest/Sources/VariadicPointerTest/VariadicPointerTest.swift -emit-dependencies-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.d -emit-const-values-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.swiftconstvalues -emit-reference-dependencies-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.swiftdeps -serialize-diagnostics-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.dia -target arm64-apple-ios12.0 -Xllvm -aarch64-use-tbi -enable-objc-interop -sdk /Applications/Xcode15-beta1.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk -I /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Products/Debug-iphoneos -I /Applications/Xcode15-beta1.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/lib -F /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Products/Debug-iphoneos -F /Applications/Xcode15-beta1.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Frameworks -F /Applications/Xcode15-beta1.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/Developer/Library/Frameworks -no-color-diagnostics -enable-testing -g -module-cache-path /Users/jbash/Library/Developer/Xcode/DerivedData/ModuleCache.noindex -swift-version 5 -enforce-exclusivity=checked -Onone -D SWIFT_PACKAGE -D DEBUG -D Xcode -serialize-debugging-options -const-gather-protocols-file /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest_const_extract_protocols.json -empty-abi-descriptor -plugin-path /Applications/Xcode15-beta1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/plugins -plugin-path /Applications/Xcode15-beta1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/local/lib/swift/host/plugins -validate-clang-modules-once -clang-build-session-file /Users/jbash/Library/Developer/Xcode/DerivedData/ModuleCache.noindex/Session.modulevalidation -Xcc -working-directory -Xcc /Users/jbash/repos/Playgrounds/VariadicPointerTest -resource-dir /Applications/Xcode15-beta1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift -enable-anonymous-context-mangled-names -Xcc -ivfsstatcache -Xcc /Users/jbash/Library/Developer/Xcode/DerivedData/SDKStatCaches.noindex/iphoneos17.0-21A5248u-b23d71c8e10368373ae3270184a0e6b4.sdkstatcache -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/swift-overrides.hmap -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Products/Debug-iphoneos/include -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/DerivedSources-normal/arm64 -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/DerivedSources/arm64 -Xcc -I/Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/DerivedSources -Xcc -DSWIFT_PACKAGE -Xcc -DDEBUG=1 -module-name VariadicPointerTest -frontend-parseable-output -disable-clang-spi -target-sdk-version 17.0 -target-sdk-name iphoneos17.0 -parse-as-library -o /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Build/Intermediates.noindex/VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.o -index-unit-output-path /VariadicPointerTest.build/Debug-iphoneos/VariadicPointerTest.build/Objects-normal/arm64/VariadicPointerTest.o -index-store-path /Users/jbash/Library/Developer/Xcode/DerivedData/VariadicPointerTest-fpcjylqvuqqnqwfzfahkauuzfuhj/Index.noindex/DataStore -index-system-modules
1.	Apple Swift version 5.9 (swiftlang-5.9.0.114.6 clang-1500.0.27.1)
2.	Compiling with the current language version
3.	While evaluating request ASTLoweringRequest(Lowering AST to SIL for file "/Users/jbash/repos/Playgrounds/VariadicPointerTest/Sources/VariadicPointerTest/VariadicPointerTest.swift")
4.	While silgen emitFunction SIL function "@$s19VariadicPointerTest18withUnsafePointers2to9operationq_xxQp_q_SPyxGxQpKXEtKRvzr0_lF".
 for 'withUnsafePointers(to:operation:)' (at /Users/jbash/repos/Playgrounds/VariadicPointerTest/Sources/VariadicPointerTest/VariadicPointerTest.swift:1:8)
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0  swift-frontend           0x0000000105e6391c llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56
1  swift-frontend           0x0000000105e62ae0 llvm::sys::RunSignalHandlers() + 112
2  swift-frontend           0x0000000105e63f1c SignalHandler(int) + 352
3  libsystem_platform.dylib 0x00000001a7832a84 _sigtramp + 56
4  swift-frontend           0x0000000100fdd854 void llvm::function_ref<void (swift::SILValue, swift::SILValue, swift::SILValue)>::callback_fn<(anonymous namespace)::RValueEmitter::visitPackExpansionExpr(swift::PackExpansionExpr*, swift::Lowering::SGFContext)::$_7>(long, swift::SILValue, swift::SILValue, swift::SILValue) + 96
5  swift-frontend           0x000000010100f2e0 swift::Lowering::SILGenFunction::emitDynamicPackLoop(swift::SILLocation, swift::CanTypeWrapper<swift::PackType>, unsigned int, swift::SILValue, swift::SILValue, swift::GenericEnvironment*, bool, llvm::function_ref<void (swift::SILValue, swift::SILValue, swift::SILValue)>) + 1340
6  swift-frontend           0x0000000100fce2e4 (anonymous namespace)::RValueEmitter::visitPackExpansionExpr(swift::PackExpansionExpr*, swift::Lowering::SGFContext) + 332
7  swift-frontend           0x0000000100fb8f50 swift::Lowering::SILGenFunction::emitExprInto(swift::Expr*, swift::Lowering::Initialization*, llvm::Optional<swift::SILLocation>) + 128
8  swift-frontend           0x0000000100fa3cd8 swift::Lowering::SILGenFunction::emitPatternBinding(swift::PatternBindingDecl*, unsigned int, bool) + 1984
9  swift-frontend           0x0000000100f47114 swift::ASTVisitor<swift::Lowering::SILGenFunction, void, void, void, void, void, void>::visit(swift::Decl*) + 140
10 swift-frontend           0x0000000101044408 swift::ASTVisitor<(anonymous namespace)::StmtEmitter, void, void, void, void, void, void>::visit(swift::Stmt*) + 4704
11 swift-frontend           0x0000000100fe8c9c swift::Lowering::SILGenFunction::emitFunction(swift::FuncDecl*) + 632
12 swift-frontend           0x0000000100f436d0 swift::Lowering::SILGenModule::emitFunctionDefinition(swift::SILDeclRef, swift::SILFunction*) + 7640
13 swift-frontend           0x0000000100f4502c emitOrDelayFunction(swift::Lowering::SILGenModule&, swift::SILDeclRef) + 168
14 swift-frontend           0x0000000100f418e4 swift::Lowering::SILGenModule::emitFunction(swift::FuncDecl*) + 292
15 swift-frontend           0x0000000100f47cec swift::ASTLoweringRequest::evaluate(swift::Evaluator&, swift::ASTLoweringDescriptor) const + 2940
16 swift-frontend           0x0000000101042de4 swift::SimpleRequest<swift::ASTLoweringRequest, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>> (swift::ASTLoweringDescriptor), (swift::RequestFlags)9>::evaluateRequest(swift::ASTLoweringRequest const&, swift::Evaluator&) + 200
17 swift-frontend           0x0000000100f4b8ec llvm::Expected<swift::ASTLoweringRequest::OutputType> swift::Evaluator::getResultUncached<swift::ASTLoweringRequest>(swift::ASTLoweringRequest const&) + 620
18 swift-frontend           0x0000000100894d04 performCompile(swift::CompilerInstance&, int&, swift::FrontendObserver*) + 1680
19 swift-frontend           0x00000001008931e0 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 4216
20 swift-frontend           0x0000000100858a58 swift::mainEntry(int, char const**) + 4112
21 dyld                     0x00000001a74abf28 start + 2236
Command SwiftCompile failed with a nonzero exit code

Yeah, I think the compiler functionality for variadic generics aren't fully implemented yet.