I recently discovered that the following two methods conflict.
func write<T>(_ value: borrowing T) where T: StringConvertible & ~Copyable
func write<each T: StringConvertible>(_ value: (repeat each T))
It may be that ~Copyable in the method signature interferes with the compiler's choice, resulting in the following error in overload resolution:
# swift build
error: ambiguous use of 'write'
write(Foo())
^
note: found this candidate
func write<T>(_ value: borrowing T) where T: StringConvertible & ~Copyable {
^
note: found this candidate
func write<each T: StringConvertible>(_ value: (repeat each T)) {
There are currently 2 methods:
Remove ~Copyable: Not what I want.
Add @_disfavoredOverload to the tuple version: Force the compiler to lower the priority of this method, which can achieve the goal, but uses underscore attributes, which is not perfect.
I guess the method conflict might be a compiler bug?
Full code
protocol StringConvertible: ~Copyable {
var description: String { get }
}
struct Foo: StringConvertible {
var description: String { "42" }
}
func write(_ value: some CustomStringConvertible) {
print(value.description)
}
// or: func write(_ value: some StringConvertible)
func write<T>(_ value: T) where T: StringConvertible {
print(value.description)
}
func write<each T: CustomStringConvertible>(_ value: (repeat each T)) {
for item in repeat each value {
print(item.description, terminator: ",")
}
print()
}
func write2<T>(_ value: borrowing T) where T: StringConvertible & ~Copyable {
print(value.description)
}
// @_disfavoredOverload
func write2<each T: StringConvertible>(_ value: (repeat each T)) {
for item in repeat each value {
print(item.description, terminator: ",")
}
print()
}
write(100)
write(Foo())
write((1, 3, 5))
write2(Foo()) // ❌
write2((Foo(), Foo(), Foo()))
It's not surprising that there are ambiguous in bare methods.
My methods are constrained, and tuples can not implement any protocol in Swift (at least for now), so these methods are unambiguous when '~Copyable' is not added (see full codes).
My problem is that after adding '~Copyable', there should be no ambiguity either. '~Copyable' may just be a trigger, not a root cause.
It can satisfy both overloads because in isolation, either one will work (try it). If an expression involves overloads, the type checker will find all valid solutions and compare them to pick the best one. The behavior is that "by default", two solutions are ambiguous unless an explicit rule has been implemented to disambiguate them.
In this case though, if you change the example slightly, it becomes unambiguous:
func write<T>(_ value: T) { }
func write<each T>(_ values: repeat each T) { }
write(1) // We prefer the first overload
Notice how we have a bare pack here, instead of a pack wrapped in a tuple.
It should be possible to expand the current rule to cover the tuple case as well; please file an issue
If not an expansion of a current rule, I'd argue it's still justifiable (just by common sense) to add a standalone rule that overloads which take an argument T are "more specific" than overloads which as a degenerate case take a single-element tuple (T).