Skip to content

Referring to outer class in implicit inline breaks Expr summoning mechanism #12179

Open
@deusaquilus

Description

@deusaquilus

Compiler version

RC3

Minimized code

Create a simple class that does something which we want an implicit instance of e.g. an Encoder.

class Encoder[T]:
  def apply(element: T): List[String] = List(element.toString)

class EncoderContext { self =>
  def encode[Cls](cls: Cls) = List(cls.toString)
  implicit inline def anyClsEncoder[Cls]: Encoder[Cls] =
    MappedEncoderMaker[Cls](self)
}

Then create the macro MappedEncoderMaker in which we will access this EncoderContext.
I decided to also add a print statement to see if the macro is being invoked.

object MappedEncoderMaker:
  inline def apply[T](inline ctx: EncoderContext): Encoder[T] = ${ applyImpl[T]('ctx) }
  def applyImpl[T: Type](ctx: Expr[EncoderContext])(using Quotes): Expr[Encoder[T]] =
    import quotes.reflect._
    println(s"===== Creating Instance for: ${Printer.TypeReprShortCode.show(TypeRepr.of[T])}")
    '{ new Encoder[T] { def encode(m: T) = $ctx.encode[T](m) } }

Creating a summoning macro that will attempt to summon the encoder created by our macro:

object SummonAndEncode {
  inline def apply[Cls <: AnyVal](cls: Cls): Unit = ${ applyImpl[Cls]('cls) }
  def applyImpl[Cls <: AnyVal: Type](cls: Expr[Cls])(using Quotes): Expr[Unit] = {
    import quotes.reflect._
    Expr.summon[Encoder[Cls]] match
      case Some(value) => println(s"ENCODER FOUND")
      case None => println("ENCODER NOT FOUND")
    '{ () }
  }
}

Finally, create a class that will import the context and attempt to summon the Encoder.

case class Wrap(value: String) extends AnyVal

def main(args: Array[String]): Unit = {
  val ctx = new EncoderContext()
  import ctx._
  val w = new Wrap("stuff")
  SummonAndEncode[Wrap](w)
}

Output

The result is that the summoning macro will not be able to find the Encoder. However, right before, I can see that the MappedEncoderMaker is actually working as intended.

[info] compiling 1 Scala source to....
===== Creating Instance for: Wrap
ENCODER NOT FOUND

Expectation

Then encoder for the Wrap object should be successfully summoned.

Note that if you remove the self from the MappedEncoderMaker[Cls](self) call i.e:

class EncoderContext { self =>
  def encode[Cls](cls: Cls) = List(cls.toString)
  implicit inline def anyClsEncoder[Cls]: Encoder[Cls] =
    MappedEncoderMaker[Cls](???) // <- Remove the 'self' reference here.
}

Then the encoder will be summoned:

===== Creating Instance for: Wrap
[info] compiling 1 Scala source to...
ENCODER FOUND

Repo

https://github.com/deusaquilus/anyval_encoder_issue

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions