Inlined parameters

So I see that in dotty parameters can be marked inline. What does this actually mean? What does it change about how an linline def expands? Here’s a toy example to motivate my question.

package tokendb

trait Buffer[B, T] { def length(b: B): Int }

object Buffer {
  implied LongArrayBuffer1 for Buffer[Array[Long], Long] {
    def length(b: Array[Long]) = b.length
  }
}

So the idea is that I can write various ops against a buffer abstraction, and use inlining to get efficient implementation code. Let’s do a silly transform - double the length.

val ls = Array.ofDim[Long](42)

def twiceLength[B, T](b: B) given (B: Buffer[B, T]): Int = B.length(b) * 2
val a = twiceLength(ls)
// val a: scala.Int = tokendb.TestBuff.twiceLength[scala.Array[scala.Long], scala.Long](ls)(tokendb.Buffer.LongArrayBuffer1)

Not surprisingly it’s compiled down to the obvious implementation

inline def twiceLengthInlined[B, T](b: B) given (B: Buffer[B, T]): Int = B.length(b) * 2
val b = twiceLengthInlined(ls)
// val b: scala.Int = (tokendb.Buffer.LongArrayBuffer1.length(ls).*(2): scala.Int)

This is better. The call is inlined. However, it’s still going through the length abstraction on Buffer. Granted it’s resolved it to the concrete implementation.

inline def twiceInliningParam[B, T](b: B) given (inline B: Buffer[B, T]): Int = B.length(b) * 2

Unfortunately this fails to compile with:

no implicit argument of type tokendb.Buffer[Array[Long], T] @InlineParam was found for parameter B of method twiceInliningParam in object TestBuff

I was hoping that it would recursively unpack the instance of Buffer.length to get us back to ll.length.

So I guess I have 2 questions. Firstly, what are inline parameters and how and why do you use them? Secondly, can we get things to recursively inline across typeclass instances?