// RUN: %empty-directory(%t) // RUN: %target-build-swift %s -Xfrontend -enable-sil-ownership -Xfrontend -g -o %t/a.out // RUN: %target-run %t/a.out // REQUIRES: executable_test import StdlibUnittest var keyPath = TestSuite("key paths") final class C { var x: Int var y: LifetimeTracked? var z: T var computed: T { get { return z } set { z = newValue } } init(x: Int, y: LifetimeTracked?, z: T) { self.x = x self.y = y self.z = z } } struct Point: Equatable { var x: Double var y: Double var trackLifetime = LifetimeTracked(123) init(x: Double, y: Double) { self.x = x self.y = y } static func ==(a: Point, b: Point) -> Bool { return a.x == b.x && a.y == b.y } } struct S: Equatable { var x: Int var y: LifetimeTracked? var z: T var p: Point var c: C static func ==(a: S, b: S) -> Bool { return a.x == b.x && a.y === b.y && a.z == b.z && a.p == b.p && a.c === b.c } } final class ComputedA { var readOnly: ComputedB { fatalError() } var nonmutating: ComputedB { get { fatalError() } set { fatalError() } } var reabstracted: () -> () = {} } struct ComputedB { var readOnly: ComputedA { fatalError() } var mutating: ComputedA { get { fatalError() } set { fatalError() } } var nonmutating: ComputedA { get { fatalError() } nonmutating set { fatalError() } } var reabstracted: () -> () = {} } keyPath.test("key path in-place instantiation") { for _ in 1...2 { let s_x = (\S.x as AnyKeyPath) as! WritableKeyPath, Int> let s_y = (\S.y as AnyKeyPath) as! WritableKeyPath, LifetimeTracked?> let s_z = (\S.z as AnyKeyPath) as! WritableKeyPath, Int> let s_p = (\S.p as AnyKeyPath) as! WritableKeyPath, Point> let s_p_x = (\S.p.x as AnyKeyPath) as! WritableKeyPath, Double> let s_p_y = (\S.p.y as AnyKeyPath) as! WritableKeyPath, Double> let s_c = (\S.c as AnyKeyPath) as! WritableKeyPath, C> let s_c_x = (\S.c.x as AnyKeyPath) as! ReferenceWritableKeyPath, Int> let c_x = (\C.x as AnyKeyPath) as! ReferenceWritableKeyPath, Int> let s_c_x_2 = s_c.appending(path: c_x) expectEqual(s_c_x, s_c_x_2) expectEqual(s_c_x_2, s_c_x) expectEqual(s_c_x.hashValue, s_c_x_2.hashValue) let point_x = (\Point.x as AnyKeyPath) as! WritableKeyPath let point_y = (\Point.y as AnyKeyPath) as! WritableKeyPath let s_p_x_2 = s_p.appending(path: point_x) let s_p_y_2 = s_p.appending(path: point_y) expectEqual(s_p_x, s_p_x_2) expectEqual(s_p_x_2, s_p_x) expectEqual(s_p_x_2.hashValue, s_p_x.hashValue) expectEqual(s_p_y, s_p_y_2) expectEqual(s_p_y_2, s_p_y) expectEqual(s_p_y_2.hashValue, s_p_y.hashValue) let ca_readOnly = (\ComputedA.readOnly as AnyKeyPath) as! KeyPath let ca_nonmutating = (\ComputedA.nonmutating as AnyKeyPath) as! ReferenceWritableKeyPath let ca_reabstracted = (\ComputedA.reabstracted as AnyKeyPath) as! ReferenceWritableKeyPath ()> let cb_readOnly = (\ComputedB.readOnly as AnyKeyPath) as! KeyPath let cb_mutating = (\ComputedB.mutating as AnyKeyPath) as! WritableKeyPath let cb_nonmutating = (\ComputedB.nonmutating as AnyKeyPath) as! ReferenceWritableKeyPath let cb_reabstracted = (\ComputedB.reabstracted as AnyKeyPath) as! WritableKeyPath ()> let ca_readOnly_mutating = (\ComputedA.readOnly.mutating as AnyKeyPath) as! KeyPath let cb_mutating_readOnly = (\ComputedB.mutating.readOnly as AnyKeyPath) as! KeyPath let ca_readOnly_nonmutating = (\ComputedA.readOnly.nonmutating as AnyKeyPath) as! ReferenceWritableKeyPath let cb_readOnly_reabstracted = (\ComputedB.readOnly.reabstracted as AnyKeyPath) as! ReferenceWritableKeyPath ()> let ca_readOnly_mutating2 = ca_readOnly.appending(path: cb_mutating) expectEqual(ca_readOnly_mutating, ca_readOnly_mutating2) expectEqual(ca_readOnly_mutating2, ca_readOnly_mutating) expectEqual(ca_readOnly_mutating.hashValue, ca_readOnly_mutating2.hashValue) let cb_mutating_readOnly2 = cb_mutating.appending(path: ca_readOnly) expectEqual(cb_mutating_readOnly, cb_mutating_readOnly2) expectEqual(cb_mutating_readOnly2, cb_mutating_readOnly) expectEqual(cb_mutating_readOnly.hashValue, cb_mutating_readOnly2.hashValue) let ca_readOnly_nonmutating2 = ca_readOnly.appending(path: cb_nonmutating) expectEqual(ca_readOnly_nonmutating, ca_readOnly_nonmutating2) expectEqual(ca_readOnly_nonmutating2, ca_readOnly_nonmutating) expectEqual(ca_readOnly_nonmutating.hashValue, ca_readOnly_nonmutating2.hashValue) let cb_readOnly_reabstracted2 = cb_readOnly.appending(path: ca_reabstracted) expectEqual(cb_readOnly_reabstracted, cb_readOnly_reabstracted2) expectEqual(cb_readOnly_reabstracted2, cb_readOnly_reabstracted) expectEqual(cb_readOnly_reabstracted2.hashValue, cb_readOnly_reabstracted.hashValue) } } keyPath.test("key path generic instantiation") { func testWithGenericParam(_: T.Type) -> ReferenceWritableKeyPath, Int> { for i in 1...2 { let s_x = (\S.x as AnyKeyPath) as! WritableKeyPath, Int> let s_y = (\S.y as AnyKeyPath) as! WritableKeyPath, LifetimeTracked?> let s_z = (\S.z as AnyKeyPath) as! WritableKeyPath, T> let s_p = (\S.p as AnyKeyPath) as! WritableKeyPath, Point> let s_p_x = (\S.p.x as AnyKeyPath) as! WritableKeyPath, Double> let s_p_y = (\S.p.y as AnyKeyPath) as! WritableKeyPath, Double> let s_c = (\S.c as AnyKeyPath) as! WritableKeyPath, C> let s_c_x = (\S.c.x as AnyKeyPath) as! ReferenceWritableKeyPath, Int> let c_x = (\C.x as AnyKeyPath) as! ReferenceWritableKeyPath, Int> let s_c_x_2 = s_c.appending(path: c_x) expectEqual(s_c_x, s_c_x_2) expectEqual(s_c_x_2, s_c_x) expectEqual(s_c_x.hashValue, s_c_x_2.hashValue) let point_x = (\Point.x as AnyKeyPath) as! WritableKeyPath let point_y = (\Point.y as AnyKeyPath) as! WritableKeyPath let s_p_x_2 = s_p.appending(path: point_x) let s_p_y_2 = s_p.appending(path: point_y) expectEqual(s_p_x, s_p_x_2) expectEqual(s_p_x_2, s_p_x) expectEqual(s_p_x_2.hashValue, s_p_x.hashValue) expectEqual(s_p_y, s_p_y_2) expectEqual(s_p_y_2, s_p_y) expectEqual(s_p_y_2.hashValue, s_p_y.hashValue) if i == 2 { return s_c_x } } fatalError() } let s_c_x_int = testWithGenericParam(Int.self) let s_c_x_int2 = \S.c.x expectEqual(s_c_x_int, s_c_x_int2) let s_c_x_string = testWithGenericParam(String.self) let s_c_x_string2 = \S.c.x expectEqual(s_c_x_string, s_c_x_string2) let s_c_x_lt = testWithGenericParam(LifetimeTracked.self) let s_c_x_lt2 = \S.c.x expectEqual(s_c_x_lt, s_c_x_lt2) } protocol P {} struct TestComputed: P { static var numNonmutatingSets = 0 static var numMutatingSets = 0 static func resetCounts() { numNonmutatingSets = 0 numMutatingSets = 0 } var canary = LifetimeTracked(0) var readonly: LifetimeTracked { return LifetimeTracked(1) } var nonmutating: LifetimeTracked { get { return LifetimeTracked(2) } nonmutating set { TestComputed.numNonmutatingSets += 1 } } var mutating: LifetimeTracked { get { return LifetimeTracked(3) } set { canary = newValue } } } extension P { var readonlyProtoExt: Self { return self } var mutatingProtoExt: Self { get { return self } set { self = newValue } } } keyPath.test("computed properties") { var test = TestComputed() do { let tc_readonly = \TestComputed.readonly expectTrue(test[keyPath: tc_readonly] !== test[keyPath: tc_readonly]) expectEqual(test[keyPath: tc_readonly].value, test[keyPath: tc_readonly].value) } do { let tc_nonmutating = \TestComputed.nonmutating expectTrue(test[keyPath: tc_nonmutating] !== test[keyPath: tc_nonmutating]) expectEqual(test[keyPath: tc_nonmutating].value, test[keyPath: tc_nonmutating].value) TestComputed.resetCounts() test[keyPath: tc_nonmutating] = LifetimeTracked(4) expectEqual(TestComputed.numNonmutatingSets, 1) } do { let tc_mutating = \TestComputed.mutating expectTrue(test[keyPath: tc_mutating] !== test[keyPath: tc_mutating]) expectEqual(test[keyPath: tc_mutating].value, test[keyPath: tc_mutating].value) let newObject = LifetimeTracked(5) test[keyPath: tc_mutating] = newObject expectTrue(test.canary === newObject) } do { let tc_readonlyProtoExt = \TestComputed.readonlyProtoExt expectTrue(test.canary === test[keyPath: tc_readonlyProtoExt].canary) } do { let tc_mutatingProtoExt = \TestComputed.mutatingProtoExt expectTrue(test.canary === test[keyPath: tc_mutatingProtoExt].canary) let oldTest = test test[keyPath: tc_mutatingProtoExt] = TestComputed() expectTrue(oldTest.canary !== test.canary) expectTrue(test.canary === test[keyPath: tc_mutatingProtoExt].canary) } } class AB { } class ABC: AB, ABCProtocol { var a = LifetimeTracked(1) var b = LifetimeTracked(2) var c = LifetimeTracked(3) } protocol ABCProtocol { var a: LifetimeTracked { get } var b: LifetimeTracked { get set } var c: LifetimeTracked { get nonmutating set } } keyPath.test("dynamically-typed application") { let cPaths = [\ABC.a, \ABC.b, \ABC.c] let subject = ABC() do { let fields = cPaths.map { subject[keyPath: $0] } expectTrue(fields[0] as! AnyObject === subject.a) expectTrue(fields[1] as! AnyObject === subject.b) expectTrue(fields[2] as! AnyObject === subject.c) } let erasedSubject: AB = subject let erasedPaths: [AnyKeyPath] = cPaths let wrongSubject = AB() do { let fields = erasedPaths.map { erasedSubject[keyPath: $0] } expectTrue(fields[0]! as! AnyObject === subject.a) expectTrue(fields[1]! as! AnyObject === subject.b) expectTrue(fields[2]! as! AnyObject === subject.c) let wrongFields = erasedPaths.map { wrongSubject[keyPath: $0] } expectTrue(wrongFields[0] == nil) expectTrue(wrongFields[1] == nil) expectTrue(wrongFields[2] == nil) } var protoErasedSubject: ABCProtocol = subject let protoErasedPathA = \ABCProtocol.a let protoErasedPathB = \ABCProtocol.b let protoErasedPathC = \ABCProtocol.c do { expectTrue(protoErasedSubject.a === protoErasedSubject[keyPath: protoErasedPathA]) let newB = LifetimeTracked(4) expectTrue(protoErasedSubject.b === protoErasedSubject[keyPath: protoErasedPathB]) protoErasedSubject[keyPath: protoErasedPathB] = newB expectTrue(protoErasedSubject.b === protoErasedSubject[keyPath: protoErasedPathB]) expectTrue(protoErasedSubject.b === newB) let newC = LifetimeTracked(5) expectTrue(protoErasedSubject.c === protoErasedSubject[keyPath: protoErasedPathC]) protoErasedSubject[keyPath: protoErasedPathC] = newC expectTrue(protoErasedSubject.c === protoErasedSubject[keyPath: protoErasedPathC]) expectTrue(protoErasedSubject.c === newC) } } struct TestOptional { var origin: Point? var questionableCanary: LifetimeTracked? = LifetimeTracked(123) init(origin: Point?) { self.origin = origin } } keyPath.test("optional force-unwrapping") { let origin_x = \TestOptional.origin!.x let canary = \TestOptional.questionableCanary! var value = TestOptional(origin: Point(x: 3, y: 4)) expectEqual(value[keyPath: origin_x], 3) expectEqual(value.origin!.x, 3) value[keyPath: origin_x] = 5 expectEqual(value[keyPath: origin_x], 5) expectEqual(value.origin!.x, 5) expectTrue(value[keyPath: canary] === value.questionableCanary) let newCanary = LifetimeTracked(456) value[keyPath: canary] = newCanary expectTrue(value[keyPath: canary] === newCanary) expectTrue(value.questionableCanary === newCanary) } keyPath.test("optional force-unwrapping trap") { let origin_x = \TestOptional.origin!.x var value = TestOptional(origin: nil) expectCrashLater() _ = value[keyPath: origin_x] } struct TestOptional2 { var optional: TestOptional? } keyPath.test("optional chaining") { let origin_x = \TestOptional.origin?.x let canary = \TestOptional.questionableCanary?.value let withPoint = TestOptional(origin: Point(x: 3, y: 4)) expectEqual(withPoint[keyPath: origin_x]!, 3) expectEqual(withPoint[keyPath: canary]!, 123) let withoutPoint = TestOptional(origin: nil) expectNil(withoutPoint[keyPath: origin_x]) let optional2: TestOptional2? = TestOptional2(optional: withPoint) let optional2_optional = \TestOptional2?.?.optional expectEqual(optional2[keyPath: optional2_optional]!.origin!.x, 3) expectEqual(optional2[keyPath: optional2_optional]!.origin!.y, 4) } func makeKeyPathInGenericContext(of: T.Type) -> ReferenceWritableKeyPath, T> { return \C.computed } keyPath.test("computed generic key paths") { let path = makeKeyPathInGenericContext(of: LifetimeTracked.self) let z = LifetimeTracked(456) let c = C(x: 42, y: LifetimeTracked(123), z: z) expectTrue(c[keyPath: path] === z) let z2 = LifetimeTracked(789) c[keyPath: path] = z2 expectTrue(c[keyPath: path] === z2) expectTrue(c.z === z2) let path2 = makeKeyPathInGenericContext(of: LifetimeTracked.self) expectEqual(path, path2) expectEqual(path.hashValue, path2.hashValue) let pathNonGeneric = \C.computed expectEqual(path, pathNonGeneric) expectEqual(path.hashValue, pathNonGeneric.hashValue) let valuePath = path.appending(path: \LifetimeTracked.value) expectEqual(c[keyPath: valuePath], 789) let valuePathNonGeneric = pathNonGeneric.appending(path: \LifetimeTracked.value) expectEqual(valuePath, valuePathNonGeneric) expectEqual(valuePath.hashValue, valuePathNonGeneric.hashValue) } var numberOfMutatingWritebacks = 0 var numberOfNonmutatingWritebacks = 0 struct NoisyWriteback { var canary = LifetimeTracked(246) var mutating: LifetimeTracked { get { return canary } set { numberOfMutatingWritebacks += 1 } } var nonmutating: LifetimeTracked { get { return canary } nonmutating set { numberOfNonmutatingWritebacks += 1 } } } keyPath.test("read-only accesses don't trigger writebacks") { var x = NoisyWriteback() x = NoisyWriteback() // suppress "never mutated" warnings let wkp = \NoisyWriteback.mutating let rkp = \NoisyWriteback.nonmutating numberOfMutatingWritebacks = 0 numberOfNonmutatingWritebacks = 0 _ = x[keyPath: wkp] _ = x[keyPath: rkp] expectEqual(x[keyPath: wkp].value, 246) expectEqual(x[keyPath: rkp].value, 246) expectEqual(numberOfMutatingWritebacks, 0) expectEqual(numberOfNonmutatingWritebacks, 0) let y = x _ = y[keyPath: wkp] _ = y[keyPath: rkp] expectEqual(y[keyPath: wkp].value, 246) expectEqual(y[keyPath: rkp].value, 246) expectEqual(numberOfMutatingWritebacks, 0) expectEqual(numberOfNonmutatingWritebacks, 0) } var nestedWritebackLog = 0 struct NoisyNestingWriteback { var value: Int var nested: NoisyNestingWriteback { get { return NoisyNestingWriteback(value: value + 1) } set { nestedWritebackLog = nestedWritebackLog << 8 | newValue.value value = newValue.value - 1 } } } keyPath.test("writebacks nest properly") { var test = NoisyNestingWriteback(value: 0) nestedWritebackLog = 0 test.nested.nested.nested.value = 0x38 expectEqual(nestedWritebackLog, 0x383736) nestedWritebackLog = 0 let kp = \NoisyNestingWriteback.nested.nested.nested test[keyPath: kp].value = 0x38 expectEqual(nestedWritebackLog, 0x383736) } struct IUOWrapper { var wrapped: IUOWrapped! } struct IUOWrapped { var value: Int } keyPath.test("IUO and key paths") { var subject = IUOWrapper(wrapped: IUOWrapped(value: 1989)) let kp1 = \IUOWrapper.wrapped.value expectEqual(subject[keyPath: kp1], 1989) subject[keyPath: kp1] = 1738 expectEqual(subject[keyPath: kp1], 1738) expectEqual(subject.wrapped.value, 1738) let kp2 = \IUOWrapper.wrapped!.value expectEqual(kp1, kp2) expectEqual(kp1.hashValue, kp2.hashValue) } struct SubscriptResult { var canary = LifetimeTracked(3333) var left: T var right: U init(left: T, right: U) { self.left = left self.right = right } subscript(left: T) -> Bool { return self.left == left } subscript(right: U) -> Bool { return self.right == right } } struct Subscripts { var canary = LifetimeTracked(4444) subscript(x: T, y: U) -> SubscriptResult { return SubscriptResult(left: x, right: y) } subscript(x: Int, y: Int) -> Int { return x + y } } struct KeyA: Hashable { var canary = LifetimeTracked(1111) var value: String init(value: String) { self.value = value } static func ==(a: KeyA, b: KeyA) -> Bool { return a.value == b.value } var hashValue: Int { return value.hashValue } } struct KeyB: Hashable { var canary = LifetimeTracked(2222) var value: Int init(value: Int) { self.value = value } static func ==(a: KeyB, b: KeyB) -> Bool { return a.value == b.value } var hashValue: Int { return value.hashValue } } func fullGenericContext(x: T, y: U) -> KeyPath, SubscriptResult> { return \Subscripts.[x, y] } func halfGenericContext(x: KeyA, y: U) -> KeyPath, SubscriptResult> { return \Subscripts.[x, y] } func nonGenericContext(x: KeyA, y: KeyB) -> KeyPath, SubscriptResult> { return \Subscripts.[x, y] } keyPath.test("subscripts") { let a = fullGenericContext(x: KeyA(value: "hey"), y: KeyB(value: 1738)) let b = halfGenericContext(x: KeyA(value: "hey"), y: KeyB(value: 1738)) let c = nonGenericContext(x: KeyA(value: "hey"), y: KeyB(value: 1738)) expectEqual(a, b) expectEqual(a, c) expectEqual(b, a) expectEqual(b, c) expectEqual(c, a) expectEqual(c, b) expectEqual(a.hashValue, b.hashValue) expectEqual(a.hashValue, c.hashValue) expectEqual(b.hashValue, a.hashValue) expectEqual(b.hashValue, c.hashValue) expectEqual(c.hashValue, a.hashValue) expectEqual(c.hashValue, b.hashValue) let base = Subscripts() let kp2 = \SubscriptResult.[KeyA(value: "hey")] for kp in [a, b, c] { let projected = base[keyPath: kp] expectEqual(projected.left.value, "hey") expectEqual(projected.right.value, 1738) expectEqual(projected[keyPath: kp2], true) let kp12 = \Subscripts.[KeyA(value: "hey"), KeyB(value: 1738)][KeyA(value: "hey")] let kp12a = kp.appending(path: kp2) expectEqual(kp12, kp12a) expectEqual(kp12a, kp12) expectEqual(kp12.hashValue, kp12a.hashValue) } let ints = \Subscripts.[17, 38] let ints2 = \Subscripts.[17, 38] let ints3 = \Subscripts.[38, 17] expectEqual(base[keyPath: ints], 17 + 38) expectEqual(ints, ints2) expectEqual(ints2, ints) expectNotEqual(ints, ints3) expectNotEqual(ints2, ints3) expectNotEqual(ints3, ints) expectNotEqual(ints3, ints2) expectEqual(ints.hashValue, ints2.hashValue) let ints_be = ints.appending(path: \Int.bigEndian) expectEqual(base[keyPath: ints_be], (17 + 38).bigEndian) } runAllTests()