swift 从扩展到泛型的类型检查协议一致性

yshpjwxd  于 2023-06-28  发布在  Swift
关注(0)|答案(2)|浏览(133)

给定一个外部类型:

struct Factory<T> {
    public func callAsFunction() -> T {
        registration.resolve(with: ())
    }
}

我把它扩展成这样:

extension Factory {
    func callAsFunction<U>() -> U {
        guard let result = callAsFunction() as? U else {
            preconditionFailure("Found '\(T.self)', but unable to convert it to '\(U.self)'")
        }
        return result
    }
}

例如,你可以给出:

protocol P {}
struct MockP: P {}

extension Container {
    var mockP: Factory<any P> {
         Factory<any P>(self) { MockP() }
    }
}

let mock: any P = Container.shared.mockP() // <- Does not check compile time that mockP implements P

我想限制它,这样你就不能例如:

protocol P { }
struct Valid: P { }
protocol Invalid { }

let result: any P = Container.shared.mockP() // <- This is a cast from P to Valid
let invalid: any Invalid = Container.shared.mockP() // <- Error: MockP does not conform to Invalid

但我不希望像现在这样出现运行时错误,而是编译器错误,因为检查Invalid不符合P
有什么办法可以做到这一点吗?
编辑:
我正在寻找类似于func callAsFunction<U: T>() -> U的东西,但实际上是有效的。

mwyxok5s

mwyxok5s1#

Swift中,不可能使用扩展来实现编译时检查您正在寻找的限制类型。您提供的扩展在运行时运行,并依赖于类型转换,而类型转换只能在运行时检查。
要强制泛型类型T在使用callAsFunction方法时必须符合P的约束,您可以尝试以下操作(创建func而不是extent):

protocol P {}

struct Valid: P {}
struct Invalid {}

struct Factory<T> {
    func callAsFunction<U: P>() -> U {
        guard let result = callAsFunction() as? U else {
            fatalError("Found '\(T.self)', but unable to convert it to '\(U.self)'")
        }
        return result
    }
    
    func callAsFunction<U>() -> U {
        fatalError("Found '\(T.self)', but unable to convert it to '\(U.self)'")
    }
}

let result: Valid = Factory<P>().callAsFunction() // Compiler error: Invalid conversion
let invalid: Invalid = Factory<P>().callAsFunction() // Compiler error: Invalid does not conform to P

我创建了两个独立的callAsFunction方法

*第一个只有当泛型类型U符合P协议时才可用,以确保在编译时满足约束。
*第二个callAsFunction方法是一个回退方法,将在类型U不符合P时使用

如果你想把泛型类型U修改成和T一样,你可以修改代码如下:

protocol P {}

struct Valid: P {}
struct Invalid {}

struct Factory<T> {
    func callAsFunction<U>() -> U where U == T {
        guard let result = callAsFunction() as? U else {
            fatalError("Found '\(T.self)', but unable to convert it to '\(U.self)'")
        }
        return result
    }
    
    func callAsFunction<U>() -> U {
        fatalError("Found '\(T.self)', but unable to convert it to '\(U.self)'")
    }
}

let result: Valid = Factory<P>().callAsFunction() // Compiler error: Invalid conversion
let invalid: Invalid = Factory<P>().callAsFunction() // Compiler error: Invalid conversion

*通过此修改,可以实现编译时检查,确保调用callAsFunction方法时泛型类型U匹配类型T

我希望它能帮上忙。如果有,请留下您的意见!!^^

2cmtqfgy

2cmtqfgy2#

你所描述的是行不通的。特别地,这个函数正在做出承诺:

func callAsFunction<U>() -> U

它声称调用者可以传递任何类型U,并且该函数将返回U的示例或永远不会返回。(“永不返回”意味着无限循环或崩溃。从技术上讲,它与返回Never类型的值相同,在本例中,您可以将其视为每种类型的子类型。
对于any Invalid,编译器不会给出错误,因为它是U类型,而您编写了一个返回该类型的函数。
在这里给出的情况下,答案是删除扩展,并在构造过程中捕获错误。我不得不猜测Factory是什么样子的(也许问题是它过于聪明,充满了Any,这是一个坏主意,但假设它看起来是这样的):

struct Factory<T> {
    var f: () -> T
    init(_:T, f: @escaping (() -> T)) { self.f = f }
    public func callAsFunction() -> T {
        f() // I assume ultimately this is what "registration.resolve(with: ())" does
    }
}

然后是基本的东西:

protocol P {}
struct MockP: P {}
protocol Invalid {}

class Container: P {
    static var shared = Container()
}
extension Container {
    var mockP: Factory<any P> {
         Factory<any P>(self) { MockP() }
    }
}

使用它:

let mock: any P = Container.shared.mockP() // Compiler time OK
let invalid: any Invalid = Container.shared.mockP() // Compile time ERROR

如果你让MockP不符合P,那么编译错误会发生在mockP的定义上。
也就是说,我敢肯定,工厂并不完全这样工作,还有更多的问题。您需要编辑您的问题以包括其他内容。但是你的扩展没有任何用处(它只能在不需要的时候工作)。所以我会回到你真正的问题是什么。

相关问题