Skip to content

Make quoted.Type fully contextual #10207

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
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 7 additions & 4 deletions compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1001,7 +1001,10 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.intern
case _ => None
end TypeTreeTypeTest

object TypeTree extends TypeTreeModule
object TypeTree extends TypeTreeModule:
def of[T <: AnyKind](using tp: scala.quoted.Type[T]): TypeTree =
tp.asInstanceOf[scala.internal.quoted.Type[TypeTree]].typeTree
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that cast needed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More precisely, why is it Type[TypeTree] and not Type[T]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it is the scala.internal.quoted.Type and not scala.quoted.Type. That variant is parametrised with the tree type to make it easier to cast and extract internally.

end TypeTree

object TypeTreeMethodsImpl extends TypeTreeMethods:
extension (self: TypeTree):
Expand Down Expand Up @@ -1568,8 +1571,8 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.intern
type TypeRepr = dotc.core.Types.Type

object TypeRepr extends TypeReprModule:
def of[T <: AnyKind](using qtype: scala.quoted.Type[T]): TypeRepr =
qtype.asInstanceOf[scala.internal.quoted.Type[TypeTree]].typeTree.tpe
def of[T <: AnyKind](using tp: scala.quoted.Type[T]): TypeRepr =
tp.asInstanceOf[scala.internal.quoted.Type[TypeTree]].typeTree.tpe
def typeConstructorOf(clazz: Class[?]): TypeRepr =
if (clazz.isPrimitive)
if (clazz == classOf[Boolean]) dotc.core.Symbols.defn.BooleanType
Expand Down Expand Up @@ -2640,7 +2643,7 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.intern
treeMatch(scrutinee.unseal(using this), pattern.unseal(using this))

def typeMatch(scrutinee: scala.quoted.Type[?], pattern: scala.quoted.Type[?]): Option[Tuple] =
treeMatch(scrutinee.unseal(using this), pattern.unseal(using this))
treeMatch(reflect.TypeTree.of(using scrutinee), reflect.TypeTree.of(using pattern))

