使用具有两个选项的nil-coalescing运算符时,类型推断失败

时间:2022-01-20 17:04:01

We are trying to figure whether this is a bug in Swift or us misusing generics, optionals, type inference and/or nil coalescing operator.

我们试图确定这是否是Swift中的错误或我们滥用泛型,选项,类型推断和/或零合并运算符。

Our framework contains some code for parsing dictionaries into models and we've hit a problem with optional properties with default values.

我们的框架包含一些用于将字典解析为模型的代码,并且我们遇到了具有默认值的可选属性的问题。

We have a protocol SomeProtocol and two generic functions defined in a protocol extension:

我们有协议SomeProtocol和协议扩展中定义的两个通用函数:

mapped<T>(...) -> T?
mapped<T : SomeProtocol>(...) -> T?

Our structs and classes adhere to this protocol and then parse their properties inside an init function required by the protocol.

我们的结构和类遵循此协议,然后在协议所需的init函数内解析它们的属性。

Inside the init(...) function we try to set a value of the property someNumber like this:

在init(...)函数内部,我们尝试设置someNumber属性的值,如下所示:

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

The dictionary of course contains the actual value for key someNumber. However, this will always fail and the actual value will never get returned from the mapped() function.

当然字典包含key someNumber的实际值。但是,这将始终失败,并且实际值将永远不会从mapped()函数返回。

Either commenting out the second generic function or force downcasting the value on the rhs of the assignment will fix this issue, but we think this should work the way it currently is written.

注释掉第二个泛型函数或者强制向下转换赋值的rhs上的值将解决这个问题,但我们认为这应该按照当前编写的方式工作。


Below is a complete code snippet demonstrating the issue, along with two options that (temporarily) fix the issue labeled OPTION 1 and OPTION 2 in the code:

下面是一个完整的代码片段,演示了该问题,以及两个选项(暂时)修复了代码中标记为OPTION 1和OPTION 2的问题:

import Foundation

// Some protocol

protocol SomeProtocol {
    init(dictionary: NSDictionary?)
}

extension SomeProtocol {
    func mapped<T>(dictionary: NSDictionary?, key: String) -> T? {
        guard let dictionary = dictionary else {
            return nil
        }

        let source = dictionary[key]
        switch source {

        case is T:
            return source as? T

        default:
            break
        }

        return nil
    }

    // ---
    // OPTION 1: Commenting out this makes it work
    // ---

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

// Some struct

struct SomeStruct {
    var someNumber: Double? = 0.0
}

extension SomeStruct: SomeProtocol {
    init(dictionary: NSDictionary?) {
        someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber

        // OPTION 2: Writing this makes it work
        // someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber!
    }
}

// Test code

let test = SomeStruct(dictionary: NSDictionary(object: 1234.4567, forKey: "someNumber"))
if test.someNumber == 1234.4567 {
    print("success \(test.someNumber!)")
} else {
    print("failure \(test.someNumber)")
}

Please note, that this is an example which misses the actual implementations of the mapped functions, but the outcome is identical and for the sake of this question the code should be sufficient.

请注意,这是一个错过映射函数的实际实现的示例,但结果是相同的,并且为了这个问题,代码应该足够了。


EDIT: I had reported this issue a while back and now it was marked as fixed, so hopefully this shouldn't happen anymore in Swift 3.
https://bugs.swift.org/browse/SR-574

编辑:我曾经报告过这个问题,现在它被标记为已修复,所以希望这不应该在Swift 3中发生.https://bugs.swift.org/browse/SR-574

1 个解决方案

#1


7  

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??).

你给编译器提供了太多选项,它选错了(至少不是你想要的那个)。问题是每个T都可以简单地提升到T ?,包括T? (升至T ??)。

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

Wow. Such types. So Optional. :D

哇。这种类型。所以可选。 :d

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

那么Swift如何开始解决这个问题呢。好吧,someNumber是Double?,所以它试图把它变成:

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.

要做到这一点,T必须是Double?。是Double?:SomeProtocol?不。继续。

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

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

这有用吗?当然! T可以加倍吗?我们回双?一切都解决了。

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.)

小心这么多Optionals。 someNumber真的必须是可选的吗?这些东西应该扔掉吗? (我不是建议抛出是可选问题的一般解决方法,但至少这个问题让你有时间考虑这是否真的是一个错误条件。)

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:

在映射方式中,以Swift中的返回值排序参数化几乎总是一个坏主意。这往往是Swift中的一个真正的混乱(或任何具有大量类型推断的通用语言,但当涉及Optionals时,它在Swift中真的会爆炸)。类型参数通常应出现在参数中。如果您尝试以下方法,您会看到问题:

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.

它无法推断出x的类型。这不是一种反模式,有时麻烦是值得的(并且公平地说,你正在解决的问题可能是其中一种情况),但如果可以,就要避免它。

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.

编辑:Dominik提出了一个非常好的问题,即当删除映射的约束版本时,为什么这种行为会有所不同。我不知道。显然,类型匹配引擎以一个不同的顺序检查有效类型,具体取决于映射的通用方式。你可以通过将print(T.self)添加到映射的 来看到这一点。这可能被认为是编译器中的错误。

#1


7  

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??).

你给编译器提供了太多选项,它选错了(至少不是你想要的那个)。问题是每个T都可以简单地提升到T ?,包括T? (升至T ??)。

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

Wow. Such types. So Optional. :D

哇。这种类型。所以可选。 :d

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

那么Swift如何开始解决这个问题呢。好吧,someNumber是Double?,所以它试图把它变成:

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.

要做到这一点,T必须是Double?。是Double?:SomeProtocol?不。继续。

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

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

这有用吗?当然! T可以加倍吗?我们回双?一切都解决了。

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.)

小心这么多Optionals。 someNumber真的必须是可选的吗?这些东西应该扔掉吗? (我不是建议抛出是可选问题的一般解决方法,但至少这个问题让你有时间考虑这是否真的是一个错误条件。)

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:

在映射方式中,以Swift中的返回值排序参数化几乎总是一个坏主意。这往往是Swift中的一个真正的混乱(或任何具有大量类型推断的通用语言,但当涉及Optionals时,它在Swift中真的会爆炸)。类型参数通常应出现在参数中。如果您尝试以下方法,您会看到问题:

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.

它无法推断出x的类型。这不是一种反模式,有时麻烦是值得的(并且公平地说,你正在解决的问题可能是其中一种情况),但如果可以,就要避免它。

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.

编辑:Dominik提出了一个非常好的问题,即当删除映射的约束版本时,为什么这种行为会有所不同。我不知道。显然,类型匹配引擎以一个不同的顺序检查有效类型,具体取决于映射的通用方式。你可以通过将print(T.self)添加到映射的 来看到这一点。这可能被认为是编译器中的错误。