From eeb1218ee13e1bb2d065753be3dcceb82e9b7088 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 25 Jan 2019 16:26:24 +0100 Subject: [PATCH 1/5] Add constructors for Select --- .../tools/dotc/tastyreflect/TreeOpsImpl.scala | 9 ++++ library/src/scala/tasty/reflect/TreeOps.scala | 3 +- .../reflect-select-constructor/assert_1.scala | 45 +++++++++++++++++++ .../reflect-select-constructor/test_2.scala | 29 ++++++++++++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 tests/run/reflect-select-constructor/assert_1.scala create mode 100644 tests/run/reflect-select-constructor/test_2.scala diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala index 35b4007963e8..0d0da9ee766a 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala @@ -401,6 +401,15 @@ trait TreeOpsImpl extends scala.tasty.reflect.TreeOps with RootPositionImpl with object Select extends SelectModule { + def unique(qualifier: Term, name: String)(implicit ctx: Context): Select = { + val denot = qualifier.tpe.member(name.toTermName) + assert(!denot.isOverloaded, s"The symbol `$name` is overloaded. The method Select.unique can only be used for non-overloaded symbols.") + tpd.Select(qualifier, name.toTermName) + } + + def overloaded(qualifier: Term, name: String, targs: List[Type], args: List[Term])(implicit ctx: Context): Apply = + tpd.applyOverloaded(qualifier, name.toTermName, args, targs, Types.WildcardType).asInstanceOf[Apply] + def copy(original: Tree)(qualifier: Term, name: String)(implicit ctx: Context): Select = tpd.cpy.Select(original)(qualifier, name.toTermName) diff --git a/library/src/scala/tasty/reflect/TreeOps.scala b/library/src/scala/tasty/reflect/TreeOps.scala index 2b7930902dc7..4a93e8e9d307 100644 --- a/library/src/scala/tasty/reflect/TreeOps.scala +++ b/library/src/scala/tasty/reflect/TreeOps.scala @@ -272,8 +272,9 @@ trait TreeOps extends Core { /** Scala term selection */ val Select: SelectModule abstract class SelectModule { + def unique(qualifier: Term, name: String)(implicit ctx: Context): Select - // TODO def apply(qualifier: Term, name: String, signature: Option[Signature])(implicit ctx: Context): Select + def overloaded(qualifier: Term, name: String, targs: List[Type], args: List[Term])(implicit ctx: Context): Apply def copy(original: Tree)(qualifier: Term, name: String)(implicit ctx: Context): Select diff --git a/tests/run/reflect-select-constructor/assert_1.scala b/tests/run/reflect-select-constructor/assert_1.scala new file mode 100644 index 000000000000..230ec9c63170 --- /dev/null +++ b/tests/run/reflect-select-constructor/assert_1.scala @@ -0,0 +1,45 @@ +import scala.quoted._ +import scala.tasty._ + +object scalatest { + + inline def assert(condition: => Boolean): Unit = ~assertImpl('(condition), '("")) + + def assertImpl(cond: Expr[Boolean], clue: Expr[Any])(implicit refl: Reflection): Expr[Unit] = { + import refl._ + import util._ + import quoted.Toolbox.Default._ + + def isImplicitMethodType(tp: Type): Boolean = + Type.IsMethodType.unapply(tp).flatMap(tp => if tp.isImplicit then Some(true) else None).nonEmpty + + cond.unseal.underlyingArgument match { + case Term.Apply(Term.Select(lhs, op), rhs :: Nil) => + let(lhs) { left => + let(rhs) { right => + let(Term.Select.overloaded(left, op, Nil, right :: Nil)) { result => + val l = left.seal[Any] + val r = right.seal[Any] + val b = result.seal[Boolean] + val code = '{ scala.Predef.assert(~b) } + code.unseal + } + } + }.seal[Unit] + case Term.Apply(f @ Term.Apply(Term.Select(Term.Apply(qual, lhs :: Nil), op), rhs :: Nil), implicits) + if isImplicitMethodType(f.tpe) => + let(lhs) { left => + let(rhs) { right => + let(Term.Apply(Term.Select.overloaded(Term.Apply(qual, left :: Nil), op, Nil, right :: Nil), implicits)) { result => + val l = left.seal[Any] + val r = right.seal[Any] + val b = result.seal[Boolean] + val code = '{ scala.Predef.assert(~b) } + code.unseal + } + } + }.seal[Unit] + } + } + +} diff --git a/tests/run/reflect-select-constructor/test_2.scala b/tests/run/reflect-select-constructor/test_2.scala new file mode 100644 index 000000000000..1525360a71f7 --- /dev/null +++ b/tests/run/reflect-select-constructor/test_2.scala @@ -0,0 +1,29 @@ +object Test { + import scalatest._ + + case class Box[T](v: T) { + def >(that: Box[T]): Boolean = this == that + } + + trait EqInt + implicit val eq: EqInt = new EqInt {} + + implicit class AnyOps[T](x: T) { + def === (y: T)(implicit c: EqInt) = x == y + } + + def main(args: Array[String]): Unit = { + val a = Box(Some(10)) + val five: Float = 5.0f + val six: Double = 6.0 + val ten: Int = 10 + assert(a.v === Some(10)) + assert(five < six) + assert(five > 4) + assert(ten > 5) + assert(six < 7) + assert(six > 5L) + assert(Box(6) > Box(6)) + assert(Box("h") > Box("h")) + } +} From bf6a1b7821ab6a8e45b3684a1520bbe26324a145 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 8 Feb 2019 16:27:17 +0100 Subject: [PATCH 2/5] Set positions for constructed trees manually --- .../dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala | 1 + .../scala/tasty/reflect/utils/TreeUtils.scala | 9 ++++++--- library/src/scala/tasty/reflect/TreeOps.scala | 7 +++++++ .../reflect-select-constructor/assert_1.scala | 10 +++++++--- .../reflect-select-constructor/test_2.scala | 0 5 files changed, 21 insertions(+), 6 deletions(-) rename tests/{run => run-with-compiler}/reflect-select-constructor/assert_1.scala (76%) rename tests/{run => run-with-compiler}/reflect-select-constructor/test_2.scala (100%) diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala index 0d0da9ee766a..d14c750f2ec9 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala @@ -12,6 +12,7 @@ trait TreeOpsImpl extends scala.tasty.reflect.TreeOps with RootPositionImpl with def TreeDeco(tree: Tree): TreeAPI = new TreeAPI { def pos(implicit ctx: Context): Position = tree.sourcePos def symbol(implicit ctx: Context): Symbol = tree.symbol + def withPos(pos: Position)(implicit ctx: Context): Unit = tree.withSpan(pos.span) } def PackageClauseDeco(pack: PackageClause): PackageClauseAPI = new PackageClauseAPI { diff --git a/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala b/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala index a6a2a31f0d6c..125e1db5b516 100644 --- a/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala +++ b/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala @@ -16,13 +16,16 @@ trait TreeUtils { implicit val bodyTpe: quoted.Type[U] = bodyType.seal.asInstanceOf[quoted.Type[U]] implicit val rhsTpe: quoted.Type[T] = rhs.tpe.seal.asInstanceOf[quoted.Type[T]] val rhsExpr = rhs.seal[T] - let[T, U](rhsExpr)(x => body(x.unseal.asInstanceOf[Term.Ident]).seal[U]).unseal + + let[T, U](rhsExpr) { x => + val id = ('(x)).unseal.asInstanceOf[Term.Ident] + id.withPos(rhs.pos) + body(id).seal[U] + }.unseal } - /** */ private def let[T: quoted.Type, U: quoted.Type](rhs: Expr[T])(in: Expr[T] => Expr[U]): Expr[U] = '{ val x = ~rhs ~in('(x)) } - } diff --git a/library/src/scala/tasty/reflect/TreeOps.scala b/library/src/scala/tasty/reflect/TreeOps.scala index 4a93e8e9d307..086e90364590 100644 --- a/library/src/scala/tasty/reflect/TreeOps.scala +++ b/library/src/scala/tasty/reflect/TreeOps.scala @@ -45,6 +45,13 @@ trait TreeOps extends Core { /** Position in the source code */ def pos(implicit ctx: Context): Position + /** Set the position of the tree + * + * @note It only changes positions, not source file of the tree. Source + * file of trees are derived from context and cannot be changed. + */ + def withPos(pos: Position)(implicit ctx: Context): Unit + def symbol(implicit ctx: Context): Symbol } diff --git a/tests/run/reflect-select-constructor/assert_1.scala b/tests/run-with-compiler/reflect-select-constructor/assert_1.scala similarity index 76% rename from tests/run/reflect-select-constructor/assert_1.scala rename to tests/run-with-compiler/reflect-select-constructor/assert_1.scala index 230ec9c63170..fc98da41a077 100644 --- a/tests/run/reflect-select-constructor/assert_1.scala +++ b/tests/run-with-compiler/reflect-select-constructor/assert_1.scala @@ -14,10 +14,12 @@ object scalatest { Type.IsMethodType.unapply(tp).flatMap(tp => if tp.isImplicit then Some(true) else None).nonEmpty cond.unseal.underlyingArgument match { - case Term.Apply(Term.Select(lhs, op), rhs :: Nil) => + case t @ Term.Apply(Term.Select(lhs, op), rhs :: Nil) => let(lhs) { left => let(rhs) { right => - let(Term.Select.overloaded(left, op, Nil, right :: Nil)) { result => + val app = Term.Select.overloaded(left, op, Nil, right :: Nil) + app.withPos(t.pos) + let(app) { result => val l = left.seal[Any] val r = right.seal[Any] val b = result.seal[Boolean] @@ -30,7 +32,9 @@ object scalatest { if isImplicitMethodType(f.tpe) => let(lhs) { left => let(rhs) { right => - let(Term.Apply(Term.Select.overloaded(Term.Apply(qual, left :: Nil), op, Nil, right :: Nil), implicits)) { result => + val app = Term.Select.overloaded(Term.Apply(qual, left :: Nil), op, Nil, right :: Nil) + app.withPos(f.pos) + let(Term.Apply(app, implicits)) { result => val l = left.seal[Any] val r = right.seal[Any] val b = result.seal[Boolean] diff --git a/tests/run/reflect-select-constructor/test_2.scala b/tests/run-with-compiler/reflect-select-constructor/test_2.scala similarity index 100% rename from tests/run/reflect-select-constructor/test_2.scala rename to tests/run-with-compiler/reflect-select-constructor/test_2.scala From 7e433cf08b322c80c834d8bbaceed9f518796714 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 14 Feb 2019 16:15:38 +0100 Subject: [PATCH 3/5] Fix rebase errors --- .../scala/tasty/reflect/utils/TreeUtils.scala | 24 +++++++------------ .../reflect-select-copy/assert_1.scala | 12 +++++----- .../tasty-unsafe-let/quoted_1.scala | 2 +- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala b/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala index 125e1db5b516..ccc2bf6a3a82 100644 --- a/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala +++ b/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala @@ -9,23 +9,17 @@ trait TreeUtils { import reflect._ /** Bind the `rhs` to a `val` and use it in `body` */ - def let(rhs: Term)(bodyType: Type)(body: Term.Ident => Term): Term = { - // Recover all lost type information + def let(rhs: Term)(body: Term.Ident => Term): Term = { type T // TODO probably it is better to use the Sealed contruct rather than let the user create their own existential type - type U // TODO probably it is better to use the Sealed contruct rather than let the user create their own existential type - implicit val bodyTpe: quoted.Type[U] = bodyType.seal.asInstanceOf[quoted.Type[U]] implicit val rhsTpe: quoted.Type[T] = rhs.tpe.seal.asInstanceOf[quoted.Type[T]] val rhsExpr = rhs.seal[T] - - let[T, U](rhsExpr) { x => - val id = ('(x)).unseal.asInstanceOf[Term.Ident] - id.withPos(rhs.pos) - body(id).seal[U] - }.unseal - } - - private def let[T: quoted.Type, U: quoted.Type](rhs: Expr[T])(in: Expr[T] => Expr[U]): Expr[U] = '{ - val x = ~rhs - ~in('(x)) + val expr = '{ + val x = ~rhsExpr + ~{ + val id = ('(x)).unseal.asInstanceOf[Term.Ident] + body(id).seal[Any] + } + } + expr.unseal } } diff --git a/tests/run-with-compiler/reflect-select-copy/assert_1.scala b/tests/run-with-compiler/reflect-select-copy/assert_1.scala index 7b0f7ccaa6fd..3537676ff1e2 100644 --- a/tests/run-with-compiler/reflect-select-copy/assert_1.scala +++ b/tests/run-with-compiler/reflect-select-copy/assert_1.scala @@ -15,9 +15,9 @@ object scalatest { cond.unseal.underlyingArgument match { case Term.Apply(sel @ Term.Select(lhs, op), rhs :: Nil) => - let(lhs)(definitions.UnitType) { left => - let(rhs)(definitions.UnitType) { right => - let(Term.Apply(Term.Select.copy(sel)(left, op), right :: Nil))(definitions.UnitType) { result => + let(lhs) { left => + let(rhs) { right => + let(Term.Apply(Term.Select.copy(sel)(left, op), right :: Nil)) { result => val l = left.seal[Any] val r = right.seal[Any] val b = result.seal[Boolean] @@ -28,9 +28,9 @@ object scalatest { }.seal[Unit] case Term.Apply(f @ Term.Apply(Term.IsSelect(sel @ Term.Select(Term.Apply(qual, lhs :: Nil), op)), rhs :: Nil), implicits) if isImplicitMethodType(f.tpe) => - let(lhs)(definitions.UnitType) { left => - let(rhs)(definitions.UnitType) { right => - let(Term.Apply(Term.Apply(Term.Select.copy(sel)(Term.Apply(qual, left :: Nil), op), right :: Nil), implicits))(definitions.UnitType) { result => + let(lhs) { left => + let(rhs) { right => + let(Term.Apply(Term.Apply(Term.Select.copy(sel)(Term.Apply(qual, left :: Nil), op), right :: Nil), implicits)) { result => val l = left.seal[Any] val r = right.seal[Any] val b = result.seal[Boolean] diff --git a/tests/run-with-compiler/tasty-unsafe-let/quoted_1.scala b/tests/run-with-compiler/tasty-unsafe-let/quoted_1.scala index 37806ce1851a..793cb85c9e47 100644 --- a/tests/run-with-compiler/tasty-unsafe-let/quoted_1.scala +++ b/tests/run-with-compiler/tasty-unsafe-let/quoted_1.scala @@ -13,7 +13,7 @@ object Macros { val rhsTerm = rhs.unseal import reflect.util.{let => letTerm} - letTerm(rhsTerm)(('[Unit]).unseal.tpe) { rhsId => + letTerm(rhsTerm) { rhsId => body(rhsId.seal[Any].asInstanceOf[Expr[T]]).unseal // Dangerous uncheked cast! }.seal[Unit] } From 29d4ccb3a87d7e9a7933e3ca8f5fe55faf9585f7 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 18 Feb 2019 11:10:24 +0100 Subject: [PATCH 4/5] Rebase to set default pos in constructors --- .../src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala | 5 ++--- library/src/scala/tasty/reflect/TreeOps.scala | 7 ------- .../reflect-select-constructor/assert_1.scala | 2 -- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala index d14c750f2ec9..beb38c381ac4 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala @@ -12,7 +12,6 @@ trait TreeOpsImpl extends scala.tasty.reflect.TreeOps with RootPositionImpl with def TreeDeco(tree: Tree): TreeAPI = new TreeAPI { def pos(implicit ctx: Context): Position = tree.sourcePos def symbol(implicit ctx: Context): Symbol = tree.symbol - def withPos(pos: Position)(implicit ctx: Context): Unit = tree.withSpan(pos.span) } def PackageClauseDeco(pack: PackageClause): PackageClauseAPI = new PackageClauseAPI { @@ -405,11 +404,11 @@ trait TreeOpsImpl extends scala.tasty.reflect.TreeOps with RootPositionImpl with def unique(qualifier: Term, name: String)(implicit ctx: Context): Select = { val denot = qualifier.tpe.member(name.toTermName) assert(!denot.isOverloaded, s"The symbol `$name` is overloaded. The method Select.unique can only be used for non-overloaded symbols.") - tpd.Select(qualifier, name.toTermName) + withDefaultPos(implicit ctx => tpd.Select(qualifier, name.toTermName)) } def overloaded(qualifier: Term, name: String, targs: List[Type], args: List[Term])(implicit ctx: Context): Apply = - tpd.applyOverloaded(qualifier, name.toTermName, args, targs, Types.WildcardType).asInstanceOf[Apply] + withDefaultPos(implicit ctx => tpd.applyOverloaded(qualifier, name.toTermName, args, targs, Types.WildcardType).asInstanceOf[Apply]) def copy(original: Tree)(qualifier: Term, name: String)(implicit ctx: Context): Select = tpd.cpy.Select(original)(qualifier, name.toTermName) diff --git a/library/src/scala/tasty/reflect/TreeOps.scala b/library/src/scala/tasty/reflect/TreeOps.scala index 086e90364590..4a93e8e9d307 100644 --- a/library/src/scala/tasty/reflect/TreeOps.scala +++ b/library/src/scala/tasty/reflect/TreeOps.scala @@ -45,13 +45,6 @@ trait TreeOps extends Core { /** Position in the source code */ def pos(implicit ctx: Context): Position - /** Set the position of the tree - * - * @note It only changes positions, not source file of the tree. Source - * file of trees are derived from context and cannot be changed. - */ - def withPos(pos: Position)(implicit ctx: Context): Unit - def symbol(implicit ctx: Context): Symbol } diff --git a/tests/run-with-compiler/reflect-select-constructor/assert_1.scala b/tests/run-with-compiler/reflect-select-constructor/assert_1.scala index fc98da41a077..15416a9a544d 100644 --- a/tests/run-with-compiler/reflect-select-constructor/assert_1.scala +++ b/tests/run-with-compiler/reflect-select-constructor/assert_1.scala @@ -18,7 +18,6 @@ object scalatest { let(lhs) { left => let(rhs) { right => val app = Term.Select.overloaded(left, op, Nil, right :: Nil) - app.withPos(t.pos) let(app) { result => val l = left.seal[Any] val r = right.seal[Any] @@ -33,7 +32,6 @@ object scalatest { let(lhs) { left => let(rhs) { right => val app = Term.Select.overloaded(Term.Apply(qual, left :: Nil), op, Nil, right :: Nil) - app.withPos(f.pos) let(Term.Apply(app, implicits)) { result => val l = left.seal[Any] val r = right.seal[Any] From 3b9306bd9f5fbab4f8353289bdcae090ceda3b34 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 20 Feb 2019 10:17:07 +0100 Subject: [PATCH 5/5] Add documentation for Select constructors --- library/src/scala/tasty/reflect/TreeOps.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/src/scala/tasty/reflect/TreeOps.scala b/library/src/scala/tasty/reflect/TreeOps.scala index 4a93e8e9d307..7358fb9d64c4 100644 --- a/library/src/scala/tasty/reflect/TreeOps.scala +++ b/library/src/scala/tasty/reflect/TreeOps.scala @@ -272,8 +272,15 @@ trait TreeOps extends Core { /** Scala term selection */ val Select: SelectModule abstract class SelectModule { + /** Select a field or a non-overloaded method by name + * + * @note The method will produce an assertion error if the selected + * method is overloaded. The method `overloaded` should be used + * in that case. + */ def unique(qualifier: Term, name: String)(implicit ctx: Context): Select + /** Call an overloaded method with the given type and term parameters */ def overloaded(qualifier: Term, name: String, targs: List[Type], args: List[Term])(implicit ctx: Context): Apply def copy(original: Tree)(qualifier: Term, name: String)(implicit ctx: Context): Select