Skip to content

Commit f07bb38

Browse files
committed
Add Term.toExpr and TypeTree.toType
1 parent a51e587 commit f07bb38

File tree

7 files changed

+144
-31
lines changed

7 files changed

+144
-31
lines changed

compiler/src/dotty/tools/dotc/tasty/TastyImpl.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package dotty.tools.dotc.tasty
22

33
import dotty.tools.dotc.ast.{Trees, tpd, untpd}
44
import dotty.tools.dotc.core
5+
import dotty.tools.dotc.core.Contexts.FreshContext
56
import dotty.tools.dotc.core._
67
import dotty.tools.dotc.core.StdNames.nme
78
import dotty.tools.dotc.core.Symbols.Symbol
89
import dotty.tools.dotc.core.Decorators._
910
import dotty.tools.dotc.core.quoted.PickledQuotes
11+
import dotty.tools.dotc.reporting.Reporter
12+
import dotty.tools.dotc.reporting.diagnostic.MessageContainer
1013
import dotty.tools.dotc.util.SourcePosition
1114

1215
import scala.quoted
@@ -219,7 +222,36 @@ object TastyImpl extends scala.tasty.Tasty {
219222

220223
implicit def TermDeco(t: Term): AbstractTerm = new AbstractTerm {
221224
def pos(implicit ctx: Context): Position = new TastyPosition(t.pos)
225+
222226
def tpe(implicit ctx: Context): Types.Type = t.tpe
227+
228+
def toExpr[T: quoted.Type](implicit ctx: Context): quoted.Expr[T] = {
229+
def typecheck(implicit ctx0: FreshContext) = {
230+
ctx0.setTyperState(ctx0.typerState.fresh())
231+
ctx0.typerState.setReporter(new Reporter {
232+
def doReport(m: MessageContainer)(implicit ctx: Contexts.Context): Unit = ()
233+
})
234+
val tp = implicitly[quoted.Type[T]].toTasty
235+
ctx0.typer.typed(t, tp.tpe)
236+
if (ctx0.reporter.hasErrors) {
237+
val stack = new Exception().getStackTrace
238+
def filter(elem: StackTraceElement) =
239+
elem.getClassName.startsWith("dotty.tools.dotc.tasty.TastyImpl") ||
240+
!elem.getClassName.startsWith("dotty.tools.dotc")
241+
throw new quoted.QuoteError(
242+
s"""Error during macro expansion while typing term
243+
|term: ${t.show}
244+
|with expected type: ${tp.tpe.show}
245+
|
246+
| ${stack.takeWhile(filter).mkString("\n ")}
247+
""".stripMargin
248+
)
249+
}
250+
251+
}
252+
typecheck(ctx.fresh)
253+
new quoted.Exprs.TreeExpr(t).asInstanceOf[quoted.Expr[T]]
254+
}
223255
}
224256

225257
def termClassTag: ClassTag[Term] = implicitly[ClassTag[Term]]
@@ -460,6 +492,7 @@ object TastyImpl extends scala.tasty.Tasty {
460492
implicit def TypeTreeDeco(x: TypeTree): AbstractTypeTree = new AbstractTypeTree {
461493
def pos(implicit ctx: Context): Position = new TastyPosition(x.pos)
462494
def tpe(implicit ctx: Context): Types.Type = x.tpe.stripTypeVar
495+
def toType(implicit ctx: Context): quoted.Type[_] = new quoted.Types.TreeType(x)
463496
}
464497

465498
def typeTreeClassTag: ClassTag[TypeTree] = implicitly[ClassTag[TypeTree]]

compiler/src/dotty/tools/dotc/transform/Splicer.scala

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,22 @@ object Splicer {
3838
val interpreter = new Interpreter(pos, classLoader)
3939
val interpreted = interpreter.interpretCallToSymbol[Seq[Any] => Object](call.symbol)
4040
val cu = new CompilationUniverse(ctx)
41-
interpreted.flatMap(lambda => evaluateLambda(lambda, cu :: liftedArgs, pos)).fold(tree)(PickledQuotes.quotedExprToTree)
41+
try {
42+
val evaluated = interpreted.map(lambda => lambda(cu :: liftedArgs).asInstanceOf[scala.quoted.Expr[Nothing]])
43+
evaluated.fold(tree)(PickledQuotes.quotedExprToTree)
44+
} catch {
45+
case ex: scala.quoted.QuoteError =>
46+
ctx.error(ex.getMessage, pos)
47+
EmptyTree
48+
case NonFatal(ex) =>
49+
val msg =
50+
s"""Failed to evaluate inlined quote.
51+
| Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage}
52+
| ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").init.mkString("\n ")}
53+
""".stripMargin
54+
ctx.error(msg, pos)
55+
EmptyTree
56+
}
4257
}
4358

4459
/** Given the inline code and bindings, compute the lifted arguments that will be used to execute the macro
@@ -75,23 +90,6 @@ object Splicer {
7590
liftArgs(call.symbol.info, allArgs(call, Nil))
7691
}
7792

78-
private def evaluateLambda(lambda: Seq[Any] => Object, args: Seq[Any], pos: Position)(implicit ctx: Context): Option[scala.quoted.Expr[Nothing]] = {
79-
try Some(lambda(args).asInstanceOf[scala.quoted.Expr[Nothing]])
80-
catch {
81-
case ex: scala.quoted.QuoteError =>
82-
ctx.error(ex.getMessage, pos)
83-
None
84-
case NonFatal(ex) =>
85-
val msg =
86-
s"""Failed to evaluate inlined quote.
87-
| Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage}
88-
| ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").init.mkString("\n ")}
89-
""".stripMargin
90-
ctx.error(msg, pos)
91-
None
92-
}
93-
}
94-
9593
/** Tree interpreter that can interpret calls to static methods with it's default arguments
9694
*
9795
* The interpreter assumes that all calls in the trees are to code that was

library/src/scala/tasty/Tasty.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ abstract class Tasty {
168168

169169
type Term <: Statement with Parent
170170

171-
trait AbstractTerm extends Typed with Positioned
171+
trait AbstractTerm extends Typed with Positioned {
172+
def toExpr[T: quoted.Type](implicit ctx: Context): quoted.Expr[T]
173+
}
172174
implicit def TermDeco(t: Term): AbstractTerm
173175

174176
implicit def termClassTag: ClassTag[Term]
@@ -344,7 +346,9 @@ abstract class Tasty {
344346

345347
type TypeTree <: TypeOrBoundsTree with Parent
346348

347-
trait AbstractTypeTree extends Typed with Positioned
349+
trait AbstractTypeTree extends Typed with Positioned {
350+
def toType(implicit ctx: Context): quoted.Type[_]
351+
}
348352
implicit def TypeTreeDeco(x: TypeTree): AbstractTypeTree
349353

350354
implicit def typeTreeClassTag: ClassTag[TypeTree]
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import scala.quoted._
2+
3+
import scala.tasty.Universe
4+
import scala.tasty.util.TastyPrinter
5+
6+
object Asserts {
7+
8+
implicit class Ops[T](left: T) {
9+
def ===(right: T): Boolean = left == right
10+
def !==(right: T): Boolean = left != right
11+
}
12+
13+
object Ops
14+
15+
inline def macroAssert(cond: Boolean): Unit =
16+
~impl('(cond))(Universe.compilationUniverse) // FIXME infer Universe.compilationUniverse within top level ~
17+
18+
def impl(cond: Expr[Boolean])(implicit u: Universe): Expr[Unit] = {
19+
import u._
20+
import u.tasty._
21+
22+
val tree = cond.toTasty
23+
24+
def isOps(tpe: TypeOrBounds): Boolean = tpe match {
25+
case Type.SymRef(DefDef("Ops", _, _, _, _), _) => true // TODO check that the parent is Asserts
26+
case _ => false
27+
}
28+
29+
object OpsTree {
30+
def unapply(arg: Term): Option[Term] = arg match {
31+
case Term.Apply(Term.TypeApply(term, _), left :: Nil) if isOps(term.tpe) =>
32+
Some(left)
33+
case _ => None
34+
}
35+
}
36+
37+
tree match {
38+
case Term.Apply(Term.Select(OpsTree(left), op, _), right :: Nil) =>
39+
'(assertTrue(~left.toExpr[Boolean])) // Buggy code. To generate the errors
40+
case _ =>
41+
'(assertTrue(~cond))
42+
}
43+
44+
}
45+
46+
def assertEquals[T](left: T, right: T): Unit = {
47+
if (left != right) {
48+
println(
49+
s"""Error left did not equal right:
50+
| left = $left
51+
| right = $right""".stripMargin)
52+
}
53+
54+
}
55+
56+
def assertNotEquals[T](left: T, right: T): Unit = {
57+
if (left == right) {
58+
println(
59+
s"""Error left was equal to right:
60+
| left = $left
61+
| right = $right""".stripMargin)
62+
}
63+
64+
}
65+
66+
def assertTrue(cond: Boolean): Unit = {
67+
if (!cond)
68+
println("Condition was false")
69+
}
70+
71+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
import Asserts._
3+
4+
object Test {
5+
def main(args: Array[String]): Unit = {
6+
macroAssert(true === "cde")
7+
macroAssert("acb" === "cde") // error
8+
macroAssert(false !== "acb")
9+
macroAssert("acb" !== "acb") // error
10+
}
11+
12+
}

tests/run/tasty-macro-assert.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Condition was false
22
Error left did not equal right:
3-
left = Literal(String(acb))
4-
right = Literal(String(cde))
3+
left = acb
4+
right = cde
55
Error left was equal to right:
6-
left = Literal(String(acb))
7-
right = Literal(String(acb))
6+
left = acb
7+
right = acb

tests/run/tasty-macro-assert/quoted_1.scala

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import scala.quoted._
2-
import dotty.tools.dotc.quoted.Toolbox._
32

43
import scala.tasty.Universe
54
import scala.tasty.util.TastyPrinter
@@ -37,13 +36,9 @@ object Asserts {
3736

3837
tree match {
3938
case Term.Apply(Term.Select(OpsTree(left), op, _), right :: Nil) =>
40-
// FIXME splice the threes directly
41-
val printer = new TastyPrinter(tasty)
42-
val lExpr = printer.stringOfTree(left).toExpr
43-
val rExpr = printer.stringOfTree(right).toExpr
4439
op match {
45-
case "===" => '(assertEquals(~lExpr, ~rExpr))
46-
case "!==" => '(assertNotEquals(~lExpr, ~rExpr))
40+
case "===" => '(assertEquals(~left.toExpr[Any], ~right.toExpr[Any]))
41+
case "!==" => '(assertNotEquals(~left.toExpr[Any], ~right.toExpr[Any]))
4742
}
4843
case _ =>
4944
'(assertTrue(~cond))

0 commit comments

Comments
 (0)