Skip to content

Tasty reflect underlying tree #5164

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def outerSelect(levels: Int, tp: Type)(implicit ctx: Context): Tree =
untpd.Select(tree, OuterSelectName(EmptyTermName, levels)).withType(SkolemType(tp))

def underlyingArgument(implicit ctx: Context): Tree = mapToUnderlying.transform(tree)
/** Replace Inlined nodes and InlineProxy references to underlying arguments */
def underlyingArgument(implicit ctx: Context): Tree = {
val mapToUnderlying = new MapToUnderlying {
override def skipLocal(sym: Symbol): Boolean =
sym.is(InlineProxy) || sym.is(Synthetic)
}
mapToUnderlying.transform(tree)
}

/** Replace Ident nodes references to the underlying tree that defined them */
def underlying(implicit ctx: Context): Tree = new MapToUnderlying().transform(tree)

// --- Higher order traversal methods -------------------------------

Expand Down Expand Up @@ -947,18 +957,22 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
}
}

/** Map Inlined nodes, InlineProxy references and Synthetic val references to underlying arguments */
object mapToUnderlying extends TreeMap {
/** Map Inlined nodes, NamedArgs, Blocks with no statements and local references to underlying arguments.
* Also drops Inline and Block with no statements.
*/
class MapToUnderlying extends TreeMap {
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
case tree: Ident if tree.symbol.is(InlineProxy) || (tree.symbol.is(Synthetic) && !tree.symbol.owner.isClass) =>
case tree: Ident if !tree.symbol.owner.isClass && skipLocal(tree.symbol) =>
tree.symbol.defTree match {
case defTree: ValOrDefDef => transform(defTree.rhs)
case _ => tree
}
case Inlined(_, _, arg) => transform(arg)
case Block(Nil, arg) => transform(arg)
case NamedArg(_, arg) => transform(arg)
case tree => super.transform(tree)
}
def skipLocal(sym: Symbol): Boolean = true
}

implicit class ListOfTreeDecorator(val xs: List[tpd.Tree]) extends AnyVal {
Expand Down
7 changes: 3 additions & 4 deletions compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,11 @@ trait TreeOpsImpl extends scala.tasty.reflect.TreeOps with TastyCoreImpl with He
// ----- Terms ----------------------------------------------------

def TermDeco(term: Term): TermAPI = new TermAPI {
import tpd._
def pos(implicit ctx: Context): Position = term.pos
def tpe(implicit ctx: Context): Type = term.tpe
def underlyingArgument(implicit ctx: Context): Term = {
import tpd._
term.underlyingArgument
}
def underlyingArgument(implicit ctx: Context): Term = term.underlyingArgument
def underlying(implicit ctx: Context): Term = term.underlying
}

object IsTerm extends IsTermExtractor {
Expand Down
4 changes: 3 additions & 1 deletion compiler/test/dotc/run-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ t7374
t7859
t8133
t8133b
tasty-argument-tree-1
tasty-custom-show
tasty-definitions-1
tasty-definitions-2
Expand Down Expand Up @@ -91,4 +92,5 @@ typelevel-patmat.scala
typelevel.scala
typelevel1.scala
typelevel3.scala
xml-interpolation
xml-interpolation-1
xml-interpolation-2
1 change: 1 addition & 0 deletions library/src/scala/tasty/reflect/TreeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ trait TreeOps extends TastyCore {
def tpe(implicit ctx: Context): Type
def pos(implicit ctx: Context): Position
def underlyingArgument(implicit ctx: Context): Term
def underlying(implicit ctx: Context): Term
}
implicit def TermDeco(term: Term): TermAPI

Expand Down
33 changes: 33 additions & 0 deletions tests/run/tasty-argument-tree-1.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

tree: Term.Literal(Constant.Int(3))
tree deref. vals: Term.Literal(Constant.Int(3))

tree: Term.Ident("v")
tree deref. vals: Term.Literal(Constant.Int(1))

tree: Term.Ident("x")
tree deref. vals: Term.Literal(Constant.Int(2))

tree: Term.Ident("l")
tree deref. vals: Term.Literal(Constant.Int(3))

tree: Term.Ident("a")
tree deref. vals: Term.Ident("a")

tree: Term.Ident("x")
tree deref. vals: Term.Ident("b")

tree: Term.Ident("vv")
tree deref. vals: Term.Literal(Constant.Int(1))

tree: Term.Ident("x")
tree deref. vals: Term.Literal(Constant.Int(1))

tree: Term.Ident("vd")
tree deref. vals: Term.Literal(Constant.Int(2))

tree: Term.Ident("x")
tree deref. vals: Term.Literal(Constant.Int(2))

tree: Term.Ident("x")
tree deref. vals: Term.Apply(Term.TypeApply(Term.Select(Term.Ident("Tuple2"), "apply", Some(Signature(List(java.lang.Object, java.lang.Object), scala.Tuple2))), List(TypeTree.Synthetic(), TypeTree.Synthetic())), List(Term.Literal(Constant.Int(1)), Term.Literal(Constant.Int(2))))
17 changes: 17 additions & 0 deletions tests/run/tasty-argument-tree-1/quoted_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import scala.quoted._
import scala.tasty._

object Macros {

inline def inspect[T](x: T): Unit = ~impl('(x))

def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = {
import tasty._
val tree = x.toTasty
'{
println()
println("tree: " + ~tree.show.toExpr)
println("tree deref. vals: " + ~tree.underlying.show.toExpr)
}
}
}
31 changes: 31 additions & 0 deletions tests/run/tasty-argument-tree-1/quoted_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

import Macros.inspect

object Test {
val a: Int = 4
def b: Int = 5

def main(args: Array[String]): Unit = {
val v: Int = 1
def d: Int = 2
lazy val l: Int = 3
inspect(3)
inspect(v)
inspect(d)
inspect(l)
inspect(a)
inspect(b)

val vv = v
def dv = v
val vd = d
def dd = d
inspect(vv)
inspect(dv)
inspect(vd)
inspect(dd)

inspect((dv, vd))

}
}
27 changes: 27 additions & 0 deletions tests/run/xml-interpolation-2/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import XmlQuote._

object Test {
def main(args: Array[String]): Unit = {

assert(xml"Hello Allan!" == Xml("Hello Allan!", Nil))

val name = new Object{}
assert(xml"Hello $name!" == Xml("Hello ??!", List(name)))

val ctx0 = new StringContext("Hello !")
assert(ctx0.xml() == Xml("Hello !", Nil))
assert(new SCOps(ctx0).xml() == Xml("Hello !", Nil))

val ctx1 = new StringContext("Hello ", "!")
assert(ctx1.xml(name) == Xml("Hello ??!", List(name)))
assert(new SCOps(ctx1).xml(name) == Xml("Hello ??!", List(name)))

val hello: String = "Hello "
val ctx2 = new StringContext(hello, "!")
assert(ctx2.xml(name) == Xml("Hello ??!", List(name)))
assert(new SCOps(ctx2).xml(name) == Xml("Hello ??!", List(name)))

val args = Seq(name)
assert(new SCOps(ctx2).xml(args: _*) == Xml("Hello ??!", List(name)))
}
}
66 changes: 66 additions & 0 deletions tests/run/xml-interpolation-2/XmlQuote_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import scala.quoted._

import scala.tasty.Tasty

import scala.language.implicitConversions

case class Xml(parts: String, args: List[Any])

object XmlQuote {

class SCOps(ctx: => StringContext) {
inline def xml(args: Any*): Xml = ~XmlQuote.impl('(this), '(args))
}
implicit inline def SCOps(ctx: => StringContext): SCOps = new SCOps(ctx)

def impl(receiver: Expr[SCOps], args: Expr[Seq[Any]])
(implicit tasty: Tasty): Expr[Xml] = {
import tasty._
import Term._

// for debugging purpose
def pp(tree: Tree): Unit = {
println(tree.show)
println(tasty.showSourceCode.showTree(tree))
}

def isSCOpsConversion(tree: Term) =
tree.symbol.fullName == "XmlQuote$.SCOps" ||
tree.symbol.fullName == "XmlQuote$.SCOps.<init>"

def isStringContextApply(tree: Term) =
tree.symbol.fullName == "scala.StringContext$.apply" ||
tree.symbol.fullName == "scala.StringContext.<init>"

// XmlQuote.SCOps(StringContext.apply([p0, ...]: String*)
val parts: List[String] = receiver.toTasty.underlying match {
case Apply(conv, List(ctx1)) if isSCOpsConversion(conv) =>
ctx1 match {
case Apply(fun, List(Typed(Repeated(values), _))) if isStringContextApply(fun) =>
values.iterator.map {
case Literal(Constant.String(value)) => value
case _ => QuoteError("Expected statically known String")
}.toList
case _ => QuoteError("Expected statically known StringContext")
}
case _ =>
QuoteError("Expected statically known SCOps")
}

// [a0, ...]: Any*
val args2: Expr[List[Any]] = args.toTasty.underlyingArgument match {
case Typed(Repeated(args0), _) => // statically known args, make list directly
def liftListOfAny(lst: List[Expr[Any]]): Expr[List[Any]] = lst match {
case x :: xs => '{ ~x :: ~liftListOfAny(xs) }
case Nil => '(Nil)
}
liftListOfAny(args0.map(_.toExpr[Any]))
case _ =>
'((~args).toList)

}

val string = parts.mkString("??")
'(new Xml(~string.toExpr, ~args2))
}
}