Skip to content

Change signature of quoted.Type encoding #11608

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
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
13 changes: 9 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -936,10 +936,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
* The result can be the contents of a term or type quote, which
* will return a term or type tree respectively.
*/
def unapply(tree: tpd.Tree)(using Context): Option[tpd.Tree] = tree match {
case tree: GenericApply if tree.symbol.isQuote => Some(tree.args.head)
case _ => None
}
def unapply(tree: tpd.Apply)(using Context): Option[tpd.Tree] =
if tree.symbol == defn.QuotedRuntime_exprQuote then
// quoted.runtime.Expr.quote[T](<body>)
Some(tree.args.head)
else if tree.symbol == defn.QuotedTypeModule_of then
// quoted.Type.of[<body>](quotes)
val TypeApply(_, body :: _) = tree.fun
Some(body)
else None
}

/** Extractors for splices */
Expand Down
35 changes: 21 additions & 14 deletions compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import scala.annotation.constructorOnly
* Local type references can be used at the level of their definition or lower. If used used at a higher level,
* it will be healed if possible, otherwise it is inconsistent.
*
* Type healing consists in transforming a phase inconsistent type `T` into a splice of `${summon[Type[T]]}`.
* Type healing consists in transforming a phase inconsistent type `T` into `summon[Type[T]].Underlying`.
*
* As references to types do not necessarily have an associated tree it is not always possible to replace the types directly.
* Instead we always generate a type alias for it and place it at the start of the surrounding quote. This also avoids duplication.
Expand All @@ -48,7 +48,7 @@ import scala.annotation.constructorOnly
* is transformed to
*
* '{
* type t$1 = ${summon[Type[T]]}
* type t$1 = summon[Type[T]].Underlying
* val x: List[t$1] = List[t$1]();
* ()
* }
Expand Down Expand Up @@ -103,7 +103,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
}

/** Transform quoted trees while maintaining phase correctness */
override protected def transformQuotation(body: Tree, quote: Tree)(using Context): Tree = {
override protected def transformQuotation(body: Tree, quote: Apply)(using Context): Tree = {
val taggedTypes = new PCPCheckAndHeal.QuoteTypeTags(quote.span)

if (ctx.property(InAnnotation).isDefined)
Expand All @@ -118,13 +118,20 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
case Nil => body1
case tags => tpd.Block(tags, body1).withSpan(body.span)

quote match {
case Apply(fn1 @ TypeApply(fn0, targs), _) =>
val targs1 = targs.map(targ => TypeTree(healTypeOfTerm(fn1.srcPos)(targ.tpe)))
cpy.Apply(quote)(cpy.TypeApply(fn1)(fn0, targs1), body2 :: Nil)
case quote: TypeApply =>
cpy.TypeApply(quote)(quote.fun, body2 :: Nil)
}
if body.isTerm then
// `quoted.runtime.Expr.quote[T](<body>)` --> `quoted.runtime.Expr.quote[T2](<body2>)`
val TypeApply(fun, targs) = quote.fun
val targs2 = targs.map(targ => TypeTree(healTypeOfTerm(quote.fun.srcPos)(targ.tpe)))
cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, targs2), body2 :: Nil)
else
body.tpe match
case tp @ TypeRef(x: TermRef, _) if tp.symbol == defn.QuotedType_splice =>
// Optimization: `quoted.Type.of[x.Underlying](quotes)` --> `x`
ref(x)
Copy link
Contributor

Choose a reason for hiding this comment

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

Here the body is not inspected. Is it possible that the body contains trees other than quoted.Type.of[x.Underlying](quotes)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. This tree is generated on the QuotesAndSpliced typing class and its shape is kept as is. If We get something else it would be considered a bug and it would probably break more than this.

case _ =>
// `quoted.Type.of[<body>](quotes)` --> `quoted.Type.of[<body2>](quotes)`
val TypeApply(fun, _) = quote.fun
cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, body2 :: Nil), quote.args)
}

