In a scala 3.6.4 serialization library I am developing there is the DiscriminatorCriteria type-class that allows the user to determine which discriminator value to use for each variant P of a sum-type S.
trait DiscriminationCriteria[-S] {
transparent inline def discriminator[P <: S]: Int
}
Its discriminator method must be inline to allow the user to use the scala.compiletime.erasedValue tool. And I think it must be transparent so that the returned values be constant (single instances of singleton types).
The instances of the DiscriminatorCriteria type-class are summoned by a serializer/deserializer derivation macro which then calls the discriminator method from within a quote. Here is how the summoning and the call is done in the macros:
...
val discriminatorExpr: Expr[Int] = Expr.summon[DiscriminationCriteria[SumType]] match {
case Some(discriminatorCriteriaExpr) =>
'{ $discriminatorCriteriaExpr.discriminator[VariantType & SumType] }
case None =>
// Fall back to the alphanumerical index
Expr[Int](alphanumericIndex)
}
Where VariantType is the type of a variant of the SumType.
The expression resulting from the quote expansion would be:
(discriminatorCriteria: DiscriminatorCriteria[Animal]).discriminator[Dog]
which is a call to an transparent inline method that expands to a singleton Int instance.
The problem is that it does not compile because: "Deferred inline method discriminator in trait DiscriminationCriteria cannot be invoked."
The question is, how to call an inline method (discriminator in this case) from within a macro? I was unable to create the correct prompt for a free AI to respond correctly.
@DmytroMitin said "Your issue is not related to macros. It relates to ordinary inline methods (not necessarily macros)." But that is not totally true. If the given returns a singleton like in
sealed trait Animal
case class Dog(field: String) extends Animal
object animalDc extends DiscriminationCriteria[Animal] {
override transparent inline def discriminator[S <: Animal]: Int = 32
}
given animalDc.type = animalDc
val thirtyTwo: 32 = summon[DiscriminationCriteria[Animal]].discriminator[Dog]
the summon returns a singleton object and the call to discriminator compiles and returns a constant.
He proposed two solutions in his answer. I didn't understand the first one, so I tried the second:
//// Library code
trait DiscriminationCriteria[-S] {
transparent inline def discriminator[P <: S]: Int
}
inline def summonDiscriminator[SumType, VariantType <: SumType]: Int =
summonInline[DiscriminationCriteria[SumType]].discriminator[VariantType]
transparent inline def someMacro[SumType, VariantType]: Int = ${ someMacroImpl[SumType, VariantType] }
def someMacroImpl[SumType: Type, VariantType: Type](using quotes: Quotes): Expr[Int] = {
val discriminatorExpr: Expr[Int] = Expr.summon[DiscriminationCriteria[SumType]] match {
case Some(_) => '{ summonDiscriminator[SumType, VariantType & SumType] }
case None => Expr[Int](77)
}
discriminatorExpr
}
//// User code
sealed trait Animal
case class Dog(dogField: Int) extends Animal
case class Cat(catField: String) extends Animal
object animalDc extends DiscriminationCriteria[Animal] {
override transparent inline def discriminator[P <: Animal]: Int =
inline erasedValue[P] match {
case _: Dog => 1
case _: Cat => 2
}
}
transparent inline given animalDc.type = animalDc
@main def s2(): Unit = {
println(someMacro[Animal, Cat]) // Error: Deferred inline method discriminator in trait DiscriminationCriteria cannot be invoked
}
Unfortunately it produces the same compilation error but in the call site.
@DmytroMitin found a solution using the low level Implicits.search method. It is at the end of his answer. Congratulations!
inline defswith handwritten code andinline defs with macros have some differences - one of them is that normalinline defexpands otherinlines andsummons things that are visible in scope (don't confuse withsummonInline), while macros' quotes only constructs raw, desugared expressions (it doesn't automatically summon things from scope, it doesn't expand other macros, it doesn't infer types). It can desugar some code, sure, but it has to be doable without any magically provided information from the callsite.defthat takesExprs,Types andQuotesand returns anotherExpr.inline defis the same for all instances then, no. It is not possible.