Skip to content

Commit e9cddb2

Browse files
committed
Define splice hole in library
Fixes scala#17137
1 parent 1f574e8 commit e9cddb2

File tree

7 files changed

+86
-21
lines changed

7 files changed

+86
-21
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,9 @@ class Definitions {
848848
@tu lazy val QuoteUnpicklerClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteUnpickler")
849849
@tu lazy val QuoteUnpickler_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2")
850850
@tu lazy val QuoteUnpickler_unpickleTypeV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleTypeV2")
851+
@tu lazy val QuoteUnpicklerModule: Symbol = requiredModule("scala.quoted.runtime.QuoteUnpickler")
852+
@tu lazy val QuoteUnpickler_exprHole : Symbol = QuoteUnpicklerModule.requiredMethod("hole")
853+
@tu lazy val QuoteUnpickler_typeHole : Symbol = QuoteUnpicklerModule.requiredType("hole")
851854

852855
@tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching")
853856
@tu lazy val QuoteMatching_ExprMatch: Symbol = QuoteMatchingClass.requiredMethod("ExprMatch")

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ class TreePickler(pickler: TastyPickler) {
334334
pickleName(sym.name)
335335
pickleParams
336336
tpt match {
337-
case _: Template | _: Hole => pickleTree(tpt)
337+
case _: Template => pickleTree(tpt)
338338
case _ if tpt.isType => pickleTpt(tpt)
339339
}
340340
pickleTreeUnlessEmpty(rhs)
@@ -413,7 +413,6 @@ class TreePickler(pickler: TastyPickler) {
413413
var ename = tree.symbol.targetName
414414
val selectFromQualifier =
415415
name.isTypeName
416-
|| qual.isInstanceOf[Hole] // holes have no symbol
417416
|| sig == Signature.NotAMethod // no overload resolution necessary
418417
|| !tree.denot.symbol.exists // polymorphic function type
419418
|| tree.denot.asSingleDenotation.isRefinedMethod // refined methods have no defining class symbol
@@ -665,13 +664,6 @@ class TreePickler(pickler: TastyPickler) {
665664
pickleTree(hi)
666665
pickleTree(alias)
667666
}
668-
case Hole(_, idx, args, _, tpt) =>
669-
writeByte(HOLE)
670-
withLength {
671-
writeNat(idx)
672-
pickleType(tpt.tpe, richTypes = true)
673-
args.foreach(pickleTree)
674-
}
675667
}
676668
catch {
677669
case ex: TypeError =>

compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dotty.tools.dotc.quoted
33
import dotty.tools.dotc.ast.Trees._
44
import dotty.tools.dotc.ast.{TreeTypeMap, tpd}
55
import dotty.tools.dotc.config.Printers._
6+
import dotty.tools.dotc.core.Constants._
67
import dotty.tools.dotc.core.Contexts._
78
import dotty.tools.dotc.core.Decorators._
89
import dotty.tools.dotc.core.Flags._
@@ -125,6 +126,34 @@ object PickledQuotes {
125126
val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args))
126127
PickledQuotes.quotedTypeToTree(quotedType)
127128
}
129+
case Apply(TypeApply(hole, List(idxTree, tpt, targs)), List(Typed(SeqLiteral(args, _), _))) if hole.symbol == defn.QuoteUnpickler_exprHole =>
130+
inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) {
131+
val idx = (idxTree.tpe: @unchecked) match
132+
case ConstantType(Constant(idx: Int)) => idx
133+
134+
def kListTypes(tp: Type): List[TypeTree] = tp match
135+
case AppliedType(kCons: TypeRef, headType :: tailType :: Nil) if kCons.symbol == defn.QuoteMatching_KCons =>
136+
TypeTree(headType) :: kListTypes(tailType)
137+
case kNil: TypeRef if kNil.symbol == defn.QuoteMatching_KNil =>
138+
Nil
139+
140+
val targsList = kListTypes(targs.tpe)
141+
val reifiedTypeArgs = reifyTypeHoleArgs(targsList)
142+
val reifiedArgs = reifyTypeHoleArgs(targsList)
143+
144+
val argRefs = args.map(methPart) // strip dummy arguments
145+
146+
val quotedExpr = (termHole: @unchecked) match
147+
case ExprHole.V2(evalHole) =>
148+
evalHole.nn.apply(idx, reifiedTypeArgs ::: reifyExprHoleV2Args(argRefs), QuotesImpl())
149+
150+
val filled = PickledQuotes.quotedExprToTree(quotedExpr)
151+
152+
// We need to make sure a hole is created with the source file of the surrounding context, even if
153+
// it filled with contents a different source file.
154+
if filled.source == ctx.source then filled
155+
else filled.cloneIn(ctx.source).withSpan(tree.span)
156+
}
128157
case tree =>
129158
if tree.isDef then
130159
tree.symbol.annotations = tree.symbol.annotations.map {
@@ -173,7 +202,11 @@ object PickledQuotes {
173202
// To keep for backwards compatibility. In some older version we missed the creation of some holes.
174203
tpt
175204
case TypeHole.V2(types) =>
176-
val Hole(_, idx, _, _, _) = tdef.rhs: @unchecked
205+
val idx = tdef.rhs match
206+
case Hole(_, idx, _, _, _) => idx
207+
case rhs: TypeTree =>
208+
rhs.tpe match
209+
case AppliedType(tycon, ConstantType(Constant(idx: Int)) :: _) if tycon.typeSymbol == defn.QuoteUnpickler_typeHole => idx
177210
PickledQuotes.quotedTypeToTree(types.nn.apply(idx))
178211
(tdef.symbol, tree.tpe)
179212
}.toMap

compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ import scala.annotation.constructorOnly
3333
* val x1: U1 = ???
3434
* val x2: U2 = ???
3535
* ...
36-
* {{{ 3 | x1 | contents0 | T0 }}} // hole
36+
* {{{ 3 | T0 | x1 | contents0 }}} // hole
3737
* ...
38-
* {{{ 4 | x2 | contents1 | T1 }}} // hole
38+
* {{{ 4 | T1 | x2 | contents1 }}} // hole
3939
* ...
40-
* {{{ 5 | x1, x2 | contents2 | T2 }}} // hole
40+
* {{{ 5 | T2 | x1, x2 | contents2 | }}} // hole
41+
* ...
42+
* {{{ 6 | T2 | X0, x1 | contents2 | }}} // hole
4143
* ...
4244
* }
4345
* ```
@@ -50,11 +52,13 @@ import scala.annotation.constructorOnly
5052
* val x1 = ???
5153
* val x2 = ???
5254
* ...
53-
* {{{ 0 | x1 | | T0 }}} // hole
54-
* ...
55-
* {{{ 1 | x2 | | T1 }}} // hole
56-
* ...
57-
* {{{ 2 | x1, x2 | | T2 }}} // hole
55+
* hole[3, T0, KNil](x1) // hole
56+
* ...
57+
* hole[4, T1, KNil](x2) // hole
58+
* ...
59+
* hole[5, T2, KNil](x1, x2) // hole
60+
* ...
61+
* hole[6, T2, KCons[X0, KNil]](x1) // hole
5862
* ...
5963
* ]],
6064
* typeHole = (idx: Int, args: List[Any]) => idx match {
@@ -65,6 +69,7 @@ import scala.annotation.constructorOnly
6569
* case 3 => content0.apply(args(0).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced
6670
* case 4 => content1.apply(args(0).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced
6771
* case 5 => content2.apply(args(0).asInstanceOf[Expr[U1]], args(1).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced
72+
* case 6 => content2.apply(args(0).asInstanceOf[Type[?]], args(1).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced
6873
* },
6974
* )
7075
* ```
@@ -126,13 +131,35 @@ class PickleQuotes extends MacroTransform {
126131
private val contents = List.newBuilder[Tree]
127132
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =
128133
tree match
129-
case tree @ Hole(isTerm, _, _, content, _) =>
134+
case tree @ Hole(isTerm, idx, rawArgs, content, tpt) =>
130135
if !content.isEmpty then
131136
contents += content
132137
val holeType =
133138
if isTerm then getTermHoleType(tree.tpe) else getTypeHoleType(tree.tpe)
134139
val hole = cpy.Hole(tree)(content = EmptyTree, TypeTree(holeType))
135-
if isTerm then Inlined(EmptyTree, Nil, hole).withSpan(tree.span) else hole
140+
val args = rawArgs.filter(_.isTerm).map { arg =>
141+
def fullyAppliedToDummyArgs(arg: Tree, tpe: Type): Tree =
142+
tpe match
143+
case tpe: MethodType =>
144+
fullyAppliedToDummyArgs(arg.appliedToArgs(tpe.paramNames.map(_ => tpd.ref(defn.Predef_undefined))), tpe.resultType)
145+
case tpe: PolyType =>
146+
fullyAppliedToDummyArgs(arg.appliedToTypes(tpe.paramInfos.map(_.loBound)), tpe.resultType)
147+
case _ => arg
148+
fullyAppliedToDummyArgs(arg, arg.tpe.widenTermRefExpr)
149+
}
150+
val targs = rawArgs.filter(!_.isTerm)
151+
val typeArgs = tpd.hkNestedPairsTypeTree(targs).tpe
152+
val holeTypeArgs = List(ConstantType(Constant(idx)), holeType, typeArgs)
153+
val newHole =
154+
if isTerm then
155+
ref(defn.QuoteUnpickler_exprHole)
156+
.appliedToTypes(holeTypeArgs)
157+
.appliedToVarargs(args, TypeTree(defn.AnyType))
158+
.withSpan(tree.span)
159+
else
160+
TypeTree(AppliedType(defn.QuoteUnpickler_typeHole.typeRef, holeTypeArgs))
161+
.withSpan(tree.span)
162+
if isTerm then Inlined(EmptyTree, Nil, newHole).withSpan(tree.span) else newHole
136163
case tree: DefTree =>
137164
val newAnnotations = tree.symbol.annotations.mapconserve { annot =>
138165
annot.derivedAnnotation(transform(annot.tree)(using ctx.withOwner(tree.symbol)))

library/src/scala/quoted/runtime/QuoteUnpickler.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package scala.quoted.runtime
22

3+
import scala.annotation.compileTimeOnly
34
import scala.quoted.{Quotes, Expr, Type}
45

56
/** Part of the Quotes interface that needs to be implemented by the compiler but is not visible to users */
@@ -32,3 +33,10 @@ trait QuoteUnpickler:
3233
* Generated for code compiled with Scala 3.2.0+
3334
*/
3435
def unpickleTypeV2[T <: AnyKind](pickled: String | List[String], types: Null | Seq[Type[?]]): scala.quoted.Type[T]
36+
37+
object QuoteUnpickler:
38+
@compileTimeOnly("Illegal reference to `scala.quoted.runtime.Expr.hole`")
39+
def hole[Idx <: Int, T, TArgs](args: Any*): T = ???
40+
41+
@compileTimeOnly("Illegal reference to `scala.quoted.runtime.Expr.hole`")
42+
type hole[Idx <: Int, T, TArgs] <: T

project/MiMaFilters.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ object MiMaFilters {
1717
ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Break"),
1818
ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Label"),
1919
ProblemFilters.exclude[MissingClassProblem]("scala.quoted.runtime.QuoteMatching$"),
20+
ProblemFilters.exclude[MissingClassProblem]("scala.quoted.runtime.QuoteUnpickler$"),
21+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.hole"),
2022

2123
// Scala.js only: new runtime support class in 3.2.3; not available to users
2224
ProblemFilters.exclude[MissingClassProblem]("scala.scalajs.runtime.AnonFunctionXXL"),

tasty/src/dotty/tools/tasty/TastyFormat.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Standard-Section: "ASTs" TopLevelStat*
122122
MATCHtpt Length bound_Term? sel_Term CaseDef* -- sel match { CaseDef } where `bound` is optional upper bound of all rhs
123123
BYNAMEtpt underlying_Term -- => underlying
124124
SHAREDterm term_ASTRef -- Link to previously serialized term
125-
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
125+
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)
126126
127127
128128
CaseDef = CASEDEF Length pat_Term rhs_Tree guard_Tree? -- case pat if guard => rhs

0 commit comments

Comments
 (0)