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

We are trying to figure whether this is a bug in Swift or us misusing generics, optionals, type inference and/or nil coalescing operator.
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:
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.
Inside the init(...) function we try to set a value of the property someNumber like this:
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.
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.
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:
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

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.

Related

weird double-nullable variable error, why is it happening?

sometimes compiler produces weird optional error with double nullable operator like Value of optional type 'Data??' must be unwrapped to a value of type 'Data?', I'm wondering why is it happening? is this a bug or quirk I dont understand? Here's sample of code to reproduce
import Foundation
public class testClass {
var id: Int64?
init(id: Int64?, completionHandler: (([Data?], Bool) -> Data?)?) {
self.id = id
}
}
let tC = testClass(id: 1) { datas, val in
return datas.first
}
I'm not asking how to fix it, I'm just curious why this is happening as it looks like a bug for me - variable either exists or is nil
You are overcomplicating the issue with your example. All you need for demonstration is this, where Wrapped could be any type:
typealias Wrapped = Void
let optionals: [Wrapped?] = []
let wrapped: Wrapped? = optionals.first
What's actually going on in that last line, if you use the correct type, is this:
let doubleOptional: Wrapped?? = optionals.first
You need to provide a default value, for when the array is empty:
// `nil` is the same as Wrapped?.none, here.
let optional: Wrapped? = optionals.first ?? nil
And if it was safe to force-unwrap, then one ! would not be enough, either. You'd need two:
let wrapped: Wrapped = optionals.first!!
You should probably question why you have an array of optionals to begin with.

Why do we need to explicitly cast the optional to Any?

According to Apple Doc,
The Any type represents values of any type, including optional types.
Swift gives you a warning if you use an optional value where a value
of type Any is expected. If you really do need to use an optional
value as an Any value, you can use the as operator to explicitly cast
the optional to Any, as shown below.
var things = [Any]()
things.append(3) // No warning
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning, even though Any also represents optional types.
things.append(optionalNumber as Any) // No warning
Why do we need to explicitly cast the optional to Any?
Every type can be implicitly promoted to an optional of that type. This means that when you cast T? to Any it is very hard to know whether it was originally T or originally T? (or even T?? or worse). Most confusing is that Any can be promoted to Any? and that Any? is of type Any, so telling the difference between Any, Any?, Any??, and Any??? (etc.) is very tricky, and sometimes impossible.
Any is a very tricky type in Swift and should almost never be used. Except for explicitly tricking the compiler (in some very fancy and fragile type-eraser), I don't know of any case where it really makes sense to have Any as a variable type, and definitely not in the form of [Any]. If you're created an [Any], you've gone down a bad path that isn't going to go well.
There are a very few cases where Any as a function parameter type makes sense (print() being the most famous), but they are extremely rare in app-level code. If you find yourself needing Any, you've probably done something wrong, and the compiler is going to fuss at you about it and often make you write extra as code to make sure you really mean the messy things you're saying.
Just to give some concrete versions of this, optionality tends to be lost when you enter Any. So consider this situation:
let number: Int = 3
let optionalNumber: Int? = 3
let nilNumber: Int? = nil
let anyNumber = number as Any
let anyOptional = optionalNumber as Any
let anyNil = nilNumber as Any
if anyNumber is Int { print("number is Int")} // yes
if anyOptional is Int { print("optional number is Int")} // yes
if anyNil is Int { print("nil is Int")} // no
if anyNil is Int? { print("nil is Int?")}
// -> Error: Cannot downcast from 'Any' to a more optional type 'Int?'
Rats.
We can't get our optional back the same way we put it in. We can promote it of course:
if (anyNil as Any?) is Int? { print("nil is Int?") } // yes
But we can promote anything that way, since everything is implicitly an optional of itself:
if (anyNumber as Any?) is Int? { print("number is Int?")} // also yes
So, um. Rats. We don't really know if it was originally optional or not. It's mess, and the compiler is warning you that it's going to be a mess if you go very far down this road. T->Any is a bit of magic. T->T? is also a bit of magic. Combine the two magics, and you had better know exactly what you're doing.

Can I use `try?` with a generic method?

I have a generic throwing method:
func doSomething<T>() throws -> T? {
// do something
}
This is how I want to call it:
let obj: SomeClass? = try? doSomething() // a compiler error!
But I may not. The compiler gives me an error
Value of optional type 'SomeClass??' not unwrapped; did you mean to use 'try!' or chain with '?'?.
I can use try! or try instead. But I don't like the former because it might crash the app at some point, and I don't like the latter because it's too long for most of the cases (one line becomes five).
Does anybody know how can I keep using try??
Your method is returning an optional. The try? expression adds another level of optional, therefore your expected return value should be a double optional:
let obj: SomeClass?? = try? doSomething()
It's probably not a good idea to combine returning of optional and throws.
You could also remove the second level of optionals using ??:
let obj: SomeClass? = (try? doSomething()) ?? nil
but I really recommend to redesign the API instead of solving double optionals.

Ambiguous use of operator '??'

This article shows: ?? is very time-consuming, and the test found it to be true. So I want to optimize this:
#if DEBUG
public func ?? <T>(left: T?, right: T) -> T {
guard let value = left else {
return right
}
return value
}
#endif
BUT
string = string ?? ""
ERROR: Ambiguous use of operator '??'
The article you link to does not describe how the nil coalescing operator is "time consuming", but states the simple fact that a compound expression of two nil coalescing operator calls as well as other evaluations has a significantly longer build time than if we break down this compound expressions into smaller parts, especially if the former contains lazy evaluations at some point (which is the case e.g. for the evaluation of the rhs of the ?? operator). By breaking down complex expression we help the compiler out; Swift has been known to have some difficulty compiling complex compound statements, so this is expected. This, however, should generally not affect runtime performance, given that you build your implementation for release mode.
By overloading the implementation of the ?? operator during debug with a non-lazy evaluation of the lhs while still using long compound statements shouldn't fully redeem the issue described in the previous clause. If compile time is really an issue for you, use the same approach as the author of the article; break down your complex expressions into several smaller ones, in so doing some of the work of the compiler yourself. Refactoring with the sole purpose of decreasing compile times (which we might believe is the job of the compiler, not us) might be frustrating at times, but is an issue we (currently) has to live with when working with Swift. See e.g. the following excellent article covering the Swift compiler issues with long/complex expressions:
Exponential time complexity in the Swift type checker
W.r.t. to the ambiguity error: for reference, the official implementation of the nil coelescing operator can be found here:
#_transparent
public func ?? <T>(optional: T?, defaultValue: #autoclosure () throws -> T)
rethrows -> T {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
And we may note that it has the same specificity as your own generic implementation (compare the signatures!), meaning the ambiguity error is to be expected. E.g., compare with:
// due to the '#autoclosure', these two have the
// same precedecence/specificity in the overload
// resolution of 'foo(_:)'
func foo<T>(_ obj: T) {}
func foo<T>(_ closure: #autoclosure () -> T) {}
foo("foo") // ERROR at line 9, col 1: ambiguous use of 'foo'
When you implement your ?? overload it as in your answer, typing out Any, this implementation becomes more specific than the generic one, which means it will take precedence in the overload resolution of ??. Using Any is such contexts, however, is generally a bad idea, attempting to mimic dynamic typing rather than relying on Swift's renowned static typing.
if I overloading ??,I can't use <T>...
then
#if DEBUG
public func ?? (left: Any?, right: Any) -> Any {
guard let value = left else {
return right
}
return value
}
#endif
is OK!
BUT I can't get types of return value... 😭

Can a condition be used to determine the type of a generic?

I will first explain what I'm trying to do and how I got to where I got stuck before getting to the question.
As a learning exercise for myself, I took some problems that I had already solved in Objective-C to see how I can solve them differently with Swift. The specific case that I got stuck on is a small piece that captures a value before and after it changes and interpolates between the two to create keyframes for an animation.
For this I had an object Capture with properties for the object, the key path and two id properties for the values before and after. Later, when interpolating the captured values I made sure that they could be interpolated by wrapping each of them in a Value class that used a class cluster to return an appropriate class depending on the type of value it wrapped, or nil for types that wasn't supported.
This works, and I am able to make it work in Swift as well following the same pattern, but it doesn't feel Swift like.
What worked
Instead of wrapping the captured values as a way of enabling interpolation, I created a Mixable protocol that the types could conform to and used a protocol extension for when the type supported the necessary basic arithmetic:
protocol SimpleArithmeticType {
func +(lhs: Self, right: Self) -> Self
func *(lhs: Self, amount: Double) -> Self
}
protocol Mixable {
func mix(with other: Self, by amount: Double) -> Self
}
extension Mixable where Self: SimpleArithmeticType {
func mix(with other: Self, by amount: Double) -> Self {
return self * (1.0 - amount) + other * amount
}
}
This part worked really well and enforced homogeneous mixing (that a type could only be mixed with its own type), which wasn't enforced in the Objective-C implementation.
Where I got stuck
The next logical step, and this is where I got stuck, seemed to be to make each Capture instance (now a struct) hold two variables of the same mixable type instead of two AnyObject. I also changed the initializer argument from being an object and a key path to being a closure that returns an object ()->T
struct Capture<T: Mixable> {
typealias Evaluation = () -> T
let eval: Evaluation
let before: T
var after: T {
return eval()
}
init(eval: Evaluation) {
self.eval = eval
self.before = eval()
}
}
This works when the type can be inferred, for example:
let captureInt = Capture {
return 3.0
}
// > Capture<Double>
but not with key value coding, which return AnyObject:\
let captureAnyObject = Capture {
return myObject.valueForKeyPath("opacity")!
}
error: cannot invoke initializer for type 'Capture' with an argument list of type '(() -> _)'
AnyObject does not conform to the Mixable protocol, so I can understand why this doesn't work. But I can check what type the object really is, and since I'm only covering a handful of mixable types, I though I could cover all the cases and return the correct type of Capture. Too see if this could even work I made an even simpler example
A simpler example
struct Foo<T> {
let x: T
init(eval: ()->T) {
x = eval()
}
}
which works when type inference is guaranteed:
let fooInt = Foo {
return 3
}
// > Foo<Int>
let fooDouble = Foo {
return 3.0
}
// > Foo<Double>
But not when the closure can return different types
let condition = true
let foo = Foo {
if condition {
return 3
} else {
return 3.0
}
}
error: cannot invoke initializer for type 'Foo' with an argument list of type '(() -> _)'
I'm not even able to define such a closure on its own.
let condition = true // as simple as it could be
let evaluation = {
if condition {
return 3
} else {
return 3.0
}
}
error: unable to infer closure type in the current context
My Question
Is this something that can be done at all? Can a condition be used to determine the type of a generic? Or is there another way to hold two variables of the same type, where the type was decided based on a condition?
Edit
What I really want is to:
capture the values before and after a change and save the pair (old + new) for later (a heterogeneous collection of homogeneous pairs).
go through all the collected values and get rid of the ones that can't be interpolated (unless this step could be integrated with the collection step)
interpolate each homogeneous pair individually (mixing old + new).
But it seems like this direction is a dead end when it comes to solving that problem. I'll have to take a couple of steps back and try a different approach (and probably ask a different question if I get stuck again).
As discussed on Twitter, the type must be known at compile time. Nevertheless, for the simple example at the end of the question you could just explicitly type
let evaluation: Foo<Double> = { ... }
and it would work.
So in the case of Capture and valueForKeyPath: IMHO you should cast (either safely or with a forced cast) the value to the Mixable type you expect the value to be and it should work fine. Afterall, I'm not sure valueForKeyPath: is supposed to return different types depending on a condition.
What is the exact case where you would like to return 2 totally different types (that can't be implicitly casted as in the simple case of Int and Double above) in the same evaluation closure?
in my full example I also have cases for CGPoint, CGSize, CGRect, CATransform3D
The limitations are just as you have stated, because of Swift's strict typing. All types must be definitely known at compile time, and each thing can be of only one type - even a generic (it is resolved by the way it is called at compile time). Thus, the only thing you can do is turn your type into into an umbrella type that is much more like Objective-C itself:
let condition = true
let evaluation = {
() -> NSObject in // *
if condition {
return 3
} else {
return NSValue(CGPoint:CGPointMake(0,1))
}
}