Casting to meta type

TL;DR I need something like this:

func isCastable(_ value: Any, to t: Any.Type) -> Bool {
    if let _ = value as? t { return true }
    return false
 }

Probably, I could use @_silgen_name to call swift_dynamicCast directly, but may there is a less hackish way?


More details to give some context:

I have a typed dictionary data structure, which provides a generic subscript like this:

subscript<T: Hashable>(key: Key<T>) -> T {
   get { storage[key] as! T }
   set { storage[key] = AnyHashable(newValue) }
}

But I also I need to enable ObjC code to read and write to this data structure. I cannot achieve static type safety in ObjC, so instead I rely on runtime checks.

For ObjC, I'm exporting a key class, that store meta type of the value inside it. Somehow, I need to check that value of static type Any that ObjC side is trying to write is of the metatype of stored in the key.

And since there is also ObjC <-> Swift bridging happening, I cannot just check meta types for equality - if I'm getting an NSString from ObjC, but key expects String - that's ok.

After segfaulting compiler couple times by @_silgen_name, I was able to hack something inspired by Runtime/GettersSetters.swift at 69886eba092300939d5ba7a487045cf70b70557e · wickwirew/Runtime · GitHub :

private protocol Casting {}
private extension Casting {
    static func canCast(_ value: Any) -> Bool {
        if let _ = value as? Self { return true }
        return false
    }
}

private struct ProtocolTypeContainer {
    let type: Any.Type
    let witnessTable: Int
}

func canCast(_ value: Any, to type: Any.Type) -> Bool {
    let container = ProtocolTypeContainer(type: type, witnessTable: 0)
    return unsafeBitCast(container, to: Casting.Type.self).canCast(value)
}

print("Part 1")
print(canCast("abc", to: String.self))
print(canCast("abc", to: String?.self))
print(canCast("abc", to: Int.self))
print(canCast(NSString(), to: String.self))
print(canCast(NSString(), to: String?.self))
print("Part 2")
//print(NSString() as? String)
//print(NSString() as? String?)
//print(NSMutableString() as? String)
//print(NSMutableString() as? String?)
print("Part 3")
extension String: Casting {}
extension Optional: Casting {}
print(String.canCast(NSString()))
print(String?.canCast(NSString()))
print(String.canCast(NSMutableString()))
print(String?.canCast(NSMutableString()))

But running this code, gives very different results depending on if "Part 2" is present or commented out.

  1. Commented out:
Part 1
true
true
false
false
false
Part 2
Part 3
false
false
false
false
  1. Present:
Part 1
true
true
false
true
true
Part 2
Optional("")
Optional(Optional(""))
Optional("")
Optional(Optional(""))
Part 3
true
true
true
true

Note that results of the last two checks in the "Part 1" are different - and this is even before "Part 2" had a chance to be executed. Just the mere presence of the casts from the "Part 2" in the binary seems to change the casting behaviour. I'm baffled. What is going on?

1 Like

One thing you could do, at the cost of more storage, is save a closure at the point when the type is statically known:

func isa<T>(_: T.Type) -> (Any) -> Bool {
  return { $0 is T }
}

var singleSlot: Any!
var isValid: (Any) -> Bool = isa(String.self) // set this separately in a real example

func setSlot(to value: Any) {
  precondition(isValid(value))
  singleSlot = value
}
func readSlotAsString() -> String {
  return singleSlot as! String
}
1 Like

Thanks, I'll give it a try. It will require some refactoring, to reach the point where static type is known, but I can figure that out. Since extra storage is needed only when bridging with ObjC, which is a minority of cases, this should not be a problem.

But I'm really confused by the problem in the second post. Why does presence of static casting change behaviour of other code? Now I'm not sure if I can transfer learnings from my experiments with toy apps to the production code.