swift-collections icon indicating copy to clipboard operation
swift-collections copied to clipboard

Ambiguous initializers

Open lhunath opened this issue 3 years ago • 4 comments

Duplicate initializers for OrderedDictionary cause ambiguity in the compiler when it tries to resolve an initializer, however only under certain circumstances. I'm not 100% certain about what the scope of the issue is, but have included sample code for a data point on where the issue is reproduced.

The issue also reproduces in other scenarios which do not look like the included sample (in more complex projects).

Competing initializers:

  • https://github.com/apple/swift-collections/blob/main/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary%2BInitializers.swift#L79
  • https://github.com/apple/swift-collections/blob/main/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary%2BInitializers.swift#L115

Information

  • Package version: 1.0.1
  • Platform version: macOS 11.3.1 (20E241)
  • Swift version: 5.5.1 (swiftlang-1300.0.31.4 clang-1300.0.29.6)

Checklist

  • [ ] If possible, I've reproduced the issue using the main branch of this package.
  • [x] I've searched for existing GitHub issues.

Steps to Reproduce

let names = ["dylan", "bob", "aaron", "carol"]
let dict1 = OrderedDictionary(uniqueKeysWithValues: names.map { ($0, 0) })
let dict2 = OrderedDictionary(uniqueKeysWithValues: ["dylan", "bob", "aaron", "carol"].map { ($0, 0) })

Expected behavior

Notice that dict1 initialization compiles fine. I expect dict2 initialization to compile fine as well.

Actual behavior

dict2 initialization fails, error:

/Users/lhunath/workspace/local/Sandbox/Sandbox/SandApp.swift:25:17: error: ambiguous use of 'init(uniqueKeysWithValues:)'
    let dict2 = OrderedDictionary(uniqueKeysWithValues: ["dylan", "bob", "aaron", "carol"].map { ($0, 0) })
                ^
/Users/lhunath/Library/Developer/Xcode/DerivedData/Sandbox-cngswxsbmagplshicoojnvdjrear/SourcePackages/checkouts/swift-collections/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Initializers.swift:79:10: note: found this candidate
  public init<S: Sequence>(
         ^
/Users/lhunath/Library/Developer/Xcode/DerivedData/Sandbox-cngswxsbmagplshicoojnvdjrear/SourcePackages/checkouts/swift-collections/Sources/OrderedCollections/OrderedDictionary/OrderedDictionary+Initializers.swift:115:10: note: found this candidate
  public init<S: Sequence>(
         ^

lhunath avatar Nov 12 '21 22:11 lhunath

Although I haven't yet been able to reduce it to a reproducible example, the following code also fails:

class X {
    private let coreDaily:   [CppReportWorkDaySummary]
    lazy var daily: OrderedDictionary<Date, ReportMetrics?> = .init(uniqueKeysWithValues: self.coreDaily.map {
        ($0.dayDate, $0.summary.flatMap(CoreReportMetrics.init))
    })
}

In this case, the trigger appears to be $0.summary.flatMap(CoreReportMetrics.init) - replacing it with nil removes the compiler error.

(127, 64) Ambiguous use of 'init(uniqueKeysWithValues:)'

lhunath avatar Nov 12 '21 23:11 lhunath

Interesting! We have two overloads for these, one for sequences where Element == (Key, Value) and another for Element == (key: Key, value: Value). I'm guessing the tuple labels can be inferred in your second example, which makes the expression ambiguous.

I think the candidate that requires inferring tuple labels ought to be considered less favored than the one where labels line up, so this is arguably a compiler issue -- cc @hborla what do you think?

lorentey avatar Nov 12 '21 23:11 lorentey

My naive expectation was that the labels are mostly sugar and a distinction between them is unnecessary; though I was surprised the compiler even allowed overloading method signatures where labels are the only distinguishing feature.

lhunath avatar Nov 13 '21 05:11 lhunath

I wrote up the issue in https://bugs.swift.org/browse/SR-15539. While the type checker problem is being investigated, we can use the non-public, underscored @_disfavoredOverload attribute to work around this bug. That attribute isn't stable, so using it needs to be weighed against the possibility of source compatibility problems with future versions of the Swift compiler.

The client-level workaround is to move the dictionary literal into its own variable, as you have shown above. This eliminates the type inference case that triggers the issue.

lorentey avatar Dec 01 '21 03:12 lorentey

This is now fixed on the release/1.0 branch. (Merge to main will happen after 1.0.3 is tagged.)

lorentey avatar Sep 02 '22 00:09 lorentey