StanfordSpezi / Spezi

Open-source framework for rapid development of modern, interoperable digital health applications.
https://swiftpackageindex.com/StanfordSpezi/Spezi/documentation
MIT License
129 stars 10 forks source link

Swift 6 breaks `@Dependency` property wrapper #107

Open Supereg opened 1 week ago

Supereg commented 1 week ago

Description

With the latest beta toolchain of Swift 6, the @Dependency property wrapper is essentially broken as the dynamic type check is broken with Swift 6.

Optional @Dependency property declared as follows are broken:´

@Dependency var dependency: TestModule?

what this calls is this overload of the initializer:

extension _DependencyPropertyWrapper: OptionalModuleDependency where Value: AnyOptional, Value.Wrapped: Module {
    /// Create a empty, optional dependency.
    public convenience init() {
        self.init(DependencyCollection(DependencyContext(for: Value.Wrapped.self)))
    }

    // ...
}

Upon accessing the wrappedValue the @Dependency property wrapper implements the following logic:

public class _DependencyPropertyWrapper<Value> {
    public var wrappedValue: Value {
        if let singleModule = self as? SingleModuleDependency {
            return singleModule.wrappedValue(as: Value.self)
        } else if let moduleArray = self as? ModuleArrayDependency {
            return moduleArray.wrappedValue(as: Value.self)
        } else if let optionalModule = self as? OptionalModuleDependency {
            return optionalModule.wrappedValue(as: Value.self)
        }

        preconditionFailure("Reached inconsistent state. Wrapped value must be of type `Module`, `Module?` or `[any Module]`!")
    }
}

The problem: we reach the preconditionFailure at the bottom.

To summarize: Swift initializes the property wrapper with an initializer that is only available when the property wrapper conforms to OptionalModuleDependency, however it is later unable to cast the same property wrapper instance to the said protocol OptionalModuleDependency.

Reproduction

tldr: You can easily reproduce by running the test case testLoadingAdditionalDependency().

Minimal Steps:

  1. Create a Module that has an optional dependency with @Dependency var module0: TestModule?
  2. Load both modules.
  3. Access module0
  4. Observe crash

To verify that Swift is at fault, I augmented the above code snippets with the following print statements:

extension _DependencyPropertyWrapper: OptionalModuleDependency where Value: AnyOptional, Value.Wrapped: Module {
    /// Create a empty, optional dependency.
    public convenience init() {
        print("DID CALL THE INITIALIZER with Value type \(Value.self)!")        
        self.init(DependencyCollection(DependencyContext(for: Value.Wrapped.self)))
    }

    // ...
}
public class _DependencyPropertyWrapper<Value> {
    public var wrappedValue: Value {
        if let singleModule = self as? SingleModuleDependency {
            return singleModule.wrappedValue(as: Value.self)
        } else if let moduleArray = self as? ModuleArrayDependency {
            return moduleArray.wrappedValue(as: Value.self)
        } else if let optionalModule = self as? OptionalModuleDependency {
            return optionalModule.wrappedValue(as: Value.self)
        }

        print("Module Type: \(Value.self), value type is Moduel \(Value.self is (any Module).Type)")
        print("Is AnyOptional: \(Value.self is any AnyOptional.Type)")
        print("Is AnyArray: \(Value.self is any AnyArray.Type)")
        print("singleModule: \(self is SingleModuleDependency), moduleArray: \(self is ModuleArrayDependency), optionalModule: \(self is OptionalModuleDependency)")

        preconditionFailure("Reached inconsistent state. Wrapped value must be of type `Module`, `Module?` or `[any Module]`!")
    }
}

The output when running the test case:

Test Suite 'Selected tests' started at 2024-06-26 14:11:07.941.
Test Suite 'SpeziTests.xctest' started at 2024-06-26 14:11:07.941.
Test Suite 'DependencyTests' started at 2024-06-26 14:11:07.941.
Test Case '-[SpeziTests.DependencyTests testLoadingAdditionalDependency]' started.
DID CALL THE INITIALIZER with Value type Optional<TestModule3>!
Loading module(s) DefaultStandard, OptionalModuleDependency ...
Module Type: Optional<TestModule3>, value type is Moduel false
Is AnyOptional: false
Is AnyArray: false
singleModule: false, moduleArray: false, optionalModule: false
/Users/andi/XcodeProjects/Stanford/Spezi/Sources/Spezi/Dependencies/Property/DependencyPropertyWrapper.swift:52: Precondition failed: Reached inconsistent state. Wrapped value must be of type `Module`, `Module?` or `[any Module]`!

Expected behavior

Swift should not create paradoxes.

Additional context

No response

Code of Conduct

PSchmiedmayer commented 1 week ago

@Supereg Does it make sense to add this issue to the Swift issue tracker at https://github.com/swiftlang/swift/issues to raise some awareness for this and link to this issue here?

Supereg commented 1 week ago

Definitely makes sense. I would just aim to maybe create a small example project. I haven't yet created a minimal example. But will do.

Supereg commented 1 week ago

Here seems to be a similar issues with conformances https://github.com/swiftlang/swift/issues/74718

PSchmiedmayer commented 1 week ago

That would be amazing @Supereg!

Supereg commented 1 week ago

I think I found the exact issue already reported here https://github.com/swiftlang/swift/issues/74646

PSchmiedmayer commented 1 week ago

Nice; thanks for the find. That is a good small reproducible example 👍