Type inference fails when using nil-coalescing operator with two optionals

扶醉桌前 提交于 2019-12-05 04:50:25

You've given the compiler too many options, and it's picking the wrong one (at least not the one you wanted). The problem is that every T can be trivially elevated to T?, including T? (elevated to T??).

someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber

Wow. Such types. So Optional. :D

So how does Swift begin to figure this thing out. Well, someNumber is Double?, so it tries to turn this into:

Double? = Double?? ?? Double?

Does that work? Let's look for a generic mapped, starting at the most specific.

func mapped<T where T:SomeProtocol>(dictionary: NSDictionary?, key: String) -> T? {

To make this work, T has to be Double?. Is Double?:SomeProtocol? Nope. Moving on.

func mapped<T>(dictionary: NSDictionary?, key: String) -> T? {

Does this work? Sure! T can be Double? We return Double?? and everything resolves.

So why does this one work?

someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber!

This resolves to:

Double? = Optional(Double? ?? Double)

And then things work the way you think they're supposed to.

Be careful with so many Optionals. Does someNumber really have to be Optional? Should any of these things throw? (I'm not suggesting throw is a general work-around for Optional problems, but at least this problem gives you a moment to consider if this is really an error condition.)

It is almost always a bad idea to type-parameterize exclusively on the return value in Swift the way mapped does. This tends to be a real mess in Swift (or any generic language that has lots of type inference, but it really blows up in Swift when there are Optionals involved). Type parameters should generally appear in the arguments. You'll see the problem if you try something like:

let x = test.mapped(...)

It won't be able to infer the type of x. This isn't an anti-pattern, and sometimes the hassle is worth it (and in fairness, the problem you're solving may be one of those cases), but avoid it if you can.

But it's the Optionals that are killing you.


EDIT: Dominik asks a very good question about why this behaves differently when the constrained version of mapped is removed. I don't know. Obviously the type matching engine checks for valid types in a little different order depending on how many ways mapped is generic. You can see this by adding print(T.self) to mapped<T>. That might be considered a bug in the compiler.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!