From 9f1f1ef0041187b08982fe284874e6ee258911f4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 4 Mar 2021 20:17:50 +0100 Subject: [PATCH] Change signature of quoted.Type encoding --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 13 ++++--- .../dotc/transform/PCPCheckAndHeal.scala | 35 +++++++++++-------- .../tools/dotc/transform/PickleQuotes.scala | 26 ++++++-------- .../dotty/tools/dotc/transform/Splicer.scala | 4 +-- .../dotc/transform/TreeMapWithStages.scala | 27 ++++++++------ .../tools/dotc/typer/QuotesAndSplices.scala | 2 +- .../scala/quoted/Type.scala | 2 +- .../scala/quoted/Type.scala | 22 ++++++++++++ tests/run-staging/quote-nested-4.check | 2 +- 9 files changed, 85 insertions(+), 48 deletions(-) rename library/{src => src-bootstrapped}/scala/quoted/Type.scala (93%) create mode 100644 library/src-non-bootstrapped/scala/quoted/Type.scala diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 08dc3dbb30dd..eb28c78fd746 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -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]() + Some(tree.args.head) + else if tree.symbol == defn.QuotedTypeModule_of then + // quoted.Type.of[](quotes) + val TypeApply(_, body :: _) = tree.fun + Some(body) + else None } /** Extractors for splices */ diff --git a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala index 34e2b444bace..626455da63d1 100644 --- a/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala +++ b/compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala @@ -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. @@ -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](); * () * } @@ -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) @@ -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]()` --> `quoted.runtime.Expr.quote[T2]()` + 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) + case _ => + // `quoted.Type.of[](quotes)` --> `quoted.Type.of[](quotes)` + val TypeApply(fun, _) = quote.fun + cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, body2 :: Nil), quote.args) } /** Transform splice @@ -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) } diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 8f72cb204487..a0c2385e7866 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -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) @@ -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 @@ -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[]]) * ).asInstanceOf[scala.quoted.Type[]] * ``` @@ -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) @@ -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) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 4acc0e5e38ed..28744e4a9cf0 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -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)) => @@ -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)) => diff --git a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala index c23e90352d10..c8019bf5deb8 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala @@ -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]()` --> `quoted.runtime.Expr.quote[T]()` + * - `quoted.Type.of[](quotes)` --> `quoted.Type.of[](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 @@ -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) @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 0240dad48e82..18c08bb8a3ad 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -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) diff --git a/library/src/scala/quoted/Type.scala b/library/src-bootstrapped/scala/quoted/Type.scala similarity index 93% rename from library/src/scala/quoted/Type.scala rename to library/src-bootstrapped/scala/quoted/Type.scala index 58b4cd63b827..1269371cbd0e 100644 --- a/library/src/scala/quoted/Type.scala +++ b/library/src-bootstrapped/scala/quoted/Type.scala @@ -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 diff --git a/library/src-non-bootstrapped/scala/quoted/Type.scala b/library/src-non-bootstrapped/scala/quoted/Type.scala new file mode 100644 index 000000000000..721843a48d90 --- /dev/null +++ b/library/src-non-bootstrapped/scala/quoted/Type.scala @@ -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 diff --git a/tests/run-staging/quote-nested-4.check b/tests/run-staging/quote-nested-4.check index 2e326526d898..895bd0ddc914 100644 --- a/tests/run-staging/quote-nested-4.check +++ b/tests/run-staging/quote-nested-4.check @@ -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]) })