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 1 commit
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
Next Next commit
Add underlying term to know the static value of some ident
  • Loading branch information
nicolasstucki committed Oct 22, 2018
commit ccff48e8fb7ae9561fb6e1e7991e85ecb4ece264
25 changes: 23 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,11 @@ 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 = mapToUnderlyingArgument.transform(tree)

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

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

Expand Down Expand Up @@ -948,7 +952,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
}

/** Map Inlined nodes, InlineProxy references and Synthetic val references to underlying arguments */
object mapToUnderlying extends TreeMap {
object mapToUnderlyingArgument 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) =>
tree.symbol.defTree match {
Expand All @@ -961,6 +965,23 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
}
}

/** Map Ident nodes references to underlying tree that defined them.
* Also drops Inline and Block with no statements
*/
object mapToUnderlying extends TreeMap {
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
case tree: Ident if !tree.symbol.owner.isClass =>
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)
}
}

implicit class ListOfTreeDecorator(val xs: List[tpd.Tree]) extends AnyVal {
def tpes: List[Type] = xs map (_.tpe)
}
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))
}
}