diff --git a/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala b/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala index 9296aa31a680..3a4fe5736362 100644 --- a/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala +++ b/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala @@ -1,7 +1,9 @@ package dotty.tools.dotc.quoted import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Constants._ +import dotty.tools.dotc.core.quoted.PickledQuotes import dotty.tools.dotc.printing.RefinedPrinter import scala.quoted.Expr diff --git a/compiler/src/dotty/tools/dotc/tasty/TastyImpl.scala b/compiler/src/dotty/tools/dotc/tasty/TastyImpl.scala index db74e2d077bf..8cd3303de74c 100644 --- a/compiler/src/dotty/tools/dotc/tasty/TastyImpl.scala +++ b/compiler/src/dotty/tools/dotc/tasty/TastyImpl.scala @@ -7,6 +7,8 @@ import dotty.tools.dotc.core.StdNames.nme import dotty.tools.dotc.core.Symbols.Symbol import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.quoted.PickledQuotes +import dotty.tools.dotc.reporting.Reporter +import dotty.tools.dotc.reporting.diagnostic.MessageContainer import dotty.tools.dotc.util.SourcePosition import scala.quoted @@ -228,9 +230,40 @@ object TastyImpl extends scala.tasty.Tasty { type Term = tpd.Tree - def TermDeco(t: Term): AbstractTerm = new AbstractTerm { - def pos(implicit ctx: Context): Position = t.pos - def tpe(implicit ctx: Context): Types.Type = t.tpe + def TermDeco(tree: Term): AbstractTerm = new AbstractTerm { + + def pos(implicit ctx: Context): Position = tree.pos + + def tpe(implicit ctx: Context): Types.Type = tree.tpe + + def toExpr[T: quoted.Type](implicit ctx: Context): quoted.Expr[T] = { + typecheck(ctx) + new quoted.Exprs.TastyTreeExpr(tree).asInstanceOf[quoted.Expr[T]] + } + + private def typecheck[T: quoted.Type](ctx: Contexts.Context): Unit = { + implicit val ctx0: Contexts.FreshContext = ctx.fresh + ctx0.setTyperState(ctx0.typerState.fresh()) + ctx0.typerState.setReporter(new Reporter { + def doReport(m: MessageContainer)(implicit ctx: Contexts.Context): Unit = () + }) + val tp = QuotedTypeDeco(implicitly[quoted.Type[T]]).toTasty + ctx0.typer.typed(tree, tp.tpe) + if (ctx0.reporter.hasErrors) { + val stack = new Exception().getStackTrace + def filter(elem: StackTraceElement) = + elem.getClassName.startsWith("dotty.tools.dotc.tasty.TastyImpl") || + !elem.getClassName.startsWith("dotty.tools.dotc") + throw new scala.tasty.TastyTypecheckError( + s"""Error during tasty reflection while typing term + |term: ${tree.show} + |with expected type: ${tp.tpe.show} + | + | ${stack.takeWhile(filter).mkString("\n ")} + """.stripMargin + ) + } + } } def termClassTag: ClassTag[Term] = implicitly[ClassTag[Term]] diff --git a/library/src/scala/tasty/Tasty.scala b/library/src/scala/tasty/Tasty.scala index 43f379e69c0c..164bebfac77c 100644 --- a/library/src/scala/tasty/Tasty.scala +++ b/library/src/scala/tasty/Tasty.scala @@ -180,7 +180,9 @@ abstract class Tasty { tasty => type Term <: Statement with Parent - trait AbstractTerm extends Typed with Positioned + trait AbstractTerm extends Typed with Positioned { + def toExpr[T: quoted.Type](implicit ctx: Context): quoted.Expr[T] + } implicit def TermDeco(t: Term): AbstractTerm implicit def termClassTag: ClassTag[Term] diff --git a/library/src/scala/tasty/TastyTypecheckError.scala b/library/src/scala/tasty/TastyTypecheckError.scala new file mode 100644 index 000000000000..fc506c1ddecb --- /dev/null +++ b/library/src/scala/tasty/TastyTypecheckError.scala @@ -0,0 +1,3 @@ +package scala.tasty + +class TastyTypecheckError(msg: String) extends Throwable diff --git a/tests/neg/tasty-macro-assert/quoted_1.scala b/tests/neg/tasty-macro-assert/quoted_1.scala new file mode 100644 index 000000000000..8b5f1725bf13 --- /dev/null +++ b/tests/neg/tasty-macro-assert/quoted_1.scala @@ -0,0 +1,70 @@ +import scala.quoted._ + +import scala.tasty.Universe + +object Asserts { + + implicit class Ops[T](left: T) { + def ===(right: T): Boolean = left == right + def !==(right: T): Boolean = left != right + } + + object Ops + + inline def macroAssert(cond: Boolean): Unit = + ~impl('(cond))(Universe.compilationUniverse) // FIXME infer Universe.compilationUniverse within top level ~ + + def impl(cond: Expr[Boolean])(implicit u: Universe): Expr[Unit] = { + import u._ + import u.tasty._ + + val tree = cond.toTasty + + def isOps(tpe: TypeOrBounds): Boolean = tpe match { + case Type.SymRef(DefDef("Ops", _, _, _, _), _) => true // TODO check that the parent is Asserts + case _ => false + } + + object OpsTree { + def unapply(arg: Term): Option[Term] = arg match { + case Term.Apply(Term.TypeApply(term, _), left :: Nil) if isOps(term.tpe) => + Some(left) + case _ => None + } + } + + tree match { + case Term.Apply(Term.Select(OpsTree(left), op, _), right :: Nil) => + '(assertTrue(~left.toExpr[Boolean])) // Buggy code. To generate the errors + case _ => + '(assertTrue(~cond)) + } + + } + + def assertEquals[T](left: T, right: T): Unit = { + if (left != right) { + println( + s"""Error left did not equal right: + | left = $left + | right = $right""".stripMargin) + } + + } + + def assertNotEquals[T](left: T, right: T): Unit = { + if (left == right) { + println( + s"""Error left was equal to right: + | left = $left + | right = $right""".stripMargin) + } + + } + + def assertTrue(cond: Boolean): Unit = { + if (!cond) + println("Condition was false") + } + +} diff --git a/tests/neg/tasty-macro-assert/quoted_2.scala b/tests/neg/tasty-macro-assert/quoted_2.scala new file mode 100644 index 000000000000..b94c98ab6b70 --- /dev/null +++ b/tests/neg/tasty-macro-assert/quoted_2.scala @@ -0,0 +1,12 @@ + +import Asserts._ + +object Test { + def main(args: Array[String]): Unit = { + macroAssert(true === "cde") + macroAssert("acb" === "cde") // error + macroAssert(false !== "acb") + macroAssert("acb" !== "acb") // error + } + +} diff --git a/tests/run/tasty-macro-assert.check b/tests/run/tasty-macro-assert.check index df774273df9d..c9f084a0a9b2 100644 --- a/tests/run/tasty-macro-assert.check +++ b/tests/run/tasty-macro-assert.check @@ -1,7 +1,7 @@ Condition was false Error left did not equal right: - left = Term.Literal(Constant.String("acb")) - right = Term.Literal(Constant.String("cde")) + left = acb + right = cde Error left was equal to right: - left = Term.Literal(Constant.String("acb")) - right = Term.Literal(Constant.String("acb")) + left = acb + right = acb diff --git a/tests/run/tasty-macro-assert/quoted_1.scala b/tests/run/tasty-macro-assert/quoted_1.scala index 72684e9a4862..8b327e6e6e71 100644 --- a/tests/run/tasty-macro-assert/quoted_1.scala +++ b/tests/run/tasty-macro-assert/quoted_1.scala @@ -1,5 +1,4 @@ import scala.quoted._ -import dotty.tools.dotc.quoted.Toolbox._ import scala.tasty.Universe @@ -36,12 +35,9 @@ object Asserts { tree match { case Term.Apply(Term.Select(OpsTree(left), op, _), right :: Nil) => - // FIXME splice the threes directly - val lExpr = left.show.toExpr - val rExpr = right.show.toExpr op match { - case "===" => '(assertEquals(~lExpr, ~rExpr)) - case "!==" => '(assertNotEquals(~lExpr, ~rExpr)) + case "===" => '(assertEquals(~left.toExpr[Any], ~right.toExpr[Any])) + case "!==" => '(assertNotEquals(~left.toExpr[Any], ~right.toExpr[Any])) } case _ => '(assertTrue(~cond))