6

I'm porting a macro from Scala 2 to Scala 3. As part of it's work, the Scala 2 macro creates an instance of a generic type using the default constructor. This is simple to do with a quasiquote in Scala 2, but I'm struggling with Scala 3 macros. This is my best approach so far:

import scala.quoted.*

inline def make[A <: AnyRef]: A = ${ makeThat[A] }

private def makeThat[A <: AnyRef : Type](using Quotes): Expr[A] =
  import quotes.reflect.*

  '{ new A().asInstanceOf[A] }

Without the .asInstanceOf[A], the compiler emits an error message:

[error] -- [E007] Type Mismatch Error: ...
[error] 17 |  '{ new A() }
[error]    |     ^^^^^^^
[error]    |Found:    Object
[error]    |Required: A
[error]    |
[error]    |where:    A is a type in method makeThat with bounds <: AnyRef
[error] one error found

Is there a better solution without a downcast at runtime?

EDIT: As of Scala 3.0.1, this doesn't even compile anymore.

Christian Schlichtherle
  • 3,125
  • 1
  • 23
  • 47

1 Answers1

6

You can create the right tree with the lower level reflect API.

import scala.quoted.*

inline def make[A <: AnyRef]: A = ${ makeThat[A] }

def makeThat[A <: AnyRef : Type](using Quotes): Expr[A] =
  import quotes.reflect.*

  TypeRepr.of[A].classSymbol.map( sym =>
    Apply(Select(New(TypeTree.of[A]), sym.primaryConstructor), Nil).asExprOf[A]
  )
  .getOrElse(???) // not a class, so can't instantiate

Though you should include a check to see if your constructor doesn't have parameters.

Jasper-M
  • 14,966
  • 2
  • 26
  • 37
  • Sigh, this is so terrible in comparison to the quasiquotes in Scala 2... – Christian Schlichtherle Sep 03 '21 at 11:46
  • 4
    Yes quotes and splices in Scala 3 only allow well-typed code, which in this case is not possible because the compiler cannot know which constructors some type `A <: AnyRef` has, if any. – Jasper-M Sep 03 '21 at 12:15
  • If I understand, this solution hinges on their being a fixed number of arguments (none in this case). If the number of arguments varied, am I correct in thinking a [type class derivation](https://dotty.epfl.ch/docs/reference/contextual/derivation-macro.html) is probably the way to go? e.g. for some trait with a method that instantiates the generic type, `A`. – ecoe Sep 07 '22 at 14:06
  • @ecoe If you can go with a type class that would definitely be preferable. – Jasper-M Sep 08 '22 at 12:17
  • @Jasper-M thanks I'm close to getting it working with type class here: https://stackoverflow.com/questions/73639857/scala-3-macro-for-deriving-type-class-that-instantiates-case-class – ecoe Sep 08 '22 at 12:39