diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index df2b586ac6ae..c25d271f39a9 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -47,15 +47,10 @@ class CompilationUnit protected (val source: SourceFile) { var needsMirrorSupport: Boolean = false /** Will be set to `true` if contains `Quote`. - * The information is used in phase `Staging` in order to avoid traversing trees that need no transformations. + * The information is used in phase `Staging`/`Splicing`/`PickleQuotes` in order to avoid traversing trees that need no transformations. */ var needsStaging: Boolean = false - /** Will be set to `true` if contains `Quote` that needs to be pickled - * The information is used in phase `PickleQuotes` in order to avoid traversing trees that need no transformations. - */ - var needsQuotePickling: Boolean = false - var suspended: Boolean = false var suspendedAtInliningPhase: Boolean = false @@ -115,7 +110,6 @@ object CompilationUnit { val force = new Force force.traverse(unit1.tpdTree) unit1.needsStaging = force.containsQuote - unit1.needsQuotePickling = force.containsQuote unit1.needsInlining = force.containsInline } unit1 diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 51891beef79f..d41c57ab116e 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -54,6 +54,7 @@ class Compiler { List(new Inlining) :: // Inline and execute macros List(new PostInlining) :: // Add mirror support for inlined code List(new Staging) :: // Check staging levels and heal staged types + List(new Splicing) :: // Replace level 1 splices with holes List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures Nil diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index c393b257af11..2ccd646c3226 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -134,8 +134,11 @@ class TreeTypeMap( val bind1 = tmap.transformSub(bind) val expr1 = tmap.transform(expr) cpy.Labeled(labeled)(bind1, expr1) - case Hole(isTermHole, n, args) => - Hole(isTermHole, n, args.mapConserve(transform)).withSpan(tree.span).withType(mapType(tree.tpe)) + case tree @ Hole(_, _, args, content, tpt) => + val args1 = args.mapConserve(transform) + val content1 = transform(content) + val tpt1 = transform(tpt) + cpy.Hole(tree)(args = args1, content = content1, tpt = tpt1) case lit @ Literal(Constant(tpe: Type)) => cpy.Literal(lit)(Constant(mapType(tpe))) case tree1 => diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index c3e826578906..02ea87dca7d6 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -503,6 +503,11 @@ object Trees { def forwardTo: Tree[T] = fun } + object GenericApply: + def unapply[T >: Untyped](tree: Tree[T]): Option[(Tree[T], List[Tree[T]])] = tree match + case tree: GenericApply[T] => Some((tree.fun, tree.args)) + case _ => None + /** The kind of application */ enum ApplyKind: case Regular // r.f(x) @@ -525,8 +530,6 @@ object Trees { attachmentOrElse(untpd.KindOfApply, ApplyKind.Regular) } - - /** fun[args] */ case class TypeApply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends GenericApply[T] { @@ -972,10 +975,16 @@ object Trees { def genericEmptyValDef[T >: Untyped]: ValDef[T] = theEmptyValDef.asInstanceOf[ValDef[T]] def genericEmptyTree[T >: Untyped]: Thicket[T] = theEmptyTree.asInstanceOf[Thicket[T]] - /** Tree that replaces a splice in pickled quotes. - * It is only used when picking quotes (Will never be in a TASTy file). + /** Tree that replaces a level 1 splices in pickled (level 0) quotes. + * It is only used when picking quotes (will never be in a TASTy file). + * + * @param isTermHole If this hole is a term, otherwise it is a type hole. + * @param idx The index of the hole in it's enclosing level 0 quote. + * @param args The arguments of the splice to compute its content + * @param content Lambda that computes the content of the hole. This tree is empty when in a quote pickle. + * @param tpt Type of the hole */ - case class Hole[-T >: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends Tree[T] { + case class Hole[-T >: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]], content: Tree[T], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] { type ThisTree[-T >: Untyped] <: Hole[T] override def isTerm: Boolean = isTermHole override def isType: Boolean = !isTermHole @@ -1331,6 +1340,10 @@ object Trees { case tree: Thicket if (trees eq tree.trees) => tree case _ => finalize(tree, untpd.Thicket(trees)(sourceFile(tree))) } + def Hole(tree: Tree)(isTerm: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = tree match { + case tree: Hole if isTerm == tree.isTerm && idx == tree.idx && args.eq(tree.args) && content.eq(tree.content) && content.eq(tree.content) => tree + case _ => finalize(tree, untpd.Hole(isTerm, idx, args, content, tpt)(sourceFile(tree))) + } // Copier methods with default arguments; these demand that the original tree // is of the same class as the copy. We only include trees with more than 2 elements here. @@ -1352,6 +1365,9 @@ object Trees { TypeDef(tree: Tree)(name, rhs) def Template(tree: Template)(constr: DefDef = tree.constr, parents: List[Tree] = tree.parents, derived: List[untpd.Tree] = tree.derived, self: ValDef = tree.self, body: LazyTreeList = tree.unforcedBody)(using Context): Template = Template(tree: Tree)(constr, parents, derived, self, body) + def Hole(tree: Hole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, args: List[Tree] = tree.args, content: Tree = tree.content, tpt: Tree = tree.tpt)(using Context): Hole = + Hole(tree: Tree)(isTerm, idx, args, content, tpt) + } /** Hook to indicate that a transform of some subtree should be skipped */ @@ -1481,6 +1497,8 @@ object Trees { case Thicket(trees) => val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) + case tree @ Hole(_, _, args, content, tpt) => + cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt)) case _ => transformMoreCases(tree) } @@ -1620,8 +1638,8 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) - case Hole(_, _, args) => - this(x, args) + case Hole(_, _, args, content, tpt) => + this(this(this(x, args), content), tpt) case _ => foldMoreCases(x, tree) } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index fb8bd917ca20..2e87194ff207 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -377,6 +377,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Throw(expr: Tree)(using Context): Tree = ref(defn.throwMethod).appliedTo(expr) + def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = + ta.assignType(untpd.Hole(isTermHole, idx, args, content, tpt), tpt) + // ------ Making references ------------------------------------------------------ def prefixIsElidable(tp: NamedType)(using Context): Boolean = { @@ -1518,10 +1521,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { * @param tpe the type of the elements of the resulting list. * */ - def mkList(trees: List[Tree], tpe: Tree)(using Context): Tree = + def mkList(trees: List[Tree], tpt: Tree)(using Context): Tree = ref(defn.ListModule).select(nme.apply) - .appliedToTypeTree(tpe) - .appliedToVarargs(trees, tpe) + .appliedToTypeTree(tpt) + .appliedToVarargs(trees, tpt) protected def FunProto(args: List[Tree], resType: Type)(using Context) = diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index de95526c5991..365b969faffc 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -411,6 +411,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Export(expr: Tree, selectors: List[ImportSelector])(implicit src: SourceFile): Export = new Export(expr, selectors) def PackageDef(pid: RefTree, stats: List[Tree])(implicit src: SourceFile): PackageDef = new PackageDef(pid, stats) def Annotated(arg: Tree, annot: Tree)(implicit src: SourceFile): Annotated = new Annotated(arg, annot) + def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(implicit src: SourceFile): Hole = new Hole(isTermHole, idx, args, content, tpt) // ------ Additional creation methods for untyped only ----------------- diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5db706197245..e09b7ca98955 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -791,10 +791,44 @@ class Definitions { @tu lazy val QuotedExprClass: ClassSymbol = requiredClass("scala.quoted.Expr") @tu lazy val QuotesClass: ClassSymbol = requiredClass("scala.quoted.Quotes") + @tu lazy val Quotes_reflect: Symbol = QuotesClass.requiredValue("reflect") + @tu lazy val Quotes_reflect_asTerm: Symbol = Quotes_reflect.requiredMethod("asTerm") + @tu lazy val Quotes_reflect_Apply: Symbol = Quotes_reflect.requiredValue("Apply") + @tu lazy val Quotes_reflect_Apply_apply: Symbol = Quotes_reflect_Apply.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_TypeApply: Symbol = Quotes_reflect.requiredValue("TypeApply") + @tu lazy val Quotes_reflect_TypeApply_apply: Symbol = Quotes_reflect_TypeApply.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_Assign: Symbol = Quotes_reflect.requiredValue("Assign") + @tu lazy val Quotes_reflect_Assign_apply: Symbol = Quotes_reflect_Assign.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_Inferred: Symbol = Quotes_reflect.requiredValue("Inferred") + @tu lazy val Quotes_reflect_Inferred_apply: Symbol = Quotes_reflect_Inferred.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_Literal: Symbol = Quotes_reflect.requiredValue("Literal") + @tu lazy val Quotes_reflect_Literal_apply: Symbol = Quotes_reflect_Literal.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_TreeMethods: Symbol = Quotes_reflect.requiredMethod("TreeMethods") + @tu lazy val Quotes_reflect_TreeMethods_asExpr: Symbol = Quotes_reflect_TreeMethods.requiredMethod("asExpr") + @tu lazy val Quotes_reflect_TypeRepr: Symbol = Quotes_reflect.requiredValue("TypeRepr") + @tu lazy val Quotes_reflect_TypeRepr_of: Symbol = Quotes_reflect_TypeRepr.requiredMethod("of") + @tu lazy val Quotes_reflect_TypeRepr_typeConstructorOf: Symbol = Quotes_reflect_TypeRepr.requiredMethod("typeConstructorOf") + @tu lazy val Quotes_reflect_TypeReprMethods: Symbol = Quotes_reflect.requiredValue("TypeReprMethods") + @tu lazy val Quotes_reflect_TypeReprMethods_asType: Symbol = Quotes_reflect_TypeReprMethods.requiredMethod("asType") + @tu lazy val Quotes_reflect_TypeTreeType: Symbol = Quotes_reflect.requiredType("TypeTree") + @tu lazy val Quotes_reflect_TermType: Symbol = Quotes_reflect.requiredType("Term") + @tu lazy val Quotes_reflect_BooleanConstant: Symbol = Quotes_reflect.requiredValue("BooleanConstant") + @tu lazy val Quotes_reflect_ByteConstant: Symbol = Quotes_reflect.requiredValue("ByteConstant") + @tu lazy val Quotes_reflect_ShortConstant: Symbol = Quotes_reflect.requiredValue("ShortConstant") + @tu lazy val Quotes_reflect_IntConstant: Symbol = Quotes_reflect.requiredValue("IntConstant") + @tu lazy val Quotes_reflect_LongConstant: Symbol = Quotes_reflect.requiredValue("LongConstant") + @tu lazy val Quotes_reflect_FloatConstant: Symbol = Quotes_reflect.requiredValue("FloatConstant") + @tu lazy val Quotes_reflect_DoubleConstant: Symbol = Quotes_reflect.requiredValue("DoubleConstant") + @tu lazy val Quotes_reflect_CharConstant: Symbol = Quotes_reflect.requiredValue("CharConstant") + @tu lazy val Quotes_reflect_StringConstant: Symbol = Quotes_reflect.requiredValue("StringConstant") + @tu lazy val Quotes_reflect_UnitConstant: Symbol = Quotes_reflect.requiredValue("UnitConstant") + @tu lazy val Quotes_reflect_NullConstant: Symbol = Quotes_reflect.requiredValue("NullConstant") + @tu lazy val Quotes_reflect_ClassOfConstant: Symbol = Quotes_reflect.requiredValue("ClassOfConstant") + @tu lazy val QuoteUnpicklerClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteUnpickler") - @tu lazy val QuoteUnpickler_unpickleExpr: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExpr") - @tu lazy val QuoteUnpickler_unpickleType: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleType") + @tu lazy val QuoteUnpickler_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2") + @tu lazy val QuoteUnpickler_unpickleTypeV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleTypeV2") @tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching") @tu lazy val QuoteMatching_ExprMatch: Symbol = QuoteMatchingClass.requiredMethod("ExprMatch") diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index e43f617bf965..b72515b518a6 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -201,7 +201,7 @@ object Phases { private var mySbtExtractDependenciesPhase: Phase = _ private var myPicklerPhase: Phase = _ private var myInliningPhase: Phase = _ - private var myPickleQuotesPhase: Phase = _ + private var mySplicingPhase: Phase = _ private var myFirstTransformPhase: Phase = _ private var myCollectNullableFieldsPhase: Phase = _ private var myRefChecksPhase: Phase = _ @@ -224,7 +224,7 @@ object Phases { final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def picklerPhase: Phase = myPicklerPhase final def inliningPhase: Phase = myInliningPhase - final def pickleQuotesPhase: Phase = myPickleQuotesPhase + final def splicingPhase: Phase = mySplicingPhase final def firstTransformPhase: Phase = myFirstTransformPhase final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase final def refchecksPhase: Phase = myRefChecksPhase @@ -250,7 +250,7 @@ object Phases { mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) myPicklerPhase = phaseOfClass(classOf[Pickler]) myInliningPhase = phaseOfClass(classOf[Inlining]) - myPickleQuotesPhase = phaseOfClass(classOf[PickleQuotes]) + mySplicingPhase = phaseOfClass(classOf[Splicing]) myFirstTransformPhase = phaseOfClass(classOf[FirstTransform]) myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields]) myRefChecksPhase = phaseOfClass(classOf[RefChecks]) @@ -426,7 +426,7 @@ object Phases { def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase def picklerPhase(using Context): Phase = ctx.base.picklerPhase def inliningPhase(using Context): Phase = ctx.base.inliningPhase - def pickleQuotesPhase(using Context): Phase = ctx.base.pickleQuotesPhase + def splicingPhase(using Context): Phase = ctx.base.splicingPhase def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase def elimRepeatedPhase(using Context): Phase = ctx.base.elimRepeatedPhase diff --git a/compiler/src/dotty/tools/dotc/core/StagingContext.scala b/compiler/src/dotty/tools/dotc/core/StagingContext.scala index 4a4f451df099..41e77655d5d6 100644 --- a/compiler/src/dotty/tools/dotc/core/StagingContext.scala +++ b/compiler/src/dotty/tools/dotc/core/StagingContext.scala @@ -8,11 +8,11 @@ import dotty.tools.dotc.transform.PCPCheckAndHeal object StagingContext { - /** A key to be used in a context property that tracks the quoteation level */ + /** A key to be used in a context property that tracks the quotation level */ private val QuotationLevel = new Property.Key[Int] - /** A key to be used in a context property that tracks the quoteation stack. - * Stack containing the Quotes references recieved by the surrounding quotes. + /** A key to be used in a context property that tracks the quotation stack. + * Stack containing the Quotes references received by the surrounding quotes. */ private val QuotesStack = new Property.Key[List[tpd.Tree]] @@ -26,7 +26,7 @@ object StagingContext { def quoteContext(using Context): Context = ctx.fresh.setProperty(QuotationLevel, level + 1) - /** Context with an incremented quotation level and pushes a refecence to a Quotes on the quote context stack */ + /** Context with an incremented quotation level and pushes a reference to a Quotes on the quote context stack */ def pushQuotes(qctxRef: tpd.Tree)(using Context): Context = val old = ctx.property(QuotesStack).getOrElse(List.empty) ctx.fresh.setProperty(QuotationLevel, level + 1) @@ -43,7 +43,7 @@ object StagingContext { ctx.property(TaggedTypes).get /** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty. - * The quotation stack could be empty if we are in a top level splice or an eroneous splice directly witin a top level splice. + * The quotation stack could be empty if we are in a top level splice or an erroneous splice directly within a top level splice. */ def popQuotes()(using Context): (Option[tpd.Tree], Context) = val ctx1 = ctx.fresh.setProperty(QuotationLevel, level - 1) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index dc9e48b65f47..949a99c1e016 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -433,6 +433,7 @@ object StdNames { val common: N = "common" val compiletime : N = "compiletime" val conforms_ : N = "$conforms" + val contents: N = "contents" val copy: N = "copy" val currentMirror: N = "currentMirror" val create: N = "create" @@ -486,6 +487,7 @@ object StdNames { val hash_ : N = "hash" val head: N = "head" val higherKinds: N = "higherKinds" + val idx: N = "idx" val identity: N = "identity" val implicitConversions: N = "implicitConversions" val implicitly: N = "implicitly" @@ -553,6 +555,7 @@ object StdNames { val productElementName: N = "productElementName" val productIterator: N = "productIterator" val productPrefix: N = "productPrefix" + val quotes : N = "quotes" val raw_ : N = "raw" val refl: N = "refl" val reflect: N = "reflect" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index dd2cc46c406d..09aae581fcfc 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -637,11 +637,11 @@ class TreePickler(pickler: TastyPickler) { pickleTree(hi) pickleTree(alias) } - case Hole(_, idx, args) => + case Hole(_, idx, args, _, tpt) => writeByte(HOLE) withLength { writeNat(idx) - pickleType(tree.tpe, richTypes = true) + pickleType(tpt.tpe, richTypes = true) args.foreach(pickleTree) } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 8ba0d8a8a60b..979fe02b1c4f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1323,7 +1323,7 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val tpe = readType() val args = until(end)(readTerm()) - Hole(true, idx, args).withType(tpe) + Hole(true, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) case _ => readPathTerm() } @@ -1357,7 +1357,7 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val tpe = readType() val args = until(end)(readTerm()) - Hole(false, idx, args).withType(tpe) + Hole(false, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) case _ => if (isTypeTreeTag(nextByte)) readTerm() else { diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6d90b168a29f..9401efc58135 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -697,10 +697,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}" case MacroTree(call) => keywordStr("macro ") ~ toTextGlobal(call) - case Hole(isTermHole, idx, args) => - val (prefix, postfix) = if isTermHole then ("{{{ ", " }}}") else ("[[[ ", " ]]]") + case Hole(isTermHole, idx, args, content, tpt) => + val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]") val argsText = toTextGlobal(args, ", ") - prefix ~~ idx.toString ~~ "|" ~~ argsText ~~ postfix + val contentText = toTextGlobal(content) + val tptText = toTextGlobal(tpt) + prefix ~~ idx.toString ~~ "|" ~~ tptText ~~ "|" ~~ argsText ~~ "|" ~~ contentText ~~ postfix case _ => tree.fallbackToText(this) } diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 9de869c994e6..2e0454c3a7aa 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -47,34 +47,67 @@ object PickledQuotes { changeOwnerOfTree(tpe1.typeTree, ctx.owner) } + /** `typeHole`/`types` argument of `QuoteUnpickler.{unpickleExpr,unpickleExprV2,unpickleType,unpickleTypeV2}` */ + enum TypeHole: + /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. + * From code compiled with Scala 3.0.x and 3.1.x. + * Note: For `unpickleType` it will always be `null`. + */ + case V1(evalHole: Null | ((Int, Seq[scala.quoted.Type[?]]) => scala.quoted.Type[?])) + /** `termHole` argument of `QuoteUnpickler.unpickleExprV2` + * From code compiled with Scala 3.2.0+ + */ + case V2(types: Null | Seq[scala.quoted.Type[?]]) + + def isEmpty: Boolean = this match + case V1(evalHole) => evalHole == null + case V2(types) => types == null + + enum ExprHole: + /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. + * From code compiled with Scala 3.0.x and 3.1.x. + * Note: For `unpickleType` it will always be `null`. + */ + case V1(evalHole: Null | ((Int, Seq[ExprHole.ArgV1], scala.quoted.Quotes) => scala.quoted.Expr[?])) + /** `termHole` argument of `QuoteUnpickler.unpickleExprV2` + * From code compiled with Scala 3.2.0+ + */ + case V2(evalHole: Null | ((Int, Seq[ExprHole.ArgV2], scala.quoted.Quotes) => scala.quoted.Expr[?])) + + object ExprHole: + type ArgV1 = scala.quoted.Type[?] | (Quotes ?=> scala.quoted.Expr[Any]) + type ArgV2 = scala.quoted.Type[?] | scala.quoted.Expr[Any] + /** Unpickle the tree contained in the TastyExpr */ - def unpickleTerm(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = { + def unpickleTerm(pickled: String | List[String], typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = false)) - val Inlined(call, Nil, expnasion) = unpickled + val Inlined(call, Nil, expansion) = unpickled val inlineCtx = inlineContext(call) - val expansion1 = spliceTypes(expnasion, typeHole, termHole)(using inlineCtx) + val expansion1 = spliceTypes(expansion, typeHole)(using inlineCtx) val expansion2 = spliceTerms(expansion1, typeHole, termHole)(using inlineCtx) cpy.Inlined(unpickled)(call, Nil, expansion2) } + /** Unpickle the tree contained in the TastyType */ - def unpickleTypeTree(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = { + def unpickleTypeTree(pickled: String | List[String], typeHole: TypeHole)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = true)) - spliceTypes(unpickled, typeHole, termHole) + spliceTypes(unpickled, typeHole) } /** Replace all term holes with the spliced terms */ - private def spliceTerms(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = { - val evaluateHoles = new TreeMap { + private def spliceTerms(tree: Tree, typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = { + def evaluateHoles = new TreeMap { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { - case Hole(isTerm, idx, args) => + case Hole(isTermHole, idx, args, _, _) => inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) { - val reifiedArgs = args.map { arg => - if (arg.isTerm) (q: Quotes) ?=> new ExprImpl(arg, SpliceScope.getCurrent) - else new TypeImpl(arg, SpliceScope.getCurrent) - } - if isTerm then - val quotedExpr = termHole(idx, reifiedArgs, QuotesImpl()) + if isTermHole then + val quotedExpr = termHole match + case ExprHole.V1(evalHole) => + evalHole.nn.apply(idx, reifyExprHoleV1Args(args), QuotesImpl()) + case ExprHole.V2(evalHole) => + evalHole.nn.apply(idx, reifyExprHoleV2Args(args), QuotesImpl()) + val filled = PickledQuotes.quotedExprToTree(quotedExpr) // We need to make sure a hole is created with the source file of the surrounding context, even if @@ -82,9 +115,13 @@ object PickledQuotes { if filled.source == ctx.source then filled else filled.cloneIn(ctx.source).withSpan(tree.span) else + // For backwards compatibility with 3.0.x and 3.1.x + // In 3.2.0+ all these holes are handled by `spliceTypes` before we call `spliceTerms`. + // // Replaces type holes generated by PickleQuotes (non-spliced types). // These are types defined in a quote and used at the same level in a nested quote. - val quotedType = typeHole(idx, reifiedArgs) + val TypeHole.V1(evalHole) = typeHole + val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args)) PickledQuotes.quotedTypeToTree(quotedType) } case tree => @@ -109,24 +146,34 @@ object PickledQuotes { } } } - val tree1 = evaluateHoles.transform(tree) + val tree1 = termHole match + case ExprHole.V2(null) => tree + case _ => evaluateHoles.transform(tree) quotePickling.println(i"**** evaluated quote\n$tree1") tree1 } /** Replace all type holes generated with the spliced types */ - private def spliceTypes(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Int], scala.quoted.Quotes) => Any)(using Context): Tree = { - tree match + private def spliceTypes(tree: Tree, typeHole: TypeHole)(using Context): Tree = { + if typeHole.isEmpty then tree + else tree match case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => val typeSpliceMap = (stat :: rest).iterator.map { case tdef: TypeDef => assert(tdef.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot)) - val tree = tdef.rhs match - case TypeBoundsTree(_, Hole(_, idx, args), _) => - val quotedType = typeHole(idx, args) - PickledQuotes.quotedTypeToTree(quotedType) - case TypeBoundsTree(_, tpt, _) => - tpt + val tree = typeHole match + case TypeHole.V1(evalHole) => + tdef.rhs match + case TypeBoundsTree(_, Hole(_, idx, args, _, _), _) => + // To keep for backwards compatibility. In some older version holes where created in the bounds. + val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args)) + PickledQuotes.quotedTypeToTree(quotedType) + case TypeBoundsTree(_, tpt, _) => + // To keep for backwards compatibility. In some older version we missed the creation of some holes. + tpt + case TypeHole.V2(types) => + val Hole(_, idx, _, _, _) = tdef.rhs + PickledQuotes.quotedTypeToTree(types.nn.apply(idx)) (tdef.symbol, tree.tpe) }.toMap class ReplaceSplicedTyped extends TypeMap() { @@ -148,6 +195,21 @@ object PickledQuotes { tree } + def reifyTypeHoleArgs(args: List[Tree])(using Context): List[scala.quoted.Type[?]] = + args.map(arg => new TypeImpl(arg, SpliceScope.getCurrent)) + + def reifyExprHoleV1Args(args: List[Tree])(using Context): List[ExprHole.ArgV1] = + args.map { arg => + if arg.isTerm then (q: Quotes) ?=> new ExprImpl(arg, SpliceScope.getCurrent) + else new TypeImpl(arg, SpliceScope.getCurrent) + } + + def reifyExprHoleV2Args(args: List[Tree])(using Context): List[ExprHole.ArgV2] = + args.map { arg => + if arg.isTerm then new ExprImpl(arg, SpliceScope.getCurrent) + else new TypeImpl(arg, SpliceScope.getCurrent) + } + // TASTY picklingtests/pos/quoteTest.scala /** Pickle tree into it's TASTY bytes s*/ diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index ca17cf607641..3c5da3ffadf3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -69,8 +69,6 @@ class Inlining extends MacroTransform { if tree1.tpe.isError then tree1 else Inliner.inlineCall(tree1) case _: GenericApply if tree.symbol.isQuote => - if level == 0 then - ctx.compilationUnit.needsQuotePickling = true super.transform(tree)(using StagingContext.quoteContext) case _: GenericApply if tree.symbol.isExprSplice => super.transform(tree)(using StagingContext.spliceContext) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 82a187f57109..a12ef6d50bed 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -13,6 +13,7 @@ import ast.TreeTypeMap import SymUtils._ import NameKinds._ import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.config.ScalaRelease.* import scala.collection.mutable import dotty.tools.dotc.core.Annotations._ @@ -23,45 +24,52 @@ import dotty.tools.dotc.typer.Inliner import scala.annotation.constructorOnly - -/** Translates quoted terms and types to `unpickleExpr` or `unpickleType` method calls. +/** Translates quoted terms and types to `unpickleExprV2` or `unpickleType` method calls. * * Transforms top level quote * ``` * '{ ... - * val x1 = ??? - * val x2 = ??? + * @TypeSplice type X0 = {{ 0 | .. | contentsTpe0 | .. }} + * @TypeSplice type X2 = {{ 1 | .. | contentsTpe1 | .. }} + * val x1: U1 = ??? + * val x2: U2 = ??? + * ... + * {{{ 3 | x1 | contents0 | T0 }}} // hole + * ... + * {{{ 4 | x2 | contents1 | T1 }}} // hole * ... - * ${ ... '{ ... x1 ... x2 ...} ... } + * {{{ 5 | x1, x2 | contents2 | T2 }}} // hole * ... * } * ``` * to * ``` - * unpickleExpr( + * unpickleExprV2( * pickled = [[ // PICKLED TASTY - * ... + * @TypeSplice type X0 // with bounds that do not contain captured types + * @TypeSplice type X1 // with bounds that do not contain captured types * val x1 = ??? * val x2 = ??? * ... - * Hole( | x1, x2) + * {{{ 0 | x1 | | T0 }}} // hole + * ... + * {{{ 1 | x2 | | T1 }}} // hole + * ... + * {{{ 2 | x1, x2 | | T2 }}} // hole * ... * ]], * typeHole = (idx: Int, args: List[Any]) => idx match { - * case 0 => ... + * case 0 => contentsTpe0.apply(args(0).asInstanceOf[Type[?]]) // beta reduced + * case 1 => contentsTpe1.apply(args(0).asInstanceOf[Type[?]]) // beta reduced * }, - * termHole = (idx: Int, args: List[Any], qctx: Quotes) => idx match { - * case 0 => ... - * ... - * case => - * val x1$1 = args(0).asInstanceOf[Expr[T]] - * val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]] - * ... - * { ... '{ ... ${x1$1} ... ${x2$1} ...} ... } + * termHole = (idx: Int, args: List[Any], quotes: Quotes) => idx match { + * case 3 => content0.apply(args(0).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced + * case 4 => content1.apply(args(0).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced + * case 5 => content2.apply(args(0).asInstanceOf[Expr[U1]], args(1).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced * }, * ) * ``` - * and then performs the same transformation on `'{ ... ${x1$1} ... ${x2$1} ...}`. + * and then performs the same transformation on any quote contained in the `content`s. * */ class PickleQuotes extends MacroTransform { @@ -75,375 +83,80 @@ class PickleQuotes extends MacroTransform { override def allowsImplicitSearch: Boolean = true override def checkPostCondition(tree: Tree)(using Context): Unit = - tree match { + tree match case tree: RefTree if !Inliner.inInlineMethod => assert(!tree.symbol.isQuote) assert(!tree.symbol.isExprSplice) - case _ : TypeDef => + case _ : TypeDef if !Inliner.inInlineMethod => assert(!tree.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot), s"${tree.symbol} should have been removed by PickledQuotes because it has a @quoteTypeTag") case _ => - } override def run(using Context): Unit = - if (ctx.compilationUnit.needsQuotePickling) super.run(using freshStagingContext) + if (ctx.compilationUnit.needsStaging) super.run(using freshStagingContext) protected def newTransformer(using Context): Transformer = new Transformer { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = - new QuoteReifier(null, new mutable.HashMap[Symbol, Tree => Tree], new Embedded, ctx.owner)(ctx).transform(tree) + tree match + case Apply(Select(Apply(TypeApply(fn, List(tpt)), List(code)),nme.apply), List(quotes)) + if fn.symbol == defn.QuotedRuntime_exprQuote => + val (contents, codeWithHoles) = makeHoles(code) + val sourceRef = Inliner.inlineCallTrace(ctx.owner, tree.sourcePos) + val codeWithHoles2 = Inlined(sourceRef, Nil, codeWithHoles) + val pickled = PickleQuotes(quotes, codeWithHoles2, contents, tpt.tpe, false) + transform(pickled) // pickle quotes that are in the contents + case Apply(TypeApply(_, List(tpt)), List(quotes)) if tree.symbol == defn.QuotedTypeModule_of => + tpt match + case Select(t, _) if tpt.symbol == defn.QuotedType_splice => + // `Type.of[t.Underlying](quotes)` --> `t` + ref(t.symbol)(using ctx.withSource(tpt.source)).withSpan(tpt.span) + case _ => + val (contents, tptWithHoles) = makeHoles(tpt) + PickleQuotes(quotes, tptWithHoles, contents, tpt.tpe, true) + case tree: DefDef if tree.symbol.is(Macro) => + // Shrink size of the tree. The methods have already been inlined. + // TODO move to FirstTransform to trigger even without quotes + cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + case _: DefDef if tree.symbol.isInlineMethod => + tree + case _ => + super.transform(tree) } - /** The main transformer class - * @param outer the next outer reifier, null is this is the topmost transformer - * @param embedded a list of embedded quotes (if in a splice) or splices (if in a quote) - * @param owner the owner in the destination lifted lambda - * @param capturers register a reference defined in a quote but used in another quote nested in a splice. - * Returns a version of the reference that needs to be used in its place. - * '{ - * val x = ??? - * ${ ... '{ ... x ... } ... } - * } - * Eta expanding the `x` in `${ ... '{ ... x ... } ... }` will return a `${x$1}` for which the `x$1` - * be created by some outer reifier. - * This transformation is only applied to definitions at staging level 1. - * See `isCaptured`. - */ - private class QuoteReifier(outer: QuoteReifier | Null, capturers: mutable.HashMap[Symbol, Tree => Tree], - val embedded: Embedded, val owner: Symbol)(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { self => - - import StagingContext._ - - /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ - def nested(isQuote: Boolean)(using Context): QuoteReifier = { - val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new Embedded - new QuoteReifier(this, capturers, nestedEmbedded, ctx.owner)(ctx) - } - - /** Split `body` into a core and a list of embedded splices. - * Then if inside a splice, make a hole from these parts. - * If outside a splice, generate a call tp `scala.quoted.Unpickler.unpickleType` or - * `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with - * core and splices as arguments. - */ - 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) - super.transformQuotation(body1, quote) - } - else { - val (body1, splices) = nested(isQuote = true).splitQuote(body)(using quoteContext) - if (level == 0) { - val body2 = - if (body1.isType) body1 - else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body1) - pickledQuote(quote, body2, splices, body.tpe, isType).withSpan(quote.span) - } - else - body - } - } - - private def pickledQuote(quote: Apply, body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(using Context) = { - /** Encode quote using Reflection.Literal - * - * Generate the code - * ```scala - * qctx => qctx.reflect.TreeMethods.asExpr( - * qctx.reflect.Literal.apply(x$1.reflect.Constant..apply()) - * ).asInstanceOf[scala.quoted.Expr[]] - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def pickleAsLiteral(lit: Literal) = { - val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) - val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, exprType) - def mkConst(ts: List[Tree]) = { - val reflect = ts.head.select("reflect".toTermName) - val typeName = body.tpe.typeSymbol.name - val literalValue = - if lit.const.tag == Constants.NullTag || lit.const.tag == Constants.UnitTag then Nil - else List(body) - val constant = reflect.select(s"${typeName}Constant".toTermName).select(nme.apply).appliedToTermArgs(literalValue) - val literal = reflect.select("Literal".toTermName).select(nme.apply).appliedTo(constant) - reflect.select("TreeMethods".toTermName).select("asExpr".toTermName).appliedTo(literal).asInstance(exprType) - } - Lambda(lambdaTpe, mkConst).withSpan(body.span) - } - - /** Encode quote using Reflection.Literal - * - * Generate the code - * ```scala - * qctx => scala.quoted.ToExpr.{BooleanToExpr,ShortToExpr, ...}.apply()(qctx) - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def liftedValue(lit: Literal, lifter: Symbol) = - val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) - val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, exprType) - def mkToExprCall(ts: List[Tree]) = - ref(lifter).appliedToType(originalTp).select(nme.apply).appliedTo(lit).appliedTo(ts.head) - Lambda(lambdaTpe, mkToExprCall).withSpan(body.span) - - def pickleAsValue(lit: Literal) = { - // TODO should all constants be pickled as Literals? - // Should examime the generated bytecode size to decide and performance - lit.const.tag match { - case Constants.NullTag => pickleAsLiteral(lit) - case Constants.UnitTag => pickleAsLiteral(lit) - case Constants.BooleanTag => liftedValue(lit, defn.ToExprModule_BooleanToExpr) - case Constants.ByteTag => liftedValue(lit, defn.ToExprModule_ByteToExpr) - case Constants.ShortTag => liftedValue(lit, defn.ToExprModule_ShortToExpr) - case Constants.IntTag => liftedValue(lit, defn.ToExprModule_IntToExpr) - case Constants.LongTag => liftedValue(lit, defn.ToExprModule_LongToExpr) - case Constants.FloatTag => liftedValue(lit, defn.ToExprModule_FloatToExpr) - case Constants.DoubleTag => liftedValue(lit, defn.ToExprModule_DoubleToExpr) - case Constants.CharTag => liftedValue(lit, defn.ToExprModule_CharToExpr) - case Constants.StringTag => liftedValue(lit, defn.ToExprModule_StringToExpr) - } - } - - /** Encode quote using QuoteUnpickler.{unpickleExpr, unpickleType} - * - * Generate the code - * ```scala - * qctx => qctx.asInstanceOf[QuoteUnpickler].[]( - * , - * , - * , - * ) - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def pickleAsTasty() = { - val pickleQuote = PickledQuotes.pickleQuote(body) - val pickledQuoteStrings = pickleQuote match - case x :: Nil => Literal(Constant(x)) - case xs => liftList(xs.map(x => Literal(Constant(x))), defn.StringType) - - // TODO split holes earlier into types and terms. This all holes in each category can have consecutive indices - val (typeSplices, termSplices) = splices.zipWithIndex.partition { case (splice, _) => - splice.tpe match - case defn.FunctionOf(_, res, _, _) => res.typeSymbol == defn.QuotedTypeClass - } - - // This and all closures in typeSplices are removed by the BetaReduce phase - val typeHoles = - if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without splices as small as possible - else - Lambda( - MethodType( - List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType)), - defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)), - args => { - val cases = typeSplices.map { case (splice, idx) => - CaseDef(Literal(Constant(idx)), EmptyTree, splice.select(nme.apply).appliedTo(args(1))) - } - Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) - } - ) - - // This and all closures in termSplices are removed by the BetaReduce phase - val termHoles = - if termSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without splices as small as possible - else - Lambda( - MethodType( - List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType), defn.QuotesClass.typeRef), - defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)), - args => { - val cases = termSplices.map { case (splice, idx) => - val defn.FunctionOf(_, defn.FunctionOf(qctxType :: _, _, _, _), _, _) = splice.tpe - val rhs = splice.select(nme.apply).appliedTo(args(1)).select(nme.apply).appliedTo(args(2).asInstance(qctxType)) - CaseDef(Literal(Constant(idx)), EmptyTree, rhs) - } - Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) - } - ) - - val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass - val quotedType = quoteClass.typeRef.appliedTo(originalTp) - val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) - def callUnpickle(ts: List[Tree]) = { - val qctx = ts.head.asInstance(defn.QuoteUnpicklerClass.typeRef) - val unpickleMeth = if isType then defn.QuoteUnpickler_unpickleType else defn.QuoteUnpickler_unpickleExpr - qctx.select(unpickleMeth).appliedToType(originalTp).appliedTo(pickledQuoteStrings, typeHoles, termHoles) - } - Lambda(lambdaTpe, callUnpickle).withSpan(body.span) - } - - /** Encode quote using Reflection.TypeRepr.typeConstructorOf - * - * Generate the code - * ```scala - * qctx.reflect.TypeReprMethods.asType( - * qctx.reflect.TypeRepr.typeConstructorOf(classOf[]]) - * ).asInstanceOf[scala.quoted.Type[]] - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def taggedType() = - val typeType = defn.QuotedTypeClass.typeRef.appliedTo(body.tpe) - val classTree = TypeApply(ref(defn.Predef_classOf.termRef), body :: Nil) - 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().select(nme.apply).appliedTo(quote.args.head) // TODO do not create lambda - } - else getLiteral(body) match { - case Some(lit) => pickleAsValue(lit) - case _ => pickleAsTasty() - } - } - - /** If inside a quote, split the body of the splice into a core and a list of embedded quotes - * and make a hole from these parts. Otherwise issue an error, unless we - * are in the body of an inline method. - */ - protected def transformSplice(body: Tree, splice: Apply)(using Context): Tree = - if (level > 1) { - val body1 = nested(isQuote = false).transform(body)(using spliceContext) - cpy.Apply(splice)(splice.fun, body1 :: Nil) - } - else { - assert(level == 1, "unexpected top splice outside quote") - val (body1, quotes) = nested(isQuote = false).splitSplice(body)(using spliceContext) - val tpe = outer.nn.embedded.getHoleType(body, splice) - val hole = makeHole(splice.isTerm, body1, quotes, tpe).withSpan(splice.span) - // We do not place add the inline marker for trees that where lifted as they come from the same file as their - // enclosing quote. Any intemediate splice will add it's own Inlined node and cancel it before splicig the lifted tree. - // Note that lifted trees are not necessarily expressions and that Inlined nodes are expected to be expressions. - // For example we can have a lifted tree containing the LHS of an assignment (see tests/run-with-compiler/quote-var.scala). - if (outer.nn.embedded.isLiftedSymbol(body.symbol)) hole - else Inlined(EmptyTree, Nil, hole).withSpan(splice.span) - } - - /** If inside a quote, split the body of the splice into a core and a list of embedded quotes - * and make a hole from these parts. Otherwise issue an error, unless we - * are in the body of an inline method. - */ - protected def transformSpliceType(body: Tree, splice: Select)(using Context): Tree = - if level > 1 then - val body1 = nested(isQuote = false).transform(body)(using spliceContext) - cpy.Select(splice)(body1, splice.name) - else if level == 1 then - val (body1, quotes) = nested(isQuote = false).splitSplice(body)(using spliceContext) - val tpe = outer.nn.embedded.getHoleType(body, splice) - makeHole(splice.isTerm, body1, quotes, tpe).withSpan(splice.span) - else - splice - - /** Transforms the contents of a nested splice - * Assuming - * '{ - * val x = ??? - * val y = ??? - * ${ ... '{ ... x .. y ... } ... } - * } - * then the spliced subexpression - * { ... '{ ... x ... y ... } ... } - * will be transformed to - * (args: Seq[Any]) => { - * val x$1 = args(0).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] - * val y$1 = args(1).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] - * { ... '{ ... ${x$1} ... ${y$1} ... } ... } - * } - * - * See: `capture` - * - * At the same time register embedded trees `x` and `y` to place as arguments of the hole - * placed in the original code. - * '{ - * val x = ??? - * val y = ??? - * Hole(0 | x, y) - * } - */ - private def makeLambda(tree: Tree)(using Context): Tree = { - def body(arg: Tree)(using Context): Tree = { - var i = 0 - transformWithCapturer(tree)( - (captured: mutable.Map[Symbol, Tree]) => { - (tree: Tree) => { - def newCapture = { - val tpw = tree.tpe.widen match { - case tpw: MethodicType => tpw.toFunctionType(isJava = false) - case tpw => tpw - } - assert(tpw.isInstanceOf[ValueType]) - val argTpe = - if (tree.isType) defn.QuotedTypeClass.typeRef.appliedTo(tpw) - else defn.FunctionType(1, isContextual = true).appliedTo(defn.QuotesClass.typeRef, defn.QuotedExprClass.typeRef.appliedTo(tpw)) - val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).cast(argTpe) - val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg) - i += 1 - embedded.addTree(tree, capturedArg.symbol) - captured.put(tree.symbol, capturedArg) - capturedArg - } - val refSym = captured.getOrElseUpdate(tree.symbol, newCapture).symbol - ref(refSym).withSpan(tree.span) + private def makeHoles(tree: tpd.Tree)(using Context): (List[Tree], tpd.Tree) = + + class HoleContentExtractor extends Transformer: + private val contents = List.newBuilder[Tree] + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + tree match + case tree @ Hole(isTerm, _, _, content, _) => + if !content.isEmpty then + contents += content + val holeType = + if isTerm then getTermHoleType(tree.tpe) else getTypeHoleType(tree.tpe) + val hole = cpy.Hole(tree)(content = EmptyTree, TypeTree(holeType)) + if isTerm then Inlined(EmptyTree, Nil, hole).withSpan(tree.span) else hole + case tree: DefTree => + val newAnnotations = tree.symbol.annotations.mapconserve { annot => + annot.derivedAnnotation(transform(annot.tree)(using ctx.withOwner(tree.symbol))) } - } - ) + tree.symbol.annotations = newAnnotations + super.transform(tree) + case _ => + super.transform(tree).withType(mapAnnots(tree.tpe)) + + private def mapAnnots = new TypeMap { // TODO factor out duplicated logic in Splicing + override def apply(tp: Type): Type = { + tp match + case tp @ AnnotatedType(underlying, annot) => + val underlying1 = this(underlying) + derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree))) + case _ => mapOver(tp) + } } - /* Lambdas are generated outside the quote that is being reified (i.e. in outer.owner). - * In case the case that level == -1 the code is not in a quote, it is in an inline method, - * hence we should take that as owner directly. - */ - val lambdaOwner = if (level == -1) ctx.owner else outer.nn.owner - - val tpe = MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, tree.tpe.widen) - val meth = newSymbol(lambdaOwner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe) - Closure(meth, tss => body(tss.head.head)(using ctx.withOwner(meth)).changeNonLocalOwners(meth)).withSpan(tree.span) - } - - private def transformWithCapturer(tree: Tree)(capturer: mutable.Map[Symbol, Tree] => Tree => Tree)(using Context): Tree = { - val captured = mutable.LinkedHashMap.empty[Symbol, Tree] - val captured2 = capturer(captured) - - outer.nn.localSymbols.foreach(sym => if (!sym.isInlineMethod) capturers.put(sym, captured2)) - - val tree2 = transform(tree) - capturers --= outer.nn.localSymbols - - val captures = captured.result().valuesIterator.toList - if (captures.isEmpty) tree2 - else Block(captures, tree2) - } - - /** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */ - private def isCaptured(sym: Symbol, level: Int)(using Context): Boolean = - level == 1 && levelOf(sym) == 1 && capturers.contains(sym) - - /** Transform `tree` and return the resulting tree and all `embedded` quotes - * or splices as a pair. - */ - private def splitQuote(tree: Tree)(using Context): (Tree, List[Tree]) = { - val tree1 = stipTypeAnnotations(transform(tree)) - (tree1, embedded.getTrees) - } - - private def splitSplice(tree: Tree)(using Context): (Tree, List[Tree]) = { - val tree1 = makeLambda(tree) - (tree1, embedded.getTrees) - } - - private def stipTypeAnnotations(tree: Tree)(using Context): Tree = - new TreeTypeMap(typeMap = _.stripAnnots).apply(tree) - - /** Register `body` as an `embedded` quote or splice - * and return a hole with `splices` as arguments and the given type `tpe`. - */ - private def makeHole(isTermHole: Boolean, body: Tree, splices: List[Tree], tpe: Type)(using Context): Hole = { - val idx = embedded.addTree(body, NoSymbol) /** Remove references to local types that will not be defined in this quote */ - def getTypeHoleType(using Context) = new TypeMap() { + private def getTypeHoleType(using Context) = new TypeMap() { override def apply(tp: Type): Type = tp match case tp: TypeRef if tp.typeSymbol.isTypeSplice => apply(tp.dealias) @@ -457,71 +170,33 @@ class PickleQuotes extends MacroTransform { } /** Remove references to local types that will not be defined in this quote */ - def getTermHoleType(using Context) = new TypeMap() { + private def getTermHoleType(using Context) = new TypeMap() { override def apply(tp: Type): Type = tp match - case tp @ TypeRef(NoPrefix, _) if capturers.contains(tp.symbol) => + case tp @ TypeRef(NoPrefix, _) => // reference to term with a type defined in outer quote getTypeHoleType(tp) - case tp @ TermRef(NoPrefix, _) if capturers.contains(tp.symbol) => + case tp @ TermRef(NoPrefix, _) => // widen term refs to terms defined in outer quote apply(tp.widenTermRefExpr) case tp => mapOver(tp) } - val holeType = if isTermHole then getTermHoleType(tpe) else getTypeHoleType(tpe) + /** Get the contents of the transformed tree */ + def getContents() = + val res = contents.result + contents.clear() + res + end HoleContentExtractor - Hole(isTermHole, idx, splices).withType(holeType).asInstanceOf[Hole] - } + val holeMaker = new HoleContentExtractor + val newTree = holeMaker.transform(tree) + (holeMaker.getContents(), newTree) - override def transform(tree: Tree)(using Context): Tree = - if (tree.source != ctx.source && tree.source.exists) - transform(tree)(using ctx.withSource(tree.source)) - else reporting.trace(i"Reifier.transform $tree at $level", show = true) { - tree match { - 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 Apply(Select(Apply(TypeApply(fn,_), List(ref: RefTree)),nme.apply),List(quotes)) - if fn.symbol == defn.QuotedRuntime_exprQuote && isCaptured(ref.symbol, level + 1) => - // Optimization: avoid the full conversion when capturing `x` with `x$1: Expr[X]` - // in `'{x}` to `'{ ${x$1} }'` and go directly to `x$1` - capturers(ref.symbol)(ref).select(nme.apply).appliedTo(quotes) - case tree: RefTree if isCaptured(tree.symbol, level) => - val body = capturers(tree.symbol).apply(tree) - if (tree.isType) - transformSpliceType(body, body.select(tpnme.Underlying)) - else - val splice = ref(defn.QuotedRuntime_exprSplice).appliedToType(tree.tpe).appliedTo(body) - transformSplice(body, splice) - - case tree: DefDef if tree.symbol.is(Macro) && level == 0 => - // Shrink size of the tree. The methods have already been inlined. - // TODO move to FirstTransform to trigger even without quotes - cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) - - case tree: DefTree if level >= 1 => - val newAnnotations = tree.symbol.annotations.mapconserve { annot => - val newAnnotTree = transform(annot.tree)(using ctx.withOwner(tree.symbol)) - if (annot.tree == newAnnotTree) annot - else ConcreteAnnotation(newAnnotTree) - } - tree.symbol.annotations = newAnnotations - super.transform(tree) - case _ => - super.transform(tree) - } - } - private def liftList(list: List[Tree], tpe: Type)(using Context): Tree = - list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) => - acc.select("::".toTermName).appliedToType(tpe).appliedTo(x) - } - } -} + end makeHoles +} object PickleQuotes { import tpd._ @@ -529,34 +204,180 @@ object PickleQuotes { val name: String = "pickleQuotes" val description: String = "turn quoted trees into explicit run-time data structures" - def getLiteral(tree: tpd.Tree): Option[Literal] = tree match { - case tree: Literal => Some(tree) - case Block(Nil, e) => getLiteral(e) - case Inlined(_, Nil, e) => getLiteral(e) - case _ => None - } + def apply(quotes: Tree, body: Tree, contents: List[Tree], originalTp: Type, isType: Boolean)(using Context) = { + /** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ + object reflect extends ReifiedReflect { + val quotesTree = quotes + } - class Embedded(trees: mutable.ListBuffer[tpd.Tree] = mutable.ListBuffer.empty, map: mutable.Map[Symbol, tpd.Tree] = mutable.Map.empty) { - /** Adds the tree and returns it's index */ - def addTree(tree: tpd.Tree, liftedSym: Symbol): Int = { - trees += tree - if (liftedSym ne NoSymbol) - map.put(liftedSym, tree) - trees.length - 1 + /** Encode quote using Reflection.Literal + * + * Generate the code + * ```scala + * quotes => quotes.reflect.TreeMethods.asExpr( + * quotes.reflect.Literal.apply(x$1.reflect.Constant..apply()) + * ).asInstanceOf[scala.quoted.Expr[]] + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def pickleAsLiteral(lit: Literal) = { + val typeName = body.tpe.typeSymbol.name + val literalValue = + if lit.const.tag == Constants.NullTag || lit.const.tag == Constants.UnitTag then Nil + else List(body) + val constModule = lit.const.tag match + case Constants.BooleanTag => defn. Quotes_reflect_BooleanConstant + case Constants.ByteTag => defn. Quotes_reflect_ByteConstant + case Constants.ShortTag => defn. Quotes_reflect_ShortConstant + case Constants.IntTag => defn. Quotes_reflect_IntConstant + case Constants.LongTag => defn. Quotes_reflect_LongConstant + case Constants.FloatTag => defn. Quotes_reflect_FloatConstant + case Constants.DoubleTag => defn. Quotes_reflect_DoubleConstant + case Constants.CharTag => defn. Quotes_reflect_CharConstant + case Constants.StringTag => defn. Quotes_reflect_StringConstant + case Constants.UnitTag => defn. Quotes_reflect_UnitConstant + case Constants.NullTag => defn. Quotes_reflect_NullConstant + case Constants.ClazzTag => defn. Quotes_reflect_ClassOfConstant + reflect.asExpr(body.tpe) { + reflect.Literal { + reflect.self + .select(constModule) + .select(nme.apply) + .appliedToTermArgs(literalValue) + } + } } - /** Type used for the hole that will replace this splice */ - def getHoleType(body: tpd.Tree, splice: tpd.Tree)(using Context): Type = - // For most expressions the splice.tpe but there are some types that are lost by lifting - // that can be recoverd from the original tree. Currently the cases are: - // * Method types: the splice represents a method reference - map.get(body.symbol).map(_.tpe.widen).getOrElse(splice.tpe) + /** Encode quote using Reflection.Literal + * + * Generate the code + * ```scala + * quotes => scala.quoted.ToExpr.{BooleanToExpr,ShortToExpr, ...}.apply()(quotes) + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def liftedValue(lit: Literal, lifter: Symbol) = + val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) + ref(lifter).appliedToType(originalTp).select(nme.apply).appliedTo(lit).appliedTo(quotes) + + def pickleAsValue(lit: Literal) = { + // TODO should all constants be pickled as Literals? + // Should examine the generated bytecode size to decide and performance + lit.const.tag match { + case Constants.NullTag => pickleAsLiteral(lit) + case Constants.UnitTag => pickleAsLiteral(lit) + case Constants.BooleanTag => liftedValue(lit, defn.ToExprModule_BooleanToExpr) + case Constants.ByteTag => liftedValue(lit, defn.ToExprModule_ByteToExpr) + case Constants.ShortTag => liftedValue(lit, defn.ToExprModule_ShortToExpr) + case Constants.IntTag => liftedValue(lit, defn.ToExprModule_IntToExpr) + case Constants.LongTag => liftedValue(lit, defn.ToExprModule_LongToExpr) + case Constants.FloatTag => liftedValue(lit, defn.ToExprModule_FloatToExpr) + case Constants.DoubleTag => liftedValue(lit, defn.ToExprModule_DoubleToExpr) + case Constants.CharTag => liftedValue(lit, defn.ToExprModule_CharToExpr) + case Constants.StringTag => liftedValue(lit, defn.ToExprModule_StringToExpr) + } + } - def isLiftedSymbol(sym: Symbol)(using Context): Boolean = map.contains(sym) + /** Encode quote using QuoteUnpickler.{unpickleExpr, unpickleType} + * + * Generate the code + * ```scala + * quotes => quotes.asInstanceOf[QuoteUnpickler].[]( + * , + * , + * , + * ) + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def pickleAsTasty() = { + val pickleQuote = PickledQuotes.pickleQuote(body) + val pickledQuoteStrings = pickleQuote match + case x :: Nil => Literal(Constant(x)) + case xs => tpd.mkList(xs.map(x => Literal(Constant(x))), TypeTree(defn.StringType)) + + // TODO split holes earlier into types and terms. This all holes in each category can have consecutive indices + val (typeSplices, termSplices) = contents.zipWithIndex.partition { + _._1.tpe.derivesFrom(defn.QuotedTypeClass) + } - /** Get the list of embedded trees */ - def getTrees: List[tpd.Tree] = trees.toList + // This and all closures in typeSplices are removed by the BetaReduce phase + val types = + if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else SeqLiteral(typeSplices.map(_._1), TypeTree(defn.QuotedTypeClass.typeRef.appliedTo(WildcardType))) - override def toString: String = s"Embedded($trees, $map)" + // This and all closures in termSplices are removed by the BetaReduce phase + val termHoles = + if termSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else + Lambda( + MethodType( + List(nme.idx, nme.contents, nme.quotes).map(name => UniqueName.fresh(name).toTermName), + List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType), defn.QuotesClass.typeRef), + defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)), + args => + val cases = termSplices.map { case (splice, idx) => + val defn.FunctionOf(argTypes, defn.FunctionOf(quotesType :: _, _, _, _), _, _) = splice.tpe + val rhs = { + val spliceArgs = argTypes.zipWithIndex.map { (argType, i) => + args(1).select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argType) + } + val Block(List(ddef: DefDef), _) = splice + // TODO: beta reduce inner closure? Or wait until BetaReduce phase? + BetaReduce(ddef, spliceArgs).select(nme.apply).appliedTo(args(2).asInstance(quotesType)) + } + CaseDef(Literal(Constant(idx)), EmptyTree, rhs) + } + cases match + case CaseDef(_, _, rhs) :: Nil => rhs + case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) + ) + + val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass + val quotedType = quoteClass.typeRef.appliedTo(originalTp) + val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) + val unpickleMeth = + if isType then defn.QuoteUnpickler_unpickleTypeV2 + else defn.QuoteUnpickler_unpickleExprV2 + val unpickleArgs = + if isType then List(pickledQuoteStrings, types) + else List(pickledQuoteStrings, types, termHoles) + quotes + .asInstance(defn.QuoteUnpicklerClass.typeRef) + .select(unpickleMeth).appliedToType(originalTp) + .appliedToArgs(unpickleArgs).withSpan(body.span) + } + + /** Encode quote using Reflection.TypeRepr.typeConstructorOf + * + * Generate the code + * ```scala + * quotes.reflect.TypeReprMethods.asType( + * quotes.reflect.TypeRepr.typeConstructorOf(classOf[]]) + * ).asInstanceOf[scala.quoted.Type[]] + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def taggedType() = + reflect.asType(body.tpe) { + reflect.TypeRepr_typeConstructorOf( + TypeApply(ref(defn.Predef_classOf.termRef), body :: Nil) + ) + } + + def getLiteral(tree: tpd.Tree): Option[Literal] = tree match + case tree: Literal => Some(tree) + case Block(Nil, e) => getLiteral(e) + case Inlined(_, Nil, e) => getLiteral(e) + case _ => None + + if (isType) then + if contents.isEmpty && body.symbol.isPrimitiveValueClass then taggedType() + else pickleAsTasty() + else + getLiteral(body) match + case Some(lit) => pickleAsValue(lit) + case _ => pickleAsTasty() } + } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 3c6217cf5171..1b3ff3f0dd7e 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -339,7 +339,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case tree: TypeApply => if tree.symbol.isQuote then ctx.compilationUnit.needsStaging = true - ctx.compilationUnit.needsQuotePickling = true if tree.symbol.is(Inline) then ctx.compilationUnit.needsInlining = true val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala new file mode 100644 index 000000000000..0d25053a64cb --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala @@ -0,0 +1,110 @@ +package dotty.tools.dotc +package transform + +import core._ +import Decorators._ +import Flags._ +import Types._ +import Contexts._ +import Symbols._ +import SymUtils._ +import NameKinds._ +import dotty.tools.dotc.ast.tpd +import tpd._ + +import scala.collection.mutable +import dotty.tools.dotc.core.Annotations._ +import dotty.tools.dotc.core.Names._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.quoted._ +import dotty.tools.dotc.transform.TreeMapWithStages._ +import dotty.tools.dotc.typer.Inliner + +import scala.annotation.constructorOnly + +/** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ +trait ReifiedReflect: + + /** Stable reference to the instance of `scala.quoted.Quotes` */ + def quotesTree: Tree + + def self(using Context): Tree = + quotesTree.select(defn.Quotes_reflect) + + /** Create type for `quotes.reflect.Term` */ + def TermTpt(using Context) = + self.select(defn.Quotes_reflect_TermType) + + /** Create type for `quotes.reflect.TypeTree` */ + def TypeTreeTpt(using Context) = + self.select(defn.Quotes_reflect_TypeTreeType) + + /** Create tree for `quotes.reflect.Apply(, List(*))` */ + def Apply(fn: Tree, args: List[Tree])(using Context) = + val argTrees = tpd.mkList(args, TermTpt) + self.select(defn.Quotes_reflect_Apply) + .select(defn.Quotes_reflect_Apply_apply) + .appliedTo(fn, argTrees) + + /** Create tree for `quotes.reflect.TypeApply(, List(*))` */ + def TypeApply(fn: Tree, args: List[Tree])(using Context) = + val argTrees = tpd.mkList(args, TypeTreeTpt) + self.select(defn.Quotes_reflect_TypeApply) + .select(defn.Quotes_reflect_TypeApply_apply) + .appliedTo(fn, argTrees) + + /** Create tree for `quotes.reflect.Assing(, )` */ + def Assign(lhs: Tree, rhs: Tree)(using Context) = + self.select(defn.Quotes_reflect_Assign) + .select(defn.Quotes_reflect_Assign_apply) + .appliedTo(lhs, rhs) + + /** Create tree for `quotes.reflect.Inferred()` */ + def Inferred(typeTree: Tree)(using Context) = + self.select(defn.Quotes_reflect_Inferred) + .select(defn.Quotes_reflect_Inferred_apply) + .appliedTo(typeTree) + + /** Create tree for `quotes.reflect.Literal()` */ + def Literal(constant: Tree)(using Context) = + self.select(defn.Quotes_reflect_Literal) + .select(defn.Quotes_reflect_Literal_apply) + .appliedTo(constant) + + /** Create tree for `quotes.reflect.TypeRepr.of(Type.of[](quotes))` */ + def TypeReprOf(tpe: Type)(using Context) = + self.select(defn.Quotes_reflect_TypeRepr) + .select(defn.Quotes_reflect_TypeRepr_of) + .appliedToType(tpe) + .appliedTo( + ref(defn.QuotedTypeModule_of) + .appliedToType(tpe) + .appliedTo(quotesTree) + ) + + /** Create tree for `quotes.reflect.TypeRepr.typeConstructorOf()` */ + def TypeRepr_typeConstructorOf(classTree: Tree)(using Context) = + self.select(defn.Quotes_reflect_TypeRepr) + .select(defn.Quotes_reflect_TypeRepr_typeConstructorOf) + .appliedTo(classTree) + + /** Create tree for `quotes.reflect.asTerm()` */ + def asTerm(expr: Tree)(using Context) = + self.select(defn.Quotes_reflect_asTerm) + .appliedTo(expr) + + /** Create tree for `quotes.reflect.TypeReprMethods.asType()` */ + def asType(tpe: Type)(typeRepr: Tree)(using Context) = + self.select(defn.Quotes_reflect_TypeReprMethods) + .select(defn.Quotes_reflect_TypeReprMethods_asType) + .appliedTo(typeRepr) + .asInstance(defn.QuotedTypeClass.typeRef.appliedTo(tpe)) + + /** Create tree for `quotes.reflect.TreeMethods.asExpr().asInstanceOf[]` */ + def asExpr(tpe: Type)(term: Tree)(using Context) = + self.select(defn.Quotes_reflect_TreeMethods) + .select(defn.Quotes_reflect_TreeMethods_asExpr) + .appliedTo(term) + .asInstance(defn.QuotedExprClass.typeRef.appliedTo(tpe)) + +end ReifiedReflect diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala new file mode 100644 index 000000000000..fe0c233173b7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -0,0 +1,392 @@ +package dotty.tools.dotc +package transform + +import core._ +import Decorators._ +import Flags._ +import Types._ +import Contexts._ +import Symbols._ +import Constants._ +import ast.Trees._ +import ast.{TreeTypeMap, untpd} +import util.Spans._ +import SymUtils._ +import NameKinds._ +import dotty.tools.dotc.ast.tpd +import StagingContext._ + +import scala.collection.mutable +import dotty.tools.dotc.core.Annotations._ +import dotty.tools.dotc.core.Names._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.quoted._ +import dotty.tools.dotc.transform.TreeMapWithStages._ +import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.config.ScalaRelease.* + +import scala.annotation.constructorOnly + +object Splicing: + val name: String = "splicing" + +/** Transforms level 1 splices into holes. To do so it transforms the contents of the splice into + * a lambda that receives all cross-quote references. + * + * Cross-quote reference is a reference to a definition that is not defined in the current quote. + * Those references appear in quotes that are nested in a splice. + * + * After this phase we have the invariant where all splices have the following shape + * ``` + * {{{ | | * | (*) => }}} + * ``` + * where `` does not contain any free references to quoted definitions and `*` + * contains the quotes with references to all cross-quote references. There are some special rules + * for references in the LHS of assignments and cross-quote method references. + * + * In the following code example `x1` and `x2` are cross-quote references. + * ``` + * '{ ... + * val x1: T1 = ??? + * val x2: T2 = ??? + * ${ (q: Quotes) ?=> f('{ g(x1, x2) }) }: T3 + * } + * ``` + * + * This phase identifies cross-quote references such as `x1` and replaces it with an `${x1$}`. + * All cross-quote arguments are directly applied in the lambda. + * + * ``` + * '{ ... + * val x1: T1 = ??? + * val x2: T2 = ??? + * {{{ 0 | T3 | x1, x2 | + * (x1$: Expr[T1], x2$: Expr[T2]) => // body of this lambda does not contain references to x1 or x2 + * (q: Quotes) ?=> f('{ g(${x1$}, ${x2$}) }) + * + * }}} + * } + * ``` + * + * and then performs the same transformation on `'{ g(${x1$}, ${x2$}) }`. + * + */ +class Splicing extends MacroTransform: + import tpd._ + + override def phaseName: String = Splicing.name + + override def run(using Context): Unit = + if ctx.compilationUnit.needsStaging then + super.run(using freshStagingContext) + + protected def newTransformer(using Context): Transformer = Level0QuoteTransformer + + /** Transforms all quotes at level 0 using the `QuoteTransformer` */ + private object Level0QuoteTransformer extends Transformer: + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + assert(level == 0) + tree match + case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) + if fn.symbol == defn.QuotedRuntime_exprQuote => + QuoteTransformer().transform(tree) + case TypeApply(_, _) if tree.symbol == defn.QuotedTypeModule_of => + QuoteTransformer().transform(tree) + case tree: DefDef if tree.symbol.is(Inline) => + // Quotes in inlined methods are only pickled after they are inlined. + tree + case _ => + super.transform(tree) + end Level0QuoteTransformer + + + /** Transforms all direct splices in the current quote and replace them with holes. */ + private class QuoteTransformer() extends Transformer: + /** Set of definitions in the current quote */ + private val quotedDefs = mutable.Set.empty[Symbol] + + /** Number of holes created in this quote. Used for indexing holes. */ + private var numHoles = 0 + + /** Mapping from the term symbol of a `Type[T]` to it's hole. Used to deduplicate type holes. */ + private val typeHoles = mutable.Map.empty[Symbol, Hole] + + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + tree match + case Apply(fn, List(splicedCode)) if fn.symbol == defn.QuotedRuntime_exprNestedSplice => + if level > 1 then + val splicedCode1 = super.transform(splicedCode)(using spliceContext) + cpy.Apply(tree)(fn, List(splicedCode1)) + else + val holeIdx = numHoles + numHoles += 1 + val splicer = SpliceTransformer(ctx.owner, quotedDefs.contains) + val newSplicedCode1 = splicer.transformSplice(splicedCode, tree.tpe, holeIdx)(using spliceContext) + val newSplicedCode2 = Level0QuoteTransformer.transform(newSplicedCode1)(using spliceContext) + newSplicedCode2 + case tree: TypeDef if tree.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => + val tp @ TypeRef(qual: TermRef, _) = tree.rhs.tpe.hiBound + quotedDefs += tree.symbol + val hole = typeHoles.get(qual.symbol) match + case Some (hole) => cpy.Hole(hole)(content = EmptyTree) + case None => + val holeIdx = numHoles + numHoles += 1 + val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp)) + typeHoles.put(qual.symbol, hole) + hole + cpy.TypeDef(tree)(rhs = hole) + case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) + if fn.symbol == defn.QuotedRuntime_exprQuote => + super.transform(tree)(using quoteContext) + case _: Template => + for sym <- tree.symbol.owner.info.decls do + quotedDefs += sym + super.transform(tree) + case tree: DefTree => + quotedDefs += tree.symbol + transformAnnotations(tree) + super.transform(tree) + case _: TypeTree => + super.transform(tree).withType(transformAnnotTrees(tree.tpe)) + case _ => + super.transform(tree) + + private def transformAnnotations(tree: DefTree)(using Context): Unit = + tree.symbol.annotations = tree.symbol.annotations.mapconserve { annot => + val newAnnotTree = transform(annot.tree)(using ctx.withOwner(tree.symbol)) + if (annot.tree == newAnnotTree) annot + else ConcreteAnnotation(newAnnotTree) + } + + /** Transform trees within annotations */ + private def transformAnnotTrees(using Context) = new TypeMap { + override def apply(tp: Type): Type = { + tp match + case tp @ AnnotatedType(underlying, annot) => + val underlying1 = this(underlying) + derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree))) + case _ => mapOver(tp) + } + } + + end QuoteTransformer + + /** Transforms a splice at level 1 into a hole + * + * Finds all terms and types that are defined in the current quote and used within this splice. + * The resulting hole will contain all of these terms and types as arguments. + * Note that these captured variables are stage correct. + * + * For a `x` of type `T1` and a type `X` defined in the current quote + * ```scala + * ${ (using Quotes) ?=> {... x ... X ...} }: T2 + * ``` + * is transformed into + * ```scala + * {{{ | T2 | x, X | (x$1: Expr[T1], X$1: Type[X]) => (using Quotes) ?=> {... ${x$1} ... X$1.Underlying ...} }}} + * ``` + */ + private class SpliceTransformer(spliceOwner: Symbol, isCaptured: Symbol => Boolean) extends Transformer: + private var refBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)] + /** Reference to the `Quotes` instance of the current level 1 splice */ + private var quotes: Tree | Null = null // TODO: add to the context + private var healedTypes: PCPCheckAndHeal.QuoteTypeTags | Null = null // TODO: add to the context + + def transformSplice(tree: tpd.Tree, tpe: Type, holeIdx: Int)(using Context): tpd.Tree = + assert(level == 0) + val newTree = transform(tree) + val (refs, bindings) = refBindingMap.values.toList.unzip + val bindingsTypes = bindings.map(_.termRef.widenTermRefExpr) + val methType = MethodType(bindingsTypes, newTree.tpe) + val meth = newSymbol(spliceOwner, nme.ANON_FUN, Synthetic | Method, methType) + val ddef = DefDef(meth, List(bindings), newTree.tpe, newTree.changeOwner(ctx.owner, meth)) + val fnType = defn.FunctionType(bindings.size, isContextual = false).appliedTo(bindingsTypes :+ newTree.tpe) + val closure = Block(ddef :: Nil, Closure(Nil, ref(meth), TypeTree(fnType))) + tpd.Hole(true, holeIdx, refs, closure, TypeTree(tpe)) + + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + tree match + case tree: RefTree => + if tree.isTerm then + if isCaptured(tree.symbol) then + val tpe = tree.tpe.widenTermRefExpr match { + case tpw: MethodicType => tpw.toFunctionType(isJava = false) + case tpw => tpw + } + spliced(tpe)(capturedTerm(tree)) + else super.transform(tree) + else // tree.isType then + if containsCapturedType(tree.tpe) then + if level >= 1 then getTagRefFor(tree) + else + // Dealias references to captured types + TypeTree(tree.tpe.dealias) + else super.transform(tree) + case tree: TypeTree => + if containsCapturedType(tree.tpe) && level >= 1 then getTagRefFor(tree) + else tree + case tree @ Assign(lhs: RefTree, rhs) => + if isCaptured(lhs.symbol) then transformSplicedAssign(tree) + else super.transform(tree) + case Apply(fn, args) if fn.symbol == defn.QuotedRuntime_exprNestedSplice => + val newArgs = args.mapConserve(arg => transform(arg)(using spliceContext)) + cpy.Apply(tree)(fn, newArgs) + case Apply(sel @ Select(app @ Apply(fn, args),nme.apply), quotesArgs) + if fn.symbol == defn.QuotedRuntime_exprQuote => + args match + case List(tree: RefTree) if isCaptured(tree.symbol) => + capturedTerm(tree) + case _ => + val newArgs = withCurrentQuote(quotesArgs.head) { + if level > 1 then args.mapConserve(arg => transform(arg)(using quoteContext)) + else args.mapConserve(arg => transformLevel0QuoteContent(arg)(using quoteContext)) + } + cpy.Apply(tree)(cpy.Select(sel)(cpy.Apply(app)(fn, newArgs), nme.apply), quotesArgs) + case Apply(TypeApply(_, List(tpt)), List(quotes)) + if tree.symbol == defn.QuotedTypeModule_of && containsCapturedType(tpt.tpe) => + ref(capturedType(tpt))(using ctx.withSource(tree.source)).withSpan(tree.span) + case CapturedApplication(fn, argss) => + transformCapturedApplication(tree, fn, argss) + case _ => + super.transform(tree) + + private def transformLevel0QuoteContent(tree: Tree)(using Context): Tree = + // transform and collect new healed types + val old = healedTypes + healedTypes = new PCPCheckAndHeal.QuoteTypeTags(tree.span) + val tree1 = transform(tree) + val newHealedTypes = healedTypes.nn.getTypeTags + healedTypes = old + // add new healed types to the current, merge with existing healed types if necessary + if newHealedTypes.isEmpty then tree1 + else tree1 match + case Block(stats @ (x :: _), expr) if x.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => + Block(newHealedTypes ::: stats, expr) + case _ => + Block(newHealedTypes, tree1) + + class ArgsClause(val args: List[Tree]): + def isTerm: Boolean = args.isEmpty || args.head.isTerm + + private object CapturedApplication { + + /** Matches and application `f(...)` (possibly with several argument clauses) where `f` is captured */ + def unapply(tree: Tree)(using Context): Option[(RefTree, List[ArgsClause])] = tree match + case GenericApply(fn: RefTree, args) if isCaptured(fn.symbol) => + Some((fn, ArgsClause(args) :: Nil)) + case GenericApply(CapturedApplication(fn, argss), args) => + Some((fn, argss :+ ArgsClause(args))) + case _ => + None + } + + private def containsCapturedType(tpe: Type)(using Context): Boolean = + tpe.existsPart(t => isCaptured(t.typeSymbol) || isCaptured(t.termSymbol), StopAt.Static) + + /** Transform an assignment `x = e` with a captured `x` to + * `${ Assign(x$1.asTerm, '{e}.asTerm).asExpr.asInstanceOf[Expr[T]] }` + * + * Registers `x` as a captured variable in the hole and creates an `x$1` `Expr` reference to it. + */ + private def transformSplicedAssign(tree: Assign)(using Context): Tree = + spliced(tree.tpe) { + reflect.asExpr(tree.tpe)( + reflect.Assign( + reflect.asTerm(capturedTerm(tree.lhs)), + reflect.asTerm(quoted(tree.rhs)) + ) + ) + } + + /** Transform an application `f(a1, a2, ...)` with a captured `f` to + * `${ Apply(f$1.asTerm, List('{a1$}.asTerm, '{a2$}.asTerm, ...)).asExpr.asInstanceOf[Expr[T]] }` + * + * Registers `f` as a captured variable in the hole and creates an `f$1` `Expr` reference to it. + * + * It also handles cases with multiple argument clauses using nested `Apply`/`TypeApply`. + */ + private def transformCapturedApplication(tree: Tree, fn: RefTree, argss: List[ArgsClause])(using Context): Tree = + spliced(tree.tpe) { + def TermList(args: List[Tree]): List[Tree] = + args.map(arg => reflect.asTerm(quoted(transform(arg)(using spliceContext)))) + def TypeTreeList(args: List[Tree]): List[Tree] = + args.map(arg => reflect.Inferred(reflect.TypeReprOf(transform(arg)(using spliceContext).tpe))) + reflect.asExpr(tree.tpe) { + argss.foldLeft[Tree](reflect.asTerm(capturedTerm(fn, defn.AnyType))) { (acc, clause) => + if clause.isTerm then reflect.Apply(acc, TermList(clause.args)) + else reflect.TypeApply(acc, TypeTreeList(clause.args)) + } + } + } + + private def capturedTerm(tree: Tree)(using Context): Tree = + val tpe = tree.tpe.widenTermRefExpr match + case tpw: MethodicType => tpw.toFunctionType(isJava = false) + case tpw => tpw + capturedTerm(tree, tpe) + + private def capturedTerm(tree: Tree, tpe: Type)(using Context): Tree = + def newBinding = newSymbol( + spliceOwner, + UniqueName.fresh(tree.symbol.name.toTermName).toTermName, + Param, + defn.QuotedExprClass.typeRef.appliedTo(tpe), + ) + val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (tree, newBinding))._2 + ref(bindingSym) + + private def capturedType(tree: Tree)(using Context): Symbol = + val tpe = tree.tpe.widenTermRefExpr + def newBinding = newSymbol( + spliceOwner, + UniqueName.fresh(nme.Type).toTermName, + Param, + defn.QuotedTypeClass.typeRef.appliedTo(tpe), + ) + val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (TypeTree(tree.tpe), newBinding))._2 + bindingSym + + private def getTagRefFor(tree: Tree)(using Context): Tree = + val capturedTypeSym = capturedType(tree) + TypeTree(healedTypes.nn.getTagRef(capturedTypeSym.termRef)) + + private def withCurrentQuote[T](newQuotes: Tree)(body: => T)(using Context): T = + if level == 0 then + val savedQuotes = quotes + quotes = newQuotes + try body + finally quotes = savedQuotes + else body + + private def spliced(tpe: Type)(body: Context ?=> Tree)(using Context): Tree = + val exprTpe = defn.QuotedExprClass.typeRef.appliedTo(tpe) + val closure = + val methTpe = ContextualMethodType(List(defn.QuotesClass.typeRef), exprTpe) + val meth = newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, methTpe) + Closure(meth, argss => { + withCurrentQuote(argss.head.head) { + body(using ctx.withOwner(meth)).changeOwner(ctx.owner, meth) + } + }) + ref(defn.QuotedRuntime_exprNestedSplice) + .appliedToType(tpe) + .appliedTo(Literal(Constant(null))) // Dropped when creating the Hole that contains it + .appliedTo(closure) + + private def quoted(expr: Tree)(using Context): Tree = + val tpe = expr.tpe.widenTermRefExpr + ref(defn.QuotedRuntime_exprQuote) + .appliedToType(tpe) + .appliedTo(expr) + .select(nme.apply) + .appliedTo(quotes.nn) + + /** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ + private object reflect extends ReifiedReflect { + def quotesTree = quotes.nn + } + + end SpliceTransformer + +end Splicing diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index a327cff83950..1de050a9a6c1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -31,7 +31,7 @@ class Staging extends MacroTransform { override def allowsImplicitSearch: Boolean = true override def checkPostCondition(tree: Tree)(using Context): Unit = - if (ctx.phase <= pickleQuotesPhase) { + if (ctx.phase <= splicingPhase) { // Recheck that PCP holds but do not heal any inconsistent types as they should already have been heald tree match { case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass => diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index bd3f4f44984b..1d3c4cd644f7 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -573,6 +573,37 @@ class TreeChecker extends Phase with SymTransformer { else super.typedPackageDef(tree) + override def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = { + val tree1 @ Hole(isTermHole, _, args, content, tpt) = super.typedHole(tree, pt) + + // Check result type of the hole + if isTermHole then assert(tpt.typeOpt <:< pt) + else assert(tpt.typeOpt =:= pt) + + // Check that the types of the args conform to the types of the contents of the hole + val argQuotedTypes = args.map { arg => + if arg.isTerm then + val tpe = arg.typeOpt.widenTermRefExpr match + case _: MethodicType => + // Special erasure for captured function references + // See `SpliceTransformer.transformCapturedApplication` + defn.AnyType + case tpe => tpe + defn.QuotedExprClass.typeRef.appliedTo(tpe) + else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt) + } + val expectedResultType = + if isTermHole then defn.QuotedExprClass.typeRef.appliedTo(tpt.typeOpt) + else defn.QuotedTypeClass.typeRef.appliedTo(tpt.typeOpt) + val contextualResult = + defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) + val expectedContentType = + defn.FunctionOf(argQuotedTypes, contextualResult) + assert(content.typeOpt =:= expectedContentType) + + tree1 + } + override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(using Context): Tree = tree diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 67bc68c4a495..d097ed8dcc4e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1629,12 +1629,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { case res => specializeEq(inlineIfNeeded(res)) } - if res.symbol == defn.QuotedRuntime_exprQuote then - ctx.compilationUnit.needsQuotePickling = true res override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = - inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) + val tree1 = inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) + if tree1.symbol.isQuote then + ctx.compilationUnit.needsStaging = true + tree1 override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = val tree1 = diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index f8422ed20e21..4a9102df211d 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -187,6 +187,10 @@ trait QuotesAndSplices { using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx))) pat.select(tpnme.Underlying) + def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = + val tpt = typedType(tree.tpt) + assignType(tree, tpt) + private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit = if (level == 0 && !ctx.owner.ownersIterator.exists(_.is(Inline))) report.error("Splice ${...} outside quotes '{...} or inline method", tree.srcPos) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index e8924b418c3f..80068d8632e6 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -536,6 +536,10 @@ trait TypeAssigner { def assignType(tree: untpd.PackageDef, pid: Tree)(using Context): PackageDef = tree.withType(pid.symbol.termRef) + + def assignType(tree: untpd.Hole, tpt: Tree)(using Context): Hole = + tree.withType(tpt.tpe) + } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4e03f19d42f2..0f0e0a2a5744 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2901,6 +2901,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.Splice => typedSplice(tree, pt) case tree: untpd.TypSplice => typedTypSplice(tree, pt) case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here + case tree: untpd.Hole => typedHole(tree, pt) case _ => typedUnadapted(desugar(tree), pt, locked) } diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 37da7dafd25a..da560ab6e240 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -3037,11 +3037,19 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end reflect def unpickleExpr[T](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?]): scala.quoted.Expr[T] = - val tree = PickledQuotes.unpickleTerm(pickled, typeHole, termHole) + val tree = PickledQuotes.unpickleTerm(pickled, PickledQuotes.TypeHole.V1(typeHole), PickledQuotes.ExprHole.V1(termHole)) + new ExprImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Expr[T]] + + def unpickleExprV2[T](pickled: String | List[String], types: Seq[Type[?]], termHole: Null | ((Int, Seq[Type[?] | Expr[Any]], Quotes) => Expr[?])): scala.quoted.Expr[T] = + val tree = PickledQuotes.unpickleTerm(pickled, PickledQuotes.TypeHole.V2(types), PickledQuotes.ExprHole.V2(termHole)) new ExprImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Expr[T]] def unpickleType[T <: AnyKind](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?]): scala.quoted.Type[T] = - val tree = PickledQuotes.unpickleTypeTree(pickled, typeHole, termHole) + val tree = PickledQuotes.unpickleTypeTree(pickled, PickledQuotes.TypeHole.V1(typeHole)) + new TypeImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Type[T]] + + def unpickleTypeV2[T <: AnyKind](pickled: String | List[String], types: Seq[Type[?]]): scala.quoted.Type[T] = + val tree = PickledQuotes.unpickleTypeTree(pickled, PickledQuotes.TypeHole.V2(types)) new TypeImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Type[T]] object ExprMatch extends ExprMatchModule: diff --git a/library/src/scala/quoted/runtime/QuoteUnpickler.scala b/library/src/scala/quoted/runtime/QuoteUnpickler.scala index 2d8ef54eb9e6..63e62658cbb4 100644 --- a/library/src/scala/quoted/runtime/QuoteUnpickler.scala +++ b/library/src/scala/quoted/runtime/QuoteUnpickler.scala @@ -7,11 +7,28 @@ trait QuoteUnpickler: /** Unpickle `repr` which represents a pickled `Expr` tree, * replacing splice nodes with `holes` + * + * Generated for code compiled with Scala 3.0.x and 3.1.x */ def unpickleExpr[T](pickled: String | List[String], typeHole: (Int, Seq[Any]) => Type[?], termHole: (Int, Seq[Any], Quotes) => Expr[?]): scala.quoted.Expr[T] + /** Unpickle `repr` which represents a pickled `Expr` tree, + * replacing splice nodes with `holes`. + * + * Generated for code compiled with Scala 3.2.0+ + */ + def unpickleExprV2[T](pickled: String | List[String], types: Null | Seq[Type[?]], termHole: Null | ((Int, Seq[Type[?] | Expr[Any]], Quotes) => Expr[?])): scala.quoted.Expr[T] + /** Unpickle `repr` which represents a pickled `Type` tree, * replacing splice nodes with `holes` + * + * Generated for code compiled with Scala 3.0.x and 3.1.x */ def unpickleType[T <: AnyKind](pickled: String | List[String], typeHole: (Int, Seq[Any]) => Type[?], termHole: (Int, Seq[Any], Quotes) => Expr[?]): scala.quoted.Type[T] + /** Unpickle `repr` which represents a pickled `Type` tree, + * replacing splice nodes with `holes` + * + * Generated for code compiled with Scala 3.2.0+ + */ + def unpickleTypeV2[T <: AnyKind](pickled: String | List[String], types: Null | Seq[Type[?]]): scala.quoted.Type[T] diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 2d41aca4fed1..6c68ce216685 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -3,7 +3,13 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( - // Experimental APIs that can be added in 3.2.0 or later + // APIs that must be added in 3.2.0 + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleTypeV2"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleTypeV2"), + + // Experimental APIs that can be added in 3.2.0 ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.append"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/app/Main.scala b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/app/Main.scala similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/app/Main.scala rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/app/Main.scala diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/build.sbt b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/build.sbt similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/build.sbt rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/build.sbt diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/lib/Top.scala b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/lib/Top.scala similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/lib/Top.scala rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/lib/Top.scala diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/project/DottyInjectedPlugin.scala similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/project/DottyInjectedPlugin.scala rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/project/DottyInjectedPlugin.scala diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/test b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/test similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/test rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/test diff --git a/tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala b/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala similarity index 89% rename from tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala rename to sbt-test/scala3-compat/macros-backward-3.0/app/App.scala index 8c0a8004b9cf..ce7bf1b45b13 100644 --- a/tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala +++ b/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala @@ -1,4 +1,6 @@ -import Macros.* +package app + +import lib.* def powerTest(x: Double): Unit = power(x, 0) diff --git a/sbt-test/scala3-compat/macros-backward-3.0/build.sbt b/sbt-test/scala3-compat/macros-backward-3.0/build.sbt new file mode 100644 index 000000000000..6e5bd200ecb1 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/build.sbt @@ -0,0 +1,13 @@ +lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") + +lazy val lib = project.in(file("lib")) + .settings( + scalaVersion := "3.0.2", + scalacOptions ++= checkOptions, + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalacOptions ++= checkOptions, + ) diff --git a/sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala b/sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala new file mode 100644 index 000000000000..12e69a6ce4fd --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala @@ -0,0 +1,20 @@ +package lib + +import scala.quoted.* + +inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + +private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + +private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + +inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + +private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/scala3-compat/macros-backward-3.0/test b/sbt-test/scala3-compat/macros-backward-3.0/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/test @@ -0,0 +1 @@ +> app/compile diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala b/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala similarity index 89% rename from tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala rename to sbt-test/scala3-compat/macros-backward-3.1/app/App.scala index 8c0a8004b9cf..ce7bf1b45b13 100644 --- a/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala +++ b/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala @@ -1,4 +1,6 @@ -import Macros.* +package app + +import lib.* def powerTest(x: Double): Unit = power(x, 0) diff --git a/sbt-test/scala3-compat/macros-backward-3.1/build.sbt b/sbt-test/scala3-compat/macros-backward-3.1/build.sbt new file mode 100644 index 000000000000..024e9be97524 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/build.sbt @@ -0,0 +1,13 @@ +lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") + +lazy val lib = project.in(file("lib")) + .settings( + scalaVersion := "3.1.1", + scalacOptions ++= checkOptions, + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalacOptions ++= checkOptions, + ) diff --git a/sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala b/sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala new file mode 100644 index 000000000000..12e69a6ce4fd --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala @@ -0,0 +1,20 @@ +package lib + +import scala.quoted.* + +inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + +private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + +private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + +inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + +private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/scala3-compat/macros-backward-3.1/test b/sbt-test/scala3-compat/macros-backward-3.1/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/test @@ -0,0 +1 @@ +> app/compile diff --git a/staging/src/scala/quoted/staging/QuoteCompiler.scala b/staging/src/scala/quoted/staging/QuoteCompiler.scala index 7b1899821f96..eee2dacdc5f5 100644 --- a/staging/src/scala/quoted/staging/QuoteCompiler.scala +++ b/staging/src/scala/quoted/staging/QuoteCompiler.scala @@ -16,7 +16,7 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types.ExprType import dotty.tools.dotc.quoted.PickledQuotes import dotty.tools.dotc.transform.Splicer.checkEscapedVariables -import dotty.tools.dotc.transform.{Inlining, Staging, PickleQuotes} +import dotty.tools.dotc.transform.{Inlining, Staging, Splicing, PickleQuotes} import dotty.tools.dotc.util.Spans.Span import dotty.tools.dotc.util.{SourceFile, NoSourcePosition} import dotty.tools.io.{Path, VirtualFile} @@ -41,6 +41,7 @@ private class QuoteCompiler extends Compiler: override protected def picklerPhases: List[List[Phase]] = List(new Inlining) :: List(new Staging) :: + List(new Splicing) :: List(new PickleQuotes) :: Nil diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala b/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala deleted file mode 100644 index fb06e93f91c0..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala +++ /dev/null @@ -1,20 +0,0 @@ -import scala.quoted.* - -object Macros: - - inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } - - private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = - unrolledPowerCode(x, n.valueOrError) - - private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = - if n == 0 then '{ 1.0 } // tests simple quotes without splices - else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices - else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture - - - inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } - - private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = - // tests use of Type - '{ val y: T = $x; $body(y): U } diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/why.md b/tests/disabled/pos-macros/forwardCompat-3.1/why.md deleted file mode 100644 index f281f9c08662..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.1/why.md +++ /dev/null @@ -1 +0,0 @@ -Disabled until https://github.com/lampepfl/dotty/issues/14306 is fixed diff --git a/tests/pos-macros/InlinedTypeOf.scala b/tests/pos-macros/InlinedTypeOf.scala new file mode 100644 index 000000000000..cd708eb02322 --- /dev/null +++ b/tests/pos-macros/InlinedTypeOf.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +class Sm[T](t: T) + +object Foo { + + inline def foo[T] = { compiletime.summonInline[Type[T]]; ??? } + + def toexpr[T: Type](using Quotes) = foo[Sm[T]] + +} diff --git a/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala b/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala index fb06e93f91c0..868dae764e23 100644 --- a/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala +++ b/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala @@ -18,3 +18,13 @@ object Macros: private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = // tests use of Type '{ val y: T = $x; $body(y): U } + + + // Does not compile on 3.0 + // inline def poly: Int = ${ polyCode } + // private def polyCode(using Quotes): Expr[Int] = + // def bar[T: Type](x: Expr[T])(using Quotes): Expr[T] = x + // '{ + // def f[T](x: T): T = ${ bar('x) } + // f[Int](1) + // } diff --git a/tests/pos-macros/backwardCompat-3.0/Test_2.scala b/tests/pos-macros/backwardCompat-3.0/Test_2.scala index 8c0a8004b9cf..d88e018068c8 100644 --- a/tests/pos-macros/backwardCompat-3.0/Test_2.scala +++ b/tests/pos-macros/backwardCompat-3.0/Test_2.scala @@ -13,3 +13,6 @@ def letTest: Unit = let(new Foo) { _.hashCode } class Foo + +// Does not compile on 3.0 +// def polyTest: Unit = poly diff --git a/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala b/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala index fb06e93f91c0..9bce5bb93e46 100644 --- a/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala +++ b/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala @@ -18,3 +18,13 @@ object Macros: private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = // tests use of Type '{ val y: T = $x; $body(y): U } + + + // Does not compile on 3.1 + // inline def poly: Int = ${ polyCode } + // private def polyCode(using Quotes): Expr[Int] = + // def bar[T: Type](x: Expr[T])(using Quotes): Expr[T] = x + // '{ + // def f[T](x: T): T = ${ bar('x) } + // f[Int](1) + // } diff --git a/tests/pos-macros/backwardCompat-3.1/Test_2.scala b/tests/pos-macros/backwardCompat-3.1/Test_2.scala index 8c0a8004b9cf..b7613398981f 100644 --- a/tests/pos-macros/backwardCompat-3.1/Test_2.scala +++ b/tests/pos-macros/backwardCompat-3.1/Test_2.scala @@ -13,3 +13,6 @@ def letTest: Unit = let(new Foo) { _.hashCode } class Foo + +// Does not compile on 3.1 +// def polyTest: Unit = poly diff --git a/tests/pos-macros/baseCompat/Macro_1.scala b/tests/pos-macros/baseCompat/Macro_1.scala index fb06e93f91c0..9dcf082ba023 100644 --- a/tests/pos-macros/baseCompat/Macro_1.scala +++ b/tests/pos-macros/baseCompat/Macro_1.scala @@ -18,3 +18,13 @@ object Macros: private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = // tests use of Type '{ val y: T = $x; $body(y): U } + + + inline def poly: Int = ${ polyCode } + + private def polyCode(using Quotes): Expr[Int] = + def bar[T: Type](x: Expr[T])(using Quotes): Expr[T] = x + '{ + def f[T](x: T): T = ${ bar('x) } + f[Int](1) + } diff --git a/tests/pos-macros/baseCompat/Test_2.scala b/tests/pos-macros/baseCompat/Test_2.scala index 8c0a8004b9cf..5bfed5960b3e 100644 --- a/tests/pos-macros/baseCompat/Test_2.scala +++ b/tests/pos-macros/baseCompat/Test_2.scala @@ -13,3 +13,5 @@ def letTest: Unit = let(new Foo) { _.hashCode } class Foo + +def polyTest: Unit = poly diff --git a/tests/pos-macros/captured-quoted-def-1/Macro_1.scala b/tests/pos-macros/captured-quoted-def-1/Macro_1.scala new file mode 100644 index 000000000000..fadde58dd168 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-1/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y: Int = 1; ${ identity('y) } } diff --git a/tests/pos-macros/captured-quoted-def-1/Test_2.scala b/tests/pos-macros/captured-quoted-def-1/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-1/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-2/Macro_1.scala b/tests/pos-macros/captured-quoted-def-2/Macro_1.scala new file mode 100644 index 000000000000..3d090311bca1 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-2/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y(): Int = 1; ${ identity('{ y() }) } } diff --git a/tests/pos-macros/captured-quoted-def-2/Test_2.scala b/tests/pos-macros/captured-quoted-def-2/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-2/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-3/Macro_1.scala b/tests/pos-macros/captured-quoted-def-3/Macro_1.scala new file mode 100644 index 000000000000..c3ccfdda56a4 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-3/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y(i: Int): Int = 1; ${ identity('{ y(1) }) } } diff --git a/tests/pos-macros/captured-quoted-def-3/Test_2.scala b/tests/pos-macros/captured-quoted-def-3/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-3/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-4/Macro_1.scala b/tests/pos-macros/captured-quoted-def-4/Macro_1.scala new file mode 100644 index 000000000000..e88084be84b3 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-4/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y(i: Int)(j: Int, k: Int): Int = 1; ${ identity('{ y(1)(2, 3) }) } } diff --git a/tests/pos-macros/captured-quoted-def-4/Test_2.scala b/tests/pos-macros/captured-quoted-def-4/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-4/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-5/Macro_1.scala b/tests/pos-macros/captured-quoted-def-5/Macro_1.scala new file mode 100644 index 000000000000..b047ec26b621 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-5/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y[T](i: T): T = i; ${ identity('{ y[Int](1) }) } } diff --git a/tests/pos-macros/captured-quoted-def-5/Test_2.scala b/tests/pos-macros/captured-quoted-def-5/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-5/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala b/tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala new file mode 100644 index 000000000000..533125bdef90 --- /dev/null +++ b/tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted.* + +inline def myMacro: Any = ${ myMacroExpr('{1}) } + +def myMacroExpr(x: Expr[Int])(using Quotes): Expr[Any] = + '{ + def f(using q1: Quotes) = '{ 1 + ${Expr($x)} } + () + } diff --git a/tests/pos-macros/captured-quoted-multy-stage/Test_2.scala b/tests/pos-macros/captured-quoted-multy-stage/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-multy-stage/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-type-1/Macro_1.scala b/tests/pos-macros/captured-quoted-type-1/Macro_1.scala new file mode 100644 index 000000000000..fdea3ecf4f7f --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-1/Macro_1.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def myMacro: Any = ${ myMacroExpr } + +def myMacroExpr(using Quotes): Expr[Any] = + '{ + def f[Z] = + ${ identity('{ val y: Z = ??? }) } + 42 + } diff --git a/tests/pos-macros/captured-quoted-type-1/Test_2.scala b/tests/pos-macros/captured-quoted-type-1/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-1/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-type-2/Macro_1.scala b/tests/pos-macros/captured-quoted-type-2/Macro_1.scala new file mode 100644 index 000000000000..b9cf18de7ac4 --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-2/Macro_1.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def myMacro: Any = ${ myMacroExpr } + +def myMacroExpr[T: Type](using Quotes): Expr[Any] = + '{ + def f[Z] = + ${ identity('{ val y: (T, Z) = ??? }) } + 42 + } diff --git a/tests/pos-macros/captured-quoted-type-2/Test_2.scala b/tests/pos-macros/captured-quoted-type-2/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-2/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala b/tests/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala deleted file mode 100644 index fb06e93f91c0..000000000000 --- a/tests/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala +++ /dev/null @@ -1,20 +0,0 @@ -import scala.quoted.* - -object Macros: - - inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } - - private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = - unrolledPowerCode(x, n.valueOrError) - - private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = - if n == 0 then '{ 1.0 } // tests simple quotes without splices - else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices - else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture - - - inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } - - private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = - // tests use of Type - '{ val y: T = $x; $body(y): U } diff --git a/tests/pos-macros/i12440.scala b/tests/pos-macros/i12440.scala new file mode 100644 index 000000000000..4b4c56fef568 --- /dev/null +++ b/tests/pos-macros/i12440.scala @@ -0,0 +1,21 @@ +import scala.quoted.* + +trait Mirror: + type ElemTypes <: Tuple + +class Eq: + + def test1(using Quotes): Unit = '{ + val m: Mirror = ??? + ${ summonType[m.ElemTypes]; ??? } + ${ summonType[List[m.ElemTypes]]; ??? } + } + + def test2(using Quotes): Unit = '{ + val m: Mirror = ??? + type ET = m.ElemTypes + ${ summonType[ET]; ??? } + ${ summonType[List[ET]]; ??? } + } + + def summonType[X](using Type[X]) = ??? diff --git a/tests/pos-macros/i13563.scala b/tests/pos-macros/i13563.scala new file mode 100644 index 000000000000..91a36f447d4a --- /dev/null +++ b/tests/pos-macros/i13563.scala @@ -0,0 +1,3 @@ +import scala.quoted.* +def foo(using Quotes): Unit = + '{ def bar[T](): Unit = ${ summon[Type[T]]; ??? }; () } diff --git a/tests/pos-macros/i13732.scala b/tests/pos-macros/i13732.scala new file mode 100644 index 000000000000..88d987817ca7 --- /dev/null +++ b/tests/pos-macros/i13732.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +def generateImpl(using q: Quotes): Expr[Unit] = + '{ def runEffect[T]: T = ${ runEffectImpl[T] } } + +inline def runEffectImpl[T: Type]: Expr[T] = ??? diff --git a/tests/pos-macros/i4774f.scala b/tests/pos-macros/i4774f.scala index 59d21eb2d2d4..336f20e19280 100644 --- a/tests/pos-macros/i4774f.scala +++ b/tests/pos-macros/i4774f.scala @@ -7,4 +7,10 @@ object Test { def loop2[T](x: Expr[T])(implicit t: Type[T], qctx: Quotes): Expr[T] = '{ def y(): T = $x; ${ loop2('{y()}) } } + + def loop3[T](x: Expr[T])(using Type[T], Quotes): Expr[T] = + '{ def y(i: Int): T = $x; ${ loop2('{y(1)}) } } + + def loop4[T](x: Expr[T])(using Type[T], Quotes): Expr[T] = + '{ def y(i: Int)(j: Int): T = $x; ${ loop2('{y(1)(2)}) } } } diff --git a/tests/pos-macros/i8100.scala b/tests/pos-macros/i8100.scala index 0cf80fe920ee..1a6b5f3d5ba6 100644 --- a/tests/pos-macros/i8100.scala +++ b/tests/pos-macros/i8100.scala @@ -14,8 +14,8 @@ def f[T: Type](using Quotes) = ${ g[m.E](using Type.of[ME]) } ${ g[ME](using Type.of[m.E]) } ${ g[m.E](using Type.of[m.E]) } - // ${ g[ME] } // FIXME: issue seems to be in PickleQuotes - // ${ g[m.E] } // FIXME: issue seems to be in PickleQuotes + ${ g[ME] } + ${ g[m.E] } } def g[T](using Type[T]) = ??? diff --git a/tests/pos-macros/typetags.scala b/tests/pos-macros/typetags.scala index 3a6e60c2e91c..2cf285888272 100644 --- a/tests/pos-macros/typetags.scala +++ b/tests/pos-macros/typetags.scala @@ -7,5 +7,9 @@ object Test { implicitly[Type[List[Int]]] implicitly[Type[T]] implicitly[Type[List[T]]] + Type.of[Int] + Type.of[List[Int]] + Type.of[T] + Type.of[List[T]] } }