skip to Main Content

I’m trying to write a wrapper for the print() function which also includes the current line number. I have something like this:

//  wrapper around print:
    func dbug(_ things: Any...) {
        print("(#line): ",  things.map {"($0)"}.joined(separator: ", "))
    }

//  elsewhere:
    dbug("hello", 23)

The only problem is that the #line literal prints the line number within the function, not the line number where it’s called, which is a bit of a waste of time.

Short of adding #line at the source, is there a way of getting the line number where the function is called?

3

Answers


  1. You could try something like this example code to achieve what you want.

    Example SwiftUI code:

    struct ContentView: View {
        
        var body: some View {
            VStack {
                Button("click me") {
                    dbug("(#line): ","hello", 23) //<--- here
                }
            }
        }
    
        func dbug(_ line: String, _ things: Any...) { //<--- here
            print(line, things.map {"($0)"}.joined(separator: ", "))
        }
    }
    
    Login or Signup to reply.
  2. To get rid of passing #line as parameter to dbug function from @workingdog support Ukraine’s answer, you can use Swift Macro. Here is an example:

    @freestanding(expression)
    public macro dbug(_ value: Any...) -> Void = #externalMacro(module: "MyMacroMacros", type: "DbugMacro")
    
    public func dbug(_ line: String, _ things: Any...) {
        print("(line): ",  things.map {"($0)"}.joined(separator: ", "))
    }
    
    public struct DbugMacro: ExpressionMacro {
        public static func expansion(
            of node: some FreestandingMacroExpansionSyntax,
            in context: some MacroExpansionContext
        ) -> ExprSyntax {
            return "dbug("\(#line): ", (node.arguments))"
        }
            
    }
    
    @main
    struct MyMacroPlugin: CompilerPlugin {
        let providingMacros: [Macro.Type] = [
            DbugMacro.self,
        ]
    }
    

    Now you only have to call #dbug("hello", 23)

    Login or Signup to reply.
  3. Instead of using the #line macro in the function body, add an extra parameter and use #line as the default value of that parameter.

    func dbug(_ things: Any..., line: UInt = #line) {
        print("(line): ",  things.map {"($0)"}.joined(separator: ", "))
    }
    
    // usage:
    dbug("hello", "something else") // don't pass the 'line' parameter!
    

    Then this works out of the box, because macros that are used as default values expand on the caller’s side.

    You can see the same pattern used in many built-in functions, like preconditionFailure, fatalError, etc.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search