Scala宏找不到java.util.List,java.lang.Object

时间:2021-12-28 21:01:42

Update: see answers below for solution to this problem. There's a second problem (macro now can't find Pojo), question about second problem here: Scala macro can't find my java class

更新:请参阅下面的答案以解决此问题。还有第二个问题(宏现在找不到Pojo),关于第二个问题的问题在这里:Scala宏找不到我的java类

I'm creating a scala macro to automatically generate case classes from POJOs (in order to make working with avro a little bit nicer).

我正在创建一个scala宏来自动生成来自POJO的case类(为了使avro更好一点工作)。

Everything "works" except that the compiler chokes on built-in java classes like java.util.List, and java.lang.Object.

除了编译器在java.util.List和java.lang.Object等内置java类中阻塞之外,所有东西都“有效”。

My question is: how do I generate code in the macro such that the compiler resolves java classes?

我的问题是:如何在宏中生成代码,以便编译器解析java类?

Example error message:

(without // coments in Pojo.java)

[info] Compiling 1 Scala source to /Users/marcin/development/repo/problemdemo/target/scala-2.11/classes...
fieldType:java.util.List
fieldType:Int
fieldType:java.util.List
Expr[Any](case class Demo extends scala.Product with scala.Serializable {
  <caseaccessor> <paramaccessor> val baz: java.util.List[com.squarefoot.Pojo] = _;
  <caseaccessor> <paramaccessor> val bar: Int = _;
  <caseaccessor> <paramaccessor> val foo: java.util.List[java.lang.Integer] = _;
  def <init>(baz: java.util.List[com.squarefoot.Pojo], bar: Int, foo: java.util.List[java.lang.Integer]) = {
    super.<init>();
    ()
  }
})
[error] /Users/marcin/development/repos/problemdemo/src/main/scala/com/squarefoot/converters/problemdemo.scala:5: not found: type java.util.List
[error] @Caseify(classOf[com.squarefoot.Pojo])
[error]  ^
[error] one error found
[error] (root/compile:compileIncremental) Compilation failed
[error] Total time: 17 s, completed Dec 11, 2016 12:00:57 PM

(Pojo.java as shown)

[info] Compiling 1 Scala source to /Users/marcin/development/repos/problemdemo/target/scala-2.11/classes...
fieldType:java.lang.Object
fieldType:Int
Expr[Any](case class Demo extends scala.Product with scala.Serializable {
  <caseaccessor> <paramaccessor> val qux: java.lang.Object = _;
  <caseaccessor> <paramaccessor> val bar: Int = _;
  def <init>(qux: java.lang.Object, bar: Int) = {
    super.<init>();
    ()
  }
})
[error] /Users/marcin/development/repos/problemdemo/src/main/scala/com/squarefoot/converters/problemdemo.scala:5: not found: type java.lang.Object
[error] @Caseify(classOf[com.squarefoot.Pojo])
[error]  ^
[error] one error found
[error] (root/compile:compileIncremental) Compilation failed
[error] Total time: 6 s, completed Dec 11, 2016 12:04:29 PM

Edit: Results of showRaw

showRaw gives output like this, which looks fine to me:

showRaw给出这样的输出,这看起来很好:

ValDef(Modifiers(DEFERRED), TermName("availablebuildouts"), AppliedTypeTree(Ident(TypeName("java.util.List")), List(Ident(TypeName("com.squarefoot.buildouttype")))), EmptyTree)

problemdemo/avroschemas/src/main/java/com/squarefoot/Pojo.java:

package com.squarefoot;

public class Pojo {
    //public java.util.List<Integer> foo;
    public int bar;
    //public java.util.List<Pojo> baz;
    public java.lang.Object qux;
}

problemdemo/src/main/scala/com/squarefoot/converters/problemdemo.scala:

package com.squarefoot.converters

import com.squarefoot.Pojo

class Foomin {
  val foobar: java.util.List[Int]
}

@Caseify(classOf[com.squarefoot.Pojo])
case class Demo()

problemdemo/macros/src/main/scala/com/squarefoot/converters/Caseify.scala:

package com.squarefoot.converters

import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.reflect.macros.Context

/** 
  *  Generate case class from POJO
  *  ex:
  *  @Caseify(classOf[com.squarefoot.incominglisting])
  *  case class Incominglisting()

  * NOTE that the type parameter to classOf must be provided as a fully
  * qualified name, otherwise the macro code here won't be able to find it.
  * 
  * Generates a case class with the same members as the public, non-static
  * members of the pojo
  * 
  * Note that you must have all types used in the POJO in scope where the macro
  * is invoked
  */

class Caseify[T](source: Class[T]) extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro CaseifyMacro.expand_impl[T]
}