/** Transform splice
Expand All @@ -136,13 +143,13 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
val body1 = transform(body)(using spliceContext)
splice.fun match {
case fun @ TypeApply(_, _ :: Nil) =>
// Type of the splice itsel must also be healed
// internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
// Type of the splice itself must also be healed
// `quoted.runtime.Expr.quote[F[T]](... T ...)` --> `internal.Quoted.expr[F[$t]](... T ...)`
val tp = healType(splice.srcPos)(splice.tpe.widenTermRefExpr)
cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), body1 :: Nil)
case f @ Apply(fun @ TypeApply(_, _), qctx :: Nil) =>
// Type of the splice itsel must also be healed
// internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
// Type of the splice itself must also be healed
// `quoted.runtime.Expr.quote[F[T]](... T ...)` --> `internal.Quoted.expr[F[$t]](... T ...)`
val tp = healType(splice.srcPos)(splice.tpe.widenTermRefExpr)
cpy.Apply(splice)(cpy.Apply(f)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), qctx :: Nil), body1 :: Nil)
}
Expand Down
26 changes: 11 additions & 15 deletions compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class PickleQuotes extends MacroTransform {
* `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with
* core and splices as arguments.
*/
override protected def transformQuotation(body: Tree, quote: Tree)(using Context): Tree = {
override protected def transformQuotation(body: Tree, quote: Apply)(using Context): Tree = {
val isType = quote.symbol eq defn.QuotedTypeModule_of
if (level > 0) {
val body1 = nested(isQuote = true).transform(body)(using quoteContext)
Expand All @@ -139,14 +139,14 @@ class PickleQuotes extends MacroTransform {
val body2 =
if (body1.isType) body1
else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body1)
pickledQuote(body2, splices, body.tpe, isType).withSpan(quote.span)
pickledQuote(quote, body2, splices, body.tpe, isType).withSpan(quote.span)
}
else
body
}
}

private def pickledQuote(body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(using Context) = {
private def pickledQuote(quote: Apply, body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(using Context) = {
/** Encode quote using Reflection.Literal
*
* Generate the code
Expand Down Expand Up @@ -279,7 +279,7 @@ class PickleQuotes extends MacroTransform {
*
* Generate the code
* ```scala
* qctx => qctx.reflect.TypeReprMethods.asType(
* qctx.reflect.TypeReprMethods.asType(
* qctx.reflect.TypeRepr.typeConstructorOf(classOf[<type>]])
* ).asInstanceOf[scala.quoted.Type[<type>]]
* ```
Expand All @@ -288,17 +288,13 @@ class PickleQuotes extends MacroTransform {
def taggedType() =
val typeType = defn.QuotedTypeClass.typeRef.appliedTo(body.tpe)
val classTree = TypeApply(ref(defn.Predef_classOf.termRef), body :: Nil)
val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, typeType)
def callTypeConstructorOf(ts: List[Tree]) = {
val reflect = ts.head.select("reflect".toTermName)
val typeRepr = reflect.select("TypeRepr".toTermName).select("typeConstructorOf".toTermName).appliedTo(classTree)
reflect.select("TypeReprMethods".toTermName).select("asType".toTermName).appliedTo(typeRepr).asInstance(typeType)
}
Lambda(lambdaTpe, callTypeConstructorOf).withSpan(body.span)
val reflect = quote.args.head.select("reflect".toTermName)
val typeRepr = reflect.select("TypeRepr".toTermName).select("typeConstructorOf".toTermName).appliedTo(classTree)
reflect.select("TypeReprMethods".toTermName).select("asType".toTermName).appliedTo(typeRepr).asInstance(typeType)

if (isType) {
if (splices.isEmpty && body.symbol.isPrimitiveValueClass) taggedType()
else pickleAsTasty()
else pickleAsTasty().select(nme.apply).appliedTo(quote.args.head) // TODO do not create lambda
}
else getLiteral(body) match {
case Some(lit) => pickleAsValue(lit)
Expand Down Expand Up @@ -485,9 +481,9 @@ class PickleQuotes extends MacroTransform {
transform(tree)(using ctx.withSource(tree.source))
else reporting.trace(i"Reifier.transform $tree at $level", show = true) {
tree match {
case Apply(Select(TypeApply(fn, (body: RefTree) :: Nil), _), _) if fn.symbol == defn.QuotedTypeModule_of && isCaptured(body.symbol, level + 1) =>
// Optimization: avoid the full conversion when capturing `x`
// in '{ x } to '{ ${x$1} } and go directly to `x$1`
case Apply(TypeApply(fn, (body: RefTree) :: Nil), _) if fn.symbol == defn.QuotedTypeModule_of && isCaptured(body.symbol, level + 1) =>
// Optimization: avoid the full conversion when capturing `X` with `x$1: Type[X$1]`
// in `Type.of[X]` to `Type.of[x$1.Underlying]` and go directly to `X$1`
capturers(body.symbol)(body)
case tree: RefTree if isCaptured(tree.symbol, level) =>
val body = capturers(tree.symbol).apply(tree)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ object Splicer {
case Apply(Select(Apply(fn, quoted :: Nil), nme.apply), _) if fn.symbol == defn.QuotedRuntime_exprQuote =>
// OK

case Apply(Select(TypeApply(fn, List(quoted)), nme.apply), _)if fn.symbol == defn.QuotedTypeModule_of =>
case Apply(TypeApply(fn, List(quoted)), _)if fn.symbol == defn.QuotedTypeModule_of =>
// OK

case Literal(Constant(value)) =>
Expand Down Expand Up @@ -233,7 +233,7 @@ object Splicer {
}
interpretQuote(quoted1)

case Apply(Select(TypeApply(fn, quoted :: Nil), _), _) if fn.symbol == defn.QuotedTypeModule_of =>
case Apply(TypeApply(fn, quoted :: Nil), _) if fn.symbol == defn.QuotedTypeModule_of =>
interpretTypeQuote(quoted)

case Literal(Constant(value)) =>
Expand Down
27 changes: 17 additions & 10 deletions compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,17 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap
case _ =>
}

/** Transform the quote `quote` which contains the quoted `body`. */
protected def transformQuotation(body: Tree, quote: Tree)(using Context): Tree =
quote match {
case quote: Apply => cpy.Apply(quote)(quote.fun, body :: Nil)
case quote: TypeApply => cpy.TypeApply(quote)(quote.fun, body :: Nil)
}
/** Transform the quote `quote` which contains the quoted `body`.
*
* - `quoted.runtime.Expr.quote[T](<body0>)` --> `quoted.runtime.Expr.quote[T](<body>)`
* - `quoted.Type.of[<body0>](quotes)` --> `quoted.Type.of[<body>](quotes)`
*/
protected def transformQuotation(body: Tree, quote: Apply)(using Context): Tree =
if body.isTerm then
cpy.Apply(quote)(quote.fun, body :: Nil)
else
val TypeApply(fun, _) = quote.fun
cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, body :: Nil), quote.args)

/** Transform the expression splice `splice` which contains the spliced `body`. */
protected def transformSplice(body: Tree, splice: Apply)(using Context): Tree
Expand Down Expand Up @@ -98,17 +103,17 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap
case Apply(Select(Quoted(quotedTree), _), _) if quotedTree.isType =>
dropEmptyBlocks(quotedTree) match
case SplicedType(t) =>
// '[ x.$splice ] --> x
// Optimization: `quoted.Type.of[x.Underlying]` --> `x`
transform(t)
case _ =>
super.transform(tree)

case Quoted(quotedTree) =>
case tree @ Quoted(quotedTree) =>
val old = inQuoteOrSplice
inQuoteOrSplice = true
try dropEmptyBlocks(quotedTree) match {
case Spliced(t) =>
// '{ $x } --> x
// Optimization: `'{ $x }` --> `x`
// and adapt the refinement of `Quotes { type reflect: ... } ?=> Expr[T]`
transform(t).asInstance(tree.tpe)
case _ => transformQuotation(quotedTree, tree)
Expand All @@ -119,7 +124,9 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap
val old = inQuoteOrSplice
inQuoteOrSplice = true
try dropEmptyBlocks(splicedTree) match {
case Quoted(t) => transform(t) // ${ 'x } --> x
case Quoted(t) =>
// Optimization: `${ 'x }` --> `x`
transform(t)
case _ => transformSplice(splicedTree, tree)
}
finally inQuoteOrSplice = old
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ trait QuotesAndSplices {
val quoteClass = if (tree.quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass
val quotedPattern =
if (tree.quoted.isTerm) ref(defn.QuotedRuntime_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx)
else ref(defn.QuotedTypeModule_of.termRef).appliedToTypeTree(shape).select(nme.apply).appliedTo(qctx)
else ref(defn.QuotedTypeModule_of.termRef).appliedToTypeTree(shape).appliedTo(qctx)

val matchModule = if tree.quoted.isTerm then defn.QuoteMatching_ExprMatch else defn.QuoteMatching_TypeMatch
val unapplyFun = qctx.asInstance(defn.QuoteMatchingClass.typeRef).select(matchModule).select(nme.unapply)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ object Type:

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

end Type
22 changes: 22 additions & 0 deletions library/src-non-bootstrapped/scala/quoted/Type.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package scala.quoted

import scala.annotation.compileTimeOnly

/** Type (or type constructor) `T` needed contextually when using `T` in a quoted expression `'{... T ...}` */
abstract class Type[T <: AnyKind] private[scala]:
/** The type represented `Type` */
type Underlying = T
throw Exception("non-bootstrapped-lib")
end Type

/** Methods to interact with the current `Type[T]` in scope */
object Type:

/** Show a source code like representation of this type without syntax highlight */
def show[T <: AnyKind](using Type[T])(using Quotes): String = throw Exception("non-bootstrapped-lib")

/** Return a quoted.Type with the given type */
@compileTimeOnly("Reference to `scala.quoted.Type.of` was not handled by PickleQuotes")
given of[T <: AnyKind]: (Quotes ?=> Type[T]) = throw Exception("non-bootstrapped-lib")

end Type
2 changes: 1 addition & 1 deletion tests/run-staging/quote-nested-4.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
((q: scala.quoted.Quotes) ?=> {
val t: scala.quoted.Type[scala.Predef.String] = scala.quoted.Type.of[scala.Predef.String].apply(using q)
val t: scala.quoted.Type[scala.Predef.String] = scala.quoted.Type.of[scala.Predef.String](q)

(t: scala.quoted.Type[scala.Predef.String])
})