From 2005c87babc63825a735f76840dedd3555877715 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 19 May 2021 14:28:40 +0200 Subject: [PATCH] Re-architecture quote pickling Separate the logic that creates holes in quotes from the logic that pickles the quotes. Holes are created in the `Splicer` phase and the result of the transformation can be `-Ycheck`ed. Now, the `PickleQuotes` phase only needs to extract the contents of the holes, pickle the quote and put them into a call to `unpickleExprV2`/`unpickleTypeV2`. We add `unpickleExprV2` to support some optimization in the encoding of the pickled quote. Namely we removed an unnecessary lambda from the arguments of the hole passed into the contents of the hole. By not changing `unpickleExpr` the current compiler will be able to handle the old encoding in binaries compiled with older compilers. The `unpickleTypeV2` is just a version of `unpickleType` that does not take the `termHole` parameter which is always `null`. With `-Yscala-relese` 3.0 or 3.1, the compiler will generate calls to the old `unpickleExpr`/`unpickleType`. Fixes #8100 Fixes #12440 Fixes #13563 Fixes #14337 Fixes #14373 Closes #13732 --- .../dotty/tools/dotc/CompilationUnit.scala | 8 +- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../dotty/tools/dotc/ast/TreeTypeMap.scala | 7 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 32 +- compiler/src/dotty/tools/dotc/ast/tpd.scala | 9 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 + .../dotty/tools/dotc/core/Definitions.scala | 36 + .../src/dotty/tools/dotc/core/Phases.scala | 8 +- .../tools/dotc/core/StagingContext.scala | 10 +- .../src/dotty/tools/dotc/core/StdNames.scala | 3 + .../tools/dotc/core/tasty/TreePickler.scala | 4 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 4 +- .../tools/dotc/printing/RefinedPrinter.scala | 8 +- .../tools/dotc/quoted/PickledQuotes.scala | 110 ++- .../dotty/tools/dotc/transform/Inlining.scala | 2 - .../tools/dotc/transform/PickleQuotes.scala | 741 +++++++----------- .../tools/dotc/transform/PostTyper.scala | 1 - .../tools/dotc/transform/ReifiedReflect.scala | 110 +++ .../dotty/tools/dotc/transform/Splicing.scala | 398 ++++++++++ .../dotty/tools/dotc/transform/Staging.scala | 2 +- .../tools/dotc/transform/TreeChecker.scala | 31 + .../src/dotty/tools/dotc/typer/Inliner.scala | 7 +- .../tools/dotc/typer/QuotesAndSplices.scala | 4 + .../dotty/tools/dotc/typer/TypeAssigner.scala | 4 + .../src/dotty/tools/dotc/typer/Typer.scala | 1 + .../quoted/runtime/impl/QuotesImpl.scala | 12 +- .../scala/quoted/runtime/QuoteUnpickler.scala | 17 + project/MiMaFilters.scala | 8 +- .../hierarchical-mirrors-3.0}/app/Main.scala | 0 .../hierarchical-mirrors-3.0}/build.sbt | 0 .../hierarchical-mirrors-3.0}/lib/Top.scala | 0 .../project/DottyInjectedPlugin.scala | 0 .../hierarchical-mirrors-3.0}/test | 0 .../macros-backward-3.0/app/App.scala | 17 + .../macros-backward-3.0/build.sbt | 13 + .../macros-backward-3.0/lib/Macro.scala | 20 + .../project/DottyInjectedPlugin.scala | 11 + .../scala3-compat/macros-backward-3.0/test | 1 + .../macros-backward-3.1/app/App.scala | 17 + .../macros-backward-3.1/build.sbt | 13 + .../macros-backward-3.1/lib/Macro.scala | 20 + .../project/DottyInjectedPlugin.scala | 11 + .../scala3-compat/macros-backward-3.1/test | 1 + .../macros-forward-3.0/app/App.scala | 17 + .../macros-forward-3.0/build.sbt | 14 + .../macros-forward-3.0/lib/Macro.scala | 20 + .../project/DottyInjectedPlugin.scala | 11 + .../scala3-compat/macros-forward-3.0/test | 1 + .../scala/quoted/staging/QuoteCompiler.scala | 3 +- .../forwardCompat-3.0/Macro_1_r3.0.scala | 0 .../forwardCompat-3.0/Test_2_c3.0.2.scala} | 0 .../pos-macros/forwardCompat-3.0/why.md | 3 + tests/pos-macros/InlinedTypeOf.scala | 11 + .../backwardCompat-3.0/Macro_1_c3.0.0.scala | 10 + .../backwardCompat-3.0/Test_2.scala | 3 + .../backwardCompat-3.1/Macro_1_c3.1.0.scala | 10 + .../backwardCompat-3.1/Test_2.scala | 3 + tests/pos-macros/baseCompat/Macro_1.scala | 10 + tests/pos-macros/baseCompat/Test_2.scala | 2 + .../captured-quoted-def-1/Macro_1.scala | 6 + .../captured-quoted-def-1/Test_2.scala | 1 + .../captured-quoted-def-2/Macro_1.scala | 6 + .../captured-quoted-def-2/Test_2.scala | 1 + .../captured-quoted-def-3/Macro_1.scala | 6 + .../captured-quoted-def-3/Test_2.scala | 1 + .../captured-quoted-def-4/Macro_1.scala | 6 + .../captured-quoted-def-4/Test_2.scala | 1 + .../captured-quoted-def-5/Macro_1.scala | 6 + .../captured-quoted-def-5/Test_2.scala | 1 + .../captured-quoted-multy-stage/Macro_1.scala | 9 + .../captured-quoted-multy-stage/Test_2.scala | 1 + .../captured-quoted-type-1/Macro_1.scala | 10 + .../captured-quoted-type-1/Test_2.scala | 1 + .../captured-quoted-type-2/Macro_1.scala | 10 + .../captured-quoted-type-2/Test_2.scala | 1 + tests/pos-macros/i12440.scala | 21 + tests/pos-macros/i13563.scala | 3 + tests/pos-macros/i13732.scala | 6 + tests/pos-macros/i4774f.scala | 6 + tests/pos-macros/i8100.scala | 4 +- tests/pos-macros/typetags.scala | 4 + 81 files changed, 1415 insertions(+), 517 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/Splicing.scala rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/app/Main.scala (100%) rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/build.sbt (100%) rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/lib/Top.scala (100%) rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/project/DottyInjectedPlugin.scala (100%) rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/test (100%) create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/app/App.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/build.sbt create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/test create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/app/App.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/build.sbt create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/test create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/app/App.scala create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/build.sbt create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/test rename tests/{ => disabled}/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala (100%) rename tests/{pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala => disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala} (100%) create mode 100644 tests/disabled/pos-macros/forwardCompat-3.0/why.md create mode 100644 tests/pos-macros/InlinedTypeOf.scala create mode 100644 tests/pos-macros/captured-quoted-def-1/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-1/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-def-2/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-2/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-def-3/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-3/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-def-4/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-4/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-def-5/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-5/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-multy-stage/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-type-1/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-type-1/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-type-2/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-type-2/Test_2.scala create mode 100644 tests/pos-macros/i12440.scala create mode 100644 tests/pos-macros/i13563.scala create mode 100644 tests/pos-macros/i13732.scala 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..18fbf977bbc2 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -791,10 +791,46 @@ 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_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2") @tu lazy val QuoteUnpickler_unpickleType: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleType") + @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 dd9f234e0365..c438f77bb84b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1313,7 +1313,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() } @@ -1347,7 +1347,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..b3a4783a783c 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 or with -scala-output-version 3.0 and 3.1 + * 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 or with -scala-output-version 3.0 and 3.1 + * 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..40e3526861d0 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 * ... - * ${ ... '{ ... x1 ... x2 ...} ... } + * {{{ 4 | x2 | contents1 | T1 }}} // hole + * ... + * {{{ 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,210 @@ 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 unpickleV1 = ctx.scalaRelease <= Release3_1 + + 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 unpickleV1 then + if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else + Lambda( + MethodType( + List(nme.idx, nme.contents).map(name => UniqueName.fresh(name).toTermName), + 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) + } + cases match + case CaseDef(_, _, rhs) :: Nil => rhs + case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) + } + ) + else // if unpickleV2 then + 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))) + + // 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) => + val argi = args(1).select(nme.apply).appliedTo(Literal(Constant(i))) + if unpickleV1 && argType.derivesFrom(defn.QuotedExprClass) then + val argType1 = defn.FunctionType(1).appliedTo(defn.QuotesClass.typeRef, argType) + argi.asInstance(argType1).select(nme.apply).appliedTo(args(2)) + else + argi.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 unpickleV1 then + if isType then defn.QuoteUnpickler_unpickleType + else defn.QuoteUnpickler_unpickleExpr + else // if unpickleV2 then + if isType then defn.QuoteUnpickler_unpickleTypeV2 + else defn.QuoteUnpickler_unpickleExprV2 + val unpickleArgs = + if isType && !unpickleV1 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) + ) + } - override def toString: String = s"Embedded($trees, $map)" + 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..7f99ad57b2d0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -0,0 +1,398 @@ +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 + val rhs = + if ctx.scalaRelease <= Release3_1 then + val secondHoleIdx = numHoles + numHoles += 1 + TypeBoundsTree(hole, cpy.Hole(hole)(idx = secondHoleIdx)) + else hole + cpy.TypeDef(tree)(rhs = rhs) + 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/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala b/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala new file mode 100644 index 000000000000..ce7bf1b45b13 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala @@ -0,0 +1,17 @@ +package app + +import lib.* + +def powerTest(x: Double): Unit = + power(x, 0) + power(x, 1) + power(x, 5) + power(x, 10) + +def letTest: Unit = + let(0) { _ + 1 } + let(0) { _.toString } + let((4, 'a')) { _.swap } + let(new Foo) { _.hashCode } + +class Foo 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/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala b/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala new file mode 100644 index 000000000000..ce7bf1b45b13 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala @@ -0,0 +1,17 @@ +package app + +import lib.* + +def powerTest(x: Double): Unit = + power(x, 0) + power(x, 1) + power(x, 5) + power(x, 10) + +def letTest: Unit = + let(0) { _ + 1 } + let(0) { _.toString } + let((4, 'a')) { _.swap } + let(new Foo) { _.hashCode } + +class Foo 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/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala b/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala new file mode 100644 index 000000000000..ce7bf1b45b13 --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala @@ -0,0 +1,17 @@ +package app + +import lib.* + +def powerTest(x: Double): Unit = + power(x, 0) + power(x, 1) + power(x, 5) + power(x, 10) + +def letTest: Unit = + let(0) { _ + 1 } + let(0) { _.toString } + let((4, 'a')) { _.swap } + let(new Foo) { _.hashCode } + +class Foo diff --git a/sbt-test/scala3-compat/macros-forward-3.0/build.sbt b/sbt-test/scala3-compat/macros-forward-3.0/build.sbt new file mode 100644 index 000000000000..974fd6cff6cc --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/build.sbt @@ -0,0 +1,14 @@ +lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") + +lazy val lib = project.in(file("lib")) + .settings( + scalacOptions ++= Seq("-scala-output-version", "3.0") ++ checkOptions + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalaVersion := "3.0.2", + scalacOptions ++= checkOptions, + dependencyOverrides += scalaOrganization.value %% "scala3-library" % scalaVersion.value, + ) diff --git a/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala b/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala new file mode 100644 index 000000000000..12e69a6ce4fd --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-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-forward-3.0/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-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-forward-3.0/test b/sbt-test/scala3-compat/macros-forward-3.0/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/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/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala b/tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala similarity index 100% rename from tests/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala rename to tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala diff --git a/tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala b/tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala similarity index 100% rename from tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala rename to tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala diff --git a/tests/disabled/pos-macros/forwardCompat-3.0/why.md b/tests/disabled/pos-macros/forwardCompat-3.0/why.md new file mode 100644 index 000000000000..efb05ec1e0f4 --- /dev/null +++ b/tests/disabled/pos-macros/forwardCompat-3.0/why.md @@ -0,0 +1,3 @@ +Fails `testCompilation` as if the release flag was not set. But it was and the compile used it. + +Manual tests show that this does work. 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/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]] } }