Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-7415] Compiler has trouble selecting an initializer when combining variadic initializers with those that have default values. #49958

Closed
Lukasa opened this issue Apr 11, 2018 · 4 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@Lukasa
Copy link
Contributor

Lukasa commented Apr 11, 2018

Previous ID SR-7415
Radar None
Original Reporter @Lukasa
Type Bug
Status Resolved
Resolution Done

Attachment: Download

Environment

Swift 4.1, macOS

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: 19d9fd7011045b280429da1adce83a41

Issue Description:

Bumped into on the SwiftNIO 1.4.0 release.

Consider a hypothetical SwiftPM package (a tarball containing a sample package is attached) that has two modules, First and Second. The module Second defines a type called Test with the following definition:

public struct Test {
    public init(vals: [(Bool, Bool)] = []) {
        print("Initialized with one argument")
    }
}

The module First defines an extension to Test to conform it to ExpressibleByDictionaryLiteral, and then runs some code:

import Second

extension Test: ExpressibleByDictionaryLiteral {
    public typealias Key = Bool
    public typealias Value = Bool

    public init(dictionaryLiteral: (Bool, Bool)...) {
        print("Initialized by dictionary literal")
        self.init()
    }
}

let t = Test()
print(t)

When compiled and run, this program outputs:

Initialized with one argument
Test()

Imagine, then, that the source code of Second is changed to be this instead:

public struct Test {
    public init(_ vals: [(Bool, Bool)] = [], _ other: Bool = false) {
        print("Initialized with two arguments")
    }
}

When the new program, whose only difference is the addition of an argument with a default value in its only initializer, is compiled and run, the following compile error occurs:

/Users/lukasa/tmp/srrepro/Sources/First/main.swift:13:9: error: ambiguous use of 'init'
let t = Test()
        ^
Second.Test:2:12: note: found this candidate
    public init(_ vals: [(Bool, Bool)] = default, _ other: Bool = default)
           ^
/Users/lukasa/tmp/srrepro/Sources/First/main.swift:7:12: note: found this candidate
    public init(dictionaryLiteral: (Bool, Bool)...) {
           ^
/Users/lukasa/tmp/srrepro/Sources/First/main.swift:9:9: error: ambiguous use of 'init'
        self.init()
        ^
Second.Test:2:12: note: found this candidate
    public init(_ vals: [(Bool, Bool)] = default, _ other: Bool = default)
           ^
/Users/lukasa/tmp/srrepro/Sources/First/main.swift:7:12: note: found this candidate
    public init(dictionaryLiteral: (Bool, Bool)...) {

It seems to me that this behaviour is inconsistent. The situation is, to my eye, no more ambiguous than it was before. It's clear to see that any of the functions in question may be selected as an implementation of a zero-argument initializer. It's even clear enough that needing to provide two default arguments instead of one should make the new initializer in Second a lower priority choice than the old one.

What is less clear is why having two-or-more optional arguments is considered the same level of fittedness to calling a variadic initialiser with zero entries. I would make the case that either both of the initializers in Second should be considered higher priority than the variadic initializer in First, or neither of them should.

There are multiple workarounds for this, but in general this has the effect of meaning that if you create a type that:

  • has at least one initializer that takes two or more arguments, all of which are optional; AND

  • has no initializers that take fewer than two arguments

can never safely be given a variadic initializer, and so can never safely be conformed to almost any of the ExpressibleBy family of protocols.

@belkadan
Copy link
Contributor

Hm. @DougGregor, @xedin, what do you think?

@xedin
Copy link
Member

xedin commented Apr 13, 2018

@Lukasa I think I actually fixed this issue couple of month ago and it's supposed to prefer overloads with fewer defaults which means that it second case it's going to prefer `init(dictionaryLiteral: )`, that does work on master right now in the single file setup, but I haven't tried putting declaration and extension in different modules before, going to give it a try.

@xedin
Copy link
Member

xedin commented Apr 13, 2018

@Lukasa I've tried attached example and it builds just fine with master.

@Lukasa
Copy link
Contributor Author

Lukasa commented Apr 14, 2018

Fantastic. I assume that fix is going to land in 4.2, in which case I’m happy to call this resolved.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

3 participants