Skip to content

Commit e06b0f9

Browse files
committed
Use Quote to encode Type.of in the staging phase`
1 parent e73eab7 commit e06b0f9

File tree

14 files changed

+74
-71
lines changed

14 files changed

+74
-71
lines changed

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -678,31 +678,39 @@ object Trees {
678678
override def isType = expansion.isType
679679
}
680680

681-
/** A tree representing a quote `'{ expr }`
681+
/** A tree representing a quote `'{ body }` or `'[ body ]`.
682682
* `Quote`s are created by the `Parser`. In typer they can be typed as a
683683
* `Quote` with a known `tpt` or desugared and typed as a quote pattern.
684684
*
685685
* `Quotes` are checked and transformed in the `staging`, `splicing` and `pickleQuotes`
686686
* phases. After `pickleQuotes` phase, the only quotes that exist are in `inline`
687687
* methods. These are dropped when we remove the inline method implementations.
688688
*
689-
* @param expr The tree that was quoted
689+
* Type quotes `'[body]` from the parser are desugared into quote patterns (using a `Type.of[T]]`)
690+
* when type checking. TASTy files will not contain type quotes. Type quotes are used again
691+
* in the `staging` phase to represent the reification of `Type.of[T]]`.
692+
*
693+
* @param body The tree that was quoted
690694
*/
691695
case class Quote[+T <: Untyped] private[ast] (body: Tree[T])(implicit @constructorOnly src: SourceFile)
692696
extends TermTree[T] {
693697
type ThisTree[+T <: Untyped] = Quote[T]
694698

699+
/** Is this a type quote `'[tpe]' */
700+
def isTypeQuote = body.isType
701+
695702
/** Type of the quoted expression as seen from outside the quote */
696-
def exprType(using Context): Type =
697-
val quoteType = typeOpt // Quotes ?=> Expr[T]
698-
val exprType = quoteType.argInfos.last // Expr[T]
703+
def bodyType(using Context): Type =
704+
val quoteType = typeOpt // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
705+
val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]`
699706
exprType.argInfos.head // T
700707

701-
/** Set the type of the quoted expression as seen from outside the quote */
702-
def withExprType(tpe: Type)(using Context): Quote[Type] =
703-
val exprType = // Expr[T]
704-
defn.QuotedExprClass.typeRef.appliedTo(tpe)
705-
val quoteType = // Quotes ?=> Expr[T]
708+
/** Set the type of the body of the quote */
709+
def withBodyType(tpe: Type)(using Context): Quote[Type] =
710+
val exprType = // `Expr[T]` or `Type[T]`
711+
if body.isTerm then defn.QuotedExprClass.typeRef.appliedTo(tpe)
712+
else defn.QuotedTypeClass.typeRef.appliedTo(tpe)
713+
val quoteType = // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
706714
defn.FunctionType(1, isContextual = true)
707715
.appliedTo(defn.QuotesClass.typeRef, exprType)
708716
withType(quoteType)

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
170170
def Inlined(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined =
171171
ta.assignType(untpd.Inlined(call, bindings, expansion), bindings, expansion)
172172

173-
def Quote(body: Tree, tpe: Type)(using Context): Quote =
174-
untpd.Quote(body).withExprType(tpe)
173+
def Quote(body: Tree)(using Context): Quote =
174+
untpd.Quote(body).withBodyType(body.tpe)
175175

176176
def Splice(expr: Tree, tpe: Type)(using Context): Splice =
177177
untpd.Splice(expr).withType(tpe)

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -666,15 +666,19 @@ class TreePickler(pickler: TastyPickler) {
666666
pickleTree(alias)
667667
}
668668
case tree @ Quote(body) =>
669+
// Add QUOTE tag to TASTy
670+
assert(body.isTerm,
671+
"""Quote with type should not be pickled.
672+
|Quote with type should only exists after staging phase at level staging level 0.""".stripMargin)
669673
pickleTree(
670-
// scala.quoted.runtime.Expr.quoted[<tree.exprType>](<body>)
674+
// scala.quoted.runtime.Expr.quoted[<tree.bodyType>](<body>)
671675
ref(defn.QuotedRuntime_exprQuote)
672-
.appliedToType(tree.exprType)
676+
.appliedToType(tree.bodyType)
673677
.appliedTo(body)
674678
.withSpan(tree.span)
675679
)
676680
case Splice(expr) =>
677-
pickleTree(
681+
pickleTree( // Add SPLICE tag to TASTy
678682
// scala.quoted.runtime.Expr.splice[<tree.tpe>](<expr>)
679683
ref(defn.QuotedRuntime_exprSplice)
680684
.appliedToType(tree.tpe)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1269,7 +1269,7 @@ class TreeUnpickler(reader: TastyReader,
12691269

12701270
def quotedExpr(fn: Tree, args: List[Tree]): Tree =
12711271
val TypeApply(_, targs) = fn: @unchecked
1272-
Quote(args.head, targs.head.tpe)
1272+
untpd.Quote(args.head).withBodyType(targs.head.tpe)
12731273

12741274
def splicedExpr(fn: Tree, args: List[Tree]): Tree =
12751275
val TypeApply(_, targs) = fn: @unchecked

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -717,8 +717,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
717717
case MacroTree(call) =>
718718
keywordStr("macro ") ~ toTextGlobal(call)
719719
case tree @ Quote(body) =>
720-
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tree.exprType) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
721-
keywordStr("'") ~ exprTypeText ~ keywordStr("{") ~ toTextGlobal(body) ~ keywordStr("}")
720+
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tree.bodyType) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
721+
val open = if (body.isTerm) keywordStr("{") else keywordStr("[")
722+
val close = if (body.isTerm) keywordStr("}") else keywordStr("]")
723+
keywordStr("'") ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close
722724
case Splice(expr) =>
723725
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
724726
keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}")

compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ class CrossStageSafety extends TreeMapWithStages {
101101
if (ctx.property(InAnnotation).isDefined)
102102
report.error("Cannot have a quote in an annotation", quote.srcPos)
103103
val transformedBody = transformQuoteBody(body, quote.span)
104-
val stripAnnotsDeep: TypeMap = new TypeMap:
104+
val stripAnnotationsDeep: TypeMap = new TypeMap:
105105
def apply(tp: Type): Type = mapOver(tp.stripAnnots)
106-
val exprType1 = healType(quote.srcPos)(stripAnnotsDeep(quote.exprType))
107-
cpy.Quote(quote)(transformedBody).withExprType(exprType1)
106+
val bodyType1 = healType(quote.srcPos)(stripAnnotationsDeep(quote.bodyType))
107+
cpy.Quote(quote)(transformedBody).withBodyType(bodyType1)
108108
}
109109

110110
override protected def transformQuotedType(body: Tree, quote: Apply)(using Context): Tree = {
@@ -120,10 +120,11 @@ class CrossStageSafety extends TreeMapWithStages {
120120
// Optimization: `quoted.Type.of[@SplicedType type T = x.Underlying; T](quotes)` --> `x`
121121
ref(termRef).withSpan(quote.span)
122122
case transformedBody =>
123-
val quotes = quote.args.mapConserve(transform)
123+
val quotes = transform(quote.args.head)
124124
// `quoted.Type.of[<body>](quotes)` --> `quoted.Type.of[<body2>](quotes)`
125125
val TypeApply(fun, _) = quote.fun: @unchecked
126-
cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, transformedBody :: Nil), quotes)
126+
if level != 0 then cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, transformedBody :: Nil), quotes :: Nil)
127+
else tpd.Quote(transformedBody).select(nme.apply).appliedTo(quotes).withSpan(quote.span)
127128
}
128129

129130
private def transformQuoteBody(body: Tree, span: Span)(using Context): Tree = {

compiler/src/dotty/tools/dotc/staging/TreeMapWithStages.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@ abstract class TreeMapWithStages extends TreeMapWithImplicits {
5252
}
5353

5454
tree match {
55-
case Apply(Select(QuotedTypeOf(SplicedType(t)), _), _) =>
56-
// Optimization: `quoted.Type.of[x.Underlying]` --> `x`
57-
transform(t)
58-
5955
case tree @ QuotedTypeOf(quotedTree) =>
6056
val old = inQuoteOrSplice
6157
inQuoteOrSplice = true

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

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,11 @@ class PickleQuotes extends MacroTransform {
103103
case Apply(Select(quote: Quote, nme.apply), List(quotes)) =>
104104
val (contents, codeWithHoles) = makeHoles(quote.body)
105105
val sourceRef = Inlines.inlineCallTrace(ctx.owner, tree.sourcePos)
106-
val codeWithHoles2 = Inlined(sourceRef, Nil, codeWithHoles)
107-
val pickled = PickleQuotes(quotes, codeWithHoles2, contents, quote.exprType, false)
106+
val bodyWithHoles2 =
107+
if quote.body.isType then codeWithHoles
108+
else Inlined(sourceRef, Nil, codeWithHoles)
109+
val pickled = PickleQuotes.pickle(quotes, bodyWithHoles2, contents, quote.bodyType)
108110
transform(pickled) // pickle quotes that are in the contents
109-
case Apply(TypeApply(_, List(tpt)), List(quotes)) if tree.symbol == defn.QuotedTypeModule_of =>
110-
tpt match
111-
case Select(t, _) if tpt.symbol == defn.QuotedType_splice =>
112-
// `Type.of[t.Underlying](quotes)` --> `t`
113-
ref(t.symbol)(using ctx.withSource(tpt.source)).withSpan(tpt.span)
114-
case _ =>
115-
val (contents, tptWithHoles) = makeHoles(tpt)
116-
PickleQuotes(quotes, tptWithHoles, contents, tpt.tpe, true)
117111
case tree: DefDef if !tree.rhs.isEmpty && tree.symbol.isInlineMethod =>
118112
// Shrink size of the tree. The methods have already been inlined.
119113
// TODO move to FirstTransform to trigger even without quotes
@@ -208,7 +202,7 @@ object PickleQuotes {
208202
val name: String = "pickleQuotes"
209203
val description: String = "turn quoted trees into explicit run-time data structures"
210204

211-
def apply(quotes: Tree, body: Tree, contents: List[Tree], originalTp: Type, isType: Boolean)(using Context) = {
205+
def pickle(quotes: Tree, body: Tree, contents: List[Tree], originalTp: Type)(using Context) = {
212206
/** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */
213207
object reflect extends ReifiedReflect {
214208
val quotesTree = quotes
@@ -340,14 +334,14 @@ object PickleQuotes {
340334
case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases)
341335
)
342336

343-
val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass
337+
val quoteClass = if body.isType then defn.QuotedTypeClass else defn.QuotedExprClass
344338
val quotedType = quoteClass.typeRef.appliedTo(originalTp)
345339
val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType)
346340
val unpickleMeth =
347-
if isType then defn.QuoteUnpickler_unpickleTypeV2
341+
if body.isType then defn.QuoteUnpickler_unpickleTypeV2
348342
else defn.QuoteUnpickler_unpickleExprV2
349343
val unpickleArgs =
350-
if isType then List(pickledQuoteStrings, types)
344+
if body.isType then List(pickledQuoteStrings, types)
351345
else List(pickledQuoteStrings, types, termHoles)
352346
quotes
353347
.asInstance(defn.QuoteUnpicklerClass.typeRef)
@@ -378,7 +372,7 @@ object PickleQuotes {
378372
case Inlined(_, Nil, e) => getLiteral(e)
379373
case _ => None
380374

381-
if (isType) then
375+
if body.isType then
382376
if contents.isEmpty && body.symbol.isPrimitiveValueClass then taggedType()
383377
else pickleAsTasty()
384378
else

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ trait ReifiedReflect:
7575
.select(defn.Quotes_reflect_TypeRepr_of)
7676
.appliedToType(tpe)
7777
.appliedTo(
78-
ref(defn.QuotedTypeModule_of)
79-
.appliedToType(tpe)
78+
tpd.Quote(TypeTree(tpe))
79+
.select(nme.apply)
8080
.appliedTo(quotesTree)
8181
)
8282

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

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ class Splicing extends MacroTransform:
8888
tree match
8989
case Apply(Select(_: Quote, nme.apply), _) =>
9090
QuoteTransformer().transform(tree)
91-
case TypeApply(_, _) if tree.symbol == defn.QuotedTypeModule_of =>
92-
QuoteTransformer().transform(tree)
9391
case tree: DefDef if tree.symbol.is(Inline) =>
9492
// Quotes in inlined methods are only pickled after they are inlined.
9593
tree
@@ -228,25 +226,18 @@ class Splicing extends MacroTransform:
228226
case tree @ Assign(lhs: RefTree, rhs) =>
229227
if isCaptured(lhs.symbol) then transformSplicedAssign(tree)
230228
else super.transform(tree)
231-
case Apply(sel @ Select(app @ Quote(body), nme.apply), quotesArgs) =>
232-
body match
233-
case body: RefTree if isCaptured(body.symbol) => capturedTerm(body)
234-
case _ => withCurrentQuote(quotesArgs.head) { super.transform(tree) }
235-
case Apply(TypeApply(typeof, List(tpt)), List(quotes))
236-
if tree.symbol == defn.QuotedTypeModule_of && containsCapturedType(tpt.tpe) =>
237-
val newContent = capturedPartTypes(tpt)
238-
newContent match
239-
case block: Block =>
240-
inContext(ctx.withSource(tree.source)) {
241-
Apply(TypeApply(typeof, List(newContent)), List(quotes)).withSpan(tree.span)
242-
}
243-
case _ =>
244-
ref(capturedType(newContent))(using ctx.withSource(tree.source)).withSpan(tree.span)
245229
case CapturedApplication(fn, argss) =>
246230
transformCapturedApplication(tree, fn, argss)
247-
case Quote(expr) if level == 0 =>
248-
val newExpr = transformLevel0QuoteContent(expr)(using quoteContext)
249-
cpy.Quote(tree)(newExpr)
231+
case Apply(sel @ Select(app @ Quote(body), nme.apply), quotes :: Nil) if level == 0 && body.isTerm =>
232+
body match
233+
case _: RefTree if isCaptured(body.symbol) => capturedTerm(body)
234+
case _ => withCurrentQuote(quotes) { super.transform(tree) }
235+
case Quote(body) if level == 0 =>
236+
val newBody =
237+
if body.isTerm then transformLevel0QuoteContent(body)(using quoteContext)
238+
else if containsCapturedType(body.tpe) then capturedPartTypes(body)
239+
else body
240+
cpy.Quote(tree)(newBody)
250241
case _ =>
251242
super.transform(tree)
252243

@@ -400,7 +391,7 @@ class Splicing extends MacroTransform:
400391
Splice(closure, tpe)
401392

402393
private def quoted(expr: Tree)(using Context): Tree =
403-
Quote(expr, expr.tpe.widenTermRefExpr)
394+
untpd.Quote(expr).withBodyType(expr.tpe.widenTermRefExpr) // TODO do we need widenTermRefExpr?
404395
.select(nme.apply)
405396
.appliedTo(quotes.nn)
406397

0 commit comments

Comments
 (0)