private def treeMatch(scrutinee: reflect.Tree, pattern: reflect.Tree): Option[Tuple] = {
import reflect._
Expand Down
6 changes: 3 additions & 3 deletions library/src-bootstrapped/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ abstract class Expr[+T] private[scala] {

/** Checks is the `quoted.Expr[?]` is valid expression of type `X` */
def isExprOf[X](using tp: scala.quoted.Type[X])(using qctx: QuoteContext): Boolean =
this.unseal.tpe <:< tp.unseal.tpe
this.unseal.tpe <:< qctx.reflect.TypeRepr.of[X]

/** Convert this to an `quoted.Expr[X]` if this expression is a valid expression of type `X` or throws */
def asExprOf[X](using tp: scala.quoted.Type[X])(using qctx: QuoteContext): scala.quoted.Expr[X] = {
Expand All @@ -35,7 +35,7 @@ abstract class Expr[+T] private[scala] {
throw Exception(
s"""Expr cast exception: ${this.show}
|of type: ${this.unseal.tpe.show}
|did not conform to type: ${tp.unseal.tpe.show}
|did not conform to type: ${qctx.reflect.TypeRepr.of[X].show}
|""".stripMargin
)
}
Expand Down Expand Up @@ -189,7 +189,7 @@ object Expr {
*/
def summon[T](using tpe: Type[T])(using qctx: QuoteContext): Option[Expr[T]] = {
import qctx.reflect._
Implicits.search(tpe.unseal.tpe) match {
Implicits.search(TypeRepr.of[T]) match {
case iss: ImplicitSearchSuccess => Some(iss.tree.seal.asInstanceOf[Expr[T]])
case isf: ImplicitSearchFailure => None
}
Expand Down
2 changes: 1 addition & 1 deletion library/src-bootstrapped/scala/quoted/ExprMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ trait ExprMap:
trees mapConserve (transformTypeCaseDef(_))

}
new MapChildren().transformTermChildren(e.unseal, tpe.unseal.tpe).asExprOf[T]
new MapChildren().transformTermChildren(e.unseal, TypeRepr.of[T]).asExprOf[T]
}

end ExprMap
23 changes: 10 additions & 13 deletions library/src-bootstrapped/scala/quoted/Type.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,24 @@ package scala.quoted
import scala.annotation.compileTimeOnly

/** Quoted type (or kind) `T` */
abstract class Type[T <: AnyKind] private[scala] {

abstract class Type[T <: AnyKind] private[scala]:
/** The type represented `Type` */
type Underlying = T
end Type

/** Some basic type tags, currently incomplete */
object Type:

/** Show a source code like representation of this type without syntax highlight */
def show(using qctx: QuoteContext): String = this.unseal.show
def show[T](using tp: Type[T])(using qctx: QuoteContext): String =
qctx.reflect.TypeTree.of[T].show

/** Shows the tree as fully typed source code colored with ANSI */
def showAnsiColored(using qctx: QuoteContext): String = this.unseal.showAnsiColored

/** View this expression `quoted.Type[T]` as a `TypeTree` */
def unseal(using qctx: QuoteContext): qctx.reflect.TypeTree

}

/** Some basic type tags, currently incomplete */
object Type {
def showAnsiColored[T](using tp: Type[T])(using qctx: QuoteContext): String =
qctx.reflect.TypeTree.of[T].showAnsiColored

/** Return a quoted.Type with the given type */
@compileTimeOnly("Reference to `scala.quoted.Type.apply` was not handled by ReifyQuotes")
given apply[T <: AnyKind] as (QuoteContext ?=> Type[T]) = ???

}
end Type
2 changes: 1 addition & 1 deletion library/src-bootstrapped/scala/quoted/Varargs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object Varargs {
*/
def apply[T](xs: Seq[Expr[T]])(using tp: Type[T], qctx: QuoteContext): Expr[Seq[T]] = {
import qctx.reflect._
Repeated(xs.map[Term](_.unseal).toList, tp.unseal).seal.asInstanceOf[Expr[Seq[T]]]
Repeated(xs.map[Term](_.unseal).toList, TypeTree.of[T]).seal.asInstanceOf[Expr[Seq[T]]]
}

/** Matches a literal sequence of expressions and return a sequence of expressions.
Expand Down
2 changes: 0 additions & 2 deletions library/src-non-bootstrapped/scala/quoted/Type.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import scala.annotation.compileTimeOnly
abstract class Type[T <: AnyKind] private[scala]:
type Underlying = T

def unseal(using qctx: QuoteContext): qctx.reflect.TypeTree
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the tests, it seems this method is more convenient and shorter than the proposed alternative. Conceptually, it also helps reinforce the boundary between the safer quoted world and the unsafe reflect world. In addition, the method is also symmetric to the Expr[T] variant, which is good for usability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that the Type API should be symmetric with the Type/TypeRepr/TypeTree. As Type does not share the level symmetry of Expr, Type should not try to have an API that reflects the level symmetry as Expr does. Usability wise, this symmetry between the APIs has been the source of confusion of what and how Type works.

The most common use case is to inspect the type for which Type[A].unseal.tpe becomes TypeRepr.of[T] which is shorter and much clearer as it states exactly what we want to get for this T.

There are some tests that used to give a name to the Type[T] and then call t.unseal.tpe which was slightly shorter at the cost of a summon[Type[T]] or using [T](using t: Type[T]) instead of [T: Type] which made code larger before the .unseal call.

Another classical mistake is to start with [T](...)(t: Type[T]) and call t.unseal.tpe and then realize that somewhere else it is needed implicitly and modify the signature to [T: Type](....)(t: Type[T]).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see in #10232 Type.seal is changed to asType, which is consistent with the asymmetry you mentioned above. I think it's fine as long as the whole design is consistent.

One ergonomics issue (might just be personal preference) is that I find dot-style API (tp.unseal.tpe) is more friendly than bureaucratic-style API (TypeRepr.of(tp)). However, I do see the consistency of the overall APIs is more important. So feel free to choose the one that makes more sense overall.


object Type:
@compileTimeOnly("Reference to `scala.quoted.Type.apply` was not handled by ReifyQuotes")
given apply[T <: AnyKind] as (QuoteContext ?=> Type[T]) = ???
7 changes: 5 additions & 2 deletions library/src/scala/tasty/Reflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,10 @@ trait Reflection { reflection =>

val TypeTree: TypeTreeModule

trait TypeTreeModule { this: TypeTree.type => }
trait TypeTreeModule { this: TypeTree.type =>
/** Returns the tree of type or kind (TypeTree) of T */
def of[T <: AnyKind](using tp: scala.quoted.Type[T]): TypeTree
}

given TypeTreeMethods as TypeTreeMethods = TypeTreeMethodsImpl
protected val TypeTreeMethodsImpl: TypeTreeMethods
Expand Down Expand Up @@ -1750,7 +1753,7 @@ trait Reflection { reflection =>

trait TypeReprModule { this: TypeRepr.type =>
/** Returns the type or kind (TypeRepr) of T */
def of[T <: AnyKind](using qtype: scala.quoted.Type[T]): TypeRepr
def of[T <: AnyKind](using tp: scala.quoted.Type[T]): TypeRepr

/** Returns the type constructor of the runtime (erased) class */
def typeConstructorOf(clazz: Class[?]): TypeRepr
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/delegate-match-1/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ inline def f: Any = ${ fImpl }

private def fImpl(using qctx: QuoteContext): Expr[Unit] = {
import qctx.reflect._
Implicits.search((Type[A]).unseal.tpe) match {
Implicits.search(TypeRepr.of[A]) match {
case x: ImplicitSearchSuccess =>
'{}
case x: DivergingImplicit => '{}
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/delegate-match-2/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ inline def f: Any = ${ fImpl }

private def fImpl (using qctx: QuoteContext) : Expr[Unit] = {
import qctx.reflect._
Implicits.search((Type[A]).unseal.tpe) match {
Implicits.search(TypeRepr.of[A]) match {
case x: ImplicitSearchSuccess =>
'{}
case x: DivergingImplicit => '{}
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/delegate-match-3/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ inline def f: Any = ${ fImpl }

private def fImpl(using qctx: QuoteContext) : Expr[Unit] = {
import qctx.reflect._
Implicits.search((Type[A]).unseal.tpe) match {
Implicits.search(TypeRepr.of[A]) match {
case x: ImplicitSearchSuccess =>
'{}
case x: DivergingImplicit => '{}
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/i7048e.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ abstract class Test {
{
val t: Test = this
import t.given
println(summon[Type[t.T]].show)
println(Type.show[t.T])
// val r = '{Option.empty[t.T]} // access to value t from wrong staging level
val r2 = '{Option.empty[t.T.Underlying]} // works
}
Expand Down
1 change: 0 additions & 1 deletion tests/neg-macros/i7919.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ object Test {
def staged[T](using qctx: QuoteContext) = {
import qctx.reflect._
given typeT as Type[T] // error
val tTypeTree = typeT.unseal
val tt = TypeRepr.of[T]
'{ "in staged" }
}
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-macros/i7048e.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ abstract class Test {
{
val t: Test = this
import t.given
println(summon[Type[t.T]].show)
println(Type.show[t.T])
// val r = '{Option.empty[t.T]} // access to value t from wrong staging level
val r2 = '{Option.empty[t.T.Underlying]}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-macros/i8521/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object Foo {
packageToName(sym.owner)
}

val sym = implicitly[Type[T]].unseal.symbol
val sym = TypeRepr.of[T].typeSymbol
if (!sym.isNoSymbol) {
sym.tree match {
case c: ClassDef =>
Expand Down
4 changes: 2 additions & 2 deletions tests/pos-macros/i9251/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ object Async {
case Inlined(_,_,Block(_,Apply(TypeApply(Select(q,n),tparams),List(param)))) =>
param.tpe match
case AppliedType(tp,tparams1) =>
val fType = Type[F]
val fType = TypeRepr.of[F]
val ptp = tparams1.tail.head
val ptpTree = Inferred(fType.unseal.tpe.appliedTo(ptp))
val ptpTree = Inferred(fType.appliedTo(ptp))
'{ println(${Expr(ptpTree.show)}) }

}
8 changes: 4 additions & 4 deletions tests/pos-macros/i9518/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ inline def shift : Unit = ${ shiftTerm }
def shiftTerm(using QuoteContext): Expr[Unit] = {
import qctx.reflect._
val nTree = '{ ??? : CB[Int] }.unseal
val tp1 = Type[CB[Int]].unseal.tpe
val tp2 = Type[([X] =>> CB[X])[Int]].unseal.tpe
val tp1 = TypeRepr.of[CB[Int]]
val tp2 = TypeRepr.of[([X] =>> CB[X])[Int]]
val ta = Type[[X] =>> CB[X]]
val tp3 = Type[ta.Underlying[Int]].unseal.tpe
val tp4 = Type[CB].unseal.tpe.appliedTo(TypeRepr.of[Int])
val tp3 = TypeRepr.of(using Type[ta.Underlying[Int]])
val tp4 = TypeRepr.of[CB].appliedTo(TypeRepr.of[Int])
assert(nTree.tpe <:< tp1)
assert(nTree.tpe <:< tp2)
assert(nTree.tpe <:< tp3)
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-macros/i9894/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ object X:
case lt@Lambda(params, body) =>
val paramTypes = params.map(_.tpt.tpe)
val paramNames = params.map(_.name)
val mt = MethodType(paramNames)(_ => paramTypes, _ => Type[CB].unseal.tpe.appliedTo(body.tpe.widen) )
val mt = MethodType(paramNames)(_ => paramTypes, _ => TypeRepr.of[CB].appliedTo(body.tpe.widen) )
val r = Lambda(mt, args => changeArgs(params,args,transform(body)) )
r
case _ =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import scala.quoted._


inline def isFunctionType[T]: Boolean = ${ isFunctionTypeImpl(Type[T]) }
inline def isFunctionType[T]: Boolean = ${ isFunctionTypeImpl[T] }

def isFunctionTypeImpl[T](tp: Type[T])(using qctx: QuoteContext) : Expr[Boolean] = {
def isFunctionTypeImpl[T: Type](using qctx: QuoteContext) : Expr[Boolean] = {
import qctx.reflect._
Expr(tp.unseal.tpe.isFunctionType)
Expr(TypeRepr.of[T].isFunctionType)
}


inline def isContextFunctionType[T]: Boolean = ${ isContextFunctionTypeImpl(Type[T]) }
inline def isContextFunctionType[T]: Boolean = ${ isContextFunctionTypeImpl[T] }

def isContextFunctionTypeImpl[T](tp: Type[T])(using qctx: QuoteContext) : Expr[Boolean] = {
def isContextFunctionTypeImpl[T: Type](using qctx: QuoteContext) : Expr[Boolean] = {
import qctx.reflect._
Expr(tp.unseal.tpe.isContextFunctionType)
Expr(TypeRepr.of[T].isContextFunctionType)
}


inline def isErasedFunctionType[T]: Boolean = ${ isErasedFunctionTypeImpl(Type[T]) }
inline def isErasedFunctionType[T]: Boolean = ${ isErasedFunctionTypeImpl[T] }

def isErasedFunctionTypeImpl[T](tp: Type[T])(using qctx: QuoteContext) : Expr[Boolean] = {
def isErasedFunctionTypeImpl[T: Type](using qctx: QuoteContext) : Expr[Boolean] = {
import qctx.reflect._
Expr(tp.unseal.tpe.isErasedFunctionType)
Expr(TypeRepr.of[T].isErasedFunctionType)
}

inline def isDependentFunctionType[T]: Boolean = ${ isDependentFunctionTypeImpl(Type[T]) }
inline def isDependentFunctionType[T]: Boolean = ${ isDependentFunctionTypeImpl[T] }

def isDependentFunctionTypeImpl[T](tp: Type[T])(using qctx: QuoteContext) : Expr[Boolean] = {
def isDependentFunctionTypeImpl[T: Type](using qctx: QuoteContext) : Expr[Boolean] = {
import qctx.reflect._
Expr(tp.unseal.tpe.isDependentFunctionType)
Expr(TypeRepr.of[T].isDependentFunctionType)
}

6 changes: 3 additions & 3 deletions tests/run-macros/flops-rewrite-3/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,17 @@ class CheckedTransformation(transform: PartialFunction[Expr[Any], Expr[Any]]) ex
def apply[T: Type](e: Expr[T])(using QuoteContext): Expr[T] = {
transform.applyOrElse(e, identity) match {
case '{ $e2: T } => e2
case '{ $e2: $T } =>
case '{ $e2: $T2 } =>
throw new Exception(
s"""Transformed
|${e.show}
|into
|${e2.show}
|
|Expected type to be
|${summon[Type[T]].show}
|${Type.show[T]}
|but was
|${Type[T].show}
|${Type.show[T2]}
""".stripMargin)
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/run-macros/flops-rewrite/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ private class Rewriter(preTransform: Expr[Any] => Expr[Any], postTransform: Expr
|${x.show}
|
|Expected type to be
|${summon[Type[T]].show}
|${Type.show[T]}
|but was
|${Type[t].show}
|${Type.show[t]}
""".stripMargin)
}
}
Expand Down
Loading