Skip to content

Commit 2249346

Browse files
committed
Swift: Add type inference tests for key path expressions
1 parent 038f9a2 commit 2249346

2 files changed

Lines changed: 221 additions & 3 deletions

File tree

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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+
}

swift/ql/test/library-tests/type-inference/type-inference.ql

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ private Type getTypeAt(Type t, string path) {
6262
}
6363

6464
module TypeTest implements TestSig {
65-
string getARelevantTag() { result = ["type", "certainType"] }
66-
67-
predicate tagIsOptional(string expectedTag) { expectedTag = "type" }
65+
string getARelevantTag() { result = "type" }
6866

6967
predicate hasActualResult(Location location, string element, string tag, string value) { none() }
7068

0 commit comments

Comments
 (0)