Skip to content

Commit 35cb1af

Browse files
committed
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).
1 parent a8a49af commit 35cb1af

File tree

5 files changed

+208
-1
lines changed

5 files changed

+208
-1
lines changed

stdlib/public/core/ImplicitlyUnwrappedOptional.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ extension ImplicitlyUnwrappedOptional : CustomStringConvertible {
9595
}
9696
}
9797

98+
/// Directly conform to CustomDebugStringConvertible to support
99+
/// optional printing. Implementation of that feature relies on
100+
/// _isOptional thus cannot distinguish ImplicitlyUnwrappedOptional
101+
/// from Optional. When conditional conformance is available, this
102+
/// outright conformance can be removed.
103+
extension ImplicitlyUnwrappedOptional : CustomDebugStringConvertible {
104+
public var debugDescription: String {
105+
return description
106+
}
107+
}
108+
98109
@_transparent
99110
@warn_unused_result
100111
public // COMPILER_INTRINSIC

stdlib/public/core/OutputStream.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,16 @@ internal func _adHocPrint<T, TargetStream : OutputStreamType>(
165165
internal func _print_unlocked<T, TargetStream : OutputStreamType>(
166166
value: T, inout _ target: TargetStream
167167
) {
168+
// Optional has no representation suitable for display; therefore,
169+
// values of optional type should be printed as a debug
170+
// string. Check for Optional first, before checking protocol
171+
// conformance below, because an Optional value is convertible to a
172+
// protocol if its wrapped type conforms to that protocol.
173+
if _isOptional(value.dynamicType) {
174+
let debugPrintable = value as! CustomDebugStringConvertible
175+
debugPrintable.debugDescription.writeTo(&target)
176+
return
177+
}
168178
if case let streamableObject as Streamable = value {
169179
streamableObject.writeTo(&target)
170180
return

stdlib/public/runtime/Casting.cpp

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1990,13 +1990,59 @@ static id dynamicCastValueToNSError(OpaqueValue *src,
19901990
}
19911991
#endif
19921992

1993+
static bool canCastToExistential(OpaqueValue *dest, OpaqueValue *src,
1994+
const Metadata *srcType,
1995+
const Metadata *targetType) {
1996+
if (targetType->getKind() != MetadataKind::Existential)
1997+
return false;
1998+
1999+
return _dynamicCastToExistential(dest, src, srcType,
2000+
cast<ExistentialTypeMetadata>(targetType),
2001+
DynamicCastFlags::Default);
2002+
}
2003+
19932004
/// Perform a dynamic cast to an arbitrary type.
19942005
bool swift::swift_dynamicCast(OpaqueValue *dest,
19952006
OpaqueValue *src,
19962007
const Metadata *srcType,
19972008
const Metadata *targetType,
19982009
DynamicCastFlags flags) {
2010+
// Check if the cast source is Optional and the target is not an existential
2011+
// that Optional conforms to. Unwrap one level of Optional and continue.
2012+
if (srcType->getKind() == MetadataKind::Optional
2013+
&& !canCastToExistential(dest, src, srcType, targetType)) {
2014+
const Metadata *payloadType =
2015+
cast<EnumMetadata>(srcType)->getGenericArgs()[0];
2016+
int enumCase =
2017+
swift_getEnumCaseSinglePayload(src, payloadType, 1 /*emptyCases=*/);
2018+
if (enumCase != -1) {
2019+
// Allow Optional<T>.None -> Optional<U>.None
2020+
if (targetType->getKind() != MetadataKind::Optional)
2021+
return _fail(src, srcType, targetType, flags);
2022+
// Inject the .None tag
2023+
swift_storeEnumTagSinglePayload(dest, payloadType, enumCase,
2024+
1 /*emptyCases=*/);
2025+
return _succeed(dest, src, srcType, flags);
2026+
}
2027+
// .Some
2028+
// Single payload enums are guaranteed layout compatible with their
2029+
// payload. Only the source's payload needs to be taken or destroyed.
2030+
srcType = payloadType;
2031+
}
2032+
19992033
switch (targetType->getKind()) {
2034+
// Handle wrapping an Optional target.
2035+
case MetadataKind::Optional: {
2036+
// Recursively cast into the layout compatible payload area.
2037+
const Metadata *payloadType =
2038+
cast<EnumMetadata>(targetType)->getGenericArgs()[0];
2039+
if (swift_dynamicCast(dest, src, srcType, payloadType, flags)) {
2040+
swift_storeEnumTagSinglePayload(dest, payloadType, -1 /*case*/,
2041+
1 /*emptyCases*/);
2042+
return true;
2043+
}
2044+
return false;
2045+
}
20002046

20012047
// Casts to class type.
20022048
case MetadataKind::Class:
@@ -2089,7 +2135,6 @@ bool swift::swift_dynamicCast(OpaqueValue *dest,
20892135

20902136
case MetadataKind::Struct:
20912137
case MetadataKind::Enum:
2092-
case MetadataKind::Optional:
20932138
switch (srcType->getKind()) {
20942139
case MetadataKind::Class:
20952140
case MetadataKind::ObjCClassWrapper:

test/1_stdlib/Optional.swift

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,130 @@ OptionalTests.test("flatMap") {
237237
expectEmpty((3 as Int32?).flatMap(half))
238238
}
239239

240+
@inline(never)
241+
func anyToAny<T, U>(a: T, _ : U.Type) -> U {
242+
return a as! U
243+
}
244+
@inline(never)
245+
func anyToAnyOrNil<T, U>(a: T, _ : U.Type) -> U? {
246+
return a as? U
247+
}
248+
func canGenericCast<T, U>(a: T, _ ty : U.Type) -> Bool {
249+
return anyToAnyOrNil(a, ty) != nil
250+
}
251+
252+
OptionalTests.test("Casting Optional") {
253+
let x = C()
254+
let sx : C? = x
255+
let nx : C? = nil
256+
expectTrue(anyToAny(x, Optional<C>.self)! === x)
257+
expectTrue(anyToAny(sx, C.self) === x)
258+
expectTrue(anyToAny(sx, Optional<C>.self)! === x)
259+
260+
expectTrue(anyToAny(nx, Optional<C>.self) == nil)
261+
expectTrue(anyToAnyOrNil(nx, C.self) == nil)
262+
263+
let i = Int.max
264+
let si : Int? = Int.max
265+
let ni : Int? = nil
266+
expectEqual(anyToAny(i, Optional<Int>.self)!, Int.max)
267+
expectEqual(anyToAny(si, Int.self), Int.max)
268+
expectEqual(anyToAny(si, Optional<Int>.self)!, Int.max)
269+
270+
expectTrue(anyToAny(ni, Optional<Int>.self) == nil)
271+
expectTrue(anyToAnyOrNil(ni, Int.self) == nil)
272+
273+
let ssx : C?? = sx
274+
expectTrue(anyToAny(ssx, Optional<C>.self)! === x)
275+
expectTrue(anyToAny(x, Optional<Optional<C>>.self)!! === x)
276+
expectTrue(anyToAnyOrNil(ni, Int.self) == nil)
277+
}
278+
279+
OptionalTests.test("Casting Optional Traps") {
280+
let nx : C? = nil
281+
expectCrashLater()
282+
anyToAny(nx, Int.self)
283+
}
284+
285+
class TestNoString {}
286+
class TestString : CustomStringConvertible, CustomDebugStringConvertible {
287+
var description: String {
288+
return "AString"
289+
}
290+
var debugDescription: String {
291+
return "XString"
292+
}
293+
}
294+
class TestStream : Streamable {
295+
func writeTo<Target : OutputStreamType>(inout target: Target) {
296+
target.write("AStream")
297+
}
298+
}
299+
300+
func debugPrintStr<T>(a: T) -> String {
301+
var s = ""
302+
debugPrint(a, terminator: "", toStream: &s)
303+
return s
304+
}
305+
// Optional should not conform to output stream protocols itself, but is
306+
// convertible to them if its wrapped type is.
307+
// Furthermore, printing an Optional should always print the debug
308+
// description regardless of whether the wrapper type conforms to an
309+
// output stream protocol.
310+
OptionalTests.test("Optional OutputStream") {
311+
let optNoString : TestNoString? = TestNoString()
312+
expectFalse(optNoString is CustomStringConvertible)
313+
expectFalse(canGenericCast(optNoString, CustomStringConvertible.self))
314+
expectFalse(optNoString is Streamable)
315+
expectFalse(canGenericCast(optNoString, Streamable.self))
316+
expectTrue(optNoString is CustomDebugStringConvertible)
317+
expectTrue(canGenericCast(optNoString, CustomDebugStringConvertible.self))
318+
expectEqual(String(optNoString), "Optional(main.TestNoString)")
319+
expectEqual(debugPrintStr(optNoString), "Optional(main.TestNoString)")
320+
321+
let iouNoString : TestNoString! = TestNoString()
322+
// IOU directly conforms to CustomStringConvertible.
323+
// Disabled pending SR-164
324+
// expectTrue(iouNoString is CustomStringConvertible)
325+
expectTrue(canGenericCast(iouNoString, CustomStringConvertible.self))
326+
expectFalse(iouNoString is Streamable)
327+
expectFalse(canGenericCast(iouNoString, Streamable.self))
328+
// CustomDebugStringConvertible conformance is a temporary hack.
329+
// Disabled pending SR-164
330+
// expectTrue(iouNoString is CustomDebugStringConvertible)
331+
expectTrue(canGenericCast(iouNoString, CustomDebugStringConvertible.self))
332+
expectEqual(String(iouNoString), "main.TestNoString")
333+
expectEqual(debugPrintStr(iouNoString), "main.TestNoString")
334+
335+
let optString : TestString? = TestString()
336+
expectTrue(optString is CustomStringConvertible)
337+
expectTrue(canGenericCast(optString, CustomStringConvertible.self))
338+
expectTrue(optString is CustomDebugStringConvertible)
339+
expectTrue(canGenericCast(optString, CustomDebugStringConvertible.self))
340+
expectEqual(String(TestString()), "AString")
341+
expectEqual(String(optString), "Optional(XString)")
342+
expectEqual(debugPrintStr(optString), "Optional(XString)")
343+
344+
let iouString : TestString! = TestString()
345+
expectTrue(iouString is CustomStringConvertible)
346+
expectTrue(canGenericCast(iouString, CustomStringConvertible.self))
347+
// CustomDebugStringConvertible conformance is a temporary hack.
348+
expectTrue(iouString is CustomDebugStringConvertible)
349+
expectTrue(canGenericCast(iouString, CustomDebugStringConvertible.self))
350+
expectEqual(String(iouString), "AString")
351+
// FIXME: Ideally the debug output would be "XString", but a reasonable
352+
// implemention of that behavior requires conditional conformance.
353+
// (directly invoking debugPrint(Any) already works correctly).
354+
expectEqual(debugPrintStr(iouString), "AString")
355+
356+
let optStream : TestStream? = TestStream()
357+
expectTrue(optStream is Streamable)
358+
expectTrue(canGenericCast(optStream, Streamable.self))
359+
expectTrue(optStream is CustomDebugStringConvertible)
360+
expectTrue(canGenericCast(optStream, CustomDebugStringConvertible.self))
361+
expectEqual(String(TestStream()), "AStream")
362+
expectEqual(String(optStream), "Optional(AStream)")
363+
expectEqual(debugPrintStr(optStream), "Optional(AStream)")
364+
}
365+
240366
runAllTests()

test/SILPasses/specialize_unconditional_checked_cast.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,21 @@ ExistentialToArchetype(o: o, t: c)
357357
ExistentialToArchetype(o: o, t: b)
358358
ExistentialToArchetype(o: o, t: o)
359359

360+
// Ensure that a downcast from an Optional source is not promoted to a
361+
// value cast. We could do the promotion, but the optimizer would need
362+
// to insert the Optional unwrapping logic before the cast.
363+
//
364+
// 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) -> () {
365+
// CHECK: unconditional_checked_cast_addr take_always Optional<C> in %1 : $*Optional<C> to D in %0 : $*D
366+
@inline(never)
367+
public func genericDownCast<T, U>(a: T, _ : U.Type) -> U {
368+
return a as! U
369+
}
370+
371+
public func callGenericDownCast(c: C?) -> D {
372+
return genericDownCast(c, D.self)
373+
}
374+
360375
//order: -5
361376
// x -> y where y is a class but x is not.
362377
// CHECK-LABEL: sil shared [noinline] @_TTSf4d___TTSg5C37specialize_unconditional_checked_cast1C___TF37specialize_unconditional_checked_cast31ArchetypeToConcreteConvertUInt8

0 commit comments

Comments
 (0)