Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KeyPathReflection working in Swift 5.6 and 5.7 #13

Open
wants to merge 5 commits into
base: reflection
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Test key-path-reflection",
"program": "/Applications/Xcode-13.3.0-Beta.2.app/Contents/Developer/usr/bin/xctest",
"args": [
".build/debug/key-path-reflectionPackageTests.xctest"
],
"cwd": "${workspaceFolder:swift-evolution-staging}",
"preLaunchTask": "swift: Build All"
}
]
}
18 changes: 9 additions & 9 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.1
// swift-tools-version:5.6
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
Expand All @@ -14,23 +14,23 @@
import PackageDescription

let package = Package(
name: "SE0000_KeyPathReflection",
name: "key-path-reflection",
products: [
.library(
name: "SE0000_KeyPathReflection",
targets: ["SE0000_KeyPathReflection"]),
name: "KeyPathReflection",
targets: ["KeyPathReflection"]),
],
dependencies: [
],
targets: [
.target(name: "KeyPathReflection_CShims"),
.target(name: "KeyPathReflectionCShims"),
.target(
name: "SE0000_KeyPathReflection",
dependencies: ["KeyPathReflection_CShims"]
name: "KeyPathReflection",
dependencies: ["KeyPathReflectionCShims"]
),
.testTarget(
name: "SE0000_KeyPathReflectionTests",
dependencies: ["SE0000_KeyPathReflection"]
name: "KeyPathReflectionTests",
dependencies: ["KeyPathReflection"]
),
]
)
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,21 @@ add the following to your `Package.swift` file's dependencies:
)
```

## Local

#### Build with tests
`swift build --build-tests`

#### Run Tests with Docker
`swift test --skip-build`

## Docker

#### Build Tests with Docker
`docker-compose -f "docker-compose.test.yml" build`

#### Run Tests with Docker
`docker-compose -f "docker-compose.test.yml" up --abort-on-container-exit --exit-code-from app`

## Prune
`docker system prune --all --volumes`
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//
//===----------------------------------------------------------------------===//

import KeyPathReflection_CShims
import KeyPathReflectionCShims

// This is a utility within KeyPath.swift in the standard library. If this
// gets moved into there, then this goes away, but will have to rethink if this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,10 @@ internal struct ClassMetadata: TypeMetadata, LayoutWrapper {
internal struct _ClassMetadata {
let _kind: Int
let _superclass: Any.Type?
#if !swift(>=5.4) || canImport(ObjectiveC)
let _reserved: (Int, Int)
let _rodata: Int
#endif
let _flags: UInt32
let _instanceAddressPoint: UInt32
let _instanceSize: UInt32
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//===----------------------------------------------------------------------===//

// Source: swift/include/swift/Basic/RelativePointer.h
import KeyPathReflection_CShims
import KeyPathReflectionCShims

extension UnsafeRawPointer {
/// Returns the underlying raw pointer by stripping the pointer authentication signature.
Expand Down
126 changes: 126 additions & 0 deletions Tests/KeyPathReflectionTests/CustomKeyPathTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import XCTest
@testable import KeyPathReflection
import Foundation

private protocol MyProtocol {}
extension Int: MyProtocol {}

private final class MyFinalClass {
let float: Float = 0
var int: Int = 0
// FIXME: Weak and unowned fields are not supported.
// weak var cls: Class? = nil
}

private struct MyStruct<T: Equatable> {
var existential: MyProtocol
var generic: [T]
}

extension MyStruct: MyProtocol {}
extension MyFinalClass: MyProtocol {}

/// Asserts that the given named key path collections are equal.
func assertNamedKeyPathsEqual<KeyPath: AnyKeyPath>(
_ actual: [(name: String, keyPath: KeyPath)],
_ expected: [(name: String, keyPath: KeyPath)],
file: StaticString = #file,
line: UInt = #line
) {
for (actual, expected) in zip(actual, expected) {
XCTAssertEqual(actual.name, expected.name, file: file, line: line)
XCTAssertEqual(actual.keyPath, expected.keyPath, file: file, line: line)
}
}

final class CustomKeyPathTests: XCTestCase {
func testStruct() throws {
let s = MyStruct<Int>(existential: 1, generic: [2, 3])
let allKeyPaths = Reflection.allKeyPaths(for: s)
XCTAssertEqual(
allKeyPaths,
[\MyStruct<Int>.existential, \MyStruct<Int>.generic])

let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: s)
assertNamedKeyPathsEqual(
allNamedKeyPaths,
[
("existential", \MyStruct<Int>.existential),
("generic", \MyStruct<Int>.generic),
])
}

func testClass() throws {
let c = MyFinalClass()
let allKeyPaths = Reflection.allKeyPaths(for: c)
XCTAssertEqual(allKeyPaths, [\MyFinalClass.float, \MyFinalClass.int])

let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: c)
assertNamedKeyPathsEqual(
allNamedKeyPaths,
[("float", \MyFinalClass.float), ("int", \MyFinalClass.int)])

// FIXME: Handle and test non-final class properties and weak/unowned
// properties.
}

func testExistential() throws {
let s = MyStruct<Int>(existential: 1, generic: [2, 3])
let c = MyFinalClass()
func test<T>(erasingAs existentialType: T.Type) {
// Struct
let existentialStruct = s as! T
XCTAssertEqual(
Reflection.allKeyPaths(for: existentialStruct),
[\MyStruct<Int>.existential, \MyStruct<Int>.generic])
assertNamedKeyPathsEqual(
Reflection.allNamedKeyPaths(for: existentialStruct),
[
("existential", \MyStruct<Int>.existential),
("generic", \MyStruct<Int>.generic)
])
// Class
let existentialClass = c as! T
XCTAssertEqual(
Reflection.allKeyPaths(for: existentialClass),
[\MyFinalClass.float, \MyFinalClass.int])
assertNamedKeyPathsEqual(
Reflection.allNamedKeyPaths(for: existentialClass),
[("float", \MyFinalClass.float), ("int", \MyFinalClass.int)])
}
test(erasingAs: Any.self)
test(erasingAs: MyProtocol.self)
}

func testOptional() throws {
let x: Int? = nil
XCTAssertTrue(Reflection.allKeyPaths(for: x).isEmpty)
var y: Int? = 3
let yKeyPaths = Reflection.allKeyPaths(for: y)

XCTAssertEqual(yKeyPaths, [\Optional.!])

let concreteYKeyPath = try XCTUnwrap(yKeyPaths[0] as? WritableKeyPath<Int?, Int>)

XCTAssertEqual(y[keyPath: concreteYKeyPath], 3)
y[keyPath: concreteYKeyPath] = 4
XCTAssertEqual(y, 4)
}

func testArray() throws {
let array = [0, 1, 2, 3]
let allKeyPaths = Reflection.allKeyPaths(for: array)
XCTAssertEqual(
allKeyPaths,
[\[Int][0], \[Int][1], \[Int][2], \[Int][3]])
let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: array)
assertNamedKeyPathsEqual(
allNamedKeyPaths,
[
("0", \[Int][0]),
("1", \[Int][1]),
("2", \[Int][2]),
("3", \[Int][3]),
])
}
}
95 changes: 95 additions & 0 deletions Tests/KeyPathReflectionTests/KeyPathReflectionTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import XCTest
@testable import KeyPathReflection

private protocol MyProtocol {}
extension Int: MyProtocol {}

private final class MyFinalClass {
let float: Float = 0
var int: Int = 0
// FIXME: Weak and unowned fields are not supported.
// weak var cls: Class? = nil
}

private struct MyStruct<T: Equatable> {
var existential: MyProtocol
var generic: [T]
}

extension MyStruct: MyProtocol {}
extension MyFinalClass: MyProtocol {}


final class StoredPropertyKeyPathTests: XCTestCase {
func testStruct() throws {
let allKeyPaths = Reflection.allKeyPaths(for: MyStruct<Int>.self)
XCTAssertEqual(
allKeyPaths,
[\MyStruct<Int>.existential, \MyStruct<Int>.generic])

let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: MyStruct<Int>.self)
assertNamedKeyPathsEqual(
allNamedKeyPaths,
[
("existential", \MyStruct<Int>.existential),
("generic", \MyStruct<Int>.generic),
])
}

func testClass() throws {
let allKeyPaths = Reflection.allKeyPaths(for: MyFinalClass.self)
XCTAssertEqual(allKeyPaths, [\MyFinalClass.float, \MyFinalClass.int])

let allNamedKeyPaths = Reflection.allNamedKeyPaths(
for: MyFinalClass.self)
assertNamedKeyPathsEqual(
allNamedKeyPaths,
[("float", \MyFinalClass.float), ("int", \MyFinalClass.int)])

// FIXME: Handle and test non-final class properties and weak/unowned
// properties.
}

func testExistential() throws {
func test<T>(erasingAs existentialType: T.Type) {
// MyStruct
XCTAssertEqual(
Reflection.allKeyPaths(
forUnderlyingTypeOf: MyStruct<Int>.self as! T.Type),
[\MyStruct<Int>.existential, \MyStruct<Int>.generic])
assertNamedKeyPathsEqual(
Reflection.allNamedKeyPaths(for: MyStruct<Int>.self as! T.Type),
[
("existential", \MyStruct<Int>.existential),
("generic", \MyStruct<Int>.generic)
])
// Class
XCTAssertEqual(
Reflection.allKeyPaths(forUnderlyingTypeOf: MyFinalClass.self as! T.Type),
[\MyFinalClass.float, \MyFinalClass.int])
assertNamedKeyPathsEqual(
Reflection.allNamedKeyPaths(
forUnderlyingTypeOf: MyFinalClass.self as! T.Type),
[("float", \MyFinalClass.float), ("int", \MyFinalClass.int)])
}
test(erasingAs: Any.self)
test(erasingAs: MyProtocol.self)
}
}


//final class KeyPathReflectionTests: XCTestCase {
// func testStoredPropertyKeyPaths() throws {
// try StoredPropertyKeyPaths.testMyStruct()
//// try StoredPropertyKeyPaths.testClass()
//// try StoredPropertyKeyPaths.testExistential()
// }
//
// func testCustomKeyPaths() throws {
// try CustomKeyPaths.testOptional()
//// try CustomKeyPaths.testMyStruct()
//// try CustomKeyPaths.testExistential()
//// try CustomKeyPaths.testClass()
//// try CustomKeyPaths.testArray()
// }
//}
Loading