You can't add a defaulted function argument to a protocol-satisfying method?!

For example, given:

public protocol PatternMatcher {
    associatedtype Element

    func matchingPrefix<C: Collection>(for collection: C) -> C.Index?
        where C.Element == Element
}

I thought you could do this:

enum BasicPatternMatcher { /*...*/ }

extension BasicPatternMatcher: PatternMatcher {
    public func matchingPrefix<C: Collection>(
        for collection: C,
        checkInvariants: Bool = true
    ) -> C.Index?
    where C.Element == Element {
        //...
    }
}

but it isn't actually accepted! The playground flags an error of a missing method and suggests a new stub. I know if you use a protocol with a non-generic method, and your type defines a generic method with the same signature pattern such that the protocol's requirement would match, the compiler will use that method. I thought a defaulted extra parameter would work too.

Does this actually not work? Or does it work but I messed up somewhere? I'm using Xcode 11.6 on a Catalina system.

If it doesn't actually work, I think we should add this.

Not even with non-generic one:

protocol Test {
    func foo()
}
extension Test {
    func foo(a: Bool = true) {}
}

extension Int: Test {} // Error

I don't remember if it's intentional or not.

Test.extension.foo(a: Bool = true) isn't considered a witness for Test.foo(). You'll get the same error if you did:

struct S: Test {} // Type 'S' does not conform to protocol 'Test'

although I think it would be reasonable to allow functions with default arguments to satisfy protocol requirements (as I mentioned in the Protocol Witness Matching Manifesto).

2 Likes
func f(x: Int, defaulted: Bool = true) { }
f(x: 123)

is syntactic sugar for

func f(x: Int, defaulted: Bool) { }
f(x: 123, defaulted: true)

not

func f(x: Int) { }
func f(x: Int, defaulted: Bool) { }
f(x: 123)

With that in mind a lot of quirks with defaulted arguments make sense:

you cannot refer to this method as f(x:) because we only defined f(x:defaulted:)

you cannot cast it to (Int) -> Void because there exists only two argument version

if you have #line as a default value it shows the line where the function was called, not defined

it satisfies func f(x: Int, defaulted: Bool) protocol requirement, but not func f(x: Int)


Why is it like that? I am not a compiler developer, but if swift generated a function for every combination of defaulted parameters, just imagine what would happen if you had a function with 10 parameters, all defaulted. If my math is correct, compiler would have to create 2^10 functions which is 1024. With current implementation it generates only 11 functions (one with 10 arguments, and 10 functions that return default arguments). I could imagine that it could be made to work if functions were generated only if they were used

3 Likes