Skip to content

Commit c9ebcc5

Browse files
committed
[Runtime] Generalize dynamic casting between tuple types.
This allows dynamic casting to succeed between tuple types with different element types, converting each element in turn. Fixes rdar://problem/19892202.
1 parent fcacd08 commit c9ebcc5

File tree

2 files changed

+191
-20
lines changed

2 files changed

+191
-20
lines changed

stdlib/public/runtime/Casting.cpp

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2513,6 +2513,7 @@ static bool _dynamicCastTupleToTuple(OpaqueValue *destination,
25132513
// Check that the elements line up.
25142514
const char *sourceLabels = sourceType->Labels;
25152515
const char *targetLabels = targetType->Labels;
2516+
bool anyTypeMismatches = false;
25162517
for (unsigned i = 0, n = sourceType->NumElements; i != n; ++i) {
25172518
// Check the label, if there is one.
25182519
if (sourceLabels && targetLabels && sourceLabels != targetLabels) {
@@ -2533,13 +2534,74 @@ static bool _dynamicCastTupleToTuple(OpaqueValue *destination,
25332534
targetLabels = targetSpace ? targetSpace + 1 : nullptr;
25342535
}
25352536

2536-
// Make sure the types match exactly.
2537-
// FIXME: we should dynamically cast the elements themselves.
2537+
// If the types don't match exactly, make a note of it. We'll try to
2538+
// convert them in a second pass.
25382539
if (sourceType->getElement(i).Type != targetType->getElement(i).Type)
2539-
return _fail(source, sourceType, targetType, flags);
2540+
anyTypeMismatches = true;
2541+
}
2542+
2543+
// If there were no type mismatches, the only difference was in the argument
2544+
// labels. We can directly map from the source to the destination type.
2545+
if (!anyTypeMismatches)
2546+
return _succeed(destination, source, targetType, flags);
2547+
2548+
// Determine how the individual elements will get casted.
2549+
// If both success and failure will destroy the source, then
2550+
// we can be destructively cast each element. Otherwise, we'll have to
2551+
// manually handle them.
2552+
const DynamicCastFlags alwaysDestroySourceFlags =
2553+
DynamicCastFlags::TakeOnSuccess | DynamicCastFlags::DestroyOnFailure;
2554+
bool alwaysDestroysSource =
2555+
(static_cast<DynamicCastFlags>(flags & alwaysDestroySourceFlags)
2556+
== alwaysDestroySourceFlags);
2557+
DynamicCastFlags elementFlags = flags;
2558+
if (!alwaysDestroysSource)
2559+
elementFlags = elementFlags - alwaysDestroySourceFlags;
2560+
2561+
// Local function to destroy the elements in the range [start, end).
2562+
auto destroyRange = [](const TupleTypeMetadata *type, OpaqueValue *value,
2563+
unsigned start, unsigned end) {
2564+
assert(start <= end && "invalid range in destroyRange");
2565+
for (unsigned i = start; i != end; ++i) {
2566+
const auto &elt = type->getElement(i);
2567+
elt.Type->vw_destroy(elt.findIn(value));
2568+
}
2569+
};
2570+
2571+
// Cast each element.
2572+
for (unsigned i = 0, n = sourceType->NumElements; i != n; ++i) {
2573+
// Cast the element. If it succeeds, keep going.
2574+
const auto &sourceElt = sourceType->getElement(i);
2575+
const auto &targetElt = targetType->getElement(i);
2576+
if (swift_dynamicCast(targetElt.findIn(destination),
2577+
sourceElt.findIn(source),
2578+
sourceElt.Type, targetElt.Type, elementFlags))
2579+
continue;
2580+
2581+
// Casting failed, so clean up.
2582+
2583+
// Destroy all of the elements that got casted into the destination buffer.
2584+
destroyRange(targetType, destination, 0, i);
2585+
2586+
// If we're supposed to destroy on failure, destroy any elements from the
2587+
// source buffer that haven't been destroyed/taken from yet.
2588+
if (flags & DynamicCastFlags::DestroyOnFailure)
2589+
destroyRange(sourceType, source, alwaysDestroysSource ? i : 0, n);
2590+
2591+
// If an unconditional cast failed, complain.
2592+
if (flags & DynamicCastFlags::Unconditional)
2593+
swift_dynamicCastFailure(sourceType, targetType);
2594+
return false;
25402595
}
25412596

2542-
return _succeed(destination, source, targetType, flags);
2597+
// Casting succeeded.
2598+
2599+
// If we were supposed to take on success from the source buffer but couldn't
2600+
// before, destroy the source buffer now.
2601+
if (!alwaysDestroysSource && (flags & DynamicCastFlags::TakeOnSuccess))
2602+
destroyRange(sourceType, source, 0, sourceType->NumElements);
2603+
2604+
return true;
25432605
}
25442606

25452607
/******************************************************************************/

test/Interpreter/tuple_casts.swift

Lines changed: 125 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
// RUN: %target-run-simple-swift | %FileCheck %s
1+
// RUN: %target-run-simple-swift
22
// RUN: %target-build-swift -O %s -o %t/a.out.optimized
3-
// RUN: %target-run %t/a.out.optimized | %FileCheck %s
3+
// RUN: %target-run %t/a.out.optimized
44
// REQUIRES: executable_test
55

6+
import StdlibUnittest
7+
8+
let tupleCastTests = TestSuite("Tuple casting")
9+
610
func anyToIntPoint(_ x: Any) -> (x: Int, y: Int) {
711
return x as! (x: Int, y: Int)
812
}
@@ -19,17 +23,122 @@ func anyToPartlyLabeled(_ x: Any) -> (first: Int, Int, third: Int) {
1923
return x as! (first: Int, Int, third: Int)
2024
}
2125

22-
// Labels can be added/removed.
23-
print("Label add/remove") // CHECK: Label add/remove
24-
print(anyToIntPoint((x: 1, y: 2))) // CHECK-NEXT: (x: 1, y: 2)
25-
print(anyToIntPoint((3, 4))) // CHECK-NEXT: (x: 3, y: 4)
26-
print(anyToIntPoint((x: 5, 6))) // CHECK-NEXT: (x: 5, y: 6)
27-
print(anyToInt2((1, 2))) // CHECK-NEXT: (1, 2)
28-
print(anyToInt2((x: 3, y: 4))) // CHECK-NEXT: (3, 4)
29-
print(anyToInt2((x: 5, 6))) // CHECK-NEXT: (5, 6)
30-
print(anyToPartlyLabeled((1, 2, 3))) // CHECK-NEXT: (first: 1, 2, third: 3)
31-
32-
// Labels cannot be wrong.
33-
print("Wrong labels") // CHECK: Wrong labels
34-
print(anyToIntPointOpt((x: 1, z: 2))) // CHECK-NEXT: nil
35-
print(anyToIntPointOpt((x: 1, y: 2))) // CHECK-NEXT: Optional((x: 1, y: 2))
26+
tupleCastTests.test("Adding/removing labels") {
27+
expectEqual("(x: 1, y: 2)",
28+
String(describing: anyToIntPoint((x: 1, y: 2))))
29+
expectEqual("(x: 3, y: 4)",
30+
String(describing: anyToIntPoint((3, 4))))
31+
expectEqual("(x: 5, y: 6)",
32+
String(describing: anyToIntPoint((x: 5, 6))))
33+
34+
expectEqual("(1, 2)", String(describing: anyToInt2((1, 2))))
35+
expectEqual("(3, 4)", String(describing: anyToInt2((x: 3, y: 4))))
36+
expectEqual("(5, 6)", String(describing: anyToInt2((x: 5, 6))))
37+
38+
expectEqual("(first: 1, 2, third: 3)",
39+
String(describing: anyToPartlyLabeled((1, 2, 3))))
40+
}
41+
42+
tupleCastTests.test("Incorrect labels conditional cast") {
43+
expectNil(anyToIntPointOpt((x: 1, z: 2)))
44+
expectEqual("Optional((x: 1, y: 2))",
45+
String(describing: anyToIntPointOpt((x: 1, y: 2))))
46+
}
47+
48+
tupleCastTests
49+
.test("Incorrect labels forced cast")
50+
.crashOutputMatches("Could not cast value of type '(x: Swift.Int, z: Swift.Int)'")
51+
.code {
52+
expectCrashLater()
53+
_ = anyToIntPoint((x: 1, z: 2))
54+
}
55+
56+
func castToThree<T, U, V>(_ x: Any, _: T.Type, _: U.Type, _: V.Type)
57+
-> (t: T, u: U, v: V) {
58+
return x as! (t: T, u: U, v: V)
59+
}
60+
61+
func castToThreeOpt<T, U, V>(_ x: Any, _: T.Type, _: U.Type, _: V.Type)
62+
-> (t: T, u: U, v: V)? {
63+
return x as? (t: T, u: U, v: V)
64+
}
65+
66+
class LifetimeA {
67+
var tracked: LifetimeTracked
68+
69+
init(value: Int) {
70+
tracked = LifetimeTracked(value)
71+
}
72+
}
73+
74+
class LifetimeB : LifetimeA {
75+
}
76+
77+
class LifetimeC : LifetimeA {
78+
}
79+
80+
protocol P { }
81+
extension LifetimeA : P { }
82+
83+
tupleCastTests.test("Elementwise tuple casts that succeed") {
84+
let abc: (P, Any, P) = (LifetimeA(value: 1),
85+
LifetimeB(value: 2),
86+
LifetimeC(value: 3))
87+
88+
expectEqual(
89+
"(t: main.LifetimeA, u: main.LifetimeB, v: main.LifetimeC)",
90+
String(describing: castToThree(abc, LifetimeA.self, LifetimeA.self,
91+
LifetimeA.self)))
92+
93+
expectEqual(
94+
"(t: main.LifetimeA, u: main.LifetimeB, v: main.LifetimeC)",
95+
String(describing: castToThree((LifetimeA(value: 1),
96+
LifetimeB(value: 2),
97+
LifetimeC(value: 3)) as (P, Any, P),
98+
LifetimeA.self, LifetimeA.self,
99+
LifetimeA.self)))
100+
}
101+
102+
tupleCastTests.test("Elementwise tuple casts that conditionally fail") {
103+
let abc: (P, Any, P) = (LifetimeA(value: 1),
104+
LifetimeB(value: 2),
105+
LifetimeC(value: 3))
106+
expectNil(castToThreeOpt(abc, LifetimeA.self, LifetimeB.self, LifetimeB.self))
107+
expectNil(castToThreeOpt(abc, LifetimeA.self, LifetimeC.self, LifetimeC.self))
108+
expectNil(castToThreeOpt(abc, LifetimeC.self, LifetimeB.self, LifetimeC.self))
109+
}
110+
111+
tupleCastTests
112+
.test("Elementwise tuple casts that crash (1/3)")
113+
.crashOutputMatches("Could not cast value of type 'main.LifetimeA'")
114+
.code {
115+
let abc: (P, Any, P) = (LifetimeA(value: 1),
116+
LifetimeB(value: 2),
117+
LifetimeC(value: 3))
118+
expectCrashLater()
119+
_ = castToThree(abc, LifetimeC.self, LifetimeB.self, LifetimeC.self)
120+
}
121+
122+
tupleCastTests
123+
.test("Elementwise tuple casts that crash (2/3)")
124+
.crashOutputMatches("Could not cast value of type 'main.LifetimeB")
125+
.code {
126+
let abc: (P, Any, P) = (LifetimeA(value: 1),
127+
LifetimeB(value: 2),
128+
LifetimeC(value: 3))
129+
expectCrashLater()
130+
_ = castToThree(abc, LifetimeA.self, LifetimeC.self, LifetimeC.self)
131+
}
132+
133+
tupleCastTests
134+
.test("Elementwise tuple casts that crash (3/3)")
135+
.crashOutputMatches("Could not cast value of type 'main.LifetimeC")
136+
.code {
137+
let abc: (P, Any, P) = (LifetimeA(value: 1),
138+
LifetimeB(value: 2),
139+
LifetimeC(value: 3))
140+
expectCrashLater()
141+
_ = castToThree(abc, LifetimeA.self, LifetimeB.self, LifetimeB.self)
142+
}
143+
144+
runAllTests()

0 commit comments

Comments
 (0)