Conditional casting with Any as type parameter


(Alexey Zinchenko) #1

I have stumbled upon this when upgrading to swift 4.2. I'm puzzled on what's going on here and what should happen :)

import Foundation

let data = [String: Any]()

func resolve<T>() -> T? {
    return data["123"] as? T
}

if let result = resolve() as Any? {
    print(result)  // Apple Swift version 4.2 (swiftlang-1000.11.37.1 clang-1000.11.45.1)
} else {
    print("empty") // Apple Swift version 4.1.2 (swiftlang-902.0.54 clang-902.0.39.2)
}

Looks like a bug for me, because if you try to use value of result on swift 4.2 you'll end up with fatalError

* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x0000000113562b66 libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x000000011359c080 libsystem_pthread.dylib`pthread_kill + 333
    frame #2: 0x000000011330fc45 libsystem_c.dylib`abort + 127
    frame #3: 0x00000001127f9c35 libswiftCore.dylib`swift::fatalError(unsigned int, char const*, ...) + 149
    frame #4: 0x00000001127f0462 libswiftCore.dylib`swift::swift_dynamicCastFailure(void const*, char const*, void const*, char const*, char const*) + 82
    frame #5: 0x00000001127f04d0 libswiftCore.dylib`swift::swift_dynamicCastFailure(swift::TargetMetadata<swift::InProcess> const*, swift::TargetMetadata<swift::InProcess> const*, char const*) + 96
    frame #6: 0x00000001127f52db libswiftCore.dylib`_dynamicCastToExistential(swift::OpaqueValue*, swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*, swift::TargetExistentialTypeMetadata<swift::InProcess> const*, swift::DynamicCastFlags) + 1387

Could anyone advise?


(Hamish Knight) #2

This is due to a change (#13910) where the compiler is now more conservative with unwrapping an optional value that's being cast to a generic placeholder type.

Now, the results you get are consistent with those that you would get in a non-generic context, for example:

let data = [String: Any]()

func resolve<T>() -> T? {
  return data["123"] as? T
}

if let result = resolve() as Any? {
  print(result) // (In Swift 4.2) prints: nil
} else {
  print("empty") // (In Swift 4.1) prints: empty
}

// In the previous example, `T` is inferred to be `Any`,
// so replace `as? T` with `as? Any`.
if let result = data["123"] as? Any { 
  print(result) // (In both versions) prints: nil
} else {
  print("empty")
}

However this change wasn't properly gated by Swift version – I'm working on restoring the old behaviour under Swift 4 mode (#19217). For further discussion, see SR-8704.

I cannot reproduce this, could you provide an example of code that causes this crash?


(Alexey Zinchenko) #3

Thanks a lot for providing context!

I cannot reproduce this, could you provide an example of code that causes this crash?

Crash happened because of erroneous force cast of returned nil to other type, so please ignore it.