What's actually happening in print(1 + 2 + "3")

If you'd try to execute:

print(1 + 2 + "3")

It wil fail saying Operator function '+' requires that 'RunLoop.SchedulerTimeType.Stride' conform to 'Sequence'
Can someone explain me what's actually happening behind the scenes, and what are the steps that happen in order?

Could you give a reproducible snippet of this error? With this line and Swift 5.7.2 I can only get:

error: repl.swift:1:13: error: binary operator '+' cannot be applied to operands of type 'Int' and 'String'
print(1 + 2 + "3")
      ~~~~~ ^ ~~~
repl.swift:1:13: note: overloads for '+' exist with these partially matching parameter lists: (Int, Int), (String, String)
print(1 + 2 + "3")
            ^

Which is very reasonable and clear.

2 Likes

I assume that you know why the code doesn't run and you're just interested in why the error message is so strange.

This is a side effect of Swift's operator overload resolution and type inference. The operator + has many different definitions in Swift, and during type inference, the compiler has to figure out which one you are referring to. Below are some examples of some skeleton definitions (implementations aren't important). Please note that these aren't necessarily correct signatures, they're just for demonstration purposes.

func +(_ lhs: Int, _ rhs: Int) -> Int
func +(_ lhs: String, _ rhs: String) -> String
func +<T: Sequence>(_ lhs: T, _ hrs: T) -> T
func +(_ lhs: RunLoop.SchedulerTimeType.Stride, _ rhs: RunLoop.SchedulerTimeType.Stride) -> RunLoop.SchedulerTimeType.Stride

Disclaimer: I have not worked on the Swift compiler, so this is my best guess based off other compilers I've worked on, and a simple naïve inference algorithm that makes the explanation easier.

What I assume has happened (as an over-simplification) is that the compiler went through the definitions one by one, moving to the second operator when it found an operator definition that could satisfy the first operator.

The compiler found the (Int, Int) -> Int addition definition first because 1 and 2 are both possibly integer literals. Next it would've gone through all of its definitions and found that it didn't have a definition for addition of type (Int, String) -> _, so it discarded its assumption that the first operator was Int + Int and started again. Note that the second operator didn't necessarily have to be (Int, String) -> _ to be a match, it would also be valid to have <T: Sequence>(Int, T) etc (because String is a sequence).

At some point the compiler got to the last possible operator definition that could work for 1 + 2 which was (Stride, Stride) -> Stride. This was a possible match because Stride probably implements ExpressibleByIntegerLiteral or a similar protocol. The compiler then continued to the second operator and tried all possible definitions. It found that there was no addition definition that operates on a Stride and a String. Importantly, the last partially matching operator it attempted (I assume) was <T: Sequence>(T, T) -> T (because the right hand side, a string, conforms to Sequence).

The compiler has to make a guess at which of the (possibly hundreds) of addition operator definitions that you wanted to refer to, and it probably just ended up choosing the last partially matching operator that it checked. It then told you that it 'expected' your code to work with that definition, which is somewhat misleading (because it really just expected your code to work with any of the definitions), but it's pretty difficult for the compiler to do much better, especially given that print takes an argument of type Any, so it has no clue as to what type you are expecting 1 + 2 + "3" to have.

Interestingly, I can't reproduce this behaviour at all. I get the following error which makes much more sense. The compiler has probably been updated to weight more direct matches higher when deciding which ones to print out (or it has gotten worse if your version is newer than mine lol).

Test.swift:7:21: error: binary operator '+' cannot be applied to operands of type 'Int' and 'String'
        print(1 + 2 + "a")
              ~~~~~ ^ ~~~
Test.swift:7:21: note: overloads for '+' exist with these partially matching parameter lists: (Int, Int), (String, String)
        print(1 + 2 + "a")
                    ^
3 Likes