Extra Argument in Call

Swift's horrible error reporting and how to deal with it

Posted by Grego on January 18, 2015

It’s been about 2 weeks so far since I started using Swift and overall I like it. However every once in a while Swift will throw you a curve ball. Since last night to early this morning I probably spent a good 4 hours trying to debug this strange error message:

Extra argument 'randomGenerator' in call

What’s the problem?

Let me rewind my code base a little and pull out the offending code:

class QQMultipleChoiceQuestion: QQSimpleQuestion {

  let alternatives: [QQComponent] = []
  let randomGenerator: RandomSequence

    convenience init(question: QQComponent,
       answer: QQComponent,
       alternatives: QQComponent ...)  {

     let defaultGenerator: RandomSequenceGenerator = RandomSequenceGenerator(
        range: 0 ..< alternatives.count + 1)

     self.init(randomGenerator: defaultGenerator, // Extra argument 'randomGenerator' in call
       question: question,
       answer: answer,
       alternatives: alternatives)

  }

   init (randomGenerator: RandomSequence,
    question: QQComponent,
    answer: QQComponent,
    alternatives: QQComponent ...) {

    super.init(question: question, answer: answer)
    self.alternatives = alternatives
    self.randomGenerator = randomGenerator
  }

  //... some other code here ...
}

I marked the offending line with a code comment above.

There’s a lot going on here, so let me break it down. Simply put, I’m working on a question/answer quiz model.

The class I was working on, QQMultipleChoiceQuestion inherits from its parent class QQSimpleQuestion where my question and answer properties are declared and initialized. The QQMultipleChoiceQuestion class simply adds an extra alternatives property which will hold some incorrect answers to fool you little runts. Err.. I mean… You know, to make things more challenging.

From the output of the error it made it seem as though I had an extra argument called randomGenerator in my call to the designated initializer. Anyone with eyeballs can tell that the designated init does in fact have a randomGenerator as its first parameter, and that the types match.

So what’s the hold up?

The entire signature has to match when calling another function in Swift. But wait, they match, you say? Take a closer look. There’s some trickery going on here that I was unaware of. Both init methods use what’s called a variadic parameter at the end.

A variadic parameter is one in the form of T ... where T represents a type. It’s a way to send an arbitrary number of arguments to a method. Internally, the variadic is treated as an array, therefore if you attempt to pass that variadic to another method, it will be passed as an array [T] instead of T ....

What’s the workaround?

The workaround is to change the designated init to take an array and for the syntactic sugar, we can change the previous designated init into a convenience initalizer. So the new designated init looks like this:

   init (randomGenerator: RandomSequence,
    question: QQComponent,
    answer: QQComponent,
    alternatives: [QQComponent]) {

    self.alternatives = alternatives
    self.randomGenerator = randomGenerator

    super.init(question: question, answer: answer)
  }

This is what the whole class looks like (or at least just the init methods):

class QQMultipleChoiceQuestion: QQSimpleQuestion {

  let alternatives: [QQComponent] = []
  let randomGenerator: RandomSequence

    convenience init(question: QQComponent,
       answer: QQComponent,
       alternatives: QQComponent ...)  {

        let defaultGenerator = RandomSequenceGenerator(range: 0 ..< alternatives.count + 1)

        self.init(
          randomGenerator: defaultGenerator,
          question: question,
          answer: answer,
          alternatives: alternatives
        )
  }

   convenience init (randomGenerator: RandomSequence,
    question: QQComponent,
    answer: QQComponent,
    alternatives: QQComponent ...) {

      self.init(randomGenerator: randomGenerator,
        question: question, answer: answer,
        alternatives: alternatives)
  }

   init (randomGenerator: RandomSequence,
    question: QQComponent,
    answer: QQComponent,
    alternatives: [QQComponent]) {

    self.alternatives = alternatives
    self.randomGenerator = randomGenerator

    super.init(question: question, answer: answer)
  }

  // ... some other code ...
}

A shorter example

That was a whole lot of code, so let’s take a look at the SO example from the article I linked above.

This won’t work:

// Function 1
func sumOf(numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}
// Example Usage
sumOf(2, 5, 1)

// Function 2
func averageOf(numbers: Int...) -> Int {
    return sumOf(numbers) / numbers.count //swift no likey.
}

So we add a new method to handle an array input:

func sumOf(numbers: [Int]) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}

And the full example code:

// This function does the actual work
func sumOf(numbers: [Int]) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}

// This overloads allows the variadic notation and
// forwards its args to the function above
func sumOf(numbers: Int...) -> Int {
    return sumOf(numbers)
}

sumOf(2, 5, 1)

func averageOf(numbers: Int...) -> Int {
    // This calls the first function directly
    return sumOf(numbers) / numbers.count
}

averageOf(2, 5, 1)

But… How did he know?

You might be asking yourselves right now how I figured it out, and that’s the real takeaway here.

My approach was to delete the offending ‘extra’ argument. The next thing that happened was the code complained about something different:

        self.init(
          question: question,
          answer: answer,
          alternatives: alternatives //'NSArray' is not a subtype of 'QQComponent'
        )

It pointed at the alternatives argument. This different error is what lead me to the solution.