Scala: macro to create an instance from a class body

后端 未结 2 1346
误落风尘
误落风尘 2021-01-27 16:20

I am building a DSL in Scala, and for that, I need to store \"instances\" of a class (Parent in this case), except these \"instances\" must be re-created several ti

相关标签:
2条回答
  • 2021-01-27 16:44

    Unfortunately macros will not help.

    Macro annotations (which expand before type checking) can't annotate code blocks:

    @constructMacro {
      func(1)
      func(userVar)
    }
    

    is illegal.

    Def macros (which expand during type checking) have their arguments type checked before macros are expanded. So

    constructMacro {
      func(1)
      func(userVar)
    }
    

    doesn't compile:

    Error: not found: value func
          func(1)
    Error: not found: value func
          func(userVar)
    

    That's the reason why macro shapeless.test.illTyped accepts a string rather than code block:

    illTyped("""
      val x: Int = "a"
    """)
    

    rather than

    illTyped {
      val x: Int = "a"
    }
    

    So the closest you can implement is

    constructMacro("""
      func(1)
      func(userVar)
    """)
    
    def constructMacro(block: String): ParentWrapper = macro constructMacroImpl
    
    def constructMacroImpl(c: blackbox.Context)(block: c.Tree): c.Tree = {
      import c.universe._
      val q"${blockStr: String}" = block
      val block1 = c.parse(blockStr)
      q"""construct(new Parent {
        ..$block1
      })"""
    }
    

    You can annotate a variable

    @constructMacro val x = {
      func(1)
      func(userVar)
    }
    
    //         becomes
    // val x: ParentWrapper = construct(new Parent {
    //   func(1)
    //   func(userVar)
    // })
    
    @compileTimeOnly("enable macro paradise to expand macro annotations")
    class constructMacro extends StaticAnnotation {
      def macroTransform(annottees: Any*): Any = macro constructMacroImpl.impl
    }
    
    object constructMacroImpl {
      def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
        import c.universe._
        annottees match {
          case q"$mods val $tname: $tpt = { ..$stats }" :: Nil =>
            q"$mods val $tname: ParentWrapper = construct(new Parent { ..$stats })"
    
          case _ =>
            c.abort(c.enclosingPosition, "Not a val")
        }
      }
    }
    
    0 讨论(0)
  • 2021-01-27 16:50

    If I understood correctly, you just need to change object to def in the ObjectA block:

    class User {
      var userVar = 13
      def makeParent = new Parent {
        func(1)
        func(userVar)
      }
    
      construct(makeParent)
    }
    

    and it'll do what you want.

    0 讨论(0)
提交回复
热议问题