[swift-evolution] Why is AnyObject's bridging so weird in Swift 3?

Joe Groff jgroff at apple.com
Mon Dec 12 14:46:19 CST 2016


> On Dec 12, 2016, at 12:12 PM, Charles Srstka via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I’ve been trying to figure out the rationale for why the code below behaves the way it does for some time:
> 
> import Foundation
> 
> class C: NSObject {
> 	dynamic var foo: Int { return 5 }
> 	dynamic func bar() -> Int { return 6 }
> }
> 
> struct S {}
> 
> let c = C()
> let s = S()
> 
> print(c is AnyObject) // warning: 'is' test is always true
> print(s is AnyObject) // warning: 'is' test is always true (?!)
> 
> print(c as AnyObject) // <Project.C: 0x0123456789012345>
> print(s as AnyObject) // Project.S()
> 
> print(c as? AnyObject) // warning: conditional cast from 'C' to 'AnyObject' always succeeds
> print(s as? AnyObject) // warning: conditional cast from 'S' to 'AnyObject' always succeeds
> 
> print(c as AnyObject? as Any) // Optional(<Project.C: 0x0123456789012345>)
> print(s as AnyObject? as Any) // Optional(Project.S())
> 
> print((c as AnyObject?)?.foo as Any) // Optional(5)
> print((s as AnyObject?)?.foo as Any) // nil
> print((c as AnyObject?)?.bar() as Any) // Optional(6)
> print((s as AnyObject?)?.bar() as Any) // crash! -[_SwiftValue bar]: unrecognized selector sent to instance 0x5432109876543210
> 
> So what we have is:
> 
> 1. Any type you have will always claim it’s a class type, every time, even if it’s actually a non-bridgeable value type.
> 
> 2. Conditional casting will also succeed, even if you use it on a non-bridgeable value type.
> 
> 3. Non-conditional casting works too, despite that the underlying type might be a non-bridgeable value type.
> 
> 4. Bridging to an optional will *also* always give you a value, even if what’s underneath is a non-bridgeable value type.
> 
> 5. Trying to call a property on an optional from #4 will, surprisingly, work as you’d expect. The class type that implements the property returns the property, the value type returns nil.
> 
> 6. Trying to call a method on an optional from #4 will crash.
> 
> This raises a few questions:
> 
> 1. Why in the blazes is it implemented like this? Why not only allow conditional casting to AnyObject, which would only succeed if the type either actually was an object or could actually be bridged to an object? Why make the cast guaranteed, even when in actuality it’s not?

Everything *can* be bridged to an object as a result of SE-0116 ("id-as-Any"), so there's no longer such a thing as a "non-bridgable value type". #6 is a bug, since the AnyObject method lookup ought to produce `nil` if the ObjC method isn't implemented on the object.

> 2. Why is there no obvious way to figure out whether something can actually be an object? The already kind of non-obvious “type(of: s) is AnyObject.Type” trick works to tell you if the thing is already a class, but not if something is bridgeable to a class; using it on a string, for example, returns false. And trying something like “type(of: s as AnyObject) is AnyObject.Type” returns true (?!), so that doesn’t work to detect bridgeable things.

What are you trying to do that requires you to know whether something is a class or bridgable to a class (which, as mentioned above, includes everything)?

-Joe



More information about the swift-evolution mailing list