`if {} else {} else {}` actually works when combining if expressions with guard

let getter: () -> String? = {
    MainActor.assumeIsolated {
        …
    }
}

guard let text = if Thread.isMainThread {
    getter()
} else {
    DispatchQueue.main.sync(execute: getter)
} else {
    return false
}

Ignore the use-case, just look at the syntax. I would have sworn this was explicitly not allowed, yet Swift 5.10 accepts it without so much as a whimper.

It feels like it probably shouldn't… just a bug / oversight?

Although I could see an argument for this being no worse, ultimately, than having to shove a whole bunch of extra parentheses in there too (which is what I expected the compiler to force me to do, as it usually does).

4 Likes

I don't think that's a bug per se. Consider a simpler example with nesting that shows what going on:

func foo(condition: Bool) -> String {
    guard let text =
        if condition {
            "1"
        } else {
            "2"
        }
    else {
        return "3"
    }
    return text
}

What does look a bug here is that "guard let text = " doesn't check that the following expression is a non-optional String... Ideally it should complain with "RHS expression is not optional".

1 Like

Trailing closures are banned in if but not guard for the same sort of reason: the block immediately after something condition-shaped is usually the body, but for guard there’s an else there to keep it from being two braced blocks right up against each other. So it wouldn’t have been completely unreasonable to require parentheses here. Since it’s now been released without that requirement, though, I don’t know if it’s worth adding it (though formatters or linters might want rules about it).

2 Likes

We can't do parenthesis yet anyway (if expressions are only allowed in certain limited contexts):

let x = (if condition { 1 } else { 2 }) // 🛑 'if' may only be used as expression in return, throw, or as the source of an assignment
2 Likes

Yeah, exactly - I wish if & switch expressions weren't so restricted, as that ironically causes more problems with bad / ugly syntax than it seems to solve. Plus they're so useful sometimes - I particularly do not like that I can't use them inline for arguments of function calls, since they're sometimes much easier to read than the ternary operator.

"Pointless" temporaries / local constants aren't the end of the world usually, but they have knock-on effects like disallowing use of expressions (as opposed to statement blocks), so occasionally what would otherwise have been pretty concise and neat ends up exploded into two or three times more verbage.

Anyway, I was expecting more of a reaction to this double-else situations, but evidently it doesn't bother folks too much (and as @jrose notes it's probably too late to change it anyway), so I'll just not use in my code and move on. :smile:

Probably because we've seen it before ;-)

char* foo(bool a, bool b) {
    if (a) if (b) {
        return "1";
    } else {
        return "2";
    } else {
        return "3";
    }
}
2 Likes

Huh, I've never encountered that before. I wouldn't have guessed that C has this problem too.