Scala 3 宏特征参数
Scala 3 macros trait arguments
基本设置如下:
trait MyProduct[A, B](a: A, b: B)
class X
class Y
class XxY(x: X, y: Y) extends MyProduct(x,y)
我正在尝试检查 MyProduct
特征的参数。更具体地说,我想提取一些信息,例如字符串“x”和“y”,它们指示 XxY
的哪些字段传递给 MyProduct
。
重要的是,我需要满足具有多个相同类型字段的情况,例如 class XxYxY(x: X, y1: Y, y2: Y) extends MyProduct3(x, y1, y2)
,因此从类型参数推理是不够的。
我想问题可能在于我还没有找到一种方法来获取 extends
子句本身的符号。我可以找到 XxY
的 ClassDef
,我可以从中提取 parents
并获得已经构造的类型 MyProduct[X,Y]
。我还查看了封闭模块的 symbol.declarations
和 XxY.<init>
函数,看看是否有我可以用来查找参数的数据,但是其中的 none 似乎有我要找的信息。
查看封闭模块的树让我觉得也许这些信息已被删除,而是需要从源代码中解析为文本,但我希望有人有更好的解决方案。
根据评论编辑:
作为输入,我有一个 Type
封闭 object/module 的实例。例如:
inline def simpleProductTest[T <: Singleton]: String = ${simpleProductTestImpl[T]}
def simpleProductTestImpl[T](using q: Quotes)(using Type[T]): Expr[String] = ???
作为输出,我希望能够检查 extends 子句中使用的特征的参数。
如果你这样做
import scala.quoted.*
object Macros {
inline def simpleProductTest[T]: Unit = ${simpleProductTestImpl[T]}
def simpleProductTestImpl[T](using Quotes)(using Type[T]): Expr[Unit] = {
import quotes.reflect.*
println(TypeRepr.of[T].typeSymbol.tree.asInstanceOf[TypeDef].rhs)
'{()}
}
}
你会看到
object App {
trait MyProduct[A, B](a: A, b: B)
class X
class Y
class XxY(x: X, y: Y, z: Int) extends MyProduct(x,y)
Macros.simpleProductTest[XxY]
}
没有关于 x,y
的信息
Template(
DefDef(
<init>,
List(List(
ValDef(x,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X)],EmptyTree),
ValDef(y,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y)],EmptyTree),
ValDef(z,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)],EmptyTree)
)),
TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class XxY)],
EmptyTree
),
List(
TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class lang)),class Object)],
TypeTree[AppliedType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),trait MyProduct),
List(
TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X),
TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y)
)
)]
),
ValDef(_,EmptyTree,EmptyTree),
List(
ValDef(x,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X)],EmptyTree),
ValDef(y,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y)],EmptyTree),
ValDef(z,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)],EmptyTree)
)
)
并且 Template
不存在于 scala.quoted.*
中,它是 dotty.tools.dotc.ast.Trees.Template
。
但是如果你用 TASTy inspection
打印 .tasty
文件
libraryDependencies += "org.scala-lang" %% "scala3-tasty-inspector" % "3.0.0"
import scala.quoted.*
import scala.tasty.inspector.*
class MyInspector extends Inspector:
def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit =
import quotes.reflect.*
for tasty <- tastys do
val tree = tasty.ast
println(tree)
@main def test: Unit =
val tastyFiles = List("target/scala-3.0.0/classes/App.tasty")
TastyInspector.inspectTastyFiles(tastyFiles)(new MyInspector)
你会看到
Template(
DefDef(
<init>,
List(List(
ValDef(x,Ident(X),EmptyTree),
ValDef(y,Ident(Y),EmptyTree),
ValDef(z,Ident(Int),EmptyTree)
)),
TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),Unit)],
EmptyTree
),
List(
Apply(Select(New(TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class java)),object lang),Object)]),<init>),List()),
Apply(
TypeApply(
Select(New(Ident(MyProduct)),<init>),
List(
TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),module class App$)),class X)],
TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),module class App$)),class Y)]
)
),
List(Ident(x), Ident(y)) <--- HERE!!!
)
),
ValDef(_,EmptyTree,EmptyTree),
List(
ValDef(x,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),module class App$)),class X)],EmptyTree),
ValDef(y,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),module class App$)),class Y)],EmptyTree),
ValDef(z,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),Int)],EmptyTree)
)
)
这棵树略有不同,那里有关于 x,y
的信息。
在 Scaladocs 中,Symbol
的扩展方法 .tree
是这样写的
* **Warning**: avoid using this method in macros.
*
* **Caveat**: The tree is not guaranteed to exist unless the compiler
* option `-Yretain-trees` is enabled.
让我们开启-Yretain-trees
scalacOptions += "-Yretain-trees"
然后 Macros.simpleProductTest[XxY]
打印
Template(
DefDef(
<init>,
List(List(
ValDef(x,Ident(X),EmptyTree),
ValDef(y,Ident(Y),EmptyTree),
ValDef(z,Ident(Int),EmptyTree)
)),
TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Unit)],
EmptyTree
),
List(
Apply(Select(New(TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class lang)),class Object)]),<init>),List()),
Apply(
TypeApply(
Select(New(Ident(MyProduct)),<init>),
List(
TypeTree[TypeVar(TypeParamRef(A) -> TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X))],
TypeTree[TypeVar(TypeParamRef(B) -> TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y))]
)
),
List(Ident(x), Ident(y)) <---- AGAIN HERE !!!!
)
),
ValDef(_,EmptyTree,EmptyTree),
List(
ValDef(x,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X)],EmptyTree),
ValDef(y,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y)],EmptyTree),
ValDef(z,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)],EmptyTree)
)
)
即tree 再次略有不同,但出现了有关 x,y
的信息。所以现在的问题只是如何提取此信息,因为它是 dotty.tools.dotc.ast.*
case class Template[-T >: Untyped] private[ast](
constr: DefDef[T],
parentsOrDerived: List[Tree[T]],
self: ValDef[T],
private var preBody: LazyTreeList[T @uncheckedVariance]
)(implicit @constructorOnly src: SourceFile)
最后
libraryDependencies += "org.scala-lang" %% "scala3-compiler" % "3.0.0"
import scala.quoted.*
inline def simpleProductTest[T]: List[String] = ${simpleProductTestImpl[T]}
def simpleProductTestImpl[T](using Quotes)(using Type[T]): Expr[List[String]] = {
import quotes.reflect.*
Expr(
TypeRepr.of[T].typeSymbol.tree.asInstanceOf[TypeDef].rhs
.asInstanceOf[dotty.tools.dotc.ast.Trees.Template[?]]
.parentsOrDerived
.asInstanceOf[List[Apply]]
.tail.head.args.map(_.symbol.name)
)
}
Macros.simpleProductTest[XxY] // List(x, y)
基本设置如下:
trait MyProduct[A, B](a: A, b: B)
class X
class Y
class XxY(x: X, y: Y) extends MyProduct(x,y)
我正在尝试检查 MyProduct
特征的参数。更具体地说,我想提取一些信息,例如字符串“x”和“y”,它们指示 XxY
的哪些字段传递给 MyProduct
。
重要的是,我需要满足具有多个相同类型字段的情况,例如 class XxYxY(x: X, y1: Y, y2: Y) extends MyProduct3(x, y1, y2)
,因此从类型参数推理是不够的。
我想问题可能在于我还没有找到一种方法来获取 extends
子句本身的符号。我可以找到 XxY
的 ClassDef
,我可以从中提取 parents
并获得已经构造的类型 MyProduct[X,Y]
。我还查看了封闭模块的 symbol.declarations
和 XxY.<init>
函数,看看是否有我可以用来查找参数的数据,但是其中的 none 似乎有我要找的信息。
查看封闭模块的树让我觉得也许这些信息已被删除,而是需要从源代码中解析为文本,但我希望有人有更好的解决方案。
根据评论编辑:
作为输入,我有一个 Type
封闭 object/module 的实例。例如:
inline def simpleProductTest[T <: Singleton]: String = ${simpleProductTestImpl[T]}
def simpleProductTestImpl[T](using q: Quotes)(using Type[T]): Expr[String] = ???
作为输出,我希望能够检查 extends 子句中使用的特征的参数。
如果你这样做
import scala.quoted.*
object Macros {
inline def simpleProductTest[T]: Unit = ${simpleProductTestImpl[T]}
def simpleProductTestImpl[T](using Quotes)(using Type[T]): Expr[Unit] = {
import quotes.reflect.*
println(TypeRepr.of[T].typeSymbol.tree.asInstanceOf[TypeDef].rhs)
'{()}
}
}
你会看到
object App {
trait MyProduct[A, B](a: A, b: B)
class X
class Y
class XxY(x: X, y: Y, z: Int) extends MyProduct(x,y)
Macros.simpleProductTest[XxY]
}
没有关于 x,y
Template(
DefDef(
<init>,
List(List(
ValDef(x,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X)],EmptyTree),
ValDef(y,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y)],EmptyTree),
ValDef(z,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)],EmptyTree)
)),
TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class XxY)],
EmptyTree
),
List(
TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class lang)),class Object)],
TypeTree[AppliedType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),trait MyProduct),
List(
TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X),
TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y)
)
)]
),
ValDef(_,EmptyTree,EmptyTree),
List(
ValDef(x,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X)],EmptyTree),
ValDef(y,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y)],EmptyTree),
ValDef(z,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)],EmptyTree)
)
)
并且 Template
不存在于 scala.quoted.*
中,它是 dotty.tools.dotc.ast.Trees.Template
。
但是如果你用 TASTy inspection
打印.tasty
文件
libraryDependencies += "org.scala-lang" %% "scala3-tasty-inspector" % "3.0.0"
import scala.quoted.*
import scala.tasty.inspector.*
class MyInspector extends Inspector:
def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit =
import quotes.reflect.*
for tasty <- tastys do
val tree = tasty.ast
println(tree)
@main def test: Unit =
val tastyFiles = List("target/scala-3.0.0/classes/App.tasty")
TastyInspector.inspectTastyFiles(tastyFiles)(new MyInspector)
你会看到
Template(
DefDef(
<init>,
List(List(
ValDef(x,Ident(X),EmptyTree),
ValDef(y,Ident(Y),EmptyTree),
ValDef(z,Ident(Int),EmptyTree)
)),
TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),Unit)],
EmptyTree
),
List(
Apply(Select(New(TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class java)),object lang),Object)]),<init>),List()),
Apply(
TypeApply(
Select(New(Ident(MyProduct)),<init>),
List(
TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),module class App$)),class X)],
TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),module class App$)),class Y)]
)
),
List(Ident(x), Ident(y)) <--- HERE!!!
)
),
ValDef(_,EmptyTree,EmptyTree),
List(
ValDef(x,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),module class App$)),class X)],EmptyTree),
ValDef(y,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <root>)),module class <empty>)),module class App$)),class Y)],EmptyTree),
ValDef(z,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),Int)],EmptyTree)
)
)
这棵树略有不同,那里有关于 x,y
的信息。
在 Scaladocs 中,Symbol
的扩展方法 .tree
是这样写的
* **Warning**: avoid using this method in macros.
*
* **Caveat**: The tree is not guaranteed to exist unless the compiler
* option `-Yretain-trees` is enabled.
让我们开启-Yretain-trees
scalacOptions += "-Yretain-trees"
然后 Macros.simpleProductTest[XxY]
打印
Template(
DefDef(
<init>,
List(List(
ValDef(x,Ident(X),EmptyTree),
ValDef(y,Ident(Y),EmptyTree),
ValDef(z,Ident(Int),EmptyTree)
)),
TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Unit)],
EmptyTree
),
List(
Apply(Select(New(TypeTree[TypeRef(ThisType(TypeRef(NoPrefix,module class lang)),class Object)]),<init>),List()),
Apply(
TypeApply(
Select(New(Ident(MyProduct)),<init>),
List(
TypeTree[TypeVar(TypeParamRef(A) -> TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X))],
TypeTree[TypeVar(TypeParamRef(B) -> TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y))]
)
),
List(Ident(x), Ident(y)) <---- AGAIN HERE !!!!
)
),
ValDef(_,EmptyTree,EmptyTree),
List(
ValDef(x,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class X)],EmptyTree),
ValDef(y,TypeTree[TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class <empty>)),module class App$)),class Y)],EmptyTree),
ValDef(z,TypeTree[TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),class Int)],EmptyTree)
)
)
即tree 再次略有不同,但出现了有关 x,y
的信息。所以现在的问题只是如何提取此信息,因为它是 dotty.tools.dotc.ast.*
case class Template[-T >: Untyped] private[ast](
constr: DefDef[T],
parentsOrDerived: List[Tree[T]],
self: ValDef[T],
private var preBody: LazyTreeList[T @uncheckedVariance]
)(implicit @constructorOnly src: SourceFile)
最后
libraryDependencies += "org.scala-lang" %% "scala3-compiler" % "3.0.0"
import scala.quoted.*
inline def simpleProductTest[T]: List[String] = ${simpleProductTestImpl[T]}
def simpleProductTestImpl[T](using Quotes)(using Type[T]): Expr[List[String]] = {
import quotes.reflect.*
Expr(
TypeRepr.of[T].typeSymbol.tree.asInstanceOf[TypeDef].rhs
.asInstanceOf[dotty.tools.dotc.ast.Trees.Template[?]]
.parentsOrDerived
.asInstanceOf[List[Apply]]
.tail.head.args.map(_.symbol.name)
)
}
Macros.simpleProductTest[XxY] // List(x, y)