I'm trying to overload a function with some argument, like the following:
protocol A {}
protocol B {}
func work(with value: some A & B) {
print("Doing something")
work(with: value as B)
}
func work(with value: some B) {
print("Doing something else")
print(value)
}
class D: A & B {}
work(with: D())
The idea is that work(with: some A & B) calls work(with: some B) to avoid code duplication. But the compiler shows error: type 'any B' cannot conform to 'B'. It can be fixed with creating an additional variable.
func work(with value: some A & B) {
print("Doing something")
let crutch: some B = value
work(with: crutch)
}
however, it look like a crutch, imho. Does anyone have an idea how apply as to value without such hacks?
I've tried creating typealias C = A & B, it doesn't help either. Even tried to write something like value as some B, but this is an incorrect syntax.
Normally, this would 'just work' via existential unwrapping, which allows you to pass a value of type any B to a parameter expecting a type of some B. However, this implicit conversion/opening can be suppressed via as syntax, which is what you're running into. The compiler takes the as B expression to mean "I really want the type of this expression to be any B and not some other type", and since any B isn't compatible with some B, the call has to fail.
Since you're also trying to rely on type-based overloading to direct the proper function to call, you're essentially running into an 'overload' of the meaning of as B. I'm not sure whether there's a better way to resolve this than to split the call into two steps so that the as B for type inference doesn't appear in the same position where it's used for 'suppress existential opening. Your let crutch: some B = value is one way of doing exactly that.
That's never been forced except for protocols with associated types which couldn't previously be written with the 'bare' protocol name syntax. The upcoming feature flag which would have enforced this for all protocols in the Swift 6 language mode was deferred to a future language version:
No, I'm saying that with the concise, inline version which produces the error, as B (or as any B) is doing two things: directing type inference for overload selection, and also expressing "don't allow this any B to be converted to some B via existential opening". In order to avoid this double-meaning you'll need to move the type inference 'out of line' from the function call, which (unless there's a workaround I'm not seeing) will require something like the crutch you've written to work around this error.
Thank you for your answer. Actually, such a variable looks very ugly, as for me, but it seems this is better than separate calls. I hoped there is some way to express my intent to the type system :)
I thought this should work as a workaround, but it doesn't appear to:
func work(with: some B) { // (1)
// ...
}
func work(with b: any B) { // (2)
_work(with: b)
}
private func _work(with b: some B) {
work(with: b)
}
The idea being that _work would be equivalent to (1), and (2) would call that to prevent recursion. However, _work appears to be equivalent to (2) for some reason, and in fact (2) compiles to an infinite loop:
I'm not certain this is what you're hitting but IIRC there's logic in the type checker to consider non-generic declarations 'better' overloads than generic declarations (which some B is sugar for, in argument position), which means that work(with: b) will prefer the any B-typed declaration.