From 250628525ed989f66e040d98bfeba180865aaa16 Mon Sep 17 00:00:00 2001 From: Andreas Bauer Date: Thu, 26 Sep 2024 10:49:34 +0200 Subject: [PATCH] Improve overload resolution for KnowledgeSource protocols (#16) # Improve overload resolution for KnowledgeSource protocols ## :recycle: Current situation & Problem A `SharedRepository` typically has multiple overloads for a range of different `KnowledgeSource` protocols. A knowledge source might provide default values or might be computed. Typically, you only adopt a single knowledge source protocol. However, you cannot prevent users from adopting multiple. This PR improves the overload resolution in these scenarios. All of the specialized KnowledgeSource protocols inherit the base `KnowledgeSource` protocol. Therefore, Swift resolution will automatically prefer the more specialized type. Problems arise if you accidentally (or on purpose) conform to a computed KnowledgeSource variant and the `DefaultProvidingKnowledgeSource`. In this case we resolve the conflict and generally prefer the computed knowledge source protocol. We consider a computed knowledge source more specialized than only providing a default value. ## :gear: Release Notes * Improve overload resolution for knowledge source protocols in certain situations ## :books: Documentation -- ## :white_check_mark: Testing Added some unit testing to verify overload behavior. ## :pencil: Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md). --- .../SendableSharedRepository.swift | 4 +++ .../SharedRepository/SharedRepository.swift | 4 +++ .../SendableSharedRepositoryTests.swift | 6 +++++ .../SharedRepositoryTests.swift | 25 ++++++++++++++++++- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Sources/SpeziFoundation/SharedRepository/SendableSharedRepository.swift b/Sources/SpeziFoundation/SharedRepository/SendableSharedRepository.swift index 670dbea..975fb03 100644 --- a/Sources/SpeziFoundation/SharedRepository/SendableSharedRepository.swift +++ b/Sources/SpeziFoundation/SharedRepository/SendableSharedRepository.swift @@ -61,11 +61,13 @@ public protocol SendableSharedRepository: Sendable { /// A subscript to retrieve or set a `KnowledgeSource`. /// - Parameter source: The ``KnowledgeSource`` type. /// - Returns: The stored ``KnowledgeSource/Value`` or `nil` if not present. + @_disfavoredOverload subscript>(_ source: Source.Type) -> Source.Value? where Source.Value: Sendable { get set } /// A subscript to retrieve or set a `DefaultProvidingKnowledgeSource`. /// - Parameter source: The ``DefaultProvidingKnowledgeSource`` type. /// - Returns: The stored ``KnowledgeSource/Value`` or the ``DefaultProvidingKnowledgeSource/defaultValue``. + @_disfavoredOverload subscript>(_ source: Source.Type) -> Source.Value where Source.Value: Sendable { get } /// A subscript to retrieve a `ComputedKnowledgeSource`. @@ -119,6 +121,7 @@ extension SendableSharedRepository { /// Default subscript implementation delegating to ``get(_:)`` or ``set(_:value:)``. + @_disfavoredOverload public subscript>(_ source: Source.Type) -> Source.Value? where Source.Value: Sendable { get { get(source) @@ -142,6 +145,7 @@ extension SendableSharedRepository { } /// Default subscript implementation delegating to ``get(_:)`` or providing a ``DefaultProvidingKnowledgeSource/defaultValue``. + @_disfavoredOverload public subscript>(_ source: Source.Type) -> Source.Value where Source.Value: Sendable { self.get(source) ?? source.defaultValue } diff --git a/Sources/SpeziFoundation/SharedRepository/SharedRepository.swift b/Sources/SpeziFoundation/SharedRepository/SharedRepository.swift index b8b5b7b..30e69ba 100644 --- a/Sources/SpeziFoundation/SharedRepository/SharedRepository.swift +++ b/Sources/SpeziFoundation/SharedRepository/SharedRepository.swift @@ -62,11 +62,13 @@ public protocol SharedRepository { /// A subscript to retrieve or set a `KnowledgeSource`. /// - Parameter source: The ``KnowledgeSource`` type. /// - Returns: The stored ``KnowledgeSource/Value`` or `nil` if not present. + @_disfavoredOverload subscript>(_ source: Source.Type) -> Source.Value? { get set } /// A subscript to retrieve or set a `DefaultProvidingKnowledgeSource`. /// - Parameter source: The ``DefaultProvidingKnowledgeSource`` type. /// - Returns: The stored ``KnowledgeSource/Value`` or the ``DefaultProvidingKnowledgeSource/defaultValue``. + @_disfavoredOverload subscript>(_ source: Source.Type) -> Source.Value { get } /// A subscript to retrieve a `ComputedKnowledgeSource`. @@ -120,6 +122,7 @@ extension SharedRepository { /// Default subscript implementation delegating to ``get(_:)`` or ``set(_:value:)``. + @_disfavoredOverload public subscript>(_ source: Source.Type) -> Source.Value? { get { get(source) @@ -140,6 +143,7 @@ extension SharedRepository { } /// Default subscript implementation delegating to ``get(_:)`` or providing a ``DefaultProvidingKnowledgeSource/defaultValue``. + @_disfavoredOverload public subscript>(_ source: Source.Type) -> Source.Value { self.get(source) ?? source.defaultValue } diff --git a/Tests/SpeziFoundationTests/SendableSharedRepositoryTests.swift b/Tests/SpeziFoundationTests/SendableSharedRepositoryTests.swift index 9aca98f..ce74106 100644 --- a/Tests/SpeziFoundationTests/SendableSharedRepositoryTests.swift +++ b/Tests/SpeziFoundationTests/SendableSharedRepositoryTests.swift @@ -188,4 +188,10 @@ final class SendableSharedRepositoryTests: XCTestCase { optionalComputedValue = nil XCTAssertEqual(repository[OptionalComputedTestStruct<_StoreComputePolicy, Repository>.self], 4) } + + @MainActor + func testComputedKnowledgeSourcePreferred() { + let value = repository[ComputedDefaultTestStruct<_StoreComputePolicy, Repository>.self] + XCTAssertEqual(value, computedValue) + } } diff --git a/Tests/SpeziFoundationTests/SharedRepositoryTests.swift b/Tests/SpeziFoundationTests/SharedRepositoryTests.swift index 3629e33..5f5eed5 100644 --- a/Tests/SpeziFoundationTests/SharedRepositoryTests.swift +++ b/Tests/SpeziFoundationTests/SharedRepositoryTests.swift @@ -53,7 +53,7 @@ struct DefaultedTestStruct: DefaultProvidingKnowledgeSource, TestTypes { } -struct ComputedTestStruct: ComputedKnowledgeSource { +struct ComputedTestStruct: KnowledgeSource, ComputedKnowledgeSource { typealias Anchor = TestAnchor typealias Value = Int typealias StoragePolicy = Policy @@ -65,6 +65,23 @@ struct ComputedTestStruct: ComputedKnowledgeSource, DefaultProvidingKnowledgeSource { + typealias Anchor = TestAnchor + typealias Value = Int + typealias StoragePolicy = Policy + + static var defaultValue: Int { + XCTFail("\(Self.self) access result in default value execution!") + return -1 + } + + static func compute(from repository: Repository) -> Int { + MainActor.assumeIsolated { + computedValue + } + } +} + struct OptionalComputedTestStruct: OptionalComputedKnowledgeSource { typealias Anchor = TestAnchor @@ -261,4 +278,10 @@ final class SharedRepositoryTests: XCTestCase { optionalComputedValue = nil XCTAssertEqual(repository[OptionalComputedTestStruct<_StoreComputePolicy, Repository>.self], 4) } + + @MainActor + func testComputedKnowledgeSourcePreferred() { + let value = repository[ComputedDefaultTestStruct<_StoreComputePolicy, Repository>.self] + XCTAssertEqual(value, computedValue) + } }