Skip to content

Runtime/standard library: fix tuple printing, reflection, and casting #4898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions stdlib/public/core/OutputStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,16 +270,24 @@ internal func _adHocPrint_unlocked<T, TargetStream : TextOutputStream>(
case .tuple:
target.write("(")
var first = true
for (_, value) in mirror.children {
for (label, value) in mirror.children {
if first {
first = false
} else {
target.write(", ")
}

if let label = label {
if !label.isEmpty && label[label.startIndex] != "." {
target.write(label)
target.write(": ")
}
}

_debugPrint_unlocked(value, &target)
}
target.write(")")
case .`struct`:
case .struct:
printTypeName(mirror.subjectType)
target.write("(")
var first = true
Expand All @@ -296,7 +304,7 @@ internal func _adHocPrint_unlocked<T, TargetStream : TextOutputStream>(
}
}
target.write(")")
case .`enum`:
case .enum:
if let cString = _getEnumCaseName(value),
let caseName = String(validatingUTF8: cString) {
// Write the qualified type name in debugPrint.
Expand Down
70 changes: 66 additions & 4 deletions stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2513,6 +2513,7 @@ static bool _dynamicCastTupleToTuple(OpaqueValue *destination,
// Check that the elements line up.
const char *sourceLabels = sourceType->Labels;
const char *targetLabels = targetType->Labels;
bool anyTypeMismatches = false;
for (unsigned i = 0, n = sourceType->NumElements; i != n; ++i) {
// Check the label, if there is one.
if (sourceLabels && targetLabels && sourceLabels != targetLabels) {
Expand All @@ -2533,13 +2534,74 @@ static bool _dynamicCastTupleToTuple(OpaqueValue *destination,
targetLabels = targetSpace ? targetSpace + 1 : nullptr;
}

// Make sure the types match exactly.
// FIXME: we should dynamically cast the elements themselves.
// If the types don't match exactly, make a note of it. We'll try to
// convert them in a second pass.
if (sourceType->getElement(i).Type != targetType->getElement(i).Type)
return _fail(source, sourceType, targetType, flags);
anyTypeMismatches = true;
}

// If there were no type mismatches, the only difference was in the argument
// labels. We can directly map from the source to the destination type.
if (!anyTypeMismatches)
return _succeed(destination, source, targetType, flags);

// Determine how the individual elements will get casted.
// If both success and failure will destroy the source, then
// we can be destructively cast each element. Otherwise, we'll have to
// manually handle them.
const DynamicCastFlags alwaysDestroySourceFlags =
DynamicCastFlags::TakeOnSuccess | DynamicCastFlags::DestroyOnFailure;
bool alwaysDestroysSource =
(static_cast<DynamicCastFlags>(flags & alwaysDestroySourceFlags)
== alwaysDestroySourceFlags);
DynamicCastFlags elementFlags = flags;
if (!alwaysDestroysSource)
elementFlags = elementFlags - alwaysDestroySourceFlags;

// Local function to destroy the elements in the range [start, end).
auto destroyRange = [](const TupleTypeMetadata *type, OpaqueValue *value,
unsigned start, unsigned end) {
assert(start <= end && "invalid range in destroyRange");
for (unsigned i = start; i != end; ++i) {
const auto &elt = type->getElement(i);
elt.Type->vw_destroy(elt.findIn(value));
}
};

// Cast each element.
for (unsigned i = 0, n = sourceType->NumElements; i != n; ++i) {
// Cast the element. If it succeeds, keep going.
const auto &sourceElt = sourceType->getElement(i);
const auto &targetElt = targetType->getElement(i);
if (swift_dynamicCast(targetElt.findIn(destination),
sourceElt.findIn(source),
sourceElt.Type, targetElt.Type, elementFlags))
continue;

// Casting failed, so clean up.

// Destroy all of the elements that got casted into the destination buffer.
destroyRange(targetType, destination, 0, i);

// If we're supposed to destroy on failure, destroy any elements from the
// source buffer that haven't been destroyed/taken from yet.
if (flags & DynamicCastFlags::DestroyOnFailure)
destroyRange(sourceType, source, alwaysDestroysSource ? i : 0, n);

// If an unconditional cast failed, complain.
if (flags & DynamicCastFlags::Unconditional)
swift_dynamicCastFailure(sourceType, targetType);
return false;
}

return _succeed(destination, source, targetType, flags);
// Casting succeeded.

// If we were supposed to take on success from the source buffer but couldn't
// before, destroy the source buffer now.
if (!alwaysDestroysSource && (flags & DynamicCastFlags::TakeOnSuccess))
destroyRange(sourceType, source, 0, sourceType->NumElements);

return true;
}

/******************************************************************************/
Expand Down
30 changes: 24 additions & 6 deletions stdlib/public/runtime/Reflection.mm
Original file line number Diff line number Diff line change
Expand Up @@ -372,12 +372,30 @@ void swift_TupleMirror_subscript(String *outString,

if (i < 0 || (size_t)i > Tuple->NumElements)
swift::crash("Swift mirror subscript bounds check failure");

// The name is the stringized element number '.0'.
char buf[32];
snprintf(buf, sizeof(buf), ".%zd", i);
new (outString) String(buf, strlen(buf));


// Determine whether there is a label.
bool hasLabel = false;
if (const char *labels = Tuple->Labels) {
const char *space = strchr(labels, ' ');
for (intptr_t j = 0; j != i && space; ++j) {
labels = space + 1;
space = strchr(labels, ' ');
}

// If we have a label, create it.
if (labels && space && labels != space) {
new (outString) String(labels, space - labels);
hasLabel = true;
}
}

if (!hasLabel) {
// The name is the stringized element number '.0'.
char buf[32];
snprintf(buf, sizeof(buf), ".%zd", i);
new (outString) String(buf, strlen(buf));
}

// Get a Mirror for the nth element.
auto &elt = Tuple->getElement(i);
auto bytes = reinterpret_cast<const char*>(value);
Expand Down
142 changes: 128 additions & 14 deletions test/Interpreter/tuple_casts.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// RUN: %target-run-simple-swift | %FileCheck %s
// RUN: %target-run-simple-swift
// RUN: %target-build-swift -O %s -o %t/a.out.optimized
// RUN: %target-run %t/a.out.optimized | %FileCheck %s
// RUN: %target-run %t/a.out.optimized
// REQUIRES: executable_test

import StdlibUnittest

let tupleCastTests = TestSuite("Tuple casting")

func anyToIntPoint(_ x: Any) -> (x: Int, y: Int) {
return x as! (x: Int, y: Int)
}
Expand All @@ -15,16 +19,126 @@ func anyToInt2(_ x: Any) -> (Int, Int) {
return x as! (Int, Int)
}

// Labels can be added/removed.
print("Label add/remove") // CHECK: Label add/remove
print(anyToIntPoint((x: 1, y: 2))) // CHECK-NEXT: (1, 2)
print(anyToIntPoint((3, 4))) // CHECK-NEXT: (3, 4)
print(anyToIntPoint((x: 5, 6))) // CHECK-NEXT: (5, 6)
print(anyToInt2((1, 2))) // CHECK-NEXT: (1, 2)
print(anyToInt2((x: 3, y: 4))) // CHECK-NEXT: (3, 4)
print(anyToInt2((x: 5, 6))) // CHECK-NEXT: (5, 6)
func anyToPartlyLabeled(_ x: Any) -> (first: Int, Int, third: Int) {
return x as! (first: Int, Int, third: Int)
}

tupleCastTests.test("Adding/removing labels") {
expectEqual("(x: 1, y: 2)",
String(describing: anyToIntPoint((x: 1, y: 2))))
expectEqual("(x: 3, y: 4)",
String(describing: anyToIntPoint((3, 4))))
expectEqual("(x: 5, y: 6)",
String(describing: anyToIntPoint((x: 5, 6))))

expectEqual("(1, 2)", String(describing: anyToInt2((1, 2))))
expectEqual("(3, 4)", String(describing: anyToInt2((x: 3, y: 4))))
expectEqual("(5, 6)", String(describing: anyToInt2((x: 5, 6))))

expectEqual("(first: 1, 2, third: 3)",
String(describing: anyToPartlyLabeled((1, 2, 3))))
}

tupleCastTests.test("Incorrect labels conditional cast") {
expectNil(anyToIntPointOpt((x: 1, z: 2)))
expectEqual("Optional((x: 1, y: 2))",
String(describing: anyToIntPointOpt((x: 1, y: 2))))
}

tupleCastTests
.test("Incorrect labels forced cast")
.crashOutputMatches("Could not cast value of type '(x: Swift.Int, z: Swift.Int)'")
.code {
expectCrashLater()
_ = anyToIntPoint((x: 1, z: 2))
}

func castToThree<T, U, V>(_ x: Any, _: T.Type, _: U.Type, _: V.Type)
-> (t: T, u: U, v: V) {
return x as! (t: T, u: U, v: V)
}

func castToThreeOpt<T, U, V>(_ x: Any, _: T.Type, _: U.Type, _: V.Type)
-> (t: T, u: U, v: V)? {
return x as? (t: T, u: U, v: V)
}

class LifetimeA {
var tracked: LifetimeTracked

init(value: Int) {
tracked = LifetimeTracked(value)
}
}

class LifetimeB : LifetimeA {
}

class LifetimeC : LifetimeA {
}

protocol P { }
extension LifetimeA : P { }

tupleCastTests.test("Elementwise tuple casts that succeed") {
let abc: (P, Any, P) = (LifetimeA(value: 1),
LifetimeB(value: 2),
LifetimeC(value: 3))

expectEqual(
"(t: main.LifetimeA, u: main.LifetimeB, v: main.LifetimeC)",
String(describing: castToThree(abc, LifetimeA.self, LifetimeA.self,
LifetimeA.self)))

expectEqual(
"(t: main.LifetimeA, u: main.LifetimeB, v: main.LifetimeC)",
String(describing: castToThree((LifetimeA(value: 1),
LifetimeB(value: 2),
LifetimeC(value: 3)) as (P, Any, P),
LifetimeA.self, LifetimeA.self,
LifetimeA.self)))
}

tupleCastTests.test("Elementwise tuple casts that conditionally fail") {
let abc: (P, Any, P) = (LifetimeA(value: 1),
LifetimeB(value: 2),
LifetimeC(value: 3))
expectNil(castToThreeOpt(abc, LifetimeA.self, LifetimeB.self, LifetimeB.self))
expectNil(castToThreeOpt(abc, LifetimeA.self, LifetimeC.self, LifetimeC.self))
expectNil(castToThreeOpt(abc, LifetimeC.self, LifetimeB.self, LifetimeC.self))
}

tupleCastTests
.test("Elementwise tuple casts that crash (1/3)")
.crashOutputMatches("Could not cast value of type 'main.LifetimeA'")
.code {
let abc: (P, Any, P) = (LifetimeA(value: 1),
LifetimeB(value: 2),
LifetimeC(value: 3))
expectCrashLater()
_ = castToThree(abc, LifetimeC.self, LifetimeB.self, LifetimeC.self)
}

tupleCastTests
.test("Elementwise tuple casts that crash (2/3)")
.crashOutputMatches("Could not cast value of type 'main.LifetimeB")
.code {
let abc: (P, Any, P) = (LifetimeA(value: 1),
LifetimeB(value: 2),
LifetimeC(value: 3))
expectCrashLater()
_ = castToThree(abc, LifetimeA.self, LifetimeC.self, LifetimeC.self)
}

tupleCastTests
.test("Elementwise tuple casts that crash (3/3)")
.crashOutputMatches("Could not cast value of type 'main.LifetimeC")
.code {
let abc: (P, Any, P) = (LifetimeA(value: 1),
LifetimeB(value: 2),
LifetimeC(value: 3))
expectCrashLater()
_ = castToThree(abc, LifetimeA.self, LifetimeB.self, LifetimeB.self)
}

// Labels cannot be wrong.
print("Wrong labels") // CHECK: Wrong labels
print(anyToIntPointOpt((x: 1, z: 2))) // CHECK-NEXT: nil
print(anyToIntPointOpt((x: 1, y: 2))) // CHECK-NEXT: Optional((1, 2))
runAllTests()
3 changes: 2 additions & 1 deletion test/stdlib/DebuggerSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ StringForPrintObjectTests.test("Array") {

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

StringForPrintObjectTests.test("NilOptional") {
Expand Down
8 changes: 4 additions & 4 deletions test/stdlib/Mirror.swift
Original file line number Diff line number Diff line change
Expand Up @@ -653,10 +653,10 @@ mirrors.test("Addressing") {
expectEqual(2, m0.descendant(1) as? Int)
expectEqual(3, m0.descendant(2) as? Int)

let m1 = Mirror(reflecting: (a: ["one", "two", "three"], b: 4))
let m1 = Mirror(reflecting: (a: ["one", "two", "three"], 4))
let ott0 = m1.descendant(0) as? [String]
expectNotNil(ott0)
let ott1 = m1.descendant(".0") as? [String]
let ott1 = m1.descendant("a") as? [String]
expectNotNil(ott1)
if ott0 != nil && ott1 != nil {
expectEqualSequence(ott0!, ott1!)
Expand All @@ -666,7 +666,7 @@ mirrors.test("Addressing") {
expectEqual("one", m1.descendant(0, 0) as? String)
expectEqual("two", m1.descendant(0, 1) as? String)
expectEqual("three", m1.descendant(0, 2) as? String)
expectEqual("one", m1.descendant(".0", 0) as? String)
expectEqual("one", m1.descendant("a", 0) as? String)

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

let m = Mirror(reflecting: x)
let two = m.descendant(0, ".0", 1)
let two = m.descendant(0, "a", 1)
expectEqual("two", two as? String)
expectEqual(1, m.descendant(1, 1, "bark") as? Int)
expectEqual(0, m.descendant(1, 1, "bite") as? Int)
Expand Down
Loading