| //===----------------------------------------------------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2020 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // RUN: %target-run-simple-swift |
| // REQUIRES: executable_test |
| |
| @_spi(Reflection) import Swift |
| import StdlibUnittest |
| |
| struct TestStruct { |
| var int = 0 |
| var double = 0.0 |
| var bool = false |
| } |
| |
| struct GenericStruct<T> { |
| var int = 0 |
| var first: T |
| var second: T |
| } |
| |
| enum TestEnum { |
| case one |
| case two |
| case three(TestStruct) |
| } |
| |
| class BaseClass { |
| var superInt = 0 |
| init() {} |
| } |
| |
| class TestClass: BaseClass { |
| var int = 0 |
| var double = 0.0 |
| var bool = false |
| override init() {} |
| } |
| |
| class TestSubclass: TestClass { |
| var strings: [String] = [] |
| override init() {} |
| } |
| |
| class GenericClass<T, U>: BaseClass { |
| var first: T |
| var second: U |
| |
| init(_ t: T, _ u: U) { |
| self.first = t |
| self.second = u |
| } |
| } |
| |
| class GenericSubclass<V, W>: GenericClass<V, Bool> { |
| var third: W |
| |
| init(_ v: V, _ w: W) { |
| self.third = w |
| super.init(v, false) |
| } |
| } |
| |
| class OwnershipTestClass: BaseClass { |
| weak var test1: TestClass? |
| unowned var test2: TestClass |
| unowned(unsafe) var test3: TestClass |
| |
| init(_ t: TestClass) { |
| self.test1 = t |
| self.test2 = t |
| self.test3 = t |
| } |
| } |
| |
| struct SimilarToNSPoint { |
| var x: Double |
| var y: Double |
| } |
| |
| struct SimilarToNSSize { |
| var width: Double |
| var height: Double |
| } |
| |
| struct SimilarToNSRect { |
| var origin: SimilarToNSPoint |
| var size: SimilarToNSSize |
| } |
| |
| struct ContainsObject { |
| var obj: TestClass |
| } |
| |
| struct LetKeyPaths { |
| let int : Int |
| let double: Double |
| } |
| |
| protocol TestExisential {} |
| |
| struct KeyPathTypes { |
| weak var weakObj: TestClass? |
| unowned var unownedObj: TestClass |
| var obj: TestClass |
| var tuple: (Int, Int, Int) |
| var structField: Int |
| var function: (Int) -> (Int) |
| var optionalFunction: (Int) -> (Int)? |
| var enumField: TestEnum |
| var existential: TestExisential |
| var existentialMetatype: Any.Type |
| var metatype: Int.Type |
| } |
| |
| #if _runtime(_ObjC) |
| import Foundation |
| |
| class NSObjectSubclass: NSObject { |
| var point: (Double, Double) |
| |
| init(x: Double, y: Double) { |
| self.point = (x, y) |
| } |
| } |
| |
| class EmptyNSObject: NSObject {} |
| #endif |
| |
| @available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) |
| func checkFields<T>( |
| of type: T.Type, |
| options: _EachFieldOptions = [], |
| fields: [String: (Int, Any.Type)] |
| ) { |
| var count = 0 |
| |
| _forEachField(of: T.self, options: options) { |
| charPtr, offset, type, kind in |
| count += 1 |
| |
| let fieldName = String(cString: charPtr) |
| guard let (checkOffset, checkType) = fields[fieldName] else { |
| expectTrue(false, "Unexpected field '\(fieldName)'") |
| return true |
| } |
| |
| expectEqual(checkOffset, offset) |
| expectEqual(checkType, type) |
| return true |
| } |
| |
| expectEqual(fields.count, count) |
| } |
| |
| @available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) |
| func checkFieldsWithKeyPath<T>( |
| of type: T.Type, |
| options: _EachFieldOptions = [], |
| fields: [String: PartialKeyPath<T>] |
| ) { |
| var count = 0 |
| |
| _forEachFieldWithKeyPath(of: T.self, options: options) { |
| charPtr, keyPath in |
| count += 1 |
| |
| let fieldName = String(cString: charPtr) |
| guard let checkKeyPath = fields[fieldName] else { |
| expectTrue(false, "Unexpected field '\(fieldName)'") |
| return true |
| } |
| |
| expectTrue(checkKeyPath == keyPath) |
| return true |
| } |
| |
| expectEqual(fields.count, count) |
| } |
| |
| protocol ExistentialProtocol {} |
| |
| extension TestStruct: ExistentialProtocol {} |
| extension GenericStruct: ExistentialProtocol {} |
| extension GenericSubclass: ExistentialProtocol {} |
| |
| @available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) |
| extension ExistentialProtocol { |
| static func doCheckFields( |
| options: _EachFieldOptions = [], |
| fields: [String: (Int, Any.Type)] |
| ) { |
| checkFields(of: Self.self, options: options, fields: fields) |
| } |
| } |
| |
| @available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) |
| func checkFieldsAsExistential( |
| of type: ExistentialProtocol.Type, |
| options: _EachFieldOptions = [], |
| fields: [String: (Int, Any.Type)] |
| ) { |
| type.doCheckFields(options: options, fields: fields) |
| } |
| |
| @available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) |
| func _withTypeEncodingCallback(encoding: inout String, name: UnsafePointer<CChar>, offset: Int, type: Any.Type, kind: _MetadataKind) -> Bool { |
| if type == Bool.self { |
| encoding += "B" |
| return true |
| } else if type == Int.self { |
| if MemoryLayout<Int>.size == MemoryLayout<Int64>.size { |
| encoding += "q" |
| } else if MemoryLayout<Int>.size == MemoryLayout<Int32>.size { |
| encoding += "l" |
| } else { |
| return false |
| } |
| return true |
| } else if type == Double.self { |
| encoding += "d" |
| return true |
| } |
| |
| switch kind { |
| case .struct: |
| encoding += "{" |
| defer { encoding += "}" } |
| _forEachField(of: type) { name, offset, type, kind in |
| _withTypeEncodingCallback(encoding: &encoding, name: name, offset: offset, type: type, kind: kind) |
| } |
| case .class: |
| encoding += "@" |
| default: |
| break |
| } |
| return true |
| } |
| |
| @available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) |
| func getTypeEncoding<T>(_ type: T.Type) -> String? { |
| var encoding = "" |
| _ = _forEachField(of: type) { name, offset, type, kind in |
| _withTypeEncodingCallback(encoding: &encoding, name: name, offset: offset, type: type, kind: kind) |
| } |
| return "{\(encoding)}" |
| } |
| |
| //===----------------------------------------------------------------------===// |
| |
| var tests = TestSuite("ForEachField") |
| |
| if #available(macOS 10.15.4, iOS 13.4, tvOS 13.4, watchOS 6.2, *) { |
| |
| tests.test("TestTuple") { |
| checkFields( |
| of: (Int, Bool).self, |
| fields: [".0": (0, Int.self), ".1": (MemoryLayout<Int>.stride, Bool.self)]) |
| |
| checkFields( |
| of: (a: Int, b: Bool).self, |
| fields: ["a": (0, Int.self), "b": (MemoryLayout<Int>.stride, Bool.self)]) |
| } |
| |
| tests.test("TestEnum") { |
| checkFields(of: TestEnum.self, fields: [:]) |
| } |
| |
| tests.test("TestStruct") { |
| checkFields( |
| of: TestStruct.self, |
| fields: [ |
| "int": (0, Int.self), |
| "double": (MemoryLayout<Double>.stride, Double.self), |
| "bool": (MemoryLayout<Double>.stride * 2, Bool.self), |
| ]) |
| |
| checkFieldsAsExistential( |
| of: TestStruct.self, |
| fields: [ |
| "int": (0, Int.self), |
| "double": (MemoryLayout<Double>.stride, Double.self), |
| "bool": (MemoryLayout<Double>.stride * 2, Bool.self), |
| ]) |
| |
| // Applying to struct type with .classType option fails |
| expectFalse(_forEachField(of: TestStruct.self, options: .classType) { |
| _, _, _, _ in true |
| }) |
| } |
| |
| if #available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) { |
| tests.test("StructKeyPath") { |
| checkFieldsWithKeyPath( |
| of: TestStruct.self, |
| fields: [ |
| "int": \TestStruct.int, |
| "double": \TestStruct.double, |
| "bool": \TestStruct.bool, |
| ]) |
| } |
| |
| tests.test("LetKeyPaths") { |
| checkFieldsWithKeyPath( |
| of: LetKeyPaths.self, |
| fields: [ |
| "int": \LetKeyPaths.int, |
| "double": \LetKeyPaths.double, |
| ]) |
| } |
| |
| tests.test("KeyPathTypes") { |
| checkFieldsWithKeyPath( |
| of: KeyPathTypes.self, |
| options: .ignoreUnknown, |
| fields: [ |
| "obj": \KeyPathTypes.obj, |
| "tuple": \KeyPathTypes.tuple, |
| "structField": \KeyPathTypes.structField, |
| "enumField": \KeyPathTypes.enumField, |
| "existential": \KeyPathTypes.existential, |
| "existentialMetatype": \KeyPathTypes.existentialMetatype, |
| ]) |
| } |
| |
| tests.test("TupleKeyPath") { |
| typealias TestTuple = (Int, Int, TestClass, TestStruct) |
| checkFieldsWithKeyPath( |
| of: TestTuple.self, |
| fields: [ |
| ".0": \TestTuple.0, |
| ".1": \TestTuple.1, |
| ".2": \TestTuple.2, |
| ".3": \TestTuple.3, |
| ]) |
| } |
| } |
| |
| func checkGenericStruct<T>(_: T.Type) { |
| let firstOffset = max(MemoryLayout<Int>.stride, MemoryLayout<T>.alignment) |
| |
| checkFields( |
| of: GenericStruct<T>.self, |
| fields: [ |
| "int": (0, Int.self), |
| "first": (firstOffset, T.self), |
| "second": (firstOffset + MemoryLayout<T>.stride, T.self), |
| ]) |
| |
| checkFieldsAsExistential( |
| of: GenericStruct<T>.self, |
| fields: [ |
| "int": (0, Int.self), |
| "first": (firstOffset, T.self), |
| "second": (firstOffset + MemoryLayout<T>.stride, T.self), |
| ]) |
| } |
| |
| tests.test("GenericStruct") { |
| checkGenericStruct(Bool.self) |
| checkGenericStruct(TestStruct.self) |
| checkGenericStruct((TestStruct, TestClass, Int, Int).self) |
| } |
| |
| tests.test("TestClass") { |
| let classOffset = MemoryLayout<Int>.stride * 2 |
| let doubleOffset = classOffset |
| + max(MemoryLayout<Int>.stride * 2, MemoryLayout<Double>.stride) |
| |
| checkFields( |
| of: TestClass.self, options: .classType, |
| fields: [ |
| "superInt": (classOffset, Int.self), |
| "int": (classOffset + MemoryLayout<Int>.stride, Int.self), |
| "double": (doubleOffset, Double.self), |
| "bool": (doubleOffset + MemoryLayout<Double>.stride, Bool.self), |
| ]) |
| |
| checkFields( |
| of: TestSubclass.self, options: .classType, |
| fields: [ |
| "superInt": (classOffset, Int.self), |
| "int": (classOffset + MemoryLayout<Int>.stride, Int.self), |
| "double": (doubleOffset, Double.self), |
| "bool": (doubleOffset + MemoryLayout<Double>.stride, Bool.self), |
| "strings": (doubleOffset + MemoryLayout<Double>.stride + MemoryLayout<Array<String>>.stride, Array<String>.self), |
| ]) |
| |
| let firstOffset = classOffset |
| + max(MemoryLayout<Int>.stride, MemoryLayout<TestStruct>.alignment) |
| checkFields( |
| of: GenericSubclass<TestStruct, TestStruct>.self, options: .classType, |
| fields: [ |
| "superInt": (classOffset, Int.self), |
| "first": (firstOffset, TestStruct.self), |
| "second": (firstOffset + MemoryLayout<TestStruct>.size, Bool.self), |
| "third": (firstOffset + MemoryLayout<TestStruct>.stride, TestStruct.self), |
| ]) |
| |
| checkFields( |
| of: GenericSubclass<Int, Never>.self, options: .classType, |
| fields: [ |
| "superInt": (classOffset, Int.self), |
| "first": (classOffset + MemoryLayout<Int>.stride, Int.self), |
| "second": (classOffset + MemoryLayout<Int>.stride * 2, Bool.self), |
| "third": (0, Never.self), |
| ]) |
| |
| checkFieldsAsExistential( |
| of: GenericSubclass<TestStruct, TestStruct>.self, options: .classType, |
| fields: [ |
| "superInt": (classOffset, Int.self), |
| "first": (firstOffset, TestStruct.self), |
| "second": (firstOffset + MemoryLayout<TestStruct>.size, Bool.self), |
| "third": (firstOffset + MemoryLayout<TestStruct>.stride, TestStruct.self), |
| ]) |
| |
| // Applying to class type without .classType option fails |
| expectFalse(_forEachField(of: TestClass.self) { |
| _, _, _, _ in true |
| }) |
| } |
| |
| tests.test("OwnershipTestClass") { |
| let classOffset = MemoryLayout<Int>.stride * 2 |
| |
| checkFields( |
| of: OwnershipTestClass.self, options: .classType, |
| fields: [ |
| "superInt": (classOffset, Int.self), |
| "test1": (classOffset + MemoryLayout<Int>.stride, Optional<TestClass>.self), |
| "test2": (classOffset + MemoryLayout<Int>.stride * 2, TestClass.self), |
| "test3": (classOffset + MemoryLayout<Int>.stride * 3, TestClass.self), |
| ]) |
| } |
| |
| #if _runtime(_ObjC) |
| tests.test("NSObjectSubclass") { |
| expectTrue(_forEachField(of: NSObjectSubclass.self, options: .classType) { |
| charPtr, _, type, _ in |
| |
| let fieldName = String(cString: charPtr) |
| return type == (Double, Double).self |
| && fieldName == "point" |
| }) |
| |
| expectTrue(_forEachField(of: EmptyNSObject.self, options: .classType) { |
| _, _, _, _ in true |
| }) |
| } |
| #endif |
| |
| tests.test("withTypeEncoding") { |
| expectEqual("{@}", getTypeEncoding(ContainsObject.self)) |
| expectEqual("{{dd}{dd}}", getTypeEncoding(SimilarToNSRect.self)) |
| |
| let testEncoding = getTypeEncoding(TestStruct.self) |
| expectTrue("{qdB}" == testEncoding || "{ldB}" == testEncoding) |
| } |
| |
| runAllTests() |
| } else { |
| runNoTests() |
| } |