Deferred inline in a specific case

Hello, I get a “weird” deferred inline in a very specific case. I will try to be as accurate as possible.

I have a refineValue method which ask for a Constraint[A, B] with 2 inline methods: assert(A) and getMessage(A):

implicit inline def refineValue[A, B, C <: Constraint[A, B]](value: A)(using inline constraint: C): Constrained[A, B] = {
  constraint.getMessage()
  // The below code is the "real" code but the above line is more minimal (for the sample) and has the same bug
  // Constrained(compileTime.preAssert[A, B, C](value, constraint))
}

And I’m trying to use this method in a custom shapeless Typeable[A]:

class ConstrainedTypeable[A, B, C <: Constraint[A, B]](using typeA: Typeable[A], tag: ClassTag[A], constraint: C) extends Typeable[A ==> B] {

  override inline def cast(t: Any): Option[A ==> B] = {
    refineValue[A, B, C](typeA.cast(t).get)
    None
  }

  override def describe: String = s"${typeA.describe} ==> $tag"
}

inline given [A, B, C <: Constraint[A, B]] (using Typeable[A], ClassTag[A], C): Typeable[A ==> B] = new ConstrainedTypeable

but when I try to compile it I get:

Deferred inline method getMessage in trait Constraint cannot be invoked

I tried to re-create a more minimal example without success so here is the snapshot of the non-working code:

The error seem to be related to the non-inline nature of Typeable#cast. For example, if I override this trait instead of Typeable:

trait TestTypeable[A] {

  inline def cast(t: Any): Option[A]

  def describe: String
}

it compiles.

1 Like

Do you know what the error message is for? I encounter it in a (simpler) test, and I have no clue what it means:

abstract class MyOption[+A]:
   inline def foreach[U](f: A => U): Unit

case object MyNone extends MyOption[Nothing]:
   inline def foreach[U](f: Nothing => U): Unit = ()

case class MySome[+A](get: A) extends MyOption[A]:
   inline def foreach[U](f: A => U): Unit = f(get)

val a = MySome("foo")
a.foreach(println)
val b : MyOption[String] = a
b.foreach(println)

foreach can be called on a but not on b:

Deferred inline method foreach in class MyOption cannot be invoked

It means foreach can’t be inlined because it is abstract and he doesn’t know which implementation to pick.

1 Like

Makes sense. Can’t have both inline and dynamic binding.

BTW, this was my naive attempt at trying to bring tail recursion to this pattern:

def process[A](queue: ConcurrentLinkedQueue[A]): Unit =
   for (value <- Option(queue.poll())) do
      println(value)
      process(queue)

It looks nicer than the loop-based equivalent, but uses execution stack space.

I thought Odersky once implemented a hybrid form of inline where it can fall back to dynamic binding at runtime. But I’m not sure if or how that functionality is still available in Scala 3.

You’re right. For @charpov example, you have to call it in an inline context.

The problem with my sample is refineValue is already called in an inline method but is still unable to compile.