object CaseifyMacro {
  /** generate case class from POJO */
  def expand_impl[T](c: Context)(annottees: c.Expr[Any]*) = {
    import c.universe._

    // macro expand the macro expression itself to extract param
    val source: Class[T] = c.prefix.tree match {
      case q"new Caseify($param)" => c.eval[Class[T]](c.Expr(param))
    }

    val rm = scala.reflect.runtime.currentMirror
    val vars =
      rm.classSymbol(source).toType.members.map(_.asTerm).
        filter(_.isVar).filter(_.isPublic)

    lazy val fields = vars.map({f=>
      val fieldName = TermName(f.name.toString)
      val fieldType = TypeName(f.typeSignature.typeConstructor.toString)
      val typeArgs = f.typeSignature.typeArgs.map(a=>TypeName(a.toString))
      println("fieldType:"+fieldType.toString)
      q"val $fieldName: $fieldType"
      if(typeArgs.size > 0)
        q"val $fieldName: $fieldType[..$typeArgs]"
      else
         q"val $fieldName: $fieldType"
    })

    annottees.map(_.tree) match {
      case List(q"case class $newname()") => {
        val q = c.Expr[Any](
        // Add your own logic here, possibly using arguments on the annotation.
          q"""
          case class $newname(..$fields)
        """)
        println(q.toString)
        q
      }
      // Add validation and error handling here.
    }
  }
}

Sbt files:

problemdemo/build.sbt

name := "data-importer"


addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)

scalaVersion := "2.11.8"
val avroVersion = "1.8.1"

lazy val root =
        project.in( file(".") )
          .aggregate(avroschemas, macros).dependsOn(macros, avroschemas)

lazy val macros = project.dependsOn(avroschemas)

lazy val avroschemas = project



libraryDependencies ++= Seq(
  "org.scala-lang" % "scala-reflect" % scalaVersion.value
)


// better error reporting
scalacOptions in Test ++= Seq("-Yrangepos")

run in Compile := Defaults.runTask(fullClasspath in Compile, mainClass in (Compile, run), runner in (Compile, run))

problemdemo/macros/build.sbt

name := "data-importer-macros"


addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)

organization := "com.squarefoot"
scalaVersion := "2.11.3"



libraryDependencies ++= Seq(
  "org.scala-lang" % "scala-reflect" % scalaVersion.value
)

scalacOptions in Test ++= Seq("-Yrangepos")

problemdemo/avroschemas/build.sbt

name := "data-importer-avroschemas"

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)

organization := "com.squarefoot"
scalaVersion := "2.11.8"


// better error reporting
scalacOptions in Test ++= Seq("-Yrangepos")

run in Compile := Defaults.runTask(fullClasspath in Compile, mainClass in (Compile, run), runner in (Compile, run))

2 个解决方案

#1


1  

Basically, instead of TypeName("java.util.List"), you want something like (based on the example in http://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html#tree-creation-via-reify, can't test at the moment) Select(Select(This(TypeName("java")), TypeName("util")), TypeName("List")). If you do showRaw on your input tree, you should see more precisely. So instead of TypeName(...toString), split on .. Maybe just removing TypeName:

基本上,不是TypeName(“java.util.List”),而是需要类似的东西(基于http://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html#tree中的示例) -creation-via-reify,目前无法测试)选择(选择(此(TypeName(“java”)),TypeName(“util”)),TypeName(“List”))。如果在输入树上执行showRaw,您应该更精确地看到。因此,而不是TypeName(... toString),拆分..也许只是删除TypeName:

val fieldType = f.typeSignature.typeConstructor
val typeArgs = f.typeSignature.typeArgs

will be enough?

会够的吗?

#2


1  

So, I don't yet have a working macro, but I have solved this problem with the help of Alexey Romanov's answer. This code results in the error:

所以,我还没有一个有效的宏,但我已经在Alexey Romanov的回答的帮助下解决了这个问题。此代码导致错误:

[error] /Users/marcin/development/repos/problemdemo/src/main/scala/com/squarefoot/converters/problemdemo.scala:10: not found: type com.squarefoot.Pojo
[error] @Caseify(classOf[com.squarefoot.Pojo])

I'm about to open a separate question about that issue.

我即将就这个问题单独提出一个问题。

package com.squarefoot.converters

import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.reflect.macros.Context

/** 
  *  Generate case class from POJO
  *  ex:
  *  @Caseify(classOf[com.squarefoot.incominglisting])
  *  case class Incominglisting()

  * NOTE that the type parameter to classOf must be provided as a fully
  * qualified name, otherwise the macro code here won't be able to find it.
  * 
  * Generates a case class with the same members as the public, non-static
  * members of the pojo
  * 
  * Note that you must have all types used in the POJO in scope where the macro
  * is invoked
  */

class Caseify[T](source: Class[T]) extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro CaseifyMacro.expand_impl[T]
}

object CaseifyMacro {
  /** generate case class from POJO */
  def expand_impl[T](c: Context)(annottees: c.Expr[Any]*) = {
    import c.universe._

    // macro expand the macro expression itself to extract param
    val source: Class[T] = c.prefix.tree match {
      case q"new Caseify($param)" => c.eval[Class[T]](c.Expr(param))
    }

    val rm = scala.reflect.runtime.currentMirror


    val vars =
      rm.classSymbol(source).toType.members.map(_.asTerm).
        filter(_.isVar).filter(_.isPublic)

    val fields = vars.map({f=>
      val fieldName = TermName(f.name.toString)

      val fieldType = tq"${f.typeSignature.typeConstructor.typeSymbol.fullName}"
      val rawTypeArgs = f.typeSignature.typeArgs.map(a=>TypeName(a.toString))
      val typeArgs = tq"${rawTypeArgs}"
      println("typeArgs: "+typeArgs.toString)
      println("fieldType:"+fieldType.getClass.toString+"|"+fieldType.toString)
      println(f.typeSignature.typeSymbol.asType.name.getClass.toString)
      val arraylistname = tq"java.util.ArrayList"
      println("DEBUG:"+tq"${arraylistname}".toString+"|"+f.typeSignature.typeConstructor.typeSymbol.fullName)
      q"val $fieldName: $fieldType"
      if(rawTypeArgs.nonEmpty) {
        val appliedFieldType = tq"${arraylistname}[..$rawTypeArgs]"
        q"val $fieldName: $appliedFieldType"
      }
      else
         q"val $fieldName: $fieldType"
    })

    annottees.map(_.tree) match {
      case List(q"case class $newname()") => {
        val q = c.Expr[Any](
        // Add your own logic here, possibly using arguments on the annotation.
          q"""
          case class $newname(..$fields)
        """)
        println(q.toString)
        q
      }
      // Add validation and error handling here.
    }
  }
}

#1


1  

Basically, instead of TypeName("java.util.List"), you want something like (based on the example in http://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html#tree-creation-via-reify, can't test at the moment) Select(Select(This(TypeName("java")), TypeName("util")), TypeName("List")). If you do showRaw on your input tree, you should see more precisely. So instead of TypeName(...toString), split on .. Maybe just removing TypeName:

基本上,不是TypeName(“java.util.List”),而是需要类似的东西(基于http://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html#tree中的示例) -creation-via-reify,目前无法测试)选择(选择(此(TypeName(“java”)),TypeName(“util”)),TypeName(“List”))。如果在输入树上执行showRaw,您应该更精确地看到。因此,而不是TypeName(... toString),拆分..也许只是删除TypeName:

val fieldType = f.typeSignature.typeConstructor
val typeArgs = f.typeSignature.typeArgs

will be enough?

会够的吗?

#2


1  

So, I don't yet have a working macro, but I have solved this problem with the help of Alexey Romanov's answer. This code results in the error:

所以,我还没有一个有效的宏,但我已经在Alexey Romanov的回答的帮助下解决了这个问题。此代码导致错误:

[error] /Users/marcin/development/repos/problemdemo/src/main/scala/com/squarefoot/converters/problemdemo.scala:10: not found: type com.squarefoot.Pojo
[error] @Caseify(classOf[com.squarefoot.Pojo])

I'm about to open a separate question about that issue.

我即将就这个问题单独提出一个问题。

package com.squarefoot.converters

import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.reflect.macros.Context

/** 
  *  Generate case class from POJO
  *  ex:
  *  @Caseify(classOf[com.squarefoot.incominglisting])
  *  case class Incominglisting()

  * NOTE that the type parameter to classOf must be provided as a fully
  * qualified name, otherwise the macro code here won't be able to find it.
  * 
  * Generates a case class with the same members as the public, non-static
  * members of the pojo
  * 
  * Note that you must have all types used in the POJO in scope where the macro
  * is invoked
  */

class Caseify[T](source: Class[T]) extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro CaseifyMacro.expand_impl[T]
}

object CaseifyMacro {
  /** generate case class from POJO */
  def expand_impl[T](c: Context)(annottees: c.Expr[Any]*) = {
    import c.universe._

    // macro expand the macro expression itself to extract param
    val source: Class[T] = c.prefix.tree match {
      case q"new Caseify($param)" => c.eval[Class[T]](c.Expr(param))
    }

    val rm = scala.reflect.runtime.currentMirror


    val vars =
      rm.classSymbol(source).toType.members.map(_.asTerm).
        filter(_.isVar).filter(_.isPublic)

    val fields = vars.map({f=>
      val fieldName = TermName(f.name.toString)

      val fieldType = tq"${f.typeSignature.typeConstructor.typeSymbol.fullName}"
      val rawTypeArgs = f.typeSignature.typeArgs.map(a=>TypeName(a.toString))
      val typeArgs = tq"${rawTypeArgs}"
      println("typeArgs: "+typeArgs.toString)
      println("fieldType:"+fieldType.getClass.toString+"|"+fieldType.toString)
      println(f.typeSignature.typeSymbol.asType.name.getClass.toString)
      val arraylistname = tq"java.util.ArrayList"
      println("DEBUG:"+tq"${arraylistname}".toString+"|"+f.typeSignature.typeConstructor.typeSymbol.fullName)
      q"val $fieldName: $fieldType"
      if(rawTypeArgs.nonEmpty) {
        val appliedFieldType = tq"${arraylistname}[..$rawTypeArgs]"
        q"val $fieldName: $appliedFieldType"
      }
      else
         q"val $fieldName: $fieldType"
    })

    annottees.map(_.tree) match {
      case List(q"case class $newname()") => {
        val q = c.Expr[Any](
        // Add your own logic here, possibly using arguments on the annotation.
          q"""
          case class $newname(..$fields)
        """)
        println(q.toString)
        q
      }
      // Add validation and error handling here.
    }
  }
}