mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Fix dynamic runtime casts of Optionals.
Fixes <rdar://23122310> Runtime dynamic casts... This makes runtime dynamic casts consistent with language rules, and consequently makes specialization of generic code consistent with an equivalent nongeneric implementation. The runtime now supports casts from Optional<T> to U. Naturally the cast fails on nil source, but otherwise succeeds if T is convertible to U. When casting T to Optional<U> the runtime succeeds whenever T is convertible to U and simply wraps the result in an Optional. To greatly simplify the runtime, I am assuming that target-type-specific runtime cast entry points (e.g. swift_dynamicCastClass) are never invoked with an optional source. This assumption is valid for the following reasons. At the language level optionals must be unwrapped before downcasting (via as[?!]), so we only need to worry about SIL and IR lowering. This implementation assumes (with asserts) that: - SIL promotion from an address cast to a value casts should only happen when the source is nonoptional. Handling optional unwrapping in SIL would be too complicated because we need to check for Optional's own conformances. (I added a test case to ensure this promotion does not happen). This is not an issue for unchecked_ref_cast, which implicitly unwraps optionals, so we can promote those! - IRGen lowers unchecked_ref_cast (Builtin.castReference) directly to a bitcast (will be caught by asserts). - IRGen continues to emit the generic dynamicCast entry point for address-casts (will be caught by asserts).
This commit is contained in:
@@ -95,6 +95,17 @@ extension ImplicitlyUnwrappedOptional : CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
/// Directly conform to CustomDebugStringConvertible to support
|
||||
/// optional printing. Implementation of that feature relies on
|
||||
/// _isOptional thus cannot distinguish ImplicitlyUnwrappedOptional
|
||||
/// from Optional. When conditional conformance is available, this
|
||||
/// outright conformance can be removed.
|
||||
extension ImplicitlyUnwrappedOptional : CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
@_transparent
|
||||
@warn_unused_result
|
||||
public // COMPILER_INTRINSIC
|
||||
|
||||
@@ -165,6 +165,16 @@ internal func _adHocPrint<T, TargetStream : OutputStreamType>(
|
||||
internal func _print_unlocked<T, TargetStream : OutputStreamType>(
|
||||
value: T, inout _ target: TargetStream
|
||||
) {
|
||||
// Optional has no representation suitable for display; therefore,
|
||||
// values of optional type should be printed as a debug
|
||||
// string. Check for Optional first, before checking protocol
|
||||
// conformance below, because an Optional value is convertible to a
|
||||
// protocol if its wrapped type conforms to that protocol.
|
||||
if _isOptional(value.dynamicType) {
|
||||
let debugPrintable = value as! CustomDebugStringConvertible
|
||||
debugPrintable.debugDescription.writeTo(&target)
|
||||
return
|
||||
}
|
||||
if case let streamableObject as Streamable = value {
|
||||
streamableObject.writeTo(&target)
|
||||
return
|
||||
|
||||
@@ -1990,13 +1990,59 @@ static id dynamicCastValueToNSError(OpaqueValue *src,
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool canCastToExistential(OpaqueValue *dest, OpaqueValue *src,
|
||||
const Metadata *srcType,
|
||||
const Metadata *targetType) {
|
||||
if (targetType->getKind() != MetadataKind::Existential)
|
||||
return false;
|
||||
|
||||
return _dynamicCastToExistential(dest, src, srcType,
|
||||
cast<ExistentialTypeMetadata>(targetType),
|
||||
DynamicCastFlags::Default);
|
||||
}
|
||||
|
||||
/// Perform a dynamic cast to an arbitrary type.
|
||||
bool swift::swift_dynamicCast(OpaqueValue *dest,
|
||||
OpaqueValue *src,
|
||||
const Metadata *srcType,
|
||||
const Metadata *targetType,
|
||||
DynamicCastFlags flags) {
|
||||
// Check if the cast source is Optional and the target is not an existential
|
||||
// that Optional conforms to. Unwrap one level of Optional and continue.
|
||||
if (srcType->getKind() == MetadataKind::Optional
|
||||
&& !canCastToExistential(dest, src, srcType, targetType)) {
|
||||
const Metadata *payloadType =
|
||||
cast<EnumMetadata>(srcType)->getGenericArgs()[0];
|
||||
int enumCase =
|
||||
swift_getEnumCaseSinglePayload(src, payloadType, 1 /*emptyCases=*/);
|
||||
if (enumCase != -1) {
|
||||
// Allow Optional<T>.None -> Optional<U>.None
|
||||
if (targetType->getKind() != MetadataKind::Optional)
|
||||
return _fail(src, srcType, targetType, flags);
|
||||
// Inject the .None tag
|
||||
swift_storeEnumTagSinglePayload(dest, payloadType, enumCase,
|
||||
1 /*emptyCases=*/);
|
||||
return _succeed(dest, src, srcType, flags);
|
||||
}
|
||||
// .Some
|
||||
// Single payload enums are guaranteed layout compatible with their
|
||||
// payload. Only the source's payload needs to be taken or destroyed.
|
||||
srcType = payloadType;
|
||||
}
|
||||
|
||||
switch (targetType->getKind()) {
|
||||
// Handle wrapping an Optional target.
|
||||
case MetadataKind::Optional: {
|
||||
// Recursively cast into the layout compatible payload area.
|
||||
const Metadata *payloadType =
|
||||
cast<EnumMetadata>(targetType)->getGenericArgs()[0];
|
||||
if (swift_dynamicCast(dest, src, srcType, payloadType, flags)) {
|
||||
swift_storeEnumTagSinglePayload(dest, payloadType, -1 /*case*/,
|
||||
1 /*emptyCases*/);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Casts to class type.
|
||||
case MetadataKind::Class:
|
||||
@@ -2089,7 +2135,6 @@ bool swift::swift_dynamicCast(OpaqueValue *dest,
|
||||
|
||||
case MetadataKind::Struct:
|
||||
case MetadataKind::Enum:
|
||||
case MetadataKind::Optional:
|
||||
switch (srcType->getKind()) {
|
||||
case MetadataKind::Class:
|
||||
case MetadataKind::ObjCClassWrapper:
|
||||
|
||||
@@ -237,4 +237,130 @@ OptionalTests.test("flatMap") {
|
||||
expectEmpty((3 as Int32?).flatMap(half))
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
func anyToAny<T, U>(a: T, _ : U.Type) -> U {
|
||||
return a as! U
|
||||
}
|
||||
@inline(never)
|
||||
func anyToAnyOrNil<T, U>(a: T, _ : U.Type) -> U? {
|
||||
return a as? U
|
||||
}
|
||||
func canGenericCast<T, U>(a: T, _ ty : U.Type) -> Bool {
|
||||
return anyToAnyOrNil(a, ty) != nil
|
||||
}
|
||||
|
||||
OptionalTests.test("Casting Optional") {
|
||||
let x = C()
|
||||
let sx : C? = x
|
||||
let nx : C? = nil
|
||||
expectTrue(anyToAny(x, Optional<C>.self)! === x)
|
||||
expectTrue(anyToAny(sx, C.self) === x)
|
||||
expectTrue(anyToAny(sx, Optional<C>.self)! === x)
|
||||
|
||||
expectTrue(anyToAny(nx, Optional<C>.self) == nil)
|
||||
expectTrue(anyToAnyOrNil(nx, C.self) == nil)
|
||||
|
||||
let i = Int.max
|
||||
let si : Int? = Int.max
|
||||
let ni : Int? = nil
|
||||
expectEqual(anyToAny(i, Optional<Int>.self)!, Int.max)
|
||||
expectEqual(anyToAny(si, Int.self), Int.max)
|
||||
expectEqual(anyToAny(si, Optional<Int>.self)!, Int.max)
|
||||
|
||||
expectTrue(anyToAny(ni, Optional<Int>.self) == nil)
|
||||
expectTrue(anyToAnyOrNil(ni, Int.self) == nil)
|
||||
|
||||
let ssx : C?? = sx
|
||||
expectTrue(anyToAny(ssx, Optional<C>.self)! === x)
|
||||
expectTrue(anyToAny(x, Optional<Optional<C>>.self)!! === x)
|
||||
expectTrue(anyToAnyOrNil(ni, Int.self) == nil)
|
||||
}
|
||||
|
||||
OptionalTests.test("Casting Optional Traps") {
|
||||
let nx : C? = nil
|
||||
expectCrashLater()
|
||||
anyToAny(nx, Int.self)
|
||||
}
|
||||
|
||||
class TestNoString {}
|
||||
class TestString : CustomStringConvertible, CustomDebugStringConvertible {
|
||||
var description: String {
|
||||
return "AString"
|
||||
}
|
||||
var debugDescription: String {
|
||||
return "XString"
|
||||
}
|
||||
}
|
||||
class TestStream : Streamable {
|
||||
func writeTo<Target : OutputStreamType>(inout target: Target) {
|
||||
target.write("AStream")
|
||||
}
|
||||
}
|
||||
|
||||
func debugPrintStr<T>(a: T) -> String {
|
||||
var s = ""
|
||||
debugPrint(a, terminator: "", toStream: &s)
|
||||
return s
|
||||
}
|
||||
// Optional should not conform to output stream protocols itself, but is
|
||||
// convertible to them if its wrapped type is.
|
||||
// Furthermore, printing an Optional should always print the debug
|
||||
// description regardless of whether the wrapper type conforms to an
|
||||
// output stream protocol.
|
||||
OptionalTests.test("Optional OutputStream") {
|
||||
let optNoString : TestNoString? = TestNoString()
|
||||
expectFalse(optNoString is CustomStringConvertible)
|
||||
expectFalse(canGenericCast(optNoString, CustomStringConvertible.self))
|
||||
expectFalse(optNoString is Streamable)
|
||||
expectFalse(canGenericCast(optNoString, Streamable.self))
|
||||
expectTrue(optNoString is CustomDebugStringConvertible)
|
||||
expectTrue(canGenericCast(optNoString, CustomDebugStringConvertible.self))
|
||||
expectEqual(String(optNoString), "Optional(main.TestNoString)")
|
||||
expectEqual(debugPrintStr(optNoString), "Optional(main.TestNoString)")
|
||||
|
||||
let iouNoString : TestNoString! = TestNoString()
|
||||
// IOU directly conforms to CustomStringConvertible.
|
||||
// Disabled pending SR-164
|
||||
// expectTrue(iouNoString is CustomStringConvertible)
|
||||
expectTrue(canGenericCast(iouNoString, CustomStringConvertible.self))
|
||||
expectFalse(iouNoString is Streamable)
|
||||
expectFalse(canGenericCast(iouNoString, Streamable.self))
|
||||
// CustomDebugStringConvertible conformance is a temporary hack.
|
||||
// Disabled pending SR-164
|
||||
// expectTrue(iouNoString is CustomDebugStringConvertible)
|
||||
expectTrue(canGenericCast(iouNoString, CustomDebugStringConvertible.self))
|
||||
expectEqual(String(iouNoString), "main.TestNoString")
|
||||
expectEqual(debugPrintStr(iouNoString), "main.TestNoString")
|
||||
|
||||
let optString : TestString? = TestString()
|
||||
expectTrue(optString is CustomStringConvertible)
|
||||
expectTrue(canGenericCast(optString, CustomStringConvertible.self))
|
||||
expectTrue(optString is CustomDebugStringConvertible)
|
||||
expectTrue(canGenericCast(optString, CustomDebugStringConvertible.self))
|
||||
expectEqual(String(TestString()), "AString")
|
||||
expectEqual(String(optString), "Optional(XString)")
|
||||
expectEqual(debugPrintStr(optString), "Optional(XString)")
|
||||
|
||||
let iouString : TestString! = TestString()
|
||||
expectTrue(iouString is CustomStringConvertible)
|
||||
expectTrue(canGenericCast(iouString, CustomStringConvertible.self))
|
||||
// CustomDebugStringConvertible conformance is a temporary hack.
|
||||
expectTrue(iouString is CustomDebugStringConvertible)
|
||||
expectTrue(canGenericCast(iouString, CustomDebugStringConvertible.self))
|
||||
expectEqual(String(iouString), "AString")
|
||||
// FIXME: Ideally the debug output would be "XString", but a reasonable
|
||||
// implemention of that behavior requires conditional conformance.
|
||||
// (directly invoking debugPrint(Any) already works correctly).
|
||||
expectEqual(debugPrintStr(iouString), "AString")
|
||||
|
||||
let optStream : TestStream? = TestStream()
|
||||
expectTrue(optStream is Streamable)
|
||||
expectTrue(canGenericCast(optStream, Streamable.self))
|
||||
expectTrue(optStream is CustomDebugStringConvertible)
|
||||
expectTrue(canGenericCast(optStream, CustomDebugStringConvertible.self))
|
||||
expectEqual(String(TestStream()), "AStream")
|
||||
expectEqual(String(optStream), "Optional(AStream)")
|
||||
expectEqual(debugPrintStr(optStream), "Optional(AStream)")
|
||||
}
|
||||
|
||||
runAllTests()
|
||||
|
||||
@@ -357,6 +357,21 @@ ExistentialToArchetype(o: o, t: c)
|
||||
ExistentialToArchetype(o: o, t: b)
|
||||
ExistentialToArchetype(o: o, t: o)
|
||||
|
||||
// Ensure that a downcast from an Optional source is not promoted to a
|
||||
// value cast. We could do the promotion, but the optimizer would need
|
||||
// to insert the Optional unwrapping logic before the cast.
|
||||
//
|
||||
// CHECK-LABEL: sil shared [noinline] @_TTSg5GSqC37specialize_unconditional_checked_cast1C__CS_1D___TF37specialize_unconditional_checked_cast15genericDownCastu0_rFTxMq__q_ : $@convention(thin) (@out D, @in Optional<C>, @thick D.Type) -> () {
|
||||
// CHECK: unconditional_checked_cast_addr take_always Optional<C> in %1 : $*Optional<C> to D in %0 : $*D
|
||||
@inline(never)
|
||||
public func genericDownCast<T, U>(a: T, _ : U.Type) -> U {
|
||||
return a as! U
|
||||
}
|
||||
|
||||
public func callGenericDownCast(c: C?) -> D {
|
||||
return genericDownCast(c, D.self)
|
||||
}
|
||||
|
||||
//order: -5
|
||||
// x -> y where y is a class but x is not.
|
||||
// CHECK-LABEL: sil shared [noinline] @_TTSf4d___TTSg5C37specialize_unconditional_checked_cast1C___TF37specialize_unconditional_checked_cast31ArchetypeToConcreteConvertUInt8
|
||||
|
||||
Reference in New Issue
Block a user