Ordering Of Unnamed Parameters & Parameters With Default Values

Is there some reason the compiler can't figure this one out (see compiler errors at bottom)?

struct GOOD {
    
    init (_ s: String, _ i: Int = 0) {
        self.i = i
        self.s = s
    }
    
    let i: Int
    let s: String
    
}

struct BAD {
    
    init (_ i: Int = 0, _ s: String) {
        self.i = i
        self.s = s
    }
    
    let i: Int
    let s: String
    
}

let g1 = GOOD("g1")  // Compiles fine
let b1 = BAD("b1")   // <--- Missing Argument For Parameter #2 in call
                     // or in (more complex) project code: "Type of expression is ambiguous without more context"

It’s because the type-checker goes to work *after* the arguments have been matched up to parameters. The matching of arguments-to-parameters happens purely textually, because the types involved have not been checked yet, so the arguments are assigned greedily to the first parameter where the label matches.

Essentially, the compiler finds all possible candidate functions by name (ideally this should be by compound-name, but I’m not certain that is the case yet). Then it attempts to greedily match call-site arguments to declaration-site parameters.

After the arguments have been matched up with parameters, the type-checker does its thing to figure out overload resolution and literal types and so forth.

But in your second example it never even gets that far, because the attempt at matching arguments to parameters failed:

There was only one candidate function, and it has two unlabeled parameters. The first unlabeled argument at the call-site was matched to the first unlabeled parameter of the function, and there was no subsequent argument available to match with the second parameter.

There does exist an algorithm which can match arguments to parameters non-greedily, properly accounting for default-values, but it runs in O(n²) time (where n is either the number of arguments or parameters, I can’t recall which—maybe it’s O(m*n)?).

Looks like bug. At least the error seem wrong. I would file a bug report.

Also note that the language guide recommends:

But that is the wrong type. Even the fix it is wrong.

As I explained at length above, there are no types yet at the point where arguments are matched with parameters. There are only labels, and the labels do match.

Type-checking occurs after arguments are matched to parameters.

I am considering this, but the "primary" unnamed parameter is a potentially long multi line string literal so it seemed better to put that at the end of the parameter list.

For now I am naming all of the other parameters which seems like the safest option considering this limitation. The reason I wanted to use additional unnamed parameters is that some of those parameters are enums and having both the parameter label and the enum case label is a bit redundant in the common case (but not really for the other enum cases).

I don’t think this is the user experience that we want. At the very least it needs better diagnostic messages.

1 Like

In general, it is considered good practice to have argument labels for all parameters with default values.

Since they won’t appear in every call, it is important to have a label identifying them when they do appear.