Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-8856] Optional downcast results in double optional during cast to generic value #51362

Closed
swift-ci opened this issue Sep 26, 2018 · 3 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-8856
Radar None
Original Reporter xardas (JIRA User)
Type Bug
Status Resolved
Resolution Duplicate

Attachment: Download

Environment

Xcode 10 (10A255), macOS 10.13.6, Swift 4.2

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee xardas (JIRA)
Priority Medium

md5: a129003507d833383c8d71413feec3bb

duplicates:

  • SR-8704 Cast from Any? to as? Value where Value == Any produces unexpected result in Xcode 10

Issue Description:

Attempt of optional cast to generic value results in double wrapped optional while same action for explicit type results in simple optional.

Expected result is either optional or compile-time error when trying to return double optional from function returning optional.

See attached project.

let dict = NSDictionary(dictionary: ["x": 10])
let wrapper = DictionaryWrapper<String, Any>(dict)
let value1 = wrapper["x"]
print("Value: " + String(describing: value1))

// prints Optional(Optional(10))
 


let typedWrapper = TypedDictionaryWrapper(dict)
let value2 = typedWrapper["x"]
print("Value: " + String(describing: value2))

// Optional(10)
@hamishknight
Copy link
Collaborator

Implementation of DictionaryWrapper & TypedDictionaryWrapper:

import Foundation

struct DictionaryWrapper<Key, Value> {

    private let getValue: (Key) -> Value?

    init(_ wrapped: NSDictionary) {
        getValue = { return wrapped[$0] as? Value }
    }

    subscript(key: Key) -> Value? {
        return getValue(key)
    }
}

struct TypedDictionaryWrapper {

    private let getValue: (String) -> Int?

    init(_ wrapped: NSDictionary) {
        getValue = { return wrapped[$0] as? Int }
    }

    subscript(key: String) -> Int? {
        return getValue(key)
    }
}

This is due to a behavioural change where the compiler is now more conservative with the unwrapping of an optional value when casting to a generic placeholder type – see https://bugs.swift.org/browse/SR-8704 & https://stackoverflow.com/q/52446097/2976878 for further discussion.

In your case, you get different behaviour because DictionaryWrapper<String, Any> is conditionally casting to Any, whereas TypedDictionaryWrapper is conditionally casting to Int.

In the case of the former, Any can represent the optional value returned from the dictionary's subscript, therefore no unwrapping occurs. However when casting to Int, the value returned from the subscript is unwrapped in order to see if the underlying value can be represented as an Int.

If you change your TypedDictionaryWrapper implementation to cast to Any, you'll see that the new behaviour is more consistent across generic and non-generic contexts, for example:

struct TypedDictionaryWrapper {
  private let getValue: (String) -> Any?

  init(_ wrapped: NSDictionary) {
    getValue = { return wrapped[$0] as? Any }
  }

  subscript(key: String) -> Any? {
    return getValue(key)
  }
}

// ...

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    let dict = NSDictionary(dictionary: ["x": 10])
    let wrapper = DictionaryWrapper<String, Any>(dict)
    let value1 = wrapper["x"]
    print("Value: " + String(describing: value1))
    // in Swift 4, prints: "Value: Optional(10)"
    // in Swift 5, prints: "Value: Optional(Optional(10))"

    let typedWrapper = TypedDictionaryWrapper(dict)
    let value2 = typedWrapper["x"]
    print("Value: " + String(describing: value2))
    // in both versions, prints: "Value: Optional(Optional(10))"
  }
}

@swift-ci
Copy link
Collaborator Author

Comment by Kanstantsin Charnukha (JIRA)

So, as far as I understand, that's new intended new behavior in Swift 4.2? The issue should be probably closed then.

@hamishknight
Copy link
Collaborator

That is correct – I'll close this as a dupe then.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

2 participants