Swift Impossible Type Inference

288 views Asked by At

I am trying to load a file from my app bundle in Swift 3, and I came across a weird situation with the Swift type inferencing. If I use the following code, I get an error on the third line that says Value of optional type "String?" not unwrapped.

let url = NSURL(fileURLWithPath:Bundle.main.bundlePath)
let url2 = url.appendingPathComponent("foo.txt")
let path:String = url2?.path

To fix the error I unwrap the value on the third line by changing it to:

let path:String = url2?.path!

I now get the error Cannot force unwrap value of a non-optional type 'String'. It seems like Swift can't determine whether the path property is a String or a String?. The autocomplete feature in Xcode says it is a String, but the docs say it is a String?.

The suggested fix by Xcode for the first error was to replace url2?.path with (url2?.path)!, which finally ended up working, but I have no idea why this works and the other ways don't.

let path:String = (url2?.path)!

What is going on? Is this a type inference bug in Swift, or am I missing something super obvious

3

There are 3 answers

0
OOPer On BEST ANSWER

In Swift, Optional chaining like:

let path:String = url2?.path!

... is interpreted as:

let path:String = url2 != nil ? url2!.path!
                              : nil

As you see the type of path is non-Optional String, so the expression causes error.

(url2's type is URL?, so the type of property path is String, not String?.)


This is not a direct answer to your question, but I would re-write your code as:

let url = Bundle.main.bundleURL
let url2 = url.appendingPathComponent("foo.txt")
let path:String = url2.path

Shorter, and no worry about Optionals.

5
Mike JS Choi On

You forgot to unwrap url2

appendingPathComponent returns an optional value and you are trying to access it without unwrapping it.

So,

let url2 = url.appendingPathComponent("foo.txt")!

or

guard let url2 = url.appendingPathComponent("foo.txt") else { }

should fix it

EDIT

let path:String? = url2?.path

works also

2
Pragnesh Vitthani On

You can also do this:

let url = Bundle.main.url(forResource: "foo", withExtension: "txt")!
let path:String = url.path