Guard not working with AnyObject

There may be several mistakes in this code, but the behavior seems incorrect. The obvious answer is to not do this, but I believe it may be uncovering a bug (or not, I have no idea 🙂).


In the following example playground we upcast a Dictionary to AnyObject and then perform a failing select which returns nil. The fact that we can do a subscript on an AnyObject is odd, but the compiler does not complain.


My real surprise is that the guard does not catch the nil assignment to y1. The following print shows y1 as nil and the if y1 == nil does catch the nil.


This is also odd because a "guard let foo =" does not (should not?) create an optional, so the following if y1 == nil should have been caught by the compiler? When I looked at the variable in the debugger, the type was "AnyObject?!" and the value was "nil". This might be a clue.


Finallly I downcast the AnyObject to Dictionary and it operates as expected.


import Foundation

var x = ["asdf":"qwer"]
var x1:AnyObject = x

guard let y1 = x1["1"] else {
    print("This is the expected case, but does not happen")
    exit(3)
}
print("Unexpected, the value is actually \(y1)")

if y1 == nil {
    print("caught")
}

guard let y2 = (x1 as! Dictionary<String,String>)["1"] else {
    print("This is the expected case")
    exit(3)
}


This is a weird corner case, and I would not bring this up unless I actually ran into this. It was in a program that was processing JSON. This playground is an attempt to isolate this into the smallest possible example.


Maybe this is just an FYI as I will change my code to do the downcast so that the guard works.


Since guards are the primary defense against invalid parameters, should this be a problem?


I am running Xcode Version 7.2 beta (7C62b)

This is probably related to the behaviour documented in Interacting with Objective-C APIs in the "Using Swift with Cocoa and Objective-C" book:


You can call any Objective-C method and access any property on an

AnyObject
value without casting to a more specific class type.

...

When you call a method on a value of

AnyObject
type, that method call behaves like an implicitly unwrapped optional. You can use the same optional chaining syntax you would use for optional methods in protocols to optionally invoke a method on
AnyObject

That's why

x1["1"]
returns
AnyObject?!
, and the optional binding
guard let y1 = x1["1"]

succeeds if the subscripting method could be called at all. (It would fail if x is not a dictionary).


I haven't figured out how you can use optional chaining with subscripting.


With pattern matching you could make the code work as expected:


guard case let y1?? = x1["1"] else { 
  print("This is the expected case") 
  exit(3) 
}


This checks if the subscripting method could be called on the object and the result is not nil.


But (optionally) casting to a Swift Dictionary is probably the better solution.

Hello Martin:


Thanks for your reply. I respectfully suggest that the goal of Swift, that it be simple, obvious, and clean code is not met by this current behavior. Specifically, a guard statement follows the if statement which says “You can use if and let together to work with values that might be missing.” It does not add the exception "except when a method is upcast to AnyObject".

I would also respectfuly suggest that this is a security problem. The guard statements are defined to do parameter and state checking which is the front line for security attacks. In my case, this was a parameter checking that simply did not work even though it looked reasonable and the compiler did not even belch a warning. My code failed later when the nil was passed in (even though the parameter that was being passed was not optional).

I am wondering if this will be looked at by the Swift team.

Sincerely

Jim

"I am wondering if this will be looked at by the Swift team."


Best chance of that happening is by submitting it in a bug report. Including your comments on why you find this confusing would probably be a good idea.

Guard not working with AnyObject
 
 
Q