Home > Enterprise >  Swift macros when creating Array from literals
Swift macros when creating Array from literals

Time:01-31

For the code below:

let services: [MyServices] = [
    MyService(),
    #if DEBUG
    DebugService(),
    #endif
]

I get compiler error:

expression failed to parse: error: MyPlayground.playground:375:5: error: expected expression in container literal #if DEBUG ^

error: MyPlayground.playground:376:19: error: consecutive statements on a line must be separated by ';' DebugService(), ^ ;

error: MyPlayground.playground:376:19: error: expected expression DebugService(), ^

error: MyPlayground.playground:378:1: error: expected expression ]

But the same code works in Objective-C

NSArray *array = @[
        @"11",
    #if DEBUG
        @"debug",
    #endif
        @"22"
    ];

Is this a compiler bug or expected behaviour ? Thank you.

CodePudding user response:

Generally the body of each clause in an #if ... #else ... #endif compiler directive must surround complete statements.

But with the implementation of SE-308 #if for postfix member expressions in Swift 5.5 this directive has been expanded to be able to surround postfix member expressions.

So with a custom extension

extension Array {
    func appending(_ e: Element) -> Self {
        return self   [e]
    }
}

one can conditionally initialize an array like this:

let services = [
    MyService() ]
#if DEBUG
    .appending(DebugService())
#endif

CodePudding user response:

The #if / #endif constructs are not preprocessor macros like in C. They are compiler directives. They are part of the language.

In C, it runs the preprocessor on your source file, which results in a modified source file. That modified source file gets compiled.

Not so in Swift. In Swift, the compiler parses the source file and attempts to compile it.


Edit:

To understand why what you tried doesn't work, think of the #if / #endif like a regular if statement. Would the following compile?

let debug = true
let services: [MyServices] = [
    MyService(),
    if debug {
        DebugService(),
    }
]

(Answer: No. That's not legal. You can't insert an if statement in the middle of the parameters of an array initializer. Same goes for an #if / #elseif statement.)


You can’t use an #if / #endif block to assemble fragments of a statement together.

You would have to create complete statements in both cases:

#if debug
    let services: [MyServices] = [
        MyService(),
        DebugService(),
    ]
#else
    let services: [MyServices] = [
        MyService()
    ]
#endif

(Or you could make services a var, and append DebugServices() to it inside an #if / #endif.)


Edit #2:

Better yet, use a "postfix" .appending() as in @MartinR's excellent answer.

CodePudding user response:

Swift 5.5

let services: [MyServices] = [
    #if DEBUG
    DebugService()
    #else
    MyService()
    #endif
]
  • Related