In swift 5.7, the compiler will not always pick the same default implementation for protocol methods that are present in protocol definition (which ideally should be dynamically dispatched) with generic specialization.
In the following example, ExplicitFoo
and GenericFoo<Int>
are both of type any Foo<Int>
. Hence when calling .baz()
the compiler will pick the specialized implementation. But when they're erased via AnyFoo
(or any Foo
or any Foo<Int>
), the compiler will pick the default non-specialized implementation for GenericFoo<Int>
, but won't do the same for ExplicitFoo
. Which IMHO makes it inconsistent and ambiguous.
Can anyone explain if it is the expected behavior and why? Since I thought about it and couldn't come-up with any explanations, I went ahead and reported a bug.
protocol Foo<Bar> {
associatedtype Bar
func baz() -> String
}
extension Foo {
func baz() -> String { "Foo" }
}
extension Foo<Int> {
func baz() -> String { "Foo<Int>" }
}
struct AnyFoo<Bar>: Foo {
private let _baz: () -> String
func baz() -> String { _baz() }
init<F: Foo>(foo: F) where F.Bar == Bar {
self._baz = { foo.baz() }
}
}
struct ExplicitFoo: Foo { typealias Bar = Int }
struct GenericFoo<Bar>: Foo { }
And then in usage:
let explicit = ExplicitFoo()
let generic = GenericFoo<Int>()
assert(explicit.baz() == "Foo<Int>")
assert(generic.baz() == "Foo<Int>")
let erasedExplicit = AnyFoo(foo: explicit)
let erasedGeneric = AnyFoo(foo: generic)
assert(erasedExplicit.baz() == "Foo<Int>")
assert(erasedGeneric.baz() == "Foo<Int>") // Fails since `erasedGeneric.baz()` returns "Foo"
It even fails with any form of type-erasure:
- Using non-generic type-erased
AnyFoo
struct AnyFoo: Foo {
typealias Bar = Never
private let _baz: () -> String
func baz() -> String { _baz() }
init<F: Foo>(foo: F) {
self._baz = { foo.baz() }
}
}
- Abstracting instances as
any Foo<Int>
let erasedExplicit: any Foo<Int> = explicit
let erasedGeneric: any Foo<Int> = generic
- Abstracting instances as
any Foo
let erasedExplicit: any Foo = explicit
let erasedGeneric: any Foo = generic
in all 4 approaches, the result is the same. The compiler will choose a different implementation for erasedExplicit
vs erasedGeneric
.
Environment
- Swift compiler version info
swift-driver version: 1.62.15 Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
- Xcode version info
Version 14.2 (14C18)
- Deployment target:
Target: arm64-apple-macosx13.0