It happens via a different mechanism, unique to the numeric types, called numeric widening.
SLS 6.26.1 Value Conversions says:
The following five implicit conversions can be applied to an expression e which has some value type T and which is type-checked with some expected type pt.
Static Overloading Resolution
Type Instantiation
Numeric Widening
Numeric Literal Narrowing
Value Discarding
View Application
Dynamic Member Selection
(Okay, that's more than five....not sure why :)
The one of interest is numeric widening:
If e has a primitive number type which weakly conforms to the expected type, it is widened to the expected type using one of the numeric conversion methods toShort, toChar, toInt, toLong, toFloat, toDouble defined here.
3.5.16 Weak Conformance says
In some situations Scala uses a more general conformance relation. A type S weakly conforms to a type T, written S<:wT, if S<:T or both S and T are primitive number types and S precedes T in the following ordering.
Byte <:w Short
Short <:w Int
Char <:w Int
Int <:w Long
Long <:w Float
Float <:w Double
So println(i.total) becomes println(i.total.toFloat) because Int <:w <: Long <: Float.
Java (and C# and many other languages) have numeric widening, and Scala decided to keep it.
Note that the reverse does not work: a Float cannot be implicitly converted to Int via this way, since magnitude could be lost; it's not a "widening".
You can add -Ywarn-numeric-widen and get a warning when this happens.