From 4042bbec5bb604d325bc0e9e1a189fdada012b90 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 29 Apr 2026 11:11:44 +0200 Subject: [PATCH 1/3] Swift: Add type inference tests --- .../type-inference/type-inference.expected | 0 .../type-inference/type-inference.ql | 91 ++ .../type-inference/typeinference.swift | 1372 +++++++++++++++++ 3 files changed, 1463 insertions(+) create mode 100644 swift/ql/test/library-tests/type-inference/type-inference.expected create mode 100644 swift/ql/test/library-tests/type-inference/type-inference.ql create mode 100644 swift/ql/test/library-tests/type-inference/typeinference.swift diff --git a/swift/ql/test/library-tests/type-inference/type-inference.expected b/swift/ql/test/library-tests/type-inference/type-inference.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/swift/ql/test/library-tests/type-inference/type-inference.ql b/swift/ql/test/library-tests/type-inference/type-inference.ql new file mode 100644 index 000000000000..cd51687e8af9 --- /dev/null +++ b/swift/ql/test/library-tests/type-inference/type-inference.ql @@ -0,0 +1,91 @@ +import swift +import TestUtils +import utils.test.InlineExpectationsTest + +pragma[nomagic] +private predicate declHasPotentialCommentAt(Decl d, string path, int line) { + d.getLocation().hasLocationInfo(path, line + 1, _, _, _) +} + +pragma[nomagic] +private SingleLineComment getPrecedingComment(Decl d) { + exists(string path, int line | + declHasPotentialCommentAt(d, path, line) and + result.getLocation().hasLocationInfo(path, line, _, _, _) + ) +} + +module ResolveTest implements TestSig { + string getARelevantTag() { result = "target" } + + private predicate declHasName(Decl c, string value) { + exists(string s | + s = getPrecedingComment(c).getText() and + value = s.substring(3, s.length() - 1) + ) + or + not exists(getPrecedingComment(c)) and + value = [c.(EnumElementDecl).getName(), c.(Function).getName()] + } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(AstNode source, Decl target | + location = source.getLocation() and + element = source.toString() and + target = + [ + source.(CallExpr).getStaticTarget().(Decl), source.(MethodLookupExpr).getMember(), + source.(EnumElementPattern).getElement() + ] and + declHasName(target, value) and + tag = "target" and + toBeTested(source) and + toBeTested(target) + ) + } +} + +private Type getTypeAt(Type t, string path) { + path = "" and + result = t.getUnderlyingType() + or + exists(BoundGenericType b, GenericTypeDecl decl | + b = t and + decl = b.getDeclaration() + | + exists(string prefix, string suffix, int i | + result = getTypeAt(b.getArgType(i).getUnderlyingType(), suffix) and + prefix = decl.getName() + "<" + decl.getGenericTypeParam(i).getName() + ">" and + if suffix = "" then path = prefix else path = prefix + "." + suffix + ) + ) +} + +module TypeTest implements TestSig { + string getARelevantTag() { result = ["type", "certainType"] } + + predicate tagIsOptional(string expectedTag) { expectedTag = "type" } + + predicate hasActualResult(Location location, string element, string tag, string value) { none() } + + predicate hasOptionalResult(Location location, string element, string tag, string value) { + exists(Locatable e, string path, Type t, Type t0 | + t = [e.(Expr).getType(), e.(VarDecl).getType()] and + tag = "type" and + location = e.getLocation() and + t0 = getTypeAt(t, path) and + exists(string name, string at | + name = t0.(AnyGenericType).getDeclaration().getName() + or + not t0 instanceof AnyGenericType and + name = t0.getName() + | + (if path = "" then at = "" else at = "@" + path) and + value = element + at + ":" + name and + element = e.toString() + ) + ) + } +} + +import MakeTest> diff --git a/swift/ql/test/library-tests/type-inference/typeinference.swift b/swift/ql/test/library-tests/type-inference/typeinference.swift new file mode 100644 index 000000000000..0e718fa9b927 --- /dev/null +++ b/swift/ql/test/library-tests/type-inference/typeinference.swift @@ -0,0 +1,1372 @@ +var topLevelDecl : Int = 0 +0 +topLevelDecl + 1 // $ type=topLevelDecl:Int + +class C { + var myInt : Int + // C.init + init(n: Int) { + myInt = n // $ type=n:Int + } + + // C.getMyInt + func getMyInt() -> Int { + return myInt // $ type=.myInt:Int + } +} + +class Derived : C { + // Derived.init + init() { + super.init(n: 0) // $ type=super:C target=C.init + } + + // Derived.callGetMyInt + func callGetMyInt() -> Int { + let x = getMyInt(); // $ type=x:Int target=C.getMyInt + return x + } +} + +class Generic { + var value : T + // Generic.init + init(v: T) { + value = v // $ type=v:T + } + + // Generic.getValue + func getValue() -> T { + return value // $ type=.value:T + } +} + +class GenericDerived : Generic { + // GenericDerived.init + init() { + super.init(v: 0) // $ type=super@Generic:Int target=Generic.init + } +} + +func testGeneric() { + let g = Generic(v: 42) // $ type=g@Generic:Int target=Generic.init + let x = g.getValue() // $ type=x:Int target=Generic.getValue + + let gd = GenericDerived() // $ type=gd:GenericDerived target=GenericDerived.init + let y = gd.getValue() // $ type=y:Int target=Generic.getValue +} + +// --- Protocols and protocol conformance --- + +protocol Shape { + // Shape.area + func area() -> Double +} + +struct Circle : Shape { + var radius : Double + + // Circle.init + init(radius: Double) { + self.radius = radius // $ type=radius:Double + } + + // Circle.area + func area() -> Double { + return 3.14159 * radius * radius // $ type=.radius:Double + } +} + +struct Rectangle : Shape { + var width : Double + var height : Double + + // Rectangle.init + init(width: Double, height: Double) { + self.width = width // $ type=width:Double + self.height = height // $ type=height:Double + } + + // Rectangle.area + func area() -> Double { + return width * height // $ type=.width:Double + } +} + +func testProtocol() { + let c = Circle(radius: 5.0) // $ type=c:Circle target=Circle.init + let a1 = c.area() // $ type=a1:Double target=Circle.area + + let r = Rectangle(width: 3.0, height: 4.0) // $ type=r:Rectangle target=Rectangle.init + let a2 = r.area() // $ type=a2:Double target=Rectangle.area +} + +// --- Extensions --- + +extension C { + // C.doubled + func doubled() -> Int { + return myInt * 2 // $ type=.myInt:Int + } +} + +func testExtension() { + let obj = C(n: 10) // $ target=C.init + let d = obj.doubled() // $ type=d:Int target=C.doubled +} + +// --- Static methods --- + +class MathUtils { + // MathUtils.square + static func square(x: Int) -> Int { + return x * x // $ type=x:Int + } + + // MathUtils.cube + class func cube(x: Int) -> Int { + return x * x * x // $ type=x:Int + } +} + +func testStaticMethods() { + let s = MathUtils.square(x: 4) // $ type=s:Int target=MathUtils.square + let cu = MathUtils.cube(x: 3) // $ type=cu:Int target=MathUtils.cube +} + +// --- Overloading --- + +class Overloaded { + // Overloaded.process(_:Int) + func process(_ x: Int) -> Int { + return x + 1 // $ type=x:Int + } + + // Overloaded.process(_:String) + func process(_ s: String) -> String { + return s // $ type=s:String + } +} + +func testOverloading() { + let o = Overloaded() // $ target=init() + let r1 = o.process(42) // $ type=r1:Int target=Overloaded.process(_:Int) + let r2 = o.process("hello") // $ type=r2:String target=Overloaded.process(_:String) +} + +// --- Generic functions --- + +// identity +func identity(_ x: T) -> T { + return x // $ type=x:T +} + +// makePair +func makePair(_ a: A, _ b: B) -> (A, B) { + return (a, b) // $ type=a:A +} + +func testGenericFunctions() { + let i = identity(42) // $ type=i:Int target=identity + let s = identity("hello") // $ type=s:String target=identity + let p = makePair(1, "two") // $ target=makePair +} + +// --- Generic constraints --- + +struct Pair { + var first : A + var second : B + + // Pair.init + init(first: A, second: B) { + self.first = first // $ type=first:A + self.second = second // $ type=second:B + } + + // Pair.getFirst + func getFirst() -> A { + return first // $ type=.first:A + } + + // Pair.getSecond + func getSecond() -> B { + return second // $ type=.second:B + } +} + +func testGenericStruct() { + let p = Pair(first: 1, second: "x") // $ target=Pair.init type=p@Pair:Int type=p@Pair:String + let f = p.getFirst() // $ type=f:Int target=Pair.getFirst + let sc = p.getSecond() // $ type=sc:String target=Pair.getSecond +} + +// --- Enums with associated values --- + +enum Result { + // Result.success + case success(T) + // Result.failure + case failure(String) + + // Result.getValue + func getValue() -> T? { + switch self { + case .success(let v): // $ target=Result.success + return v // $ type=v:T + case .failure: // $ target=Result.failure + return nil + } + } +} + +func testEnum() { + let r = Result.success(42) // $ type=r@Result:Int target=Result.success + let v = r.getValue() // $ target=Result.getValue + + let r2 : Result = .success(42) // $ type=r2@Result:Int target=Result.success + let v2 = r2.getValue() // $ target=Result.getValue +} + +// --- Closures and type inference --- + +// applyTransform +func applyTransform(_ value: T, _ transform: (T) -> U) -> U { + return transform(value) +} + +func testClosures() { + let result = applyTransform(5, { x in x * 2 }) // $ target=applyTransform type=result:Int + let strings = applyTransform(10, { x in String(x) }) // $ target=applyTransform type=strings:String +} + +// --- Subscripts --- + +struct Matrix { + var data : [[Int]] + + // Matrix.init + init(data: [[Int]]) { + self.data = data + } + + // Matrix.rowCount + func rowCount() -> Int { + return data.count + } +} + +func testSubscripts() { + let m = Matrix(data: [[1, 2], [3, 4]]) // $ target=Matrix.init + let rc = m.rowCount() // $ type=rc:Int target=Matrix.rowCount +} + +// --- Nested types --- + +class Outer { + class Inner { + var value : Int + + // Outer.Inner.init + init(value: Int) { + self.value = value // $ type=value:Int + } + + // Outer.Inner.getValue + func getValue() -> Int { + return self.value // $ type=.value:Int + } + } +} + +func testNestedTypes() { + let inner = Outer.Inner(value: 99) // $ type=inner:Inner target=Outer.Inner.init + let v = inner.getValue() // $ type=v:Int target=Outer.Inner.getValue +} + +// --- Method chaining --- + +class Builder { + var value : Int = 0 + + // Builder.init + init() {} + + // Builder.set + func set(_ v: Int) -> Builder { + value = v + return self + } + + // Builder.add + func add(_ v: Int) -> Builder { + value += v + return self + } + + // Builder.build + func build() -> Int { + return value // $ type=.value:Int + } +} + +func testChaining() { + let b = Builder() // $ type=b:Builder target=Builder.init + let result = b.set(10).add(5).build() // $ type=result:Int target=Builder.set target=Builder.add target=Builder.build +} + +// --- Protocol with associated type-like patterns (using generics) --- + +protocol Container { + associatedtype Item + // Container.getItem + func getItem() -> Item + // Container.count + func count() -> Int +} + +struct IntContainer : Container { + typealias Item = Int + var items : [Int] + + // IntContainer.init + init(items: [Int]) { + self.items = items + } + + // IntContainer.getItem + func getItem() -> Int { + return items[0] + } + + // IntContainer.count + func count() -> Int { + return items.count + } +} + +func testAssociatedTypes() { + let ic = IntContainer(items: [1, 2, 3]) // $ type=ic:IntContainer target=IntContainer.init + let item = ic.getItem() // $ type=item:Int target=IntContainer.getItem + let cnt = ic.count() // $ type=cnt:Int target=IntContainer.count +} + +// --- Default parameter values --- + +class Config { + // Config.init + init() {} + + // Config.setup + func setup(retries: Int = 3, timeout: Double = 30.0) -> Int { + return retries // $ type=retries:Int + } +} + +func testDefaultParams() { + let cfg = Config() // $ type=cfg:Config target=Config.init + let r1 = cfg.setup() // $ type=r1:Int target=Config.setup + let r2 = cfg.setup(retries: 5) // $ type=r2:Int target=Config.setup + let r3 = cfg.setup(retries: 2, timeout: 60.0) // $ type=r3:Int target=Config.setup +} + +// --- Multiple inheritance (protocol conformance) --- + +protocol Printable { + // Printable.display + func display() -> String +} + +protocol Identifiable { + // Identifiable.id + func id() -> Int +} + +class Entity : Printable, Identifiable { + var name : String + var entityId : Int + + // Entity.init + init(name: String, entityId: Int) { + self.name = name // $ type=name:String + self.entityId = entityId // $ type=entityId:Int + } + + // Entity.display + func display() -> String { + return name // $ type=.name:String + } + + // Entity.id + func id() -> Int { + return entityId // $ type=.entityId:Int + } +} + +func testMultipleProtocols() { + let e = Entity(name: "test", entityId: 42) // $ type=e:Entity target=Entity.init + let d = e.display() // $ type=d:String target=Entity.display + let eid = e.id() // $ type=eid:Int target=Entity.id +} + +// --- Computed properties accessed via methods --- + +class Temperature { + var celsius : Double + + // Temperature.init + init(celsius: Double) { + self.celsius = celsius // $ type=celsius:Double + } + + // Temperature.toCelsius + func toCelsius() -> Double { + return celsius // $ type=.celsius:Double + } + + // Temperature.toFahrenheit + func toFahrenheit() -> Double { + return celsius * 9.0 / 5.0 + 32.0 // $ type=.celsius:Double + } +} + +func testTemperature() { + let t = Temperature(celsius: 100.0) // $ type=t:Temperature target=Temperature.init + let c = t.toCelsius() // $ type=c:Double target=Temperature.toCelsius + let f = t.toFahrenheit() // $ type=f:Double target=Temperature.toFahrenheit +} + +// --- Inheritance with overriding --- + +class Animal { + // Animal.init + init() {} + + // Animal.speak + func speak() -> String { + return "..." + } +} + +class Dog : Animal { + // Dog.init + override init() { + super.init() // $ target=Animal.init + } + + // Dog.speak + override func speak() -> String { + return "Woof" + } + + // Dog.fetch + func fetch() -> String { + return "ball" + } +} + +class Cat : Animal { + // Cat.init + override init() { + super.init() // $ target=Animal.init + } + + // Cat.speak + override func speak() -> String { + return "Meow" + } +} + +func testOverriding() { + let d = Dog() // $ type=d:Dog target=Dog.init + let ds = d.speak() // $ type=ds:String target=Dog.speak + let df = d.fetch() // $ type=df:String target=Dog.fetch + + let ct = Cat() // $ type=ct:Cat target=Cat.init + let cs = ct.speak() // $ type=cs:String target=Cat.speak +} + +// --- Generic class with constraints --- + +protocol MyProtocol { + associatedtype MyType + + // MyProtocol.foo + func foo() -> Self + + // MyProtocol.bar + func bar() -> MyType +} + +class Wrapper { + var inner : T + + // Wrapper.init + init(_ inner: T) { + self.inner = inner // $ type=inner:T + } + + // Wrapper.get + func get() -> T { + return inner // $ type=.inner:T + } + + // Wrapper.callFoo + func callFoo() -> T { + let x = inner.foo(); // $ type=x:T target=MyProtocol.foo + return x + } + + // Wrapper.callBar + func callBar() -> T.MyType { + let x = inner.bar(); // $ type=x:MyType target=MyProtocol.bar + return x + } +} + +extension Int : MyProtocol { + typealias MyType = String + + // Int.foo + func foo() -> Int { + return self * 2 // $ type=self:Int + } + + // Int.bar + func bar() -> String { + return "number" + } +} + +func testConstrainedGeneric() { + let w = Wrapper(42) // $ type=w@Wrapper:Int target=Wrapper.init + let v = w.get() // $ type=v:Int target=Wrapper.get + let z = w.callFoo() // $ type=z:Int target=Wrapper.callFoo +} + +// --- Mutating methods on structs --- + +struct Counter { + var count : Int = 0 + + // Counter.increment + mutating func increment() { + count += 1 + } + + // Counter.getCount + func getCount() -> Int { + return count // $ type=.count:Int + } +} + +func testMutating() { + var ctr = Counter() // $ type=ctr:Counter target=init() + ctr.increment() // $ target=Counter.increment + let val = ctr.getCount() // $ type=val:Int target=Counter.getCount +} + +// ============================================================ +// Overload resolution tests +// ============================================================ + +// --- Overload by parameter type --- + +class OverloadByType { + // OverloadByType.handle(_:Int) + func handle(_ x: Int) -> String { + return "int" + } + + // OverloadByType.handle(_:Double) + func handle(_ x: Double) -> String { + return "double" + } + + // OverloadByType.handle(_:String) + func handle(_ x: String) -> String { + return "string" + } + + // OverloadByType.handle(_:Bool) + func handle(_ x: Bool) -> String { + return "bool" + } +} + +func testOverloadByType() { + let o = OverloadByType() // $ target=init() + let r1 = o.handle(42) // $ type=r1:String target=OverloadByType.handle(_:Int) + let r2 = o.handle(3.14) // $ type=r2:String target=OverloadByType.handle(_:Double) + let r3 = o.handle("hi") // $ type=r3:String target=OverloadByType.handle(_:String) + let r4 = o.handle(true) // $ type=r4:String target=OverloadByType.handle(_:Bool) +} + +// --- Overload by argument label --- + +class OverloadByLabel { + // OverloadByLabel.configure(width:) + func configure(width: Int) -> String { + return "width" + } + + // OverloadByLabel.configure(height:) + func configure(height: Int) -> String { + return "height" + } + + // OverloadByLabel.configure(width:height:) + func configure(width: Int, height: Int) -> String { + return "both" + } + + // OverloadByLabel.configure(size:) + func configure(size: Int) -> String { + return "size" + } +} + +func testOverloadByLabel() { + let o = OverloadByLabel() // $ target=init() + let r1 = o.configure(width: 10) // $ type=r1:String target=OverloadByLabel.configure(width:) + let r2 = o.configure(height: 20) // $ type=r2:String target=OverloadByLabel.configure(height:) + let r3 = o.configure(width: 10, height: 20) // $ type=r3:String target=OverloadByLabel.configure(width:height:) + let r4 = o.configure(size: 30) // $ type=r4:String target=OverloadByLabel.configure(size:) +} + +// --- Overload by arity (number of parameters) --- + +class OverloadByArity { + // OverloadByArity.compute() + func compute() -> Int { + return 0 + } + + // OverloadByArity.compute(_:Int) + func compute(_ x: Int) -> Int { + return x + } + + // OverloadByArity.compute(_:Int,_:Int) + func compute(_ x: Int, _ y: Int) -> Int { + return x + y + } + + // OverloadByArity.compute(_:Int,_:Int,_:Int) + func compute(_ x: Int, _ y: Int, _ z: Int) -> Int { + return x + y + z + } +} + +func testOverloadByArity() { + let o = OverloadByArity() // $ target=init() + let r0 = o.compute() // $ type=r0:Int target=OverloadByArity.compute() + let r1 = o.compute(1) // $ type=r1:Int target=OverloadByArity.compute(_:Int) + let r2 = o.compute(1, 2) // $ type=r2:Int target=OverloadByArity.compute(_:Int,_:Int) + let r3 = o.compute(1, 2, 3) // $ type=r3:Int target=OverloadByArity.compute(_:Int,_:Int,_:Int) +} + +// --- Overload by return type (contextual type) --- + +class OverloadByReturn { + // OverloadByReturn.create()->Int + func create() -> Int { + return 0 + } + + // OverloadByReturn.create()->String + func create() -> String { + return "" + } + + // OverloadByReturn.create()->Double + func create() -> Double { + return 0.0 + } +} + +func testOverloadByReturn() { + let o = OverloadByReturn() // $ target=init() + let r1 : Int = o.create() // $ type=r1:Int target=OverloadByReturn.create()->Int + let r2 : String = o.create() // $ type=r2:String target=OverloadByReturn.create()->String + let r3 : Double = o.create() // $ type=r3:Double target=OverloadByReturn.create()->Double +} + +// --- Overload: generic vs non-generic (non-generic preferred) --- + +class OverloadGenericVsConcrete { + // OverloadGenericVsConcrete.process(_:Int) + func process(_ x: Int) -> String { + return "concrete" + } + + // OverloadGenericVsConcrete.process(_:) + func process(_ x: T) -> String { + return "generic" + } +} + +func testOverloadGenericVsConcrete() { + let o = OverloadGenericVsConcrete() // $ target=init() + let r1 = o.process(42) // $ type=r1:String target=OverloadGenericVsConcrete.process(_:Int) + let r2 = o.process("hello") // $ type=r2:String target=OverloadGenericVsConcrete.process(_:) + let r3 = o.process(true) // $ type=r3:String target=OverloadGenericVsConcrete.process(_:) +} + +// --- Overload: free functions by parameter type --- + +// freeOverload(_:Int) +func freeOverload(_ x: Int) -> String { + return "int" +} + +// freeOverload(_:String) +func freeOverload(_ x: String) -> String { + return "string" +} + +// freeOverload(_:Double) +func freeOverload(_ x: Double) -> String { + return "double" +} + +func testFreeOverload() { + let r1 = freeOverload(42) // $ type=r1:String target=freeOverload(_:Int) + let r2 = freeOverload("hi") // $ type=r2:String target=freeOverload(_:String) + let r3 = freeOverload(1.5) // $ type=r3:String target=freeOverload(_:Double) +} + +// --- Overload: init overloading --- + +class MultiInit { + var value : String + + // MultiInit.init() + init() { + value = "default" + } + + // MultiInit.init(int:) + init(int: Int) { + value = "int" + } + + // MultiInit.init(str:) + init(str: String) { + value = str // $ type=str:String + } + + // MultiInit.init(x:y:) + init(x: Int, y: Int) { + value = "pair" + } + + // MultiInit.getValue + func getValue() -> String { + return value // $ type=.value:String + } +} + +func testInitOverloading() { + let m1 = MultiInit() // $ type=m1:MultiInit target=MultiInit.init() + let m2 = MultiInit(int: 5) // $ type=m2:MultiInit target=MultiInit.init(int:) + let m3 = MultiInit(str: "x") // $ type=m3:MultiInit target=MultiInit.init(str:) + let m4 = MultiInit(x: 1, y: 2) // $ type=m4:MultiInit target=MultiInit.init(x:y:) + let v = m1.getValue() // $ type=v:String target=MultiInit.getValue +} + +// --- Overload: static vs instance method --- + +class StaticVsInstance { + // StaticVsInstance.action()->instance + func action() -> String { + return "instance" + } + + // StaticVsInstance.action()->static + static func action() -> String { + return "static" + } + + // StaticVsInstance.init + init() {} +} + +func testStaticVsInstance() { + let o = StaticVsInstance() // $ target=StaticVsInstance.init + let r1 = o.action() // $ type=r1:String target=StaticVsInstance.action()->instance + let r2 = StaticVsInstance.action() // $ type=r2:String target=StaticVsInstance.action()->static +} + +// --- Overload: protocol extension default vs concrete implementation --- + +protocol Describable { + // Describable.describe + func describe() -> String +} + +extension Describable { + // Describable.describe(default) + func describe() -> String { + return "default" + } + + // Describable.extra + func extra() -> String { + return "extra" + } +} + +class DescribableImpl : Describable { + // DescribableImpl.init + init() {} + + // DescribableImpl.describe + func describe() -> String { + return "concrete" + } +} + +func testProtocolExtensionOverload() { + let d = DescribableImpl() // $ target=DescribableImpl.init + let r1 = d.describe() // $ type=r1:String target=DescribableImpl.describe + let r2 = d.extra() // $ type=r2:String target=Describable.extra +} + +// --- Overload: subclass override resolution --- + +class Base { + // Base.init + init() {} + + // Base.action + func action() -> String { + return "base" + } + + // Base.baseOnly + func baseOnly() -> String { + return "baseOnly" + } +} + +class Sub : Base { + // Sub.init + override init() { + super.init() // $ target=Base.init + } + + // Sub.action + override func action() -> String { + return "sub" + } + + // Sub.subOnly + func subOnly() -> String { + return "subOnly" + } +} + +class SubSub : Sub { + // SubSub.init + override init() { + super.init() // $ target=Sub.init + } + + // SubSub.action + override func action() -> String { + return "subsub" + } +} + +func testOverrideResolution() { + let b = Base() // $ target=Base.init + let rb = b.action() // $ type=rb:String target=Base.action + + let s = Sub() // $ target=Sub.init + let rs = s.action() // $ type=rs:String target=Sub.action + let rbo = s.baseOnly() // $ type=rbo:String target=Base.baseOnly + let rso = s.subOnly() // $ type=rso:String target=Sub.subOnly + + let ss = SubSub() // $ target=SubSub.init + let rss = ss.action() // $ type=rss:String target=SubSub.action + let rbo2 = ss.baseOnly() // $ type=rbo2:String target=Base.baseOnly + let rso2 = ss.subOnly() // $ type=rso2:String target=Sub.subOnly +} + +// --- Overload: by external vs internal parameter names --- + +class LabelVariants { + // LabelVariants.send(to:) + func send(to target: String) -> String { + return target // $ type=target:String + } + + // LabelVariants.send(from:) + func send(from source: String) -> String { + return source // $ type=source:String + } + + // LabelVariants.send(to:from:) + func send(to target: String, from source: String) -> String { + return target + source + } +} + +func testLabelVariants() { + let o = LabelVariants() // $ target=init() + let r1 = o.send(to: "x") // $ type=r1:String target=LabelVariants.send(to:) + let r2 = o.send(from: "y") // $ type=r2:String target=LabelVariants.send(from:) + let r3 = o.send(to: "x", from: "y") // $ type=r3:String target=LabelVariants.send(to:from:) +} + +// --- Overload: generic function with different constraint satisfaction --- + +protocol Numeric2 : Equatable { + // Numeric2.zero + static func zero() -> Self +} + +extension Int : Numeric2 { + // Int.zero + static func zero() -> Int { return 0 } +} + +extension Double : Numeric2 { + // Double.zero + static func zero() -> Double { return 0.0 } +} + +// constrainedId(_:Numeric2) +func constrainedId(_ x: T) -> T { + return x +} + +// constrainedId(_:Equatable) +func constrainedId(_ x: T) -> T { + return x +} + +func testConstrainedOverload() { + let r1 = constrainedId(42) // $ type=r1:Int target=constrainedId(_:Numeric2) + let r2 = constrainedId("hello") // $ type=r2:String target=constrainedId(_:Equatable) +} + +// --- Overload: methods on generic type specialized differently --- + +class Box { + var item : T + + // Box.init + init(_ item: T) { + self.item = item // $ type=item:T + } + + // Box.get + func get() -> T { + return item // $ type=.item:T + } + + // Box.replace + func replace(_ newItem: T) { + item = newItem // $ type=newItem:T + } +} + +func testGenericMethodResolution() { + let intBox = Box(10) // $ type=intBox@Box:Int target=Box.init + let strBox = Box("hi") // $ type=strBox@Box:String target=Box.init + let v1 = intBox.get() // $ type=v1:Int target=Box.get + let v2 = strBox.get() // $ type=v2:String target=Box.get + intBox.replace(20) // $ target=Box.replace + strBox.replace("bye") // $ target=Box.replace +} + +// --- Overload: convenience init vs designated init --- + +class Widget { + var name : String + var size : Int + + // Widget.init(name:size:) + init(name: String, size: Int) { + self.name = name // $ type=name:String + self.size = size // $ type=size:Int + } + + // Widget.init(name:) + convenience init(name: String) { + self.init(name: name, size: 1) // $ target=Widget.init(name:size:) + } + + // Widget.init(size:) + convenience init(size: Int) { + self.init(name: "default", size: size) // $ target=Widget.init(name:size:) + } +} + +func testConvenienceInit() { + let w1 = Widget(name: "a", size: 5) // $ type=w1:Widget target=Widget.init(name:size:) + let w2 = Widget(name: "b") // $ type=w2:Widget target=Widget.init(name:) + let w3 = Widget(size: 10) // $ type=w3:Widget target=Widget.init(size:) +} + +// --- Overload: methods with closure parameters of different signatures --- + +class Processor { + // Processor.init + init() {} + + // Processor.apply(_:(Int)->Int) + func apply(_ f: (Int) -> Int) -> Int { + return f(0) + } + + // Processor.apply(_:(String)->String) + func apply(_ f: (String) -> String) -> String { + return f("") + } + + // Processor.apply(_:(Int,Int)->Int) + func apply(_ f: (Int, Int) -> Int) -> Int { + return f(0, 0) + } +} + +func testClosureOverload() { + let p = Processor() // $ target=Processor.init + let r1 = p.apply({ x in x + 1 }) // $ type=r1:Int target=Processor.apply(_:(Int)->Int) + let r2 = p.apply({ s in s + "!" }) // $ type=r2:String target=Processor.apply(_:(String)->String) + let r3 = p.apply({ x, y in x + y }) // $ type=r3:Int target=Processor.apply(_:(Int,Int)->Int) +} + +// --- Overload: subscript overloading --- + +class MultiSubscript { + var data : [Int] = [1, 2, 3] + var dict : [String: Int] = ["a": 1] + + // MultiSubscript.init + init() {} + + // MultiSubscript.get(_:) + func get(_ index: Int) -> Int { + return data[index] + } + + // MultiSubscript.get(_:String) + func get(_ key: String) -> Int { + return dict[key] ?? 0 + } +} + +func testMethodSubscriptLike() { + let ms = MultiSubscript() // $ target=MultiSubscript.init + let r1 = ms.get(0) // $ type=r1:Int target=MultiSubscript.get(_:) + let r2 = ms.get("a") // $ type=r2:Int target=MultiSubscript.get(_:String) +} + +// ============================================================ +// Type parameters with constraints +// ============================================================ + +// --- where clause on generic function --- + +// minOf(_:_:) +func minOf(_ a: T, _ b: T) -> T { + if a < b { return a } else { return b } +} + +// maxOf(_:_:) +func maxOf(_ a: T, _ b: T) -> T where T : Comparable { + if a > b { return a } else { return b } +} + +func testWhereClause() { + let m1 = minOf(3, 7) // $ type=m1:Int target=minOf(_:_:) + let m2 = minOf("a", "z") // $ type=m2:String target=minOf(_:_:) + let m3 = maxOf(3, 7) // $ type=m3:Int target=maxOf(_:_:) + let m4 = maxOf("a", "z") // $ type=m4:String target=maxOf(_:_:) +} + +// --- Multiple constraints on a single type parameter --- + +protocol Displayable2 { + // Displayable2.display + func display() -> String +} + +protocol Sortable { + // Sortable.sortKey + func sortKey() -> Int +} + +struct TaggedItem : Displayable2, Sortable, Equatable { + var tag : String + var priority : Int + + // TaggedItem.init + init(tag: String, priority: Int) { + self.tag = tag // $ type=tag:String + self.priority = priority // $ type=priority:Int + } + + // TaggedItem.display + func display() -> String { + return tag // $ type=.tag:String + } + + // TaggedItem.sortKey + func sortKey() -> Int { + return priority // $ type=.priority:Int + } +} + +// showAndSort(_:) +func showAndSort(_ item: T) -> String { + return item.display() // $ target=Displayable2.display +} + +// showSortAndCompare(_:_:) +func showSortAndCompare(_ a: T, _ b: T) -> Bool where T : Displayable2, T : Sortable, T : Equatable { + return a == b +} + +func testMultipleConstraints() { + let item = TaggedItem(tag: "x", priority: 1) // $ target=TaggedItem.init + let s = showAndSort(item) // $ type=s:String target=showAndSort(_:) + let eq = showSortAndCompare(item, item) // $ type=eq:Bool target=showSortAndCompare(_:_:) +} + +// --- Generic class with multiple constrained type parameters --- + +class SortedPair { + var first : T + var second : U + + // SortedPair.init + init(first: T, second: U) { + self.first = first // $ type=first:T + self.second = second // $ type=second:U + } + + // SortedPair.isFirstSmaller + func isFirstSmaller(than other: T) -> Bool { + return first < other // $ type=other:T + } + + // SortedPair.isSecondSmaller + func isSecondSmaller(than other: U) -> Bool { + return second < other // $ type=other:U + } +} + +func testMultiConstrainedParams() { + let sp = SortedPair(first: 3, second: "b") // $ target=SortedPair.init + let r1 = sp.isFirstSmaller(than: 5) // $ type=r1:Bool target=SortedPair.isFirstSmaller + let r2 = sp.isSecondSmaller(than: "z") // $ type=r2:Bool target=SortedPair.isSecondSmaller +} + +// --- Associated type constraints (same-type constraint) --- + +protocol ElementContainer { + associatedtype Element + // ElementContainer.first + func first() -> Element +} + +struct IntArray : ElementContainer { + typealias Element = Int + var items : [Int] + + // IntArray.init + init(items: [Int]) { + self.items = items + } + + // IntArray.first + func first() -> Int { + return items[0] + } +} + +struct StringArray : ElementContainer { + typealias Element = String + var items : [String] + + // StringArray.init + init(items: [String]) { + self.items = items + } + + // StringArray.first + func first() -> String { + return items[0] + } +} + +// extractFirst(from:Int) +func extractFirst(from container: C) -> C.Element where C.Element == Int { + return container.first() // $ target=ElementContainer.first +} + +// extractFirst(from:String) +func extractFirst(from container: C) -> C.Element where C.Element == String { + return container.first() // $ target=ElementContainer.first +} + +func testSameTypeConstraint() { + let ia = IntArray(items: [10, 20]) // $ target=IntArray.init + let sa = StringArray(items: ["hi", "there"]) // $ target=StringArray.init + let r1 = extractFirst(from: ia) // $ type=r1:Int target=extractFirst(from:Int) + let r2 = extractFirst(from: sa) // $ type=r2:String target=extractFirst(from:String) +} + +// --- Superclass constraint on type parameter --- + +class Vehicle { + var speed : Int + + // Vehicle.init + init(speed: Int) { + self.speed = speed // $ type=speed:Int + } + + // Vehicle.describe + func describe() -> String { + return "vehicle" + } +} + +class Car : Vehicle { + // Car.init + override init(speed: Int) { + super.init(speed: speed) // $ target=Vehicle.init + } + + // Car.describe + override func describe() -> String { + return "car" + } + + // Car.honk + func honk() -> String { + return "beep" + } +} + +class Truck : Vehicle { + // Truck.init + override init(speed: Int) { + super.init(speed: speed) // $ target=Vehicle.init + } + + // Truck.describe + override func describe() -> String { + return "truck" + } + + // Truck.haul + func haul() -> String { + return "hauling" + } +} + +// describeVehicle(_:) +func describeVehicle(_ v: T) -> String { + return v.describe() // $ target=Vehicle.describe +} + +func testSuperclassConstraint() { + let car = Car(speed: 100) // $ type=car:Car target=Car.init + let truck = Truck(speed: 60) // $ type=truck:Truck target=Truck.init + let d1 = describeVehicle(car) // $ type=d1:String target=describeVehicle(_:) + let d2 = describeVehicle(truck) // $ type=d2:String target=describeVehicle(_:) + let h = car.honk() // $ type=h:String target=Car.honk + let hl = truck.haul() // $ type=hl:String target=Truck.haul +} + +// --- Constrained extension methods --- + +protocol Summable { + // Summable.+ + static func +(lhs: Self, rhs: Self) -> Self +} + +extension Int : Summable {} +extension Double : Summable {} +extension String : Summable {} + +struct Accumulator { + var values : [T] + + // Accumulator.init + init(values: [T]) { + self.values = values + } + + // Accumulator.count + func count() -> Int { + return values.count + } +} + +extension Accumulator where T : Summable { + // Accumulator.total + func total() -> T { + return values[0] + values[1] // $ target=Summable.+ + } +} + +extension Accumulator where T : Equatable { + // Accumulator.contains + func contains(_ item: T) -> Bool { + return values.contains(where: { $0 == item }) + } +} + +func testConstrainedExtensions() { + let intAcc = Accumulator(values: [1, 2, 3]) // $ target=Accumulator.init + let cnt = intAcc.count() // $ type=cnt:Int target=Accumulator.count + let tot = intAcc.total() // $ type=tot:Int target=Accumulator.total + let has = intAcc.contains(2) // $ type=has:Bool target=Accumulator.contains +} + +// --- Generic method with its own constrained type parameter --- + +class Transformer { + // Transformer.init + init() {} + + // Transformer.transform + func transform(_ items: [T]) -> T { + return items[0] + items[1] // $ target=Summable.+ + } + + // Transformer.merge + func merge(_ a: A, _ b: B) -> Bool { + return true + } +} + +func testMethodTypeParams() { + let t = Transformer() // $ target=Transformer.init + let r1 = t.transform([3, 1, 2]) // $ type=r1:Int target=Transformer.transform + let r2 = t.transform(["c", "a", "b"]) // $ type=r2:String target=Transformer.transform + let r3 = t.merge(1, "x") // $ type=r3:Bool target=Transformer.merge +} + +// --- Recursive constraint (Comparable requiring Equatable) --- + +// clamp(_:min:max:) +func clamp(_ value: T, min lower: T, max upper: T) -> T { + if value < lower { return lower } + if value > upper { return upper } + return value +} + +func testRecursiveConstraint() { + let r1 = clamp(5, min: 0, max: 10) // $ type=r1:Int target=clamp(_:min:max:) + let r2 = clamp(3.5, min: 1.0, max: 2.0) // $ type=r2:Double target=clamp(_:min:max:) + let r3 = clamp("m", min: "a", max: "z") // $ type=r3:String target=clamp(_:min:max:) +} \ No newline at end of file From 038f9a2c2fc022848a8b319e23da82cc918af152 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 4 May 2026 10:55:06 +0200 Subject: [PATCH 2/3] Swift: Split type inference tests into multiple files --- .../library-tests/type-inference/basics.swift | 71 + .../type-inference/classes.swift | 231 +++ .../type-inference/generics.swift | 143 ++ .../type-inference/overload_resolution.swift | 495 ++++++ .../type-inference/protocols.swift | 119 ++ .../type-inference/type_constraints.swift | 300 ++++ .../type-inference/typeinference.swift | 1372 ----------------- 7 files changed, 1359 insertions(+), 1372 deletions(-) create mode 100644 swift/ql/test/library-tests/type-inference/basics.swift create mode 100644 swift/ql/test/library-tests/type-inference/classes.swift create mode 100644 swift/ql/test/library-tests/type-inference/generics.swift create mode 100644 swift/ql/test/library-tests/type-inference/overload_resolution.swift create mode 100644 swift/ql/test/library-tests/type-inference/protocols.swift create mode 100644 swift/ql/test/library-tests/type-inference/type_constraints.swift delete mode 100644 swift/ql/test/library-tests/type-inference/typeinference.swift diff --git a/swift/ql/test/library-tests/type-inference/basics.swift b/swift/ql/test/library-tests/type-inference/basics.swift new file mode 100644 index 000000000000..21bca811056e --- /dev/null +++ b/swift/ql/test/library-tests/type-inference/basics.swift @@ -0,0 +1,71 @@ +var topLevelDecl : Int = 0 +0 +topLevelDecl + 1 // $ type=topLevelDecl:Int + +class C { + var myInt : Int + // C.init + init(n: Int) { + myInt = n // $ type=n:Int + } + + // C.getMyInt + func getMyInt() -> Int { + return myInt // $ type=.myInt:Int + } +} + +class Derived : C { + // Derived.init + init() { + super.init(n: 0) // $ type=super:C target=C.init + } + + // Derived.callGetMyInt + func callGetMyInt() -> Int { + let x = getMyInt(); // $ type=x:Int target=C.getMyInt + return x + } +} + +class Generic { + var value : T + // Generic.init + init(v: T) { + value = v // $ type=v:T + } + + // Generic.getValue + func getValue() -> T { + return value // $ type=.value:T + } +} + +class GenericDerived : Generic { + // GenericDerived.init + init() { + super.init(v: 0) // $ type=super@Generic:Int target=Generic.init + } +} + +func testGeneric() { + let g = Generic(v: 42) // $ type=g@Generic:Int target=Generic.init + let x = g.getValue() // $ type=x:Int target=Generic.getValue + + let gd = GenericDerived() // $ type=gd:GenericDerived target=GenericDerived.init + let y = gd.getValue() // $ type=y:Int target=Generic.getValue +} + +// --- Extensions --- + +extension C { + // C.doubled + func doubled() -> Int { + return myInt * 2 // $ type=.myInt:Int + } +} + +func testExtension() { + let obj = C(n: 10) // $ target=C.init + let d = obj.doubled() // $ type=d:Int target=C.doubled +} diff --git a/swift/ql/test/library-tests/type-inference/classes.swift b/swift/ql/test/library-tests/type-inference/classes.swift new file mode 100644 index 000000000000..a463839e3d68 --- /dev/null +++ b/swift/ql/test/library-tests/type-inference/classes.swift @@ -0,0 +1,231 @@ +// --- Static methods --- + +class MathUtils { + // MathUtils.square + static func square(x: Int) -> Int { + return x * x // $ type=x:Int + } + + // MathUtils.cube + class func cube(x: Int) -> Int { + return x * x * x // $ type=x:Int + } +} + +func testStaticMethods() { + let s = MathUtils.square(x: 4) // $ type=s:Int target=MathUtils.square + let cu = MathUtils.cube(x: 3) // $ type=cu:Int target=MathUtils.cube +} + +// --- Simple overloading --- + +class Overloaded { + // Overloaded.process(_:Int) + func process(_ x: Int) -> Int { + return x + 1 // $ type=x:Int + } + + // Overloaded.process(_:String) + func process(_ s: String) -> String { + return s // $ type=s:String + } +} + +func testOverloading() { + let o = Overloaded() // $ target=init() + let r1 = o.process(42) // $ type=r1:Int target=Overloaded.process(_:Int) + let r2 = o.process("hello") // $ type=r2:String target=Overloaded.process(_:String) +} + +// --- Structs and methods --- + +struct Matrix { + var data : [[Int]] + + // Matrix.init + init(data: [[Int]]) { + self.data = data + } + + // Matrix.rowCount + func rowCount() -> Int { + return data.count + } +} + +func testSubscripts() { + let m = Matrix(data: [[1, 2], [3, 4]]) // $ target=Matrix.init + let rc = m.rowCount() // $ type=rc:Int target=Matrix.rowCount +} + +// --- Nested types --- + +class Outer { + class Inner { + var value : Int + + // Outer.Inner.init + init(value: Int) { + self.value = value // $ type=value:Int + } + + // Outer.Inner.getValue + func getValue() -> Int { + return self.value // $ type=.value:Int + } + } +} + +func testNestedTypes() { + let inner = Outer.Inner(value: 99) // $ type=inner:Inner target=Outer.Inner.init + let v = inner.getValue() // $ type=v:Int target=Outer.Inner.getValue +} + +// --- Method chaining --- + +class Builder { + var value : Int = 0 + + // Builder.init + init() {} + + // Builder.set + func set(_ v: Int) -> Builder { + value = v + return self + } + + // Builder.add + func add(_ v: Int) -> Builder { + value += v + return self + } + + // Builder.build + func build() -> Int { + return value // $ type=.value:Int + } +} + +func testChaining() { + let b = Builder() // $ type=b:Builder target=Builder.init + let result = b.set(10).add(5).build() // $ type=result:Int target=Builder.set target=Builder.add target=Builder.build +} + +// --- Default parameter values --- + +class Config { + // Config.init + init() {} + + // Config.setup + func setup(retries: Int = 3, timeout: Double = 30.0) -> Int { + return retries // $ type=retries:Int + } +} + +func testDefaultParams() { + let cfg = Config() // $ type=cfg:Config target=Config.init + let r1 = cfg.setup() // $ type=r1:Int target=Config.setup + let r2 = cfg.setup(retries: 5) // $ type=r2:Int target=Config.setup + let r3 = cfg.setup(retries: 2, timeout: 60.0) // $ type=r3:Int target=Config.setup +} + +// --- Computed properties accessed via methods --- + +class Temperature { + var celsius : Double + + // Temperature.init + init(celsius: Double) { + self.celsius = celsius // $ type=celsius:Double + } + + // Temperature.toCelsius + func toCelsius() -> Double { + return celsius // $ type=.celsius:Double + } + + // Temperature.toFahrenheit + func toFahrenheit() -> Double { + return celsius * 9.0 / 5.0 + 32.0 // $ type=.celsius:Double + } +} + +func testTemperature() { + let t = Temperature(celsius: 100.0) // $ type=t:Temperature target=Temperature.init + let c = t.toCelsius() // $ type=c:Double target=Temperature.toCelsius + let f = t.toFahrenheit() // $ type=f:Double target=Temperature.toFahrenheit +} + +// --- Inheritance with overriding --- + +class Animal { + // Animal.init + init() {} + + // Animal.speak + func speak() -> String { + return "..." + } +} + +class Dog : Animal { + // Dog.init + override init() { + super.init() // $ target=Animal.init + } + + // Dog.speak + override func speak() -> String { + return "Woof" + } + + // Dog.fetch + func fetch() -> String { + return "ball" + } +} + +class Cat : Animal { + // Cat.init + override init() { + super.init() // $ target=Animal.init + } + + // Cat.speak + override func speak() -> String { + return "Meow" + } +} + +func testOverriding() { + let d = Dog() // $ type=d:Dog target=Dog.init + let ds = d.speak() // $ type=ds:String target=Dog.speak + let df = d.fetch() // $ type=df:String target=Dog.fetch + + let ct = Cat() // $ type=ct:Cat target=Cat.init + let cs = ct.speak() // $ type=cs:String target=Cat.speak +} + +// --- Mutating methods on structs --- + +struct Counter { + var count : Int = 0 + + // Counter.increment + mutating func increment() { + count += 1 + } + + // Counter.getCount + func getCount() -> Int { + return count // $ type=.count:Int + } +} + +func testMutating() { + var ctr = Counter() // $ type=ctr:Counter target=init() + ctr.increment() // $ target=Counter.increment + let val = ctr.getCount() // $ type=val:Int target=Counter.getCount +} diff --git a/swift/ql/test/library-tests/type-inference/generics.swift b/swift/ql/test/library-tests/type-inference/generics.swift new file mode 100644 index 000000000000..cdc3c8a28772 --- /dev/null +++ b/swift/ql/test/library-tests/type-inference/generics.swift @@ -0,0 +1,143 @@ +// --- Generic functions --- + +// identity +func identity(_ x: T) -> T { + return x // $ type=x:T +} + +// makePair +func makePair(_ a: A, _ b: B) -> (A, B) { + return (a, b) // $ type=a:A +} + +func testGenericFunctions() { + let i = identity(42) // $ type=i:Int target=identity + let s = identity("hello") // $ type=s:String target=identity + let p = makePair(1, "two") // $ target=makePair +} + +// --- Generic structs --- + +struct Pair { + var first : A + var second : B + + // Pair.init + init(first: A, second: B) { + self.first = first // $ type=first:A + self.second = second // $ type=second:B + } + + // Pair.getFirst + func getFirst() -> A { + return first // $ type=.first:A + } + + // Pair.getSecond + func getSecond() -> B { + return second // $ type=.second:B + } +} + +func testGenericStruct() { + let p = Pair(first: 1, second: "x") // $ target=Pair.init type=p@Pair:Int type=p@Pair:String + let f = p.getFirst() // $ type=f:Int target=Pair.getFirst + let sc = p.getSecond() // $ type=sc:String target=Pair.getSecond +} + +// --- Enums with associated values --- + +enum Result { + // Result.success + case success(T) + // Result.failure + case failure(String) + + // Result.getValue + func getValue() -> T? { + switch self { + case .success(let v): // $ target=Result.success + return v // $ type=v:T + case .failure: // $ target=Result.failure + return nil + } + } +} + +func testEnum() { + let r = Result.success(42) // $ type=r@Result:Int target=Result.success + let v = r.getValue() // $ target=Result.getValue + + let r2 : Result = .success(42) // $ type=r2@Result:Int target=Result.success + let v2 = r2.getValue() // $ target=Result.getValue +} + +// --- Closures and type inference --- + +// applyTransform +func applyTransform(_ value: T, _ transform: (T) -> U) -> U { + return transform(value) +} + +func testClosures() { + let result = applyTransform(5, { x in x * 2 }) // $ target=applyTransform type=result:Int + let strings = applyTransform(10, { x in String(x) }) // $ target=applyTransform type=strings:String +} + +// --- Generic class with constraints --- + +protocol MyProtocol { + associatedtype MyType + + // MyProtocol.foo + func foo() -> Self + + // MyProtocol.bar + func bar() -> MyType +} + +class Wrapper { + var inner : T + + // Wrapper.init + init(_ inner: T) { + self.inner = inner // $ type=inner:T + } + + // Wrapper.get + func get() -> T { + return inner // $ type=.inner:T + } + + // Wrapper.callFoo + func callFoo() -> T { + let x = inner.foo(); // $ type=x:T target=MyProtocol.foo + return x + } + + // Wrapper.callBar + func callBar() -> T.MyType { + let x = inner.bar(); // $ type=x:MyType target=MyProtocol.bar + return x + } +} + +extension Int : MyProtocol { + typealias MyType = String + + // Int.foo + func foo() -> Int { + return self * 2 // $ type=self:Int + } + + // Int.bar + func bar() -> String { + return "number" + } +} + +func testConstrainedGeneric() { + let w = Wrapper(42) // $ type=w@Wrapper:Int target=Wrapper.init + let v = w.get() // $ type=v:Int target=Wrapper.get + let z = w.callFoo() // $ type=z:Int target=Wrapper.callFoo +} diff --git a/swift/ql/test/library-tests/type-inference/overload_resolution.swift b/swift/ql/test/library-tests/type-inference/overload_resolution.swift new file mode 100644 index 000000000000..94ce8dd78704 --- /dev/null +++ b/swift/ql/test/library-tests/type-inference/overload_resolution.swift @@ -0,0 +1,495 @@ +// --- Overload by parameter type --- + +class OverloadByType { + // OverloadByType.handle(_:Int) + func handle(_ x: Int) -> String { + return "int" + } + + // OverloadByType.handle(_:Double) + func handle(_ x: Double) -> String { + return "double" + } + + // OverloadByType.handle(_:String) + func handle(_ x: String) -> String { + return "string" + } + + // OverloadByType.handle(_:Bool) + func handle(_ x: Bool) -> String { + return "bool" + } +} + +func testOverloadByType() { + let o = OverloadByType() // $ target=init() + let r1 = o.handle(42) // $ type=r1:String target=OverloadByType.handle(_:Int) + let r2 = o.handle(3.14) // $ type=r2:String target=OverloadByType.handle(_:Double) + let r3 = o.handle("hi") // $ type=r3:String target=OverloadByType.handle(_:String) + let r4 = o.handle(true) // $ type=r4:String target=OverloadByType.handle(_:Bool) +} + +// --- Overload by argument label --- + +class OverloadByLabel { + // OverloadByLabel.configure(width:) + func configure(width: Int) -> String { + return "width" + } + + // OverloadByLabel.configure(height:) + func configure(height: Int) -> String { + return "height" + } + + // OverloadByLabel.configure(width:height:) + func configure(width: Int, height: Int) -> String { + return "both" + } + + // OverloadByLabel.configure(size:) + func configure(size: Int) -> String { + return "size" + } +} + +func testOverloadByLabel() { + let o = OverloadByLabel() // $ target=init() + let r1 = o.configure(width: 10) // $ type=r1:String target=OverloadByLabel.configure(width:) + let r2 = o.configure(height: 20) // $ type=r2:String target=OverloadByLabel.configure(height:) + let r3 = o.configure(width: 10, height: 20) // $ type=r3:String target=OverloadByLabel.configure(width:height:) + let r4 = o.configure(size: 30) // $ type=r4:String target=OverloadByLabel.configure(size:) +} + +// --- Overload by arity (number of parameters) --- + +class OverloadByArity { + // OverloadByArity.compute() + func compute() -> Int { + return 0 + } + + // OverloadByArity.compute(_:Int) + func compute(_ x: Int) -> Int { + return x + } + + // OverloadByArity.compute(_:Int,_:Int) + func compute(_ x: Int, _ y: Int) -> Int { + return x + y + } + + // OverloadByArity.compute(_:Int,_:Int,_:Int) + func compute(_ x: Int, _ y: Int, _ z: Int) -> Int { + return x + y + z + } +} + +func testOverloadByArity() { + let o = OverloadByArity() // $ target=init() + let r0 = o.compute() // $ type=r0:Int target=OverloadByArity.compute() + let r1 = o.compute(1) // $ type=r1:Int target=OverloadByArity.compute(_:Int) + let r2 = o.compute(1, 2) // $ type=r2:Int target=OverloadByArity.compute(_:Int,_:Int) + let r3 = o.compute(1, 2, 3) // $ type=r3:Int target=OverloadByArity.compute(_:Int,_:Int,_:Int) +} + +// --- Overload by return type (contextual type) --- + +class OverloadByReturn { + // OverloadByReturn.create()->Int + func create() -> Int { + return 0 + } + + // OverloadByReturn.create()->String + func create() -> String { + return "" + } + + // OverloadByReturn.create()->Double + func create() -> Double { + return 0.0 + } +} + +func testOverloadByReturn() { + let o = OverloadByReturn() // $ target=init() + let r1 : Int = o.create() // $ type=r1:Int target=OverloadByReturn.create()->Int + let r2 : String = o.create() // $ type=r2:String target=OverloadByReturn.create()->String + let r3 : Double = o.create() // $ type=r3:Double target=OverloadByReturn.create()->Double +} + +// --- Overload: generic vs non-generic (non-generic preferred) --- + +class OverloadGenericVsConcrete { + // OverloadGenericVsConcrete.process(_:Int) + func process(_ x: Int) -> String { + return "concrete" + } + + // OverloadGenericVsConcrete.process(_:) + func process(_ x: T) -> String { + return "generic" + } +} + +func testOverloadGenericVsConcrete() { + let o = OverloadGenericVsConcrete() // $ target=init() + let r1 = o.process(42) // $ type=r1:String target=OverloadGenericVsConcrete.process(_:Int) + let r2 = o.process("hello") // $ type=r2:String target=OverloadGenericVsConcrete.process(_:) + let r3 = o.process(true) // $ type=r3:String target=OverloadGenericVsConcrete.process(_:) +} + +// --- Overload: free functions by parameter type --- + +// freeOverload(_:Int) +func freeOverload(_ x: Int) -> String { + return "int" +} + +// freeOverload(_:String) +func freeOverload(_ x: String) -> String { + return "string" +} + +// freeOverload(_:Double) +func freeOverload(_ x: Double) -> String { + return "double" +} + +func testFreeOverload() { + let r1 = freeOverload(42) // $ type=r1:String target=freeOverload(_:Int) + let r2 = freeOverload("hi") // $ type=r2:String target=freeOverload(_:String) + let r3 = freeOverload(1.5) // $ type=r3:String target=freeOverload(_:Double) +} + +// --- Overload: init overloading --- + +class MultiInit { + var value : String + + // MultiInit.init() + init() { + value = "default" + } + + // MultiInit.init(int:) + init(int: Int) { + value = "int" + } + + // MultiInit.init(str:) + init(str: String) { + value = str // $ type=str:String + } + + // MultiInit.init(x:y:) + init(x: Int, y: Int) { + value = "pair" + } + + // MultiInit.getValue + func getValue() -> String { + return value // $ type=.value:String + } +} + +func testInitOverloading() { + let m1 = MultiInit() // $ type=m1:MultiInit target=MultiInit.init() + let m2 = MultiInit(int: 5) // $ type=m2:MultiInit target=MultiInit.init(int:) + let m3 = MultiInit(str: "x") // $ type=m3:MultiInit target=MultiInit.init(str:) + let m4 = MultiInit(x: 1, y: 2) // $ type=m4:MultiInit target=MultiInit.init(x:y:) + let v = m1.getValue() // $ type=v:String target=MultiInit.getValue +} + +// --- Overload: static vs instance method --- + +class StaticVsInstance { + // StaticVsInstance.action()->instance + func action() -> String { + return "instance" + } + + // StaticVsInstance.action()->static + static func action() -> String { + return "static" + } + + // StaticVsInstance.init + init() {} +} + +func testStaticVsInstance() { + let o = StaticVsInstance() // $ target=StaticVsInstance.init + let r1 = o.action() // $ type=r1:String target=StaticVsInstance.action()->instance + let r2 = StaticVsInstance.action() // $ type=r2:String target=StaticVsInstance.action()->static +} + +// --- Overload: protocol extension default vs concrete implementation --- + +protocol Describable { + // Describable.describe + func describe() -> String +} + +extension Describable { + // Describable.describe(default) + func describe() -> String { + return "default" + } + + // Describable.extra + func extra() -> String { + return "extra" + } +} + +class DescribableImpl : Describable { + // DescribableImpl.init + init() {} + + // DescribableImpl.describe + func describe() -> String { + return "concrete" + } +} + +func testProtocolExtensionOverload() { + let d = DescribableImpl() // $ target=DescribableImpl.init + let r1 = d.describe() // $ type=r1:String target=DescribableImpl.describe + let r2 = d.extra() // $ type=r2:String target=Describable.extra +} + +// --- Overload: subclass override resolution --- + +class Base { + // Base.init + init() {} + + // Base.action + func action() -> String { + return "base" + } + + // Base.baseOnly + func baseOnly() -> String { + return "baseOnly" + } +} + +class Sub : Base { + // Sub.init + override init() { + super.init() // $ target=Base.init + } + + // Sub.action + override func action() -> String { + return "sub" + } + + // Sub.subOnly + func subOnly() -> String { + return "subOnly" + } +} + +class SubSub : Sub { + // SubSub.init + override init() { + super.init() // $ target=Sub.init + } + + // SubSub.action + override func action() -> String { + return "subsub" + } +} + +func testOverrideResolution() { + let b = Base() // $ target=Base.init + let rb = b.action() // $ type=rb:String target=Base.action + + let s = Sub() // $ target=Sub.init + let rs = s.action() // $ type=rs:String target=Sub.action + let rbo = s.baseOnly() // $ type=rbo:String target=Base.baseOnly + let rso = s.subOnly() // $ type=rso:String target=Sub.subOnly + + let ss = SubSub() // $ target=SubSub.init + let rss = ss.action() // $ type=rss:String target=SubSub.action + let rbo2 = ss.baseOnly() // $ type=rbo2:String target=Base.baseOnly + let rso2 = ss.subOnly() // $ type=rso2:String target=Sub.subOnly +} + +// --- Overload: by external vs internal parameter names --- + +class LabelVariants { + // LabelVariants.send(to:) + func send(to target: String) -> String { + return target // $ type=target:String + } + + // LabelVariants.send(from:) + func send(from source: String) -> String { + return source // $ type=source:String + } + + // LabelVariants.send(to:from:) + func send(to target: String, from source: String) -> String { + return target + source + } +} + +func testLabelVariants() { + let o = LabelVariants() // $ target=init() + let r1 = o.send(to: "x") // $ type=r1:String target=LabelVariants.send(to:) + let r2 = o.send(from: "y") // $ type=r2:String target=LabelVariants.send(from:) + let r3 = o.send(to: "x", from: "y") // $ type=r3:String target=LabelVariants.send(to:from:) +} + +// --- Overload: generic function with different constraint satisfaction --- + +protocol Numeric2 : Equatable { + // Numeric2.zero + static func zero() -> Self +} + +extension Int : Numeric2 { + // Int.zero + static func zero() -> Int { return 0 } +} + +extension Double : Numeric2 { + // Double.zero + static func zero() -> Double { return 0.0 } +} + +// constrainedId(_:Numeric2) +func constrainedId(_ x: T) -> T { + return x +} + +// constrainedId(_:Equatable) +func constrainedId(_ x: T) -> T { + return x +} + +func testConstrainedOverload() { + let r1 = constrainedId(42) // $ type=r1:Int target=constrainedId(_:Numeric2) + let r2 = constrainedId("hello") // $ type=r2:String target=constrainedId(_:Equatable) +} + +// --- Overload: methods on generic type specialized differently --- + +class Box { + var item : T + + // Box.init + init(_ item: T) { + self.item = item // $ type=item:T + } + + // Box.get + func get() -> T { + return item // $ type=.item:T + } + + // Box.replace + func replace(_ newItem: T) { + item = newItem // $ type=newItem:T + } +} + +func testGenericMethodResolution() { + let intBox = Box(10) // $ type=intBox@Box:Int target=Box.init + let strBox = Box("hi") // $ type=strBox@Box:String target=Box.init + let v1 = intBox.get() // $ type=v1:Int target=Box.get + let v2 = strBox.get() // $ type=v2:String target=Box.get + intBox.replace(20) // $ target=Box.replace + strBox.replace("bye") // $ target=Box.replace +} + +// --- Overload: convenience init vs designated init --- + +class Widget { + var name : String + var size : Int + + // Widget.init(name:size:) + init(name: String, size: Int) { + self.name = name // $ type=name:String + self.size = size // $ type=size:Int + } + + // Widget.init(name:) + convenience init(name: String) { + self.init(name: name, size: 1) // $ target=Widget.init(name:size:) + } + + // Widget.init(size:) + convenience init(size: Int) { + self.init(name: "default", size: size) // $ target=Widget.init(name:size:) + } +} + +func testConvenienceInit() { + let w1 = Widget(name: "a", size: 5) // $ type=w1:Widget target=Widget.init(name:size:) + let w2 = Widget(name: "b") // $ type=w2:Widget target=Widget.init(name:) + let w3 = Widget(size: 10) // $ type=w3:Widget target=Widget.init(size:) +} + +// --- Overload: methods with closure parameters of different signatures --- + +class Processor { + // Processor.init + init() {} + + // Processor.apply(_:(Int)->Int) + func apply(_ f: (Int) -> Int) -> Int { + return f(0) + } + + // Processor.apply(_:(String)->String) + func apply(_ f: (String) -> String) -> String { + return f("") + } + + // Processor.apply(_:(Int,Int)->Int) + func apply(_ f: (Int, Int) -> Int) -> Int { + return f(0, 0) + } +} + +func testClosureOverload() { + let p = Processor() // $ target=Processor.init + let r1 = p.apply({ x in x + 1 }) // $ type=r1:Int target=Processor.apply(_:(Int)->Int) + let r2 = p.apply({ s in s + "!" }) // $ type=r2:String target=Processor.apply(_:(String)->String) + let r3 = p.apply({ x, y in x + y }) // $ type=r3:Int target=Processor.apply(_:(Int,Int)->Int) +} + +// --- Overload: subscript-like method overloading --- + +class MultiSubscript { + var data : [Int] = [1, 2, 3] + var dict : [String: Int] = ["a": 1] + + // MultiSubscript.init + init() {} + + // MultiSubscript.get(_:) + func get(_ index: Int) -> Int { + return data[index] + } + + // MultiSubscript.get(_:String) + func get(_ key: String) -> Int { + return dict[key] ?? 0 + } +} + +func testMethodSubscriptLike() { + let ms = MultiSubscript() // $ target=MultiSubscript.init + let r1 = ms.get(0) // $ type=r1:Int target=MultiSubscript.get(_:) + let r2 = ms.get("a") // $ type=r2:Int target=MultiSubscript.get(_:String) +} diff --git a/swift/ql/test/library-tests/type-inference/protocols.swift b/swift/ql/test/library-tests/type-inference/protocols.swift new file mode 100644 index 000000000000..94718b3232b2 --- /dev/null +++ b/swift/ql/test/library-tests/type-inference/protocols.swift @@ -0,0 +1,119 @@ +// --- Protocols and protocol conformance --- + +protocol Shape { + // Shape.area + func area() -> Double +} + +struct Circle : Shape { + var radius : Double + + // Circle.init + init(radius: Double) { + self.radius = radius // $ type=radius:Double + } + + // Circle.area + func area() -> Double { + return 3.14159 * radius * radius // $ type=.radius:Double + } +} + +struct Rectangle : Shape { + var width : Double + var height : Double + + // Rectangle.init + init(width: Double, height: Double) { + self.width = width // $ type=width:Double + self.height = height // $ type=height:Double + } + + // Rectangle.area + func area() -> Double { + return width * height // $ type=.width:Double + } +} + +func testProtocol() { + let c = Circle(radius: 5.0) // $ type=c:Circle target=Circle.init + let a1 = c.area() // $ type=a1:Double target=Circle.area + + let r = Rectangle(width: 3.0, height: 4.0) // $ type=r:Rectangle target=Rectangle.init + let a2 = r.area() // $ type=a2:Double target=Rectangle.area +} + +// --- Protocol with associated types --- + +protocol Container { + associatedtype Item + // Container.getItem + func getItem() -> Item + // Container.count + func count() -> Int +} + +struct IntContainer : Container { + typealias Item = Int + var items : [Int] + + // IntContainer.init + init(items: [Int]) { + self.items = items + } + + // IntContainer.getItem + func getItem() -> Int { + return items[0] + } + + // IntContainer.count + func count() -> Int { + return items.count + } +} + +func testAssociatedTypes() { + let ic = IntContainer(items: [1, 2, 3]) // $ type=ic:IntContainer target=IntContainer.init + let item = ic.getItem() // $ type=item:Int target=IntContainer.getItem + let cnt = ic.count() // $ type=cnt:Int target=IntContainer.count +} + +// --- Multiple protocol conformance --- + +protocol Printable { + // Printable.display + func display() -> String +} + +protocol Identifiable { + // Identifiable.id + func id() -> Int +} + +class Entity : Printable, Identifiable { + var name : String + var entityId : Int + + // Entity.init + init(name: String, entityId: Int) { + self.name = name // $ type=name:String + self.entityId = entityId // $ type=entityId:Int + } + + // Entity.display + func display() -> String { + return name // $ type=.name:String + } + + // Entity.id + func id() -> Int { + return entityId // $ type=.entityId:Int + } +} + +func testMultipleProtocols() { + let e = Entity(name: "test", entityId: 42) // $ type=e:Entity target=Entity.init + let d = e.display() // $ type=d:String target=Entity.display + let eid = e.id() // $ type=eid:Int target=Entity.id +} diff --git a/swift/ql/test/library-tests/type-inference/type_constraints.swift b/swift/ql/test/library-tests/type-inference/type_constraints.swift new file mode 100644 index 000000000000..36f61220d3f8 --- /dev/null +++ b/swift/ql/test/library-tests/type-inference/type_constraints.swift @@ -0,0 +1,300 @@ +// --- where clause on generic function --- + +// minOf(_:_:) +func minOf(_ a: T, _ b: T) -> T { + if a < b { return a } else { return b } +} + +// maxOf(_:_:) +func maxOf(_ a: T, _ b: T) -> T where T : Comparable { + if a > b { return a } else { return b } +} + +func testWhereClause() { + let m1 = minOf(3, 7) // $ type=m1:Int target=minOf(_:_:) + let m2 = minOf("a", "z") // $ type=m2:String target=minOf(_:_:) + let m3 = maxOf(3, 7) // $ type=m3:Int target=maxOf(_:_:) + let m4 = maxOf("a", "z") // $ type=m4:String target=maxOf(_:_:) +} + +// --- Multiple constraints on a single type parameter --- + +protocol Displayable2 { + // Displayable2.display + func display() -> String +} + +protocol Sortable { + // Sortable.sortKey + func sortKey() -> Int +} + +struct TaggedItem : Displayable2, Sortable, Equatable { + var tag : String + var priority : Int + + // TaggedItem.init + init(tag: String, priority: Int) { + self.tag = tag // $ type=tag:String + self.priority = priority // $ type=priority:Int + } + + // TaggedItem.display + func display() -> String { + return tag // $ type=.tag:String + } + + // TaggedItem.sortKey + func sortKey() -> Int { + return priority // $ type=.priority:Int + } +} + +// showAndSort(_:) +func showAndSort(_ item: T) -> String { + return item.display() // $ target=Displayable2.display +} + +// showSortAndCompare(_:_:) +func showSortAndCompare(_ a: T, _ b: T) -> Bool where T : Displayable2, T : Sortable, T : Equatable { + return a == b +} + +func testMultipleConstraints() { + let item = TaggedItem(tag: "x", priority: 1) // $ target=TaggedItem.init + let s = showAndSort(item) // $ type=s:String target=showAndSort(_:) + let eq = showSortAndCompare(item, item) // $ type=eq:Bool target=showSortAndCompare(_:_:) +} + +// --- Generic class with multiple constrained type parameters --- + +class SortedPair { + var first : T + var second : U + + // SortedPair.init + init(first: T, second: U) { + self.first = first // $ type=first:T + self.second = second // $ type=second:U + } + + // SortedPair.isFirstSmaller + func isFirstSmaller(than other: T) -> Bool { + return first < other // $ type=other:T + } + + // SortedPair.isSecondSmaller + func isSecondSmaller(than other: U) -> Bool { + return second < other // $ type=other:U + } +} + +func testMultiConstrainedParams() { + let sp = SortedPair(first: 3, second: "b") // $ target=SortedPair.init + let r1 = sp.isFirstSmaller(than: 5) // $ type=r1:Bool target=SortedPair.isFirstSmaller + let r2 = sp.isSecondSmaller(than: "z") // $ type=r2:Bool target=SortedPair.isSecondSmaller +} + +// --- Associated type constraints (same-type constraint) --- + +protocol ElementContainer { + associatedtype Element + // ElementContainer.first + func first() -> Element +} + +struct IntArray : ElementContainer { + typealias Element = Int + var items : [Int] + + // IntArray.init + init(items: [Int]) { + self.items = items + } + + // IntArray.first + func first() -> Int { + return items[0] + } +} + +struct StringArray : ElementContainer { + typealias Element = String + var items : [String] + + // StringArray.init + init(items: [String]) { + self.items = items + } + + // StringArray.first + func first() -> String { + return items[0] + } +} + +// extractFirst(from:Int) +func extractFirst(from container: C) -> C.Element where C.Element == Int { + return container.first() // $ target=ElementContainer.first +} + +// extractFirst(from:String) +func extractFirst(from container: C) -> C.Element where C.Element == String { + return container.first() // $ target=ElementContainer.first +} + +func testSameTypeConstraint() { + let ia = IntArray(items: [10, 20]) // $ target=IntArray.init + let sa = StringArray(items: ["hi", "there"]) // $ target=StringArray.init + let r1 = extractFirst(from: ia) // $ type=r1:Int target=extractFirst(from:Int) + let r2 = extractFirst(from: sa) // $ type=r2:String target=extractFirst(from:String) +} + +// --- Superclass constraint on type parameter --- + +class Vehicle { + var speed : Int + + // Vehicle.init + init(speed: Int) { + self.speed = speed // $ type=speed:Int + } + + // Vehicle.describe + func describe() -> String { + return "vehicle" + } +} + +class Car : Vehicle { + // Car.init + override init(speed: Int) { + super.init(speed: speed) // $ target=Vehicle.init + } + + // Car.describe + override func describe() -> String { + return "car" + } + + // Car.honk + func honk() -> String { + return "beep" + } +} + +class Truck : Vehicle { + // Truck.init + override init(speed: Int) { + super.init(speed: speed) // $ target=Vehicle.init + } + + // Truck.describe + override func describe() -> String { + return "truck" + } + + // Truck.haul + func haul() -> String { + return "hauling" + } +} + +// describeVehicle(_:) +func describeVehicle(_ v: T) -> String { + return v.describe() // $ target=Vehicle.describe +} + +func testSuperclassConstraint() { + let car = Car(speed: 100) // $ type=car:Car target=Car.init + let truck = Truck(speed: 60) // $ type=truck:Truck target=Truck.init + let d1 = describeVehicle(car) // $ type=d1:String target=describeVehicle(_:) + let d2 = describeVehicle(truck) // $ type=d2:String target=describeVehicle(_:) + let h = car.honk() // $ type=h:String target=Car.honk + let hl = truck.haul() // $ type=hl:String target=Truck.haul +} + +// --- Constrained extension methods --- + +protocol Summable { + // Summable.+ + static func +(lhs: Self, rhs: Self) -> Self +} + +extension Int : Summable {} +extension Double : Summable {} +extension String : Summable {} + +struct Accumulator { + var values : [T] + + // Accumulator.init + init(values: [T]) { + self.values = values + } + + // Accumulator.count + func count() -> Int { + return values.count + } +} + +extension Accumulator where T : Summable { + // Accumulator.total + func total() -> T { + return values[0] + values[1] // $ target=Summable.+ + } +} + +extension Accumulator where T : Equatable { + // Accumulator.contains + func contains(_ item: T) -> Bool { + return values.contains(where: { $0 == item }) + } +} + +func testConstrainedExtensions() { + let intAcc = Accumulator(values: [1, 2, 3]) // $ target=Accumulator.init + let cnt = intAcc.count() // $ type=cnt:Int target=Accumulator.count + let tot = intAcc.total() // $ type=tot:Int target=Accumulator.total + let has = intAcc.contains(2) // $ type=has:Bool target=Accumulator.contains +} + +// --- Generic method with its own constrained type parameter --- + +class Transformer { + // Transformer.init + init() {} + + // Transformer.transform + func transform(_ items: [T]) -> T { + return items[0] + items[1] // $ target=Summable.+ + } + + // Transformer.merge + func merge(_ a: A, _ b: B) -> Bool { + return true + } +} + +func testMethodTypeParams() { + let t = Transformer() // $ target=Transformer.init + let r1 = t.transform([3, 1, 2]) // $ type=r1:Int target=Transformer.transform + let r2 = t.transform(["c", "a", "b"]) // $ type=r2:String target=Transformer.transform + let r3 = t.merge(1, "x") // $ type=r3:Bool target=Transformer.merge +} + +// --- Recursive constraint (Comparable requiring Equatable) --- + +// clamp(_:min:max:) +func clamp(_ value: T, min lower: T, max upper: T) -> T { + if value < lower { return lower } + if value > upper { return upper } + return value +} + +func testRecursiveConstraint() { + let r1 = clamp(5, min: 0, max: 10) // $ type=r1:Int target=clamp(_:min:max:) + let r2 = clamp(3.5, min: 1.0, max: 2.0) // $ type=r2:Double target=clamp(_:min:max:) + let r3 = clamp("m", min: "a", max: "z") // $ type=r3:String target=clamp(_:min:max:) +} diff --git a/swift/ql/test/library-tests/type-inference/typeinference.swift b/swift/ql/test/library-tests/type-inference/typeinference.swift deleted file mode 100644 index 0e718fa9b927..000000000000 --- a/swift/ql/test/library-tests/type-inference/typeinference.swift +++ /dev/null @@ -1,1372 +0,0 @@ -var topLevelDecl : Int = 0 -0 -topLevelDecl + 1 // $ type=topLevelDecl:Int - -class C { - var myInt : Int - // C.init - init(n: Int) { - myInt = n // $ type=n:Int - } - - // C.getMyInt - func getMyInt() -> Int { - return myInt // $ type=.myInt:Int - } -} - -class Derived : C { - // Derived.init - init() { - super.init(n: 0) // $ type=super:C target=C.init - } - - // Derived.callGetMyInt - func callGetMyInt() -> Int { - let x = getMyInt(); // $ type=x:Int target=C.getMyInt - return x - } -} - -class Generic { - var value : T - // Generic.init - init(v: T) { - value = v // $ type=v:T - } - - // Generic.getValue - func getValue() -> T { - return value // $ type=.value:T - } -} - -class GenericDerived : Generic { - // GenericDerived.init - init() { - super.init(v: 0) // $ type=super@Generic:Int target=Generic.init - } -} - -func testGeneric() { - let g = Generic(v: 42) // $ type=g@Generic:Int target=Generic.init - let x = g.getValue() // $ type=x:Int target=Generic.getValue - - let gd = GenericDerived() // $ type=gd:GenericDerived target=GenericDerived.init - let y = gd.getValue() // $ type=y:Int target=Generic.getValue -} - -// --- Protocols and protocol conformance --- - -protocol Shape { - // Shape.area - func area() -> Double -} - -struct Circle : Shape { - var radius : Double - - // Circle.init - init(radius: Double) { - self.radius = radius // $ type=radius:Double - } - - // Circle.area - func area() -> Double { - return 3.14159 * radius * radius // $ type=.radius:Double - } -} - -struct Rectangle : Shape { - var width : Double - var height : Double - - // Rectangle.init - init(width: Double, height: Double) { - self.width = width // $ type=width:Double - self.height = height // $ type=height:Double - } - - // Rectangle.area - func area() -> Double { - return width * height // $ type=.width:Double - } -} - -func testProtocol() { - let c = Circle(radius: 5.0) // $ type=c:Circle target=Circle.init - let a1 = c.area() // $ type=a1:Double target=Circle.area - - let r = Rectangle(width: 3.0, height: 4.0) // $ type=r:Rectangle target=Rectangle.init - let a2 = r.area() // $ type=a2:Double target=Rectangle.area -} - -// --- Extensions --- - -extension C { - // C.doubled - func doubled() -> Int { - return myInt * 2 // $ type=.myInt:Int - } -} - -func testExtension() { - let obj = C(n: 10) // $ target=C.init - let d = obj.doubled() // $ type=d:Int target=C.doubled -} - -// --- Static methods --- - -class MathUtils { - // MathUtils.square - static func square(x: Int) -> Int { - return x * x // $ type=x:Int - } - - // MathUtils.cube - class func cube(x: Int) -> Int { - return x * x * x // $ type=x:Int - } -} - -func testStaticMethods() { - let s = MathUtils.square(x: 4) // $ type=s:Int target=MathUtils.square - let cu = MathUtils.cube(x: 3) // $ type=cu:Int target=MathUtils.cube -} - -// --- Overloading --- - -class Overloaded { - // Overloaded.process(_:Int) - func process(_ x: Int) -> Int { - return x + 1 // $ type=x:Int - } - - // Overloaded.process(_:String) - func process(_ s: String) -> String { - return s // $ type=s:String - } -} - -func testOverloading() { - let o = Overloaded() // $ target=init() - let r1 = o.process(42) // $ type=r1:Int target=Overloaded.process(_:Int) - let r2 = o.process("hello") // $ type=r2:String target=Overloaded.process(_:String) -} - -// --- Generic functions --- - -// identity -func identity(_ x: T) -> T { - return x // $ type=x:T -} - -// makePair -func makePair(_ a: A, _ b: B) -> (A, B) { - return (a, b) // $ type=a:A -} - -func testGenericFunctions() { - let i = identity(42) // $ type=i:Int target=identity - let s = identity("hello") // $ type=s:String target=identity - let p = makePair(1, "two") // $ target=makePair -} - -// --- Generic constraints --- - -struct Pair { - var first : A - var second : B - - // Pair.init - init(first: A, second: B) { - self.first = first // $ type=first:A - self.second = second // $ type=second:B - } - - // Pair.getFirst - func getFirst() -> A { - return first // $ type=.first:A - } - - // Pair.getSecond - func getSecond() -> B { - return second // $ type=.second:B - } -} - -func testGenericStruct() { - let p = Pair(first: 1, second: "x") // $ target=Pair.init type=p@Pair:Int type=p@Pair:String - let f = p.getFirst() // $ type=f:Int target=Pair.getFirst - let sc = p.getSecond() // $ type=sc:String target=Pair.getSecond -} - -// --- Enums with associated values --- - -enum Result { - // Result.success - case success(T) - // Result.failure - case failure(String) - - // Result.getValue - func getValue() -> T? { - switch self { - case .success(let v): // $ target=Result.success - return v // $ type=v:T - case .failure: // $ target=Result.failure - return nil - } - } -} - -func testEnum() { - let r = Result.success(42) // $ type=r@Result:Int target=Result.success - let v = r.getValue() // $ target=Result.getValue - - let r2 : Result = .success(42) // $ type=r2@Result:Int target=Result.success - let v2 = r2.getValue() // $ target=Result.getValue -} - -// --- Closures and type inference --- - -// applyTransform -func applyTransform(_ value: T, _ transform: (T) -> U) -> U { - return transform(value) -} - -func testClosures() { - let result = applyTransform(5, { x in x * 2 }) // $ target=applyTransform type=result:Int - let strings = applyTransform(10, { x in String(x) }) // $ target=applyTransform type=strings:String -} - -// --- Subscripts --- - -struct Matrix { - var data : [[Int]] - - // Matrix.init - init(data: [[Int]]) { - self.data = data - } - - // Matrix.rowCount - func rowCount() -> Int { - return data.count - } -} - -func testSubscripts() { - let m = Matrix(data: [[1, 2], [3, 4]]) // $ target=Matrix.init - let rc = m.rowCount() // $ type=rc:Int target=Matrix.rowCount -} - -// --- Nested types --- - -class Outer { - class Inner { - var value : Int - - // Outer.Inner.init - init(value: Int) { - self.value = value // $ type=value:Int - } - - // Outer.Inner.getValue - func getValue() -> Int { - return self.value // $ type=.value:Int - } - } -} - -func testNestedTypes() { - let inner = Outer.Inner(value: 99) // $ type=inner:Inner target=Outer.Inner.init - let v = inner.getValue() // $ type=v:Int target=Outer.Inner.getValue -} - -// --- Method chaining --- - -class Builder { - var value : Int = 0 - - // Builder.init - init() {} - - // Builder.set - func set(_ v: Int) -> Builder { - value = v - return self - } - - // Builder.add - func add(_ v: Int) -> Builder { - value += v - return self - } - - // Builder.build - func build() -> Int { - return value // $ type=.value:Int - } -} - -func testChaining() { - let b = Builder() // $ type=b:Builder target=Builder.init - let result = b.set(10).add(5).build() // $ type=result:Int target=Builder.set target=Builder.add target=Builder.build -} - -// --- Protocol with associated type-like patterns (using generics) --- - -protocol Container { - associatedtype Item - // Container.getItem - func getItem() -> Item - // Container.count - func count() -> Int -} - -struct IntContainer : Container { - typealias Item = Int - var items : [Int] - - // IntContainer.init - init(items: [Int]) { - self.items = items - } - - // IntContainer.getItem - func getItem() -> Int { - return items[0] - } - - // IntContainer.count - func count() -> Int { - return items.count - } -} - -func testAssociatedTypes() { - let ic = IntContainer(items: [1, 2, 3]) // $ type=ic:IntContainer target=IntContainer.init - let item = ic.getItem() // $ type=item:Int target=IntContainer.getItem - let cnt = ic.count() // $ type=cnt:Int target=IntContainer.count -} - -// --- Default parameter values --- - -class Config { - // Config.init - init() {} - - // Config.setup - func setup(retries: Int = 3, timeout: Double = 30.0) -> Int { - return retries // $ type=retries:Int - } -} - -func testDefaultParams() { - let cfg = Config() // $ type=cfg:Config target=Config.init - let r1 = cfg.setup() // $ type=r1:Int target=Config.setup - let r2 = cfg.setup(retries: 5) // $ type=r2:Int target=Config.setup - let r3 = cfg.setup(retries: 2, timeout: 60.0) // $ type=r3:Int target=Config.setup -} - -// --- Multiple inheritance (protocol conformance) --- - -protocol Printable { - // Printable.display - func display() -> String -} - -protocol Identifiable { - // Identifiable.id - func id() -> Int -} - -class Entity : Printable, Identifiable { - var name : String - var entityId : Int - - // Entity.init - init(name: String, entityId: Int) { - self.name = name // $ type=name:String - self.entityId = entityId // $ type=entityId:Int - } - - // Entity.display - func display() -> String { - return name // $ type=.name:String - } - - // Entity.id - func id() -> Int { - return entityId // $ type=.entityId:Int - } -} - -func testMultipleProtocols() { - let e = Entity(name: "test", entityId: 42) // $ type=e:Entity target=Entity.init - let d = e.display() // $ type=d:String target=Entity.display - let eid = e.id() // $ type=eid:Int target=Entity.id -} - -// --- Computed properties accessed via methods --- - -class Temperature { - var celsius : Double - - // Temperature.init - init(celsius: Double) { - self.celsius = celsius // $ type=celsius:Double - } - - // Temperature.toCelsius - func toCelsius() -> Double { - return celsius // $ type=.celsius:Double - } - - // Temperature.toFahrenheit - func toFahrenheit() -> Double { - return celsius * 9.0 / 5.0 + 32.0 // $ type=.celsius:Double - } -} - -func testTemperature() { - let t = Temperature(celsius: 100.0) // $ type=t:Temperature target=Temperature.init - let c = t.toCelsius() // $ type=c:Double target=Temperature.toCelsius - let f = t.toFahrenheit() // $ type=f:Double target=Temperature.toFahrenheit -} - -// --- Inheritance with overriding --- - -class Animal { - // Animal.init - init() {} - - // Animal.speak - func speak() -> String { - return "..." - } -} - -class Dog : Animal { - // Dog.init - override init() { - super.init() // $ target=Animal.init - } - - // Dog.speak - override func speak() -> String { - return "Woof" - } - - // Dog.fetch - func fetch() -> String { - return "ball" - } -} - -class Cat : Animal { - // Cat.init - override init() { - super.init() // $ target=Animal.init - } - - // Cat.speak - override func speak() -> String { - return "Meow" - } -} - -func testOverriding() { - let d = Dog() // $ type=d:Dog target=Dog.init - let ds = d.speak() // $ type=ds:String target=Dog.speak - let df = d.fetch() // $ type=df:String target=Dog.fetch - - let ct = Cat() // $ type=ct:Cat target=Cat.init - let cs = ct.speak() // $ type=cs:String target=Cat.speak -} - -// --- Generic class with constraints --- - -protocol MyProtocol { - associatedtype MyType - - // MyProtocol.foo - func foo() -> Self - - // MyProtocol.bar - func bar() -> MyType -} - -class Wrapper { - var inner : T - - // Wrapper.init - init(_ inner: T) { - self.inner = inner // $ type=inner:T - } - - // Wrapper.get - func get() -> T { - return inner // $ type=.inner:T - } - - // Wrapper.callFoo - func callFoo() -> T { - let x = inner.foo(); // $ type=x:T target=MyProtocol.foo - return x - } - - // Wrapper.callBar - func callBar() -> T.MyType { - let x = inner.bar(); // $ type=x:MyType target=MyProtocol.bar - return x - } -} - -extension Int : MyProtocol { - typealias MyType = String - - // Int.foo - func foo() -> Int { - return self * 2 // $ type=self:Int - } - - // Int.bar - func bar() -> String { - return "number" - } -} - -func testConstrainedGeneric() { - let w = Wrapper(42) // $ type=w@Wrapper:Int target=Wrapper.init - let v = w.get() // $ type=v:Int target=Wrapper.get - let z = w.callFoo() // $ type=z:Int target=Wrapper.callFoo -} - -// --- Mutating methods on structs --- - -struct Counter { - var count : Int = 0 - - // Counter.increment - mutating func increment() { - count += 1 - } - - // Counter.getCount - func getCount() -> Int { - return count // $ type=.count:Int - } -} - -func testMutating() { - var ctr = Counter() // $ type=ctr:Counter target=init() - ctr.increment() // $ target=Counter.increment - let val = ctr.getCount() // $ type=val:Int target=Counter.getCount -} - -// ============================================================ -// Overload resolution tests -// ============================================================ - -// --- Overload by parameter type --- - -class OverloadByType { - // OverloadByType.handle(_:Int) - func handle(_ x: Int) -> String { - return "int" - } - - // OverloadByType.handle(_:Double) - func handle(_ x: Double) -> String { - return "double" - } - - // OverloadByType.handle(_:String) - func handle(_ x: String) -> String { - return "string" - } - - // OverloadByType.handle(_:Bool) - func handle(_ x: Bool) -> String { - return "bool" - } -} - -func testOverloadByType() { - let o = OverloadByType() // $ target=init() - let r1 = o.handle(42) // $ type=r1:String target=OverloadByType.handle(_:Int) - let r2 = o.handle(3.14) // $ type=r2:String target=OverloadByType.handle(_:Double) - let r3 = o.handle("hi") // $ type=r3:String target=OverloadByType.handle(_:String) - let r4 = o.handle(true) // $ type=r4:String target=OverloadByType.handle(_:Bool) -} - -// --- Overload by argument label --- - -class OverloadByLabel { - // OverloadByLabel.configure(width:) - func configure(width: Int) -> String { - return "width" - } - - // OverloadByLabel.configure(height:) - func configure(height: Int) -> String { - return "height" - } - - // OverloadByLabel.configure(width:height:) - func configure(width: Int, height: Int) -> String { - return "both" - } - - // OverloadByLabel.configure(size:) - func configure(size: Int) -> String { - return "size" - } -} - -func testOverloadByLabel() { - let o = OverloadByLabel() // $ target=init() - let r1 = o.configure(width: 10) // $ type=r1:String target=OverloadByLabel.configure(width:) - let r2 = o.configure(height: 20) // $ type=r2:String target=OverloadByLabel.configure(height:) - let r3 = o.configure(width: 10, height: 20) // $ type=r3:String target=OverloadByLabel.configure(width:height:) - let r4 = o.configure(size: 30) // $ type=r4:String target=OverloadByLabel.configure(size:) -} - -// --- Overload by arity (number of parameters) --- - -class OverloadByArity { - // OverloadByArity.compute() - func compute() -> Int { - return 0 - } - - // OverloadByArity.compute(_:Int) - func compute(_ x: Int) -> Int { - return x - } - - // OverloadByArity.compute(_:Int,_:Int) - func compute(_ x: Int, _ y: Int) -> Int { - return x + y - } - - // OverloadByArity.compute(_:Int,_:Int,_:Int) - func compute(_ x: Int, _ y: Int, _ z: Int) -> Int { - return x + y + z - } -} - -func testOverloadByArity() { - let o = OverloadByArity() // $ target=init() - let r0 = o.compute() // $ type=r0:Int target=OverloadByArity.compute() - let r1 = o.compute(1) // $ type=r1:Int target=OverloadByArity.compute(_:Int) - let r2 = o.compute(1, 2) // $ type=r2:Int target=OverloadByArity.compute(_:Int,_:Int) - let r3 = o.compute(1, 2, 3) // $ type=r3:Int target=OverloadByArity.compute(_:Int,_:Int,_:Int) -} - -// --- Overload by return type (contextual type) --- - -class OverloadByReturn { - // OverloadByReturn.create()->Int - func create() -> Int { - return 0 - } - - // OverloadByReturn.create()->String - func create() -> String { - return "" - } - - // OverloadByReturn.create()->Double - func create() -> Double { - return 0.0 - } -} - -func testOverloadByReturn() { - let o = OverloadByReturn() // $ target=init() - let r1 : Int = o.create() // $ type=r1:Int target=OverloadByReturn.create()->Int - let r2 : String = o.create() // $ type=r2:String target=OverloadByReturn.create()->String - let r3 : Double = o.create() // $ type=r3:Double target=OverloadByReturn.create()->Double -} - -// --- Overload: generic vs non-generic (non-generic preferred) --- - -class OverloadGenericVsConcrete { - // OverloadGenericVsConcrete.process(_:Int) - func process(_ x: Int) -> String { - return "concrete" - } - - // OverloadGenericVsConcrete.process(_:) - func process(_ x: T) -> String { - return "generic" - } -} - -func testOverloadGenericVsConcrete() { - let o = OverloadGenericVsConcrete() // $ target=init() - let r1 = o.process(42) // $ type=r1:String target=OverloadGenericVsConcrete.process(_:Int) - let r2 = o.process("hello") // $ type=r2:String target=OverloadGenericVsConcrete.process(_:) - let r3 = o.process(true) // $ type=r3:String target=OverloadGenericVsConcrete.process(_:) -} - -// --- Overload: free functions by parameter type --- - -// freeOverload(_:Int) -func freeOverload(_ x: Int) -> String { - return "int" -} - -// freeOverload(_:String) -func freeOverload(_ x: String) -> String { - return "string" -} - -// freeOverload(_:Double) -func freeOverload(_ x: Double) -> String { - return "double" -} - -func testFreeOverload() { - let r1 = freeOverload(42) // $ type=r1:String target=freeOverload(_:Int) - let r2 = freeOverload("hi") // $ type=r2:String target=freeOverload(_:String) - let r3 = freeOverload(1.5) // $ type=r3:String target=freeOverload(_:Double) -} - -// --- Overload: init overloading --- - -class MultiInit { - var value : String - - // MultiInit.init() - init() { - value = "default" - } - - // MultiInit.init(int:) - init(int: Int) { - value = "int" - } - - // MultiInit.init(str:) - init(str: String) { - value = str // $ type=str:String - } - - // MultiInit.init(x:y:) - init(x: Int, y: Int) { - value = "pair" - } - - // MultiInit.getValue - func getValue() -> String { - return value // $ type=.value:String - } -} - -func testInitOverloading() { - let m1 = MultiInit() // $ type=m1:MultiInit target=MultiInit.init() - let m2 = MultiInit(int: 5) // $ type=m2:MultiInit target=MultiInit.init(int:) - let m3 = MultiInit(str: "x") // $ type=m3:MultiInit target=MultiInit.init(str:) - let m4 = MultiInit(x: 1, y: 2) // $ type=m4:MultiInit target=MultiInit.init(x:y:) - let v = m1.getValue() // $ type=v:String target=MultiInit.getValue -} - -// --- Overload: static vs instance method --- - -class StaticVsInstance { - // StaticVsInstance.action()->instance - func action() -> String { - return "instance" - } - - // StaticVsInstance.action()->static - static func action() -> String { - return "static" - } - - // StaticVsInstance.init - init() {} -} - -func testStaticVsInstance() { - let o = StaticVsInstance() // $ target=StaticVsInstance.init - let r1 = o.action() // $ type=r1:String target=StaticVsInstance.action()->instance - let r2 = StaticVsInstance.action() // $ type=r2:String target=StaticVsInstance.action()->static -} - -// --- Overload: protocol extension default vs concrete implementation --- - -protocol Describable { - // Describable.describe - func describe() -> String -} - -extension Describable { - // Describable.describe(default) - func describe() -> String { - return "default" - } - - // Describable.extra - func extra() -> String { - return "extra" - } -} - -class DescribableImpl : Describable { - // DescribableImpl.init - init() {} - - // DescribableImpl.describe - func describe() -> String { - return "concrete" - } -} - -func testProtocolExtensionOverload() { - let d = DescribableImpl() // $ target=DescribableImpl.init - let r1 = d.describe() // $ type=r1:String target=DescribableImpl.describe - let r2 = d.extra() // $ type=r2:String target=Describable.extra -} - -// --- Overload: subclass override resolution --- - -class Base { - // Base.init - init() {} - - // Base.action - func action() -> String { - return "base" - } - - // Base.baseOnly - func baseOnly() -> String { - return "baseOnly" - } -} - -class Sub : Base { - // Sub.init - override init() { - super.init() // $ target=Base.init - } - - // Sub.action - override func action() -> String { - return "sub" - } - - // Sub.subOnly - func subOnly() -> String { - return "subOnly" - } -} - -class SubSub : Sub { - // SubSub.init - override init() { - super.init() // $ target=Sub.init - } - - // SubSub.action - override func action() -> String { - return "subsub" - } -} - -func testOverrideResolution() { - let b = Base() // $ target=Base.init - let rb = b.action() // $ type=rb:String target=Base.action - - let s = Sub() // $ target=Sub.init - let rs = s.action() // $ type=rs:String target=Sub.action - let rbo = s.baseOnly() // $ type=rbo:String target=Base.baseOnly - let rso = s.subOnly() // $ type=rso:String target=Sub.subOnly - - let ss = SubSub() // $ target=SubSub.init - let rss = ss.action() // $ type=rss:String target=SubSub.action - let rbo2 = ss.baseOnly() // $ type=rbo2:String target=Base.baseOnly - let rso2 = ss.subOnly() // $ type=rso2:String target=Sub.subOnly -} - -// --- Overload: by external vs internal parameter names --- - -class LabelVariants { - // LabelVariants.send(to:) - func send(to target: String) -> String { - return target // $ type=target:String - } - - // LabelVariants.send(from:) - func send(from source: String) -> String { - return source // $ type=source:String - } - - // LabelVariants.send(to:from:) - func send(to target: String, from source: String) -> String { - return target + source - } -} - -func testLabelVariants() { - let o = LabelVariants() // $ target=init() - let r1 = o.send(to: "x") // $ type=r1:String target=LabelVariants.send(to:) - let r2 = o.send(from: "y") // $ type=r2:String target=LabelVariants.send(from:) - let r3 = o.send(to: "x", from: "y") // $ type=r3:String target=LabelVariants.send(to:from:) -} - -// --- Overload: generic function with different constraint satisfaction --- - -protocol Numeric2 : Equatable { - // Numeric2.zero - static func zero() -> Self -} - -extension Int : Numeric2 { - // Int.zero - static func zero() -> Int { return 0 } -} - -extension Double : Numeric2 { - // Double.zero - static func zero() -> Double { return 0.0 } -} - -// constrainedId(_:Numeric2) -func constrainedId(_ x: T) -> T { - return x -} - -// constrainedId(_:Equatable) -func constrainedId(_ x: T) -> T { - return x -} - -func testConstrainedOverload() { - let r1 = constrainedId(42) // $ type=r1:Int target=constrainedId(_:Numeric2) - let r2 = constrainedId("hello") // $ type=r2:String target=constrainedId(_:Equatable) -} - -// --- Overload: methods on generic type specialized differently --- - -class Box { - var item : T - - // Box.init - init(_ item: T) { - self.item = item // $ type=item:T - } - - // Box.get - func get() -> T { - return item // $ type=.item:T - } - - // Box.replace - func replace(_ newItem: T) { - item = newItem // $ type=newItem:T - } -} - -func testGenericMethodResolution() { - let intBox = Box(10) // $ type=intBox@Box:Int target=Box.init - let strBox = Box("hi") // $ type=strBox@Box:String target=Box.init - let v1 = intBox.get() // $ type=v1:Int target=Box.get - let v2 = strBox.get() // $ type=v2:String target=Box.get - intBox.replace(20) // $ target=Box.replace - strBox.replace("bye") // $ target=Box.replace -} - -// --- Overload: convenience init vs designated init --- - -class Widget { - var name : String - var size : Int - - // Widget.init(name:size:) - init(name: String, size: Int) { - self.name = name // $ type=name:String - self.size = size // $ type=size:Int - } - - // Widget.init(name:) - convenience init(name: String) { - self.init(name: name, size: 1) // $ target=Widget.init(name:size:) - } - - // Widget.init(size:) - convenience init(size: Int) { - self.init(name: "default", size: size) // $ target=Widget.init(name:size:) - } -} - -func testConvenienceInit() { - let w1 = Widget(name: "a", size: 5) // $ type=w1:Widget target=Widget.init(name:size:) - let w2 = Widget(name: "b") // $ type=w2:Widget target=Widget.init(name:) - let w3 = Widget(size: 10) // $ type=w3:Widget target=Widget.init(size:) -} - -// --- Overload: methods with closure parameters of different signatures --- - -class Processor { - // Processor.init - init() {} - - // Processor.apply(_:(Int)->Int) - func apply(_ f: (Int) -> Int) -> Int { - return f(0) - } - - // Processor.apply(_:(String)->String) - func apply(_ f: (String) -> String) -> String { - return f("") - } - - // Processor.apply(_:(Int,Int)->Int) - func apply(_ f: (Int, Int) -> Int) -> Int { - return f(0, 0) - } -} - -func testClosureOverload() { - let p = Processor() // $ target=Processor.init - let r1 = p.apply({ x in x + 1 }) // $ type=r1:Int target=Processor.apply(_:(Int)->Int) - let r2 = p.apply({ s in s + "!" }) // $ type=r2:String target=Processor.apply(_:(String)->String) - let r3 = p.apply({ x, y in x + y }) // $ type=r3:Int target=Processor.apply(_:(Int,Int)->Int) -} - -// --- Overload: subscript overloading --- - -class MultiSubscript { - var data : [Int] = [1, 2, 3] - var dict : [String: Int] = ["a": 1] - - // MultiSubscript.init - init() {} - - // MultiSubscript.get(_:) - func get(_ index: Int) -> Int { - return data[index] - } - - // MultiSubscript.get(_:String) - func get(_ key: String) -> Int { - return dict[key] ?? 0 - } -} - -func testMethodSubscriptLike() { - let ms = MultiSubscript() // $ target=MultiSubscript.init - let r1 = ms.get(0) // $ type=r1:Int target=MultiSubscript.get(_:) - let r2 = ms.get("a") // $ type=r2:Int target=MultiSubscript.get(_:String) -} - -// ============================================================ -// Type parameters with constraints -// ============================================================ - -// --- where clause on generic function --- - -// minOf(_:_:) -func minOf(_ a: T, _ b: T) -> T { - if a < b { return a } else { return b } -} - -// maxOf(_:_:) -func maxOf(_ a: T, _ b: T) -> T where T : Comparable { - if a > b { return a } else { return b } -} - -func testWhereClause() { - let m1 = minOf(3, 7) // $ type=m1:Int target=minOf(_:_:) - let m2 = minOf("a", "z") // $ type=m2:String target=minOf(_:_:) - let m3 = maxOf(3, 7) // $ type=m3:Int target=maxOf(_:_:) - let m4 = maxOf("a", "z") // $ type=m4:String target=maxOf(_:_:) -} - -// --- Multiple constraints on a single type parameter --- - -protocol Displayable2 { - // Displayable2.display - func display() -> String -} - -protocol Sortable { - // Sortable.sortKey - func sortKey() -> Int -} - -struct TaggedItem : Displayable2, Sortable, Equatable { - var tag : String - var priority : Int - - // TaggedItem.init - init(tag: String, priority: Int) { - self.tag = tag // $ type=tag:String - self.priority = priority // $ type=priority:Int - } - - // TaggedItem.display - func display() -> String { - return tag // $ type=.tag:String - } - - // TaggedItem.sortKey - func sortKey() -> Int { - return priority // $ type=.priority:Int - } -} - -// showAndSort(_:) -func showAndSort(_ item: T) -> String { - return item.display() // $ target=Displayable2.display -} - -// showSortAndCompare(_:_:) -func showSortAndCompare(_ a: T, _ b: T) -> Bool where T : Displayable2, T : Sortable, T : Equatable { - return a == b -} - -func testMultipleConstraints() { - let item = TaggedItem(tag: "x", priority: 1) // $ target=TaggedItem.init - let s = showAndSort(item) // $ type=s:String target=showAndSort(_:) - let eq = showSortAndCompare(item, item) // $ type=eq:Bool target=showSortAndCompare(_:_:) -} - -// --- Generic class with multiple constrained type parameters --- - -class SortedPair { - var first : T - var second : U - - // SortedPair.init - init(first: T, second: U) { - self.first = first // $ type=first:T - self.second = second // $ type=second:U - } - - // SortedPair.isFirstSmaller - func isFirstSmaller(than other: T) -> Bool { - return first < other // $ type=other:T - } - - // SortedPair.isSecondSmaller - func isSecondSmaller(than other: U) -> Bool { - return second < other // $ type=other:U - } -} - -func testMultiConstrainedParams() { - let sp = SortedPair(first: 3, second: "b") // $ target=SortedPair.init - let r1 = sp.isFirstSmaller(than: 5) // $ type=r1:Bool target=SortedPair.isFirstSmaller - let r2 = sp.isSecondSmaller(than: "z") // $ type=r2:Bool target=SortedPair.isSecondSmaller -} - -// --- Associated type constraints (same-type constraint) --- - -protocol ElementContainer { - associatedtype Element - // ElementContainer.first - func first() -> Element -} - -struct IntArray : ElementContainer { - typealias Element = Int - var items : [Int] - - // IntArray.init - init(items: [Int]) { - self.items = items - } - - // IntArray.first - func first() -> Int { - return items[0] - } -} - -struct StringArray : ElementContainer { - typealias Element = String - var items : [String] - - // StringArray.init - init(items: [String]) { - self.items = items - } - - // StringArray.first - func first() -> String { - return items[0] - } -} - -// extractFirst(from:Int) -func extractFirst(from container: C) -> C.Element where C.Element == Int { - return container.first() // $ target=ElementContainer.first -} - -// extractFirst(from:String) -func extractFirst(from container: C) -> C.Element where C.Element == String { - return container.first() // $ target=ElementContainer.first -} - -func testSameTypeConstraint() { - let ia = IntArray(items: [10, 20]) // $ target=IntArray.init - let sa = StringArray(items: ["hi", "there"]) // $ target=StringArray.init - let r1 = extractFirst(from: ia) // $ type=r1:Int target=extractFirst(from:Int) - let r2 = extractFirst(from: sa) // $ type=r2:String target=extractFirst(from:String) -} - -// --- Superclass constraint on type parameter --- - -class Vehicle { - var speed : Int - - // Vehicle.init - init(speed: Int) { - self.speed = speed // $ type=speed:Int - } - - // Vehicle.describe - func describe() -> String { - return "vehicle" - } -} - -class Car : Vehicle { - // Car.init - override init(speed: Int) { - super.init(speed: speed) // $ target=Vehicle.init - } - - // Car.describe - override func describe() -> String { - return "car" - } - - // Car.honk - func honk() -> String { - return "beep" - } -} - -class Truck : Vehicle { - // Truck.init - override init(speed: Int) { - super.init(speed: speed) // $ target=Vehicle.init - } - - // Truck.describe - override func describe() -> String { - return "truck" - } - - // Truck.haul - func haul() -> String { - return "hauling" - } -} - -// describeVehicle(_:) -func describeVehicle(_ v: T) -> String { - return v.describe() // $ target=Vehicle.describe -} - -func testSuperclassConstraint() { - let car = Car(speed: 100) // $ type=car:Car target=Car.init - let truck = Truck(speed: 60) // $ type=truck:Truck target=Truck.init - let d1 = describeVehicle(car) // $ type=d1:String target=describeVehicle(_:) - let d2 = describeVehicle(truck) // $ type=d2:String target=describeVehicle(_:) - let h = car.honk() // $ type=h:String target=Car.honk - let hl = truck.haul() // $ type=hl:String target=Truck.haul -} - -// --- Constrained extension methods --- - -protocol Summable { - // Summable.+ - static func +(lhs: Self, rhs: Self) -> Self -} - -extension Int : Summable {} -extension Double : Summable {} -extension String : Summable {} - -struct Accumulator { - var values : [T] - - // Accumulator.init - init(values: [T]) { - self.values = values - } - - // Accumulator.count - func count() -> Int { - return values.count - } -} - -extension Accumulator where T : Summable { - // Accumulator.total - func total() -> T { - return values[0] + values[1] // $ target=Summable.+ - } -} - -extension Accumulator where T : Equatable { - // Accumulator.contains - func contains(_ item: T) -> Bool { - return values.contains(where: { $0 == item }) - } -} - -func testConstrainedExtensions() { - let intAcc = Accumulator(values: [1, 2, 3]) // $ target=Accumulator.init - let cnt = intAcc.count() // $ type=cnt:Int target=Accumulator.count - let tot = intAcc.total() // $ type=tot:Int target=Accumulator.total - let has = intAcc.contains(2) // $ type=has:Bool target=Accumulator.contains -} - -// --- Generic method with its own constrained type parameter --- - -class Transformer { - // Transformer.init - init() {} - - // Transformer.transform - func transform(_ items: [T]) -> T { - return items[0] + items[1] // $ target=Summable.+ - } - - // Transformer.merge - func merge(_ a: A, _ b: B) -> Bool { - return true - } -} - -func testMethodTypeParams() { - let t = Transformer() // $ target=Transformer.init - let r1 = t.transform([3, 1, 2]) // $ type=r1:Int target=Transformer.transform - let r2 = t.transform(["c", "a", "b"]) // $ type=r2:String target=Transformer.transform - let r3 = t.merge(1, "x") // $ type=r3:Bool target=Transformer.merge -} - -// --- Recursive constraint (Comparable requiring Equatable) --- - -// clamp(_:min:max:) -func clamp(_ value: T, min lower: T, max upper: T) -> T { - if value < lower { return lower } - if value > upper { return upper } - return value -} - -func testRecursiveConstraint() { - let r1 = clamp(5, min: 0, max: 10) // $ type=r1:Int target=clamp(_:min:max:) - let r2 = clamp(3.5, min: 1.0, max: 2.0) // $ type=r2:Double target=clamp(_:min:max:) - let r3 = clamp("m", min: "a", max: "z") // $ type=r3:String target=clamp(_:min:max:) -} \ No newline at end of file From 224934645e09ae6303b687d0729121f947cd7b29 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Mon, 4 May 2026 11:00:38 +0200 Subject: [PATCH 3/3] Swift: Add type inference tests for key path expressions --- .../type-inference/key_paths.swift | 220 ++++++++++++++++++ .../type-inference/type-inference.ql | 4 +- 2 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 swift/ql/test/library-tests/type-inference/key_paths.swift diff --git a/swift/ql/test/library-tests/type-inference/key_paths.swift b/swift/ql/test/library-tests/type-inference/key_paths.swift new file mode 100644 index 000000000000..43a51838c04b --- /dev/null +++ b/swift/ql/test/library-tests/type-inference/key_paths.swift @@ -0,0 +1,220 @@ +// --- Key-path expressions: basic property access --- + +struct Point { + var x : Double + var y : Double + + // Point.init + init(x: Double, y: Double) { + self.x = x // $ type=x:Double + self.y = y // $ type=y:Double + } + + // Point.distanceFromOrigin + func distanceFromOrigin() -> Double { + return (x * x + y * y).squareRoot() + } +} + +struct Line { + var start : Point + var end : Point + + // Line.init + init(start: Point, end: Point) { + self.start = start + self.end = end + } +} + +func testBasicKeyPaths() { + let kpX = \Point.x + let kpY = \Point.y + + let p = Point(x: 3.0, y: 4.0) // $ type=p:Point target=Point.init + let xVal = p[keyPath: kpX] // $ type=xVal:Double + let yVal = p[keyPath: kpY] // $ type=yVal:Double +} + +// --- Key-path expressions: nested property access --- + +func testNestedKeyPaths() { + let kpStartX = \Line.start.x + let kpEndY = \Line.end.y + + let s = Point(x: 0.0, y: 0.0) // $ target=Point.init + let e = Point(x: 1.0, y: 1.0) // $ target=Point.init + let line = Line(start: s, end: e) // $ target=Line.init + let startX = line[keyPath: kpStartX] // $ type=startX:Double + let endY = line[keyPath: kpEndY] // $ type=endY:Double +} + +// --- Key-path expressions: identity (\.self) --- + +func testSelfKeyPath() { + let kpSelf = \Int.self + let val = 42[keyPath: kpSelf] // $ type=val:Int +} + +// --- Key-path expressions: optional chaining --- + +struct Person { + var name : String + var address : Address? + + // Person.init + init(name: String, address: Address?) { + self.name = name // $ type=name:String + self.address = address + } +} + +struct Address { + var city : String + var zip : String + + // Address.init + init(city: String, zip: String) { + self.city = city // $ type=city:String + self.zip = zip // $ type=zip:String + } +} + +func testOptionalChainingKeyPaths() { + let kpCity = \Person.address?.city + let addr = Address(city: "NYC", zip: "10001") // $ target=Address.init + let person = Person(name: "Alice", address: addr) // $ target=Person.init + + let city = person[keyPath: kpCity] // $ type=city:String? +} + +// --- Key-path expressions: used as function arguments --- + +struct Employee { + var name : String + var salary : Int + + // Employee.init + init(name: String, salary: Int) { + self.name = name // $ type=name:String + self.salary = salary // $ type=salary:Int + } +} + +// extractField(_:keyPath:) +func extractField(_ items: [T], keyPath: KeyPath) -> [V] { + return items.map { $0[keyPath: keyPath] } +} + +func testKeyPathAsArgument() { + let e1 = Employee(name: "Alice", salary: 100) // $ target=Employee.init + let e2 = Employee(name: "Bob", salary: 200) // $ target=Employee.init + let employees = [e1, e2] + let names = extractField(employees, keyPath: \.name) // $ target=extractField(_:keyPath:) + let name = names[0] // $ type=name:String + let salaries = extractField(employees, keyPath: \.salary) // $ target=extractField(_:keyPath:) + let salary = salaries[0] // $ type=salary:Int +} + +// --- Key-path expressions: with generics --- + +class KPContainer { + var item : T + + // KPContainer.init + init(item: T) { + self.item = item // $ type=item:T + } +} + +func testGenericKeyPaths() { + let kp = \KPContainer.item + let c = KPContainer(item: 42) // $ target=KPContainer.init + let v = c[keyPath: kp] // $ type=v:Int +} + +// --- Key-path expressions: writable key paths and mutation --- + +func testWritableKeyPaths() { + var p = Point(x: 1.0, y: 2.0) // $ type=p:Point target=Point.init + let kpX = \Point.x + p[keyPath: kpX] = 10.0 + let newX = p[keyPath: kpX] // $ type=newX:Double +} + +// --- Key-path expressions: appending key paths --- + +func testKeyPathAppending() { + let kpStart = \Line.start + let kpX = \Point.x + let kpStartX = kpStart.appending(path: kpX) + + let s = Point(x: 5.0, y: 6.0) // $ target=Point.init + let e = Point(x: 7.0, y: 8.0) // $ target=Point.init + let line = Line(start: s, end: e) // $ target=Line.init + let val = line[keyPath: kpStartX] // $ type=val:Double +} + +// --- Key-path expressions: shorthand in higher-order functions --- + +func testKeyPathInMap() { + let e1 = Employee(name: "Alice", salary: 100) // $ target=Employee.init + let e2 = Employee(name: "Bob", salary: 200) // $ target=Employee.init + let employees = [e1, e2] + let names = employees.map(\.name) + let name = names[0] // $ type=name:String + let salaries = employees.map(\.salary) + let salary = salaries[0] // $ type=salary:Int +} + +// --- Key-path expressions: class hierarchy --- + +class Shape2 { + var color : String + + // Shape2.init + init(color: String) { + self.color = color // $ type=color:String + } +} + +class Circle2 : Shape2 { + var radius : Double + + // Circle2.init + init(color: String, radius: Double) { + self.radius = radius // $ type=radius:Double + super.init(color: color) // $ target=Shape2.init + } +} + +func testInheritedKeyPaths() { + let kpColor = \Circle2.color + let kpRadius = \Circle2.radius + + let c = Circle2(color: "red", radius: 5.0) // $ type=c:Circle2 target=Circle2.init + let col = c[keyPath: kpColor] // $ type=col:String + let rad = c[keyPath: kpRadius] // $ type=rad:Double +} + +// --- Key-path expressions: tuple element access --- + +func testTupleKeyPath() { + let kp0 = \(Int, String).0 + let kp1 = \(Int, String).1 + let tuple = (42, "hello") + let first = tuple[keyPath: kp0] // $ type=first:Int + let second = tuple[keyPath: kp1] // $ type=second:String +} + +// --- Key-path expressions: array/dictionary subscript --- + +func testSubscriptKeyPaths() { + let kpFirst = \[Int][0] + let arr = [10, 20, 30] + let first = arr[keyPath: kpFirst] // $ type=first:Int + + let kpKey = \[String: Int]["x"] + let dict = ["x": 1, "y": 2] + let val = dict[keyPath: kpKey] // $ type=val:Int? +} diff --git a/swift/ql/test/library-tests/type-inference/type-inference.ql b/swift/ql/test/library-tests/type-inference/type-inference.ql index cd51687e8af9..b34a72eb6937 100644 --- a/swift/ql/test/library-tests/type-inference/type-inference.ql +++ b/swift/ql/test/library-tests/type-inference/type-inference.ql @@ -62,9 +62,7 @@ private Type getTypeAt(Type t, string path) { } module TypeTest implements TestSig { - string getARelevantTag() { result = ["type", "certainType"] } - - predicate tagIsOptional(string expectedTag) { expectedTag = "type" } + string getARelevantTag() { result = "type" } predicate hasActualResult(Location location, string element, string tag, string value) { none() }