Skip to content

Commit

Permalink
Define splice hole in library
Browse files Browse the repository at this point in the history
Fixes #17137
  • Loading branch information
nicolasstucki committed Mar 23, 2023
1 parent 1f574e8 commit 919c252
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 26 deletions.
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,9 @@ class Definitions {
@tu lazy val QuoteUnpicklerClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteUnpickler")
@tu lazy val QuoteUnpickler_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2")
@tu lazy val QuoteUnpickler_unpickleTypeV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleTypeV2")
@tu lazy val QuoteUnpicklerModule: Symbol = requiredModule("scala.quoted.runtime.QuoteUnpickler")
@tu lazy val QuoteUnpickler_exprHole : Symbol = QuoteUnpicklerModule.requiredMethod("hole")
@tu lazy val QuoteUnpickler_typeHole : Symbol = QuoteUnpicklerModule.requiredType("hole")

@tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching")
@tu lazy val QuoteMatching_ExprMatch: Symbol = QuoteMatchingClass.requiredMethod("ExprMatch")
Expand Down
10 changes: 1 addition & 9 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ class TreePickler(pickler: TastyPickler) {
pickleName(sym.name)
pickleParams
tpt match {
case _: Template | _: Hole => pickleTree(tpt)
case _: Template => pickleTree(tpt)
case _ if tpt.isType => pickleTpt(tpt)
}
pickleTreeUnlessEmpty(rhs)
Expand Down Expand Up @@ -413,7 +413,6 @@ class TreePickler(pickler: TastyPickler) {
var ename = tree.symbol.targetName
val selectFromQualifier =
name.isTypeName
|| qual.isInstanceOf[Hole] // holes have no symbol
|| sig == Signature.NotAMethod // no overload resolution necessary
|| !tree.denot.symbol.exists // polymorphic function type
|| tree.denot.asSingleDenotation.isRefinedMethod // refined methods have no defining class symbol
Expand Down Expand Up @@ -665,13 +664,6 @@ class TreePickler(pickler: TastyPickler) {
pickleTree(hi)
pickleTree(alias)
}
case Hole(_, idx, args, _, tpt) =>
writeByte(HOLE)
withLength {
writeNat(idx)
pickleType(tpt.tpe, richTypes = true)
args.foreach(pickleTree)
}
}
catch {
case ex: TypeError =>
Expand Down
35 changes: 34 additions & 1 deletion compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dotty.tools.dotc.quoted
import dotty.tools.dotc.ast.Trees._
import dotty.tools.dotc.ast.{TreeTypeMap, tpd}
import dotty.tools.dotc.config.Printers._
import dotty.tools.dotc.core.Constants._
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Decorators._
import dotty.tools.dotc.core.Flags._
Expand Down Expand Up @@ -125,6 +126,34 @@ object PickledQuotes {
val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args))
PickledQuotes.quotedTypeToTree(quotedType)
}
case Apply(TypeApply(hole, List(idxTree, tpt, targs)), List(Typed(SeqLiteral(args, _), _))) if hole.symbol == defn.QuoteUnpickler_exprHole =>
inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) {
val idx = (idxTree.tpe: @unchecked) match
case ConstantType(Constant(idx: Int)) => idx

def kListTypes(tp: Type): List[TypeTree] = tp match
case AppliedType(kCons: TypeRef, headType :: tailType :: Nil) if kCons.symbol == defn.QuoteMatching_KCons =>
TypeTree(headType) :: kListTypes(tailType)
case kNil: TypeRef if kNil.symbol == defn.QuoteMatching_KNil =>
Nil

val targsList = kListTypes(targs.tpe)
val reifiedTypeArgs = reifyTypeHoleArgs(targsList)
val reifiedArgs = reifyTypeHoleArgs(targsList)

val argRefs = args.map(methPart) // strip dummy arguments

val quotedExpr = (termHole: @unchecked) match
case ExprHole.V2(evalHole) =>
evalHole.nn.apply(idx, reifiedTypeArgs ::: reifyExprHoleV2Args(argRefs), 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
// it filled with contents a different source file.
if filled.source == ctx.source then filled
else filled.cloneIn(ctx.source).withSpan(tree.span)
}
case tree =>
if tree.isDef then
tree.symbol.annotations = tree.symbol.annotations.map {
Expand Down Expand Up @@ -173,7 +202,11 @@ object PickledQuotes {
// 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: @unchecked
val idx = tdef.rhs match
case Hole(_, idx, _, _, _) => idx
case rhs: TypeTree =>
rhs.tpe match
case AppliedType(tycon, ConstantType(Constant(idx: Int)) :: _) if tycon.typeSymbol == defn.QuoteUnpickler_typeHole => idx
PickledQuotes.quotedTypeToTree(types.nn.apply(idx))
(tdef.symbol, tree.tpe)
}.toMap
Expand Down
58 changes: 43 additions & 15 deletions compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,32 @@ import scala.annotation.constructorOnly
* val x1: U1 = ???
* val x2: U2 = ???
* ...
* {{{ 3 | x1 | contents0 | T0 }}} // hole
* {{{ 3 | T0 | x1 | contents0 }}} // hole
* ...
* {{{ 4 | x2 | contents1 | T1 }}} // hole
* {{{ 4 | T1 | x2 | contents1 }}} // hole
* ...
* {{{ 5 | x1, x2 | contents2 | T2 }}} // hole
* {{{ 5 | T2 | x1, x2 | contents2 | }}} // hole
* ...
* {{{ 6 | T2 | X0, x1 | contents2 | }}} // hole
* ...
* }
* ```
* to
* ```
* 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
* @TypeSplice type X0 = hole[0, ..] // with bounds that do not contain captured types
* @TypeSplice type X1 = hole[1, ..]// with bounds that do not contain captured types
* val x1 = ???
* val x2 = ???
* ...
* {{{ 0 | x1 | | T0 }}} // hole
* ...
* {{{ 1 | x2 | | T1 }}} // hole
* ...
* {{{ 2 | x1, x2 | | T2 }}} // hole
* hole[3, T0, KNil](x1) // hole
* ...
* hole[4, T1, KNil](x2) // hole
* ...
* hole[5, T2, KNil](x1, x2) // hole
* ...
* hole[6, T2, KCons[X0, KNil]](x1) // hole
* ...
* ]],
* typeHole = (idx: Int, args: List[Any]) => idx match {
Expand All @@ -65,6 +69,7 @@ import scala.annotation.constructorOnly
* 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
* case 6 => content2.apply(args(0).asInstanceOf[Type[?]], args(1).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced
* },
* )
* ```
Expand Down Expand Up @@ -126,13 +131,36 @@ class PickleQuotes extends MacroTransform {
private val contents = List.newBuilder[Tree]
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =
tree match
case tree @ Hole(isTerm, _, _, content, _) =>
case tree @ Hole(isTerm, idx, rawArgs, content, tpt) =>
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
if isTerm then // transform into method call `hole[idx, T', Targs](args*)`
val targs = rawArgs.filter(!_.isTerm)
val args = rawArgs.filter(_.isTerm).map { arg =>
def fullyAppliedToDummyArgs(arg: Tree, tpe: Type): Tree =
tpe match
case tpe: MethodType =>
fullyAppliedToDummyArgs(arg.appliedToArgs(tpe.paramNames.map(_ => tpd.ref(defn.Predef_undefined))), tpe.resultType)
case tpe: PolyType =>
fullyAppliedToDummyArgs(arg.appliedToTypes(tpe.paramInfos.map(_.loBound)), tpe.resultType)
case _ => arg
fullyAppliedToDummyArgs(arg, arg.tpe.widenTermRefExpr)
}
val holeType = getTermHoleType(tree.tpe)
val typeArgs = tpd.hkNestedPairsTypeTree(targs).tpe
val holeTypeArgs = List(ConstantType(Constant(idx)), holeType, typeArgs)
val newHole =
ref(defn.QuoteUnpickler_exprHole)
.appliedToTypes(holeTypeArgs)
.appliedToVarargs(args, TypeTree(defn.AnyType))
.withSpan(tree.span)
Inlined(EmptyTree, Nil, newHole).withSpan(tree.span)
else // transform into type `hole[idx, T']`
assert(rawArgs.isEmpty)
val holeType = getTypeHoleType(tree.tpe)
val holeTypeArgs = List(ConstantType(Constant(idx)), holeType)
TypeTree(AppliedType(defn.QuoteUnpickler_typeHole.typeRef, holeTypeArgs))
.withSpan(tree.span)
case tree: DefTree =>
val newAnnotations = tree.symbol.annotations.mapconserve { annot =>
annot.derivedAnnotation(transform(annot.tree)(using ctx.withOwner(tree.symbol)))
Expand Down
8 changes: 8 additions & 0 deletions library/src/scala/quoted/runtime/QuoteUnpickler.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package scala.quoted.runtime

import scala.annotation.compileTimeOnly
import scala.quoted.{Quotes, Expr, Type}

/** Part of the Quotes interface that needs to be implemented by the compiler but is not visible to users */
Expand Down Expand Up @@ -32,3 +33,10 @@ trait QuoteUnpickler:
* 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]

object QuoteUnpickler:
@compileTimeOnly("Illegal reference to `scala.quoted.runtime.QuoteUnpickler.hole`")
def hole[Idx <: Int, T, TArgs](args: Any*): T = ???

@compileTimeOnly("Illegal reference to `scala.quoted.runtime.QuoteUnpickler.hole`")
type hole[Idx <: Int, T] <: T
2 changes: 2 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ object MiMaFilters {
ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Break"),
ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Label"),
ProblemFilters.exclude[MissingClassProblem]("scala.quoted.runtime.QuoteMatching$"),
ProblemFilters.exclude[MissingClassProblem]("scala.quoted.runtime.QuoteUnpickler$"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.hole"),

// Scala.js only: new runtime support class in 3.2.3; not available to users
ProblemFilters.exclude[MissingClassProblem]("scala.scalajs.runtime.AnonFunctionXXL"),
Expand Down
2 changes: 1 addition & 1 deletion tasty/src/dotty/tools/tasty/TastyFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Standard-Section: "ASTs" TopLevelStat*
MATCHtpt Length bound_Term? sel_Term CaseDef* -- sel match { CaseDef } where `bound` is optional upper bound of all rhs
BYNAMEtpt underlying_Term -- => underlying
SHAREDterm term_ASTRef -- Link to previously serialized term
HOLE Length idx_Nat tpe_Type arg_Tree* -- Splice hole with index `idx`, the type of the hole `tpe`, type and term arguments of the hole `arg`s
HOLE Length idx_Nat tpe_Type arg_Tree* -- Splice hole with index `idx`, the type of the hole `tpe`, type and term arguments of the hole `arg`s (only used in 3.0-3.3)
CaseDef = CASEDEF Length pat_Term rhs_Tree guard_Tree? -- case pat if guard => rhs
Expand Down

0 comments on commit 919c252

Please sign in to comment.