Swift 3 type inference question (Xcode 8 beta 6)

Xcode 8 beta 6.


I have the following code. The only difference between 1, 2, 3, 4 and 5 is what is in the 'metrics' parameter.


let a = 20

// 1: This compiles.
_ = NSLayoutConstraint.constraints(withVisualFormat: "|[v1(a)]|", metrics: ["a": 20], views: ["v1": v1])

// 2: This fails with "Cannot convert value of type 'Int' to expected dictionary value type 'NSNumber'".
_ = NSLayoutConstraint.constraints(withVisualFormat: "|[v1(a)]|", metrics: ["a": a], views: ["v1": v1])

let met = ["a": a]

// 3: This fails with "Cannot convert value of type '[String: Int]' to expected argument type '[String: NSNumber]?'".
_ = NSLayoutConstraint.constraints(withVisualFormat: "|[v1(a)]|", metrics: met, views: ["v1": v1])

// 4: This compiles.
_ = NSLayoutConstraint.constraints(withVisualFormat: "|[v1(a)]|", metrics: met as [String: NSNumber]?, views: ["v1": v1])

// 5: This fails with "Cannot convert value of type 'Int' to expected dictionary value type 'NSNumber'".
_ = NSLayoutConstraint.constraints(withVisualFormat: "|[v1(a)]|", metrics: ["a": a] as [String: NSNumber]?, views: ["v1": v1])


Why does 1 compile, but 2 does not?

Why does 1 compile, but 3 does not?

Why does 4 compile, but 5 does not?

Answered by QuinceyMorris in 170464022

Your #1 works because "20" is typeless, and type inference from the expected type (NSNumber) finds a NSNumber initializer that takes a numeric literal. So, the "20" literal can be treated as a NSNumber value automatically.


Your definition of a:


     let a = 20


requires the compiler to infer the type of a, and it chooses Int. It's not typeless like "20" is. (Well, 20 isn't typeless, it's a compile-time-numeric-literal type, but you know what I mean.)


Formerly, the Swift compiler would automatically bridge (that is, convert the type and value) from Swift types to certain Obj-C types, so it could handle an Int where it expected a NSNumber. This implicit behavior has recently been removed, so now you have to code the bridging explictly ("a as NSNumber").

Accepted Answer

Your #1 works because "20" is typeless, and type inference from the expected type (NSNumber) finds a NSNumber initializer that takes a numeric literal. So, the "20" literal can be treated as a NSNumber value automatically.


Your definition of a:


     let a = 20


requires the compiler to infer the type of a, and it chooses Int. It's not typeless like "20" is. (Well, 20 isn't typeless, it's a compile-time-numeric-literal type, but you know what I mean.)


Formerly, the Swift compiler would automatically bridge (that is, convert the type and value) from Swift types to certain Obj-C types, so it could handle an Int where it expected a NSNumber. This implicit behavior has recently been removed, so now you have to code the bridging explictly ("a as NSNumber").

To me, this seems like exactly the wrong decision by the Swift folks.


In essence, it means forcing developers to deliberately throw away type safety by adding " as NSNumber" to hundreds of lines of code that might otherwise have been parsed in a type safe manner.


I would argue that the ManagedObject class generation would have been a great place to do something more sensible.


It's also possible that I've got the wrong attitude here - after spending hours and hours adding silly code like this, plus adding a whole bunch more silly code to drawing operations that got broken with beta 6, I now have ... an application that just plain doesn't work anymore. I used to really like Swift.

In essence, it means forcing developers to deliberately throw away type safety by adding " as NSNumber" to hundreds of lines of code that might otherwise have been parsed in a type safe manner.

I’m not sure if you’re exaggerating for effect here, but if you literally have “hundreds of lines” of code that are packing numbers into dictionaries where the values are untyped then there’s probably better ways to do things. To continue the example started by sanjay, in their shoes I’d write a wrapper around NSLayoutConstraint that takes metrics as a

[String:Int]
value.
extension NSLayoutConstraint {
    class func constraints(withVisualFormat format: String, metrics: [String : Int]?, views: [String : Any]) -> [NSLayoutConstraint] {
        let actualMetrics: [String:Any]?
        if let metrics = metrics {
            var tmp: [String:Any] = [:]
            for (k, v) in metrics {
                tmp[k] = v as NSNumber
            }
            actualMetrics = tmp
        } else {
            actualMetrics = nil
        }
        return NSLayoutConstraint.constraints(withVisualFormat: format, options: [], metrics: actualMetrics, views: views)
    }
}

That’s both convenient and type safe.

In fact, given that the NSLayoutConstraint Class Reference says:

The dictionary’s keys must be the string values used in the visual format string. Their values must be NSNumber objects.

it’d be reasonable to file a bug to get this reality exposed at the API level.

Stepping back, if you have your own specific pain points you’d like feedback on, please post the details (preferably in a new thread).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

>> In essence, it means forcing developers to deliberately throw away type safety by adding " as NSNumber"


Also, I don't see how adding "as NSNumber" discards type safety. An "as NSNumber" construction will produce a compile-time error when used with an expression that is not bridgeable to NSNumber. That's exactly the same behavior as before, except the bridging was implicit.


In fact, the old behavior was less safe, because it was sometimes hard to predict when an expression would be bridged and when not. There used to be some very nasty edge cases.

Adding `as NSNumber` can get annoying, but it is evaluated always at compile time, so there shouldn't be any type safety issues...

Hi Quinn,


Thanks for your reply. Nice use of an extension, btw.


I was thinking specifically of CoreData, where the auto-generated NSManagedObject code bridges to an NSNumber


So, in the graphical data model editor, I can give the data entities any of the supported data types, such as boolean, double, etc.


However, the auto-generated NSManagedObjected subclass code exposes all of these as NSNumber?:

extension TestItem {

@NSManaged var myVariable1: NSNumber?

@NSManaged var myVariable2: NSNumber?

...


My thought here is that it would be preferable to made changes to the CoreData bridging such as the exposed data types match the data types defined in the model (Boolean, Int, Double, etc., instead of NSNumber?). By stuffing all of the numeric data into an NSNumber, developers are forced to add " as NSNumber" to every line of code that examines a Core Data element. I this sense, the new bridging conventions force developers to insert doe that eliminates any future possibility of acçessing Code Data elements in a typesafe manner.


By typesafe in this context, I mean that code that incorrectly pushes a numeric into a database field that was defined as a boolean, or a Double instead of an Int, etc. You can do this because of the bridging to NSNumber, but the cost can be Core Data storage of stuff that violates the intent of the data model.


While I appreciate that CoreData's internals probably do implement all of the numeric data types as NSNumber, I would hope that this is an implementation limitation that could eventually be removed. Assuming that this eventually happens (hope runs eternal), I don't see developers refactoring all of the code that coerces stuff into NSNumber.


It's also quite possible that I've missed something important in XCode 8. If I 'Create NSManagedObject Subclass...' in XCode 8, I always get Objective C code, rather than Swift code, when I do this, which is what makes me think that it might be possible to create better data model code. Perhaps there's a way to generate more type-appropriate extension code, and I've just missed it (although I did look in the release notes for such a thing).

Well, the compiler can determine whether a variable can be safely converted to NSNumber.

What it can't do, because the type information was removed from the auto-generated NSManagedObject subclass generation, is to determine whether the variable is type compatible with a particular database field.

So by type safety, I was referring to the ability of the compiler to guard against, for example, putting a numeric value into a Boolean field, or a Double into a field that is defined as an Integer in the data model.

With beta 6, and the removal of automatic bridging to NSNumber, the application code now needs explicit casts to NSNumber that weren't required before.


So, even though bridging isn't new, my concern is that those casts will become entrenched in the application code. So, if at some point in the future, those NSManaged variables become more descriptive of the data model instead of being defined as NSNumbers, the application code will still have those casts to NSNumber.


Sorry to split this into multiple posts - the forum editor keeps thinking that I've got bad words in my posts, and complains vaguely about bad characters...

>> if at some point in the future, those NSManaged variables become more descriptive of the data model instead of being defined as NSNumbers, the application code will still have those casts to NSNumber


If those properties get Swift types, then you'll have to remove the "as NSNumber" casts, because there's no automatic bridging in the other direction either, any more.


For example, if you're concerned that a future scenario like this:


@NSManaged var myVariable1: Int

myVariable1 = 10.3 as NSNumber


is going to (incorrectly) put the value 10 in the managed property, that won't happen. You'd get a compile-time error. And if you tried to "fix" it the wrong way:


     myVariable1 = 10.3 // or …
     myVariable1 = 10.3 as NSNumber as Int


well, neither of those will compile either. You'd be forced to consider what to do about the real problem, which is trying to give a non-integer value to an integer property, as you'd hope and expect.

Thanks for your reply!


Yes, you are of course quite correct, as I realized a bit later.


Let's hope that this future scenario comes to pass in the near future!

Swift 3 type inference question (Xcode 8 beta 6)
 
 
Q