diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala index 35b4007963e8..beb38c381ac4 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.") + withDefaultPos(implicit ctx => tpd.Select(qualifier, name.toTermName)) + } + + def overloaded(qualifier: Term, name: String, targs: List[Type], args: List[Term])(implicit ctx: Context): 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-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala b/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala index a6a2a31f0d6c..ccc2bf6a3a82 100644 --- a/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala +++ b/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala @@ -9,20 +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 => body(x.unseal.asInstanceOf[Term.Ident]).seal[U]).unseal + val expr = '{ + val x = ~rhsExpr + ~{ + val id = ('(x)).unseal.asInstanceOf[Term.Ident] + body(id).seal[Any] + } + } + expr.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 2b7930902dc7..7358fb9d64c4 100644 --- a/library/src/scala/tasty/reflect/TreeOps.scala +++ b/library/src/scala/tasty/reflect/TreeOps.scala @@ -272,8 +272,16 @@ trait TreeOps extends Core { /** Scala term selection */ val Select: SelectModule abstract class SelectModule { - - // TODO def apply(qualifier: Term, name: String, signature: Option[Signature])(implicit ctx: Context): Select + /** 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 diff --git a/tests/run-with-compiler/reflect-select-constructor/assert_1.scala b/tests/run-with-compiler/reflect-select-constructor/assert_1.scala new file mode 100644 index 000000000000..15416a9a544d --- /dev/null +++ b/tests/run-with-compiler/reflect-select-constructor/assert_1.scala @@ -0,0 +1,47 @@ +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 t @ Term.Apply(Term.Select(lhs, op), rhs :: Nil) => + let(lhs) { left => + let(rhs) { right => + val app = Term.Select.overloaded(left, op, Nil, right :: Nil) + let(app) { 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 => + val app = Term.Select.overloaded(Term.Apply(qual, left :: Nil), op, Nil, right :: Nil) + let(Term.Apply(app, 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-with-compiler/reflect-select-constructor/test_2.scala b/tests/run-with-compiler/reflect-select-constructor/test_2.scala new file mode 100644 index 000000000000..1525360a71f7 --- /dev/null +++ b/tests/run-with-compiler/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")) + } +} 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] }