Type of expression is ambiguous without more context error

D-A UK picture D-A UK · Aug 27, 2018 · Viewed 13.1k times · Source

How do I fix this code so it doesn't give the error: Type of expression is ambiguous without more context?

var int = 0
while int < 100 {
        DispatchQueue.main.asyncAfter(deadline: .now() + int) { // change 2 to desired number of seconds
            // do stuff
        }
        int += 1
}

The code is being run in dispatch queue in applicationDidEnterBackground, in an attempt to make text to speech work in the background.

Answer

vacawama picture vacawama · Aug 27, 2018

The Type of expression is ambiguous without more context error is stemming from two facts:

  1. Swift doesn't know how to add a DispatchTime and an Int.
  2. Because of fact 1, Swift doesn't know how to interpret .now().

Swift doesn't know how to add a DispatchTime and an Int

This can be demonstrated with:

let t = DispatchTime.now() + Int(5)

Binary operator '+' cannot be applied to operands of type 'DispatchTime' and 'Int'

but Swift does know how to add a DispatchTime and a Double:

let t = DispatchTime.now() + Double(5)

This compiles fine with no errors.

So why does DispatchQueue.main.asyncAfter(deadline: .now() + 5) work?

In this case, Swift is interpreting the integer literal 5 as a Double. Type Double conforms to the protocol ExpressibleByIntegerLiteral which is what allows you to do:

let d: Double = 5

So in this case, it works because 5 isn't an Int, it's a Double.

Because of fact 1, Swift doesn't know how to interpret .now()

When you tried to add an Int to .now():

DispatchQueue.main.asyncAfter(deadline: .now() + int)

the Swift type inference system got confused. Swift knows that it wants to pass a DispatchTime to asyncAfter(deadline:), but it no longer could figure out what .now() is because there is no way to add a DispatchTime and an Int to get a DispatchTime, it decided that .now() was not a DispatchTime but some other unknown type. So the ambiguous error message is coming from Swift's inability to determine what .now() is.

If you make that explicit by saying DispatchTime.now(), then you get a more sensible error:

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + int)

Binary operator '+' cannot be applied to operands of type 'DispatchTime' and 'Int'

As further evidence of this theory, if you provide Swift a way to add a DispatchTime and an Int, Swift is happy with your original code:

func +(_ lhs: DispatchTime, _ rhs: Int) -> DispatchTime {
    return lhs + .seconds(rhs)
}

So what is this .seconds() stuff? And how does it help solve the error?

There is a related enum type DispatchTimeInterval that has cases microseconds(Int), milliseconds(Int), nanoseconds(Int), seconds(Int) and never. Swift knows how to add a DispatchTime and a DispatchTimeInterval, so Swift is able to interpret .seconds(int) as a DispatchTimeInterval which then allows it to interpret .now() as a DispatchTime.

Why did the Swift designers choose to let you add a Double to a DispatchTime and not an Int?

You'd have to ask them. I suspect they chose the Double out of convenience (allowing literal values such as 2.5 to be used) and because it allows you to specify time intervals smaller than one second. The Int doesn't give you any extra capabilities (except of course for eliminating this very confusing error message).

Conclusion

The error is coming from the fact that you confused Swift's type inference system by trying to add an Int to a DispatchTime.

The fixes as the other answers suggested:

  1. Use a Double.

    or

  2. Use the DispatchTimeInterval enum to explicitly note what your Int represents. In your case .seconds(int).