Skip to content

Commit

Permalink
Fix dynamic runtime casts of Optionals.
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
atrick committed Dec 10, 2015
1 parent a8a49af commit 35cb1af
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 1 deletion.
11 changes: 11 additions & 0 deletions stdlib/public/core/ImplicitlyUnwrappedOptional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions stdlib/public/core/OutputStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 46 additions & 1 deletion stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
126 changes: 126 additions & 0 deletions test/1_stdlib/Optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
15 changes: 15 additions & 0 deletions test/SILPasses/specialize_unconditional_checked_cast.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 35cb1af

Please sign in to comment.