Compiler magic. The compiler translates into the intrinsic "iadd" instruction on the JVM.
class Test { def first(x: Int, y: Int) = x + y def second(x: Int, y: Int) = (x).+(y) }
Which compiles down to exactly what you'd hope for in either case
$ javap -c Test.class Compiled from "Test.scala" public class Test { public int first(int, int); Code: 0: iload_1 1: iload_2 2: iadd 3: ireturn public int second(int, int); Code: 0: iload_1 1: iload_2 2: iadd 3: ireturn public Test(); Code: 0: aload_0 1: invokespecial #20 // Method java/lang/Object."<init>":()V 4: return }
Similar things happen with other JVM primitive operations
If you implement "+" on your own class, it's just dispatched into a normal method call
class Test2 { def +(t2: Test2) = "whatever" def test = this + this }
Becomes
$ javap -c Test2.class Compiled from "Test2.scala" public class Test2 { public java.lang.String $plus(Test2); Code: 0: ldc #12 // String whatever 2: areturn public java.lang.String test(); Code: 0: aload_0 1: aload_0 2: invokevirtual #19 // Method $plus:(LTest2;)Ljava/lang/String; 5: areturn public Test2(); Code: 0: aload_0 1: invokespecial #23 // Method java/lang/Object."<init>":()V 4: return }
Notice the method is named "$plus." That's because "+" isn't a valid method name as far as the JVM is concerned. Other symbols that aren't valid JVM names have similar translations.
In all these cases, scalac uses the static type to determine wether to emit a method call or a JVM primitive.
The actual determination is made in https://github.com/scala/scala/blob/2.11.x/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala , a phase that happens very late in the compiler's chain. For the most part all previous phases treat x + y as if it's going to be a method call regardless of x's type.