Skip to content

Commit 2942b85

Browse files
authored
Merge pull request #4898 from DougGregor/tuple-casting
Runtime/standard library: fix tuple printing, reflection, and casting
2 parents ed9e01c + c9ebcc5 commit 2942b85

File tree

8 files changed

+257
-54
lines changed

8 files changed

+257
-54
lines changed

stdlib/public/core/OutputStream.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,16 +270,24 @@ internal func _adHocPrint_unlocked<T, TargetStream : TextOutputStream>(
270270
case .tuple:
271271
target.write("(")
272272
var first = true
273-
for (_, value) in mirror.children {
273+
for (label, value) in mirror.children {
274274
if first {
275275
first = false
276276
} else {
277277
target.write(", ")
278278
}
279+
280+
if let label = label {
281+
if !label.isEmpty && label[label.startIndex] != "." {
282+
target.write(label)
283+
target.write(": ")
284+
}
285+
}
286+
279287
_debugPrint_unlocked(value, &target)
280288
}
281289
target.write(")")
282-
case .`struct`:
290+
case .struct:
283291
printTypeName(mirror.subjectType)
284292
target.write("(")
285293
var first = true
@@ -296,7 +304,7 @@ internal func _adHocPrint_unlocked<T, TargetStream : TextOutputStream>(
296304
}
297305
}
298306
target.write(")")
299-
case .`enum`:
307+
case .enum:
300308
if let cString = _getEnumCaseName(value),
301309
let caseName = String(validatingUTF8: cString) {
302310
// Write the qualified type name in debugPrint.

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
/******************************************************************************/

stdlib/public/runtime/Reflection.mm

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,12 +372,30 @@ void swift_TupleMirror_subscript(String *outString,
372372

373373
if (i < 0 || (size_t)i > Tuple->NumElements)
374374
swift::crash("Swift mirror subscript bounds check failure");
375-
376-
// The name is the stringized element number '.0'.
377-
char buf[32];
378-
snprintf(buf, sizeof(buf), ".%zd", i);
379-
new (outString) String(buf, strlen(buf));
380-
375+
376+
// Determine whether there is a label.
377+
bool hasLabel = false;
378+
if (const char *labels = Tuple->Labels) {
379+
const char *space = strchr(labels, ' ');
380+
for (intptr_t j = 0; j != i && space; ++j) {
381+
labels = space + 1;
382+
space = strchr(labels, ' ');
383+
}
384+
385+
// If we have a label, create it.
386+
if (labels && space && labels != space) {
387+
new (outString) String(labels, space - labels);
388+
hasLabel = true;
389+
}
390+
}
391+
392+
if (!hasLabel) {
393+
// The name is the stringized element number '.0'.
394+
char buf[32];
395+
snprintf(buf, sizeof(buf), ".%zd", i);
396+
new (outString) String(buf, strlen(buf));
397+
}
398+
381399
// Get a Mirror for the nth element.
382400
auto &elt = Tuple->getElement(i);
383401
auto bytes = reinterpret_cast<const char*>(value);

test/Interpreter/tuple_casts.swift

Lines changed: 128 additions & 14 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
}
@@ -15,16 +19,126 @@ func anyToInt2(_ x: Any) -> (Int, Int) {
1519
return x as! (Int, Int)
1620
}
1721

18-
// Labels can be added/removed.
19-
print("Label add/remove") // CHECK: Label add/remove
20-
print(anyToIntPoint((x: 1, y: 2))) // CHECK-NEXT: (1, 2)
21-
print(anyToIntPoint((3, 4))) // CHECK-NEXT: (3, 4)
22-
print(anyToIntPoint((x: 5, 6))) // CHECK-NEXT: (5, 6)
23-
print(anyToInt2((1, 2))) // CHECK-NEXT: (1, 2)
24-
print(anyToInt2((x: 3, y: 4))) // CHECK-NEXT: (3, 4)
25-
print(anyToInt2((x: 5, 6))) // CHECK-NEXT: (5, 6)
22+
func anyToPartlyLabeled(_ x: Any) -> (first: Int, Int, third: Int) {
23+
return x as! (first: Int, Int, third: Int)
24+
}
25+
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+
}
26143

27-
// Labels cannot be wrong.
28-
print("Wrong labels") // CHECK: Wrong labels
29-
print(anyToIntPointOpt((x: 1, z: 2))) // CHECK-NEXT: nil
30-
print(anyToIntPointOpt((x: 1, y: 2))) // CHECK-NEXT: Optional((1, 2))
144+
runAllTests()

test/stdlib/DebuggerSupport.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ StringForPrintObjectTests.test("Array") {
7878

7979
StringForPrintObjectTests.test("Dictionary") {
8080
let printed = _DebuggerSupport.stringForPrintObject([1:2])
81-
expectEqual(printed, "▿ 1 element\n ▿ 0 : 2 elements\n - .0 : 1\n - .1 : 2\n")
81+
expectEqual("▿ 1 element\n ▿ 0 : 2 elements\n - key : 1\n - value : 2\n",
82+
printed)
8283
}
8384

8485
StringForPrintObjectTests.test("NilOptional") {

test/stdlib/Mirror.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -653,10 +653,10 @@ mirrors.test("Addressing") {
653653
expectEqual(2, m0.descendant(1) as? Int)
654654
expectEqual(3, m0.descendant(2) as? Int)
655655

656-
let m1 = Mirror(reflecting: (a: ["one", "two", "three"], b: 4))
656+
let m1 = Mirror(reflecting: (a: ["one", "two", "three"], 4))
657657
let ott0 = m1.descendant(0) as? [String]
658658
expectNotNil(ott0)
659-
let ott1 = m1.descendant(".0") as? [String]
659+
let ott1 = m1.descendant("a") as? [String]
660660
expectNotNil(ott1)
661661
if ott0 != nil && ott1 != nil {
662662
expectEqualSequence(ott0!, ott1!)
@@ -666,7 +666,7 @@ mirrors.test("Addressing") {
666666
expectEqual("one", m1.descendant(0, 0) as? String)
667667
expectEqual("two", m1.descendant(0, 1) as? String)
668668
expectEqual("three", m1.descendant(0, 2) as? String)
669-
expectEqual("one", m1.descendant(".0", 0) as? String)
669+
expectEqual("one", m1.descendant("a", 0) as? String)
670670

671671
struct Zee : CustomReflectable {
672672
var customMirror: Mirror {
@@ -680,7 +680,7 @@ mirrors.test("Addressing") {
680680
(a: [], b: Zee())]
681681

682682
let m = Mirror(reflecting: x)
683-
let two = m.descendant(0, ".0", 1)
683+
let two = m.descendant(0, "a", 1)
684684
expectEqual("two", two as? String)
685685
expectEqual(1, m.descendant(1, 1, "bark") as? Int)
686686
expectEqual(0, m.descendant(1, 1, "bite") as? Int)

0 commit comments

Comments
 (0)