Possible bug in Swift?

The following code prints nil although I would expect it to print 0.
I know it could be simplified to work around the bug, but this is already the simplified possible version of a complex code that requires this structure.

print(
	{ () -> Int? in
		{
			{}()
			return { () -> Int? in
				nil
			}()
		}() ?? 0
	}()
	.debugDescription // Not required for reproducing, silences the compiler warning
)

Interesting, if you'll change type annotation in first closure to non-optional () -> Int in it will start working. Also if you remove {}() also prints 0. :thinking:
Aaaaand removing all type annotations works. Also adding { () -> Int? in to second closure helps—an my guess it's where the problem is.

But looking forward for answers, interesting case. :slightly_smiling_face:

Since the outer closure returns Int?, the nil-coalescing operator ?? is inferred to return Int? as well. Thus its right-hand side 0 is promoted to Int? and its left-hand side is inferred to be Int?? (but see note at bottom).

That left-hand side of ?? is the return value of a closure, so the closure is inferred to have type () -> Int??. That closure returns the result of calling an inner closure, which has type () -> Int? and returns nil.

So the innermost closure returns nil as Int? which is the enum value Optional<Int>.none. The middle closure promotes that value to Int?? by wrapping the enum value Int?.none in another layer of optionality, where it becomes Optional<Int?>.some(.none).

The nil-coalescing operator then sees a non-nil value of type Int??, whose value is .some(.none), so it unwraps that value and returns what is inside. The value inside, of course, is Optional<Int>.none, and its debug description is simply nil.

• • •

However the standard library actually contains two different nil-coalescing operators. Both take T? on the left, but on the right one takes T and the other takes T?. The reason why the second one exists is to allow chaining things that each return optional, but because it does exist you can annotate the middle closure to use it.

If you put () -> Int? in at the start of that closure, or as Int? after calling it (just before the ?? operator), then the compiler will use the T? version of ??. This results in the middle closure returning Optional<Int>.none without wrapping it again, so the ?? operator then sees an empty optional and returns its right-hand side, which is Optional<Int>.some(0).

Or, alternatively, you could put as Int after the integer literal 0. This prevents the compiler from promoting it to an optional, and thus the ?? operator has Int on the right so it must have Int? on the left and the closure type gets inferred accordingly.

9 Likes