|
| 1 | +// --- Key-path expressions: basic property access --- |
| 2 | + |
| 3 | +struct Point { |
| 4 | + var x : Double |
| 5 | + var y : Double |
| 6 | + |
| 7 | + // Point.init |
| 8 | + init(x: Double, y: Double) { |
| 9 | + self.x = x // $ type=x:Double |
| 10 | + self.y = y // $ type=y:Double |
| 11 | + } |
| 12 | + |
| 13 | + // Point.distanceFromOrigin |
| 14 | + func distanceFromOrigin() -> Double { |
| 15 | + return (x * x + y * y).squareRoot() |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +struct Line { |
| 20 | + var start : Point |
| 21 | + var end : Point |
| 22 | + |
| 23 | + // Line.init |
| 24 | + init(start: Point, end: Point) { |
| 25 | + self.start = start |
| 26 | + self.end = end |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +func testBasicKeyPaths() { |
| 31 | + let kpX = \Point.x |
| 32 | + let kpY = \Point.y |
| 33 | + |
| 34 | + let p = Point(x: 3.0, y: 4.0) // $ type=p:Point target=Point.init |
| 35 | + let xVal = p[keyPath: kpX] // $ type=xVal:Double |
| 36 | + let yVal = p[keyPath: kpY] // $ type=yVal:Double |
| 37 | +} |
| 38 | + |
| 39 | +// --- Key-path expressions: nested property access --- |
| 40 | + |
| 41 | +func testNestedKeyPaths() { |
| 42 | + let kpStartX = \Line.start.x |
| 43 | + let kpEndY = \Line.end.y |
| 44 | + |
| 45 | + let s = Point(x: 0.0, y: 0.0) // $ target=Point.init |
| 46 | + let e = Point(x: 1.0, y: 1.0) // $ target=Point.init |
| 47 | + let line = Line(start: s, end: e) // $ target=Line.init |
| 48 | + let startX = line[keyPath: kpStartX] // $ type=startX:Double |
| 49 | + let endY = line[keyPath: kpEndY] // $ type=endY:Double |
| 50 | +} |
| 51 | + |
| 52 | +// --- Key-path expressions: identity (\.self) --- |
| 53 | + |
| 54 | +func testSelfKeyPath() { |
| 55 | + let kpSelf = \Int.self |
| 56 | + let val = 42[keyPath: kpSelf] // $ type=val:Int |
| 57 | +} |
| 58 | + |
| 59 | +// --- Key-path expressions: optional chaining --- |
| 60 | + |
| 61 | +struct Person { |
| 62 | + var name : String |
| 63 | + var address : Address? |
| 64 | + |
| 65 | + // Person.init |
| 66 | + init(name: String, address: Address?) { |
| 67 | + self.name = name // $ type=name:String |
| 68 | + self.address = address |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +struct Address { |
| 73 | + var city : String |
| 74 | + var zip : String |
| 75 | + |
| 76 | + // Address.init |
| 77 | + init(city: String, zip: String) { |
| 78 | + self.city = city // $ type=city:String |
| 79 | + self.zip = zip // $ type=zip:String |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +func testOptionalChainingKeyPaths() { |
| 84 | + let kpCity = \Person.address?.city |
| 85 | + let addr = Address(city: "NYC", zip: "10001") // $ target=Address.init |
| 86 | + let person = Person(name: "Alice", address: addr) // $ target=Person.init |
| 87 | + |
| 88 | + let city = person[keyPath: kpCity] // $ type=city:String? |
| 89 | +} |
| 90 | + |
| 91 | +// --- Key-path expressions: used as function arguments --- |
| 92 | + |
| 93 | +struct Employee { |
| 94 | + var name : String |
| 95 | + var salary : Int |
| 96 | + |
| 97 | + // Employee.init |
| 98 | + init(name: String, salary: Int) { |
| 99 | + self.name = name // $ type=name:String |
| 100 | + self.salary = salary // $ type=salary:Int |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +// extractField(_:keyPath:) |
| 105 | +func extractField<T, V>(_ items: [T], keyPath: KeyPath<T, V>) -> [V] { |
| 106 | + return items.map { $0[keyPath: keyPath] } |
| 107 | +} |
| 108 | + |
| 109 | +func testKeyPathAsArgument() { |
| 110 | + let e1 = Employee(name: "Alice", salary: 100) // $ target=Employee.init |
| 111 | + let e2 = Employee(name: "Bob", salary: 200) // $ target=Employee.init |
| 112 | + let employees = [e1, e2] |
| 113 | + let names = extractField(employees, keyPath: \.name) // $ target=extractField(_:keyPath:) |
| 114 | + let name = names[0] // $ type=name:String |
| 115 | + let salaries = extractField(employees, keyPath: \.salary) // $ target=extractField(_:keyPath:) |
| 116 | + let salary = salaries[0] // $ type=salary:Int |
| 117 | +} |
| 118 | + |
| 119 | +// --- Key-path expressions: with generics --- |
| 120 | + |
| 121 | +class KPContainer<T> { |
| 122 | + var item : T |
| 123 | + |
| 124 | + // KPContainer.init |
| 125 | + init(item: T) { |
| 126 | + self.item = item // $ type=item:T |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +func testGenericKeyPaths() { |
| 131 | + let kp = \KPContainer<Int>.item |
| 132 | + let c = KPContainer(item: 42) // $ target=KPContainer.init |
| 133 | + let v = c[keyPath: kp] // $ type=v:Int |
| 134 | +} |
| 135 | + |
| 136 | +// --- Key-path expressions: writable key paths and mutation --- |
| 137 | + |
| 138 | +func testWritableKeyPaths() { |
| 139 | + var p = Point(x: 1.0, y: 2.0) // $ type=p:Point target=Point.init |
| 140 | + let kpX = \Point.x |
| 141 | + p[keyPath: kpX] = 10.0 |
| 142 | + let newX = p[keyPath: kpX] // $ type=newX:Double |
| 143 | +} |
| 144 | + |
| 145 | +// --- Key-path expressions: appending key paths --- |
| 146 | + |
| 147 | +func testKeyPathAppending() { |
| 148 | + let kpStart = \Line.start |
| 149 | + let kpX = \Point.x |
| 150 | + let kpStartX = kpStart.appending(path: kpX) |
| 151 | + |
| 152 | + let s = Point(x: 5.0, y: 6.0) // $ target=Point.init |
| 153 | + let e = Point(x: 7.0, y: 8.0) // $ target=Point.init |
| 154 | + let line = Line(start: s, end: e) // $ target=Line.init |
| 155 | + let val = line[keyPath: kpStartX] // $ type=val:Double |
| 156 | +} |
| 157 | + |
| 158 | +// --- Key-path expressions: shorthand in higher-order functions --- |
| 159 | + |
| 160 | +func testKeyPathInMap() { |
| 161 | + let e1 = Employee(name: "Alice", salary: 100) // $ target=Employee.init |
| 162 | + let e2 = Employee(name: "Bob", salary: 200) // $ target=Employee.init |
| 163 | + let employees = [e1, e2] |
| 164 | + let names = employees.map(\.name) |
| 165 | + let name = names[0] // $ type=name:String |
| 166 | + let salaries = employees.map(\.salary) |
| 167 | + let salary = salaries[0] // $ type=salary:Int |
| 168 | +} |
| 169 | + |
| 170 | +// --- Key-path expressions: class hierarchy --- |
| 171 | + |
| 172 | +class Shape2 { |
| 173 | + var color : String |
| 174 | + |
| 175 | + // Shape2.init |
| 176 | + init(color: String) { |
| 177 | + self.color = color // $ type=color:String |
| 178 | + } |
| 179 | +} |
| 180 | + |
| 181 | +class Circle2 : Shape2 { |
| 182 | + var radius : Double |
| 183 | + |
| 184 | + // Circle2.init |
| 185 | + init(color: String, radius: Double) { |
| 186 | + self.radius = radius // $ type=radius:Double |
| 187 | + super.init(color: color) // $ target=Shape2.init |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +func testInheritedKeyPaths() { |
| 192 | + let kpColor = \Circle2.color |
| 193 | + let kpRadius = \Circle2.radius |
| 194 | + |
| 195 | + let c = Circle2(color: "red", radius: 5.0) // $ type=c:Circle2 target=Circle2.init |
| 196 | + let col = c[keyPath: kpColor] // $ type=col:String |
| 197 | + let rad = c[keyPath: kpRadius] // $ type=rad:Double |
| 198 | +} |
| 199 | + |
| 200 | +// --- Key-path expressions: tuple element access --- |
| 201 | + |
| 202 | +func testTupleKeyPath() { |
| 203 | + let kp0 = \(Int, String).0 |
| 204 | + let kp1 = \(Int, String).1 |
| 205 | + let tuple = (42, "hello") |
| 206 | + let first = tuple[keyPath: kp0] // $ type=first:Int |
| 207 | + let second = tuple[keyPath: kp1] // $ type=second:String |
| 208 | +} |
| 209 | + |
| 210 | +// --- Key-path expressions: array/dictionary subscript --- |
| 211 | + |
| 212 | +func testSubscriptKeyPaths() { |
| 213 | + let kpFirst = \[Int][0] |
| 214 | + let arr = [10, 20, 30] |
| 215 | + let first = arr[keyPath: kpFirst] // $ type=first:Int |
| 216 | + |
| 217 | + let kpKey = \[String: Int]["x"] |
| 218 | + let dict = ["x": 1, "y": 2] |
| 219 | + let val = dict[keyPath: kpKey] // $ type=val:Int? |
| 220 | +} |
0 commit comments