Skip to content

Commit 66624a3

Browse files
committed
Keep contents of quoted tree after pickling
This enables the checking phases to analyze the contents of quoted expression. Fixes #15700 Fixes #17400
1 parent 51d69e8 commit 66624a3

File tree

12 files changed

+135
-32
lines changed

12 files changed

+135
-32
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class Compiler {
9292
new StringInterpolatorOpt, // Optimizes raw and s and f string interpolators by rewriting them to string concatenations or formats
9393
new DropBreaks) :: // Optimize local Break throws by rewriting them
9494
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
95+
new PruneQuotes, // Drop non-pickled copies of the quotes
9596
new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_`
9697
new InlinePatterns, // Remove placeholders of inlined patterns
9798
new VCInlineMethods, // Inlines calls to value class methods

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,11 @@ object Trees {
704704
/** Is this a type quote `'[tpe]' */
705705
def isTypeQuote = body.isType
706706

707+
/** Returns scala.quoted.{Expr,Type} depending if this is a term or type quote */
708+
def quoteKind(using Context): Type =
709+
if isTypeQuote then defn.QuotedTypeClass.typeRef
710+
else defn.QuotedExprClass.typeRef
711+
707712
/** Type of the quoted expression as seen from outside the quote */
708713
def bodyType(using Context): Type =
709714
val quoteType = typeOpt // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ object Phases {
213213
private var myInliningPhase: Phase = _
214214
private var myStagingPhase: Phase = _
215215
private var mySplicingPhase: Phase = _
216+
private var myPickleQuotesPhase: Phase = _
216217
private var myFirstTransformPhase: Phase = _
217218
private var myCollectNullableFieldsPhase: Phase = _
218219
private var myRefChecksPhase: Phase = _
@@ -238,6 +239,7 @@ object Phases {
238239
final def inliningPhase: Phase = myInliningPhase
239240
final def stagingPhase: Phase = myStagingPhase
240241
final def splicingPhase: Phase = mySplicingPhase
242+
final def pickleQuotesPhase: Phase = myPickleQuotesPhase
241243
final def firstTransformPhase: Phase = myFirstTransformPhase
242244
final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase
243245
final def refchecksPhase: Phase = myRefChecksPhase
@@ -266,6 +268,7 @@ object Phases {
266268
myInliningPhase = phaseOfClass(classOf[Inlining])
267269
myStagingPhase = phaseOfClass(classOf[Staging])
268270
mySplicingPhase = phaseOfClass(classOf[Splicing])
271+
myPickleQuotesPhase = phaseOfClass(classOf[PickleQuotes])
269272
myFirstTransformPhase = phaseOfClass(classOf[FirstTransform])
270273
myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields])
271274
myRefChecksPhase = phaseOfClass(classOf[RefChecks])
@@ -452,8 +455,9 @@ object Phases {
452455
def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase
453456
def picklerPhase(using Context): Phase = ctx.base.picklerPhase
454457
def inliningPhase(using Context): Phase = ctx.base.inliningPhase
455-
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
458+
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
456459
def splicingPhase(using Context): Phase = ctx.base.splicingPhase
460+
def pickleQuotesPhase(using Context): Phase = ctx.base.pickleQuotesPhase
457461
def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase
458462
def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase
459463
def elimRepeatedPhase(using Context): Phase = ctx.base.elimRepeatedPhase

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase =>
184184
if (!ctx.mode.is(Mode.Pattern)) constToLiteral(tree) else tree
185185

186186
override def transformBlock(tree: Block)(using Context): Tree =
187-
constToLiteral(tree)
187+
if tree.isType then tree
188+
else constToLiteral(tree)
188189

189190
override def transformIf(tree: If)(using Context): Tree =
190191
tree.cond.tpe match {

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ class PickleQuotes extends MacroTransform {
8080

8181
override def checkPostCondition(tree: Tree)(using Context): Unit =
8282
tree match
83-
case tree: Quote =>
84-
assert(Inlines.inInlineMethod)
85-
case tree: Splice =>
86-
assert(Inlines.inInlineMethod)
83+
// case tree: Quote =>
84+
// assert(Inlines.inInlineMethod)
85+
// case tree: Splice =>
86+
// assert(Inlines.inInlineMethod)
8787
case _ =>
8888

8989
override def run(using Context): Unit =
@@ -93,16 +93,24 @@ class PickleQuotes extends MacroTransform {
9393
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =
9494
tree match
9595
case Apply(Select(quote: Quote, nme.apply), List(quotes)) =>
96-
val (holeContents, quote1) = extractHolesContents(quote)
96+
val (contents, quote1) = extractHolesContents(quote)
9797
val quote2 = encodeTypeArgs(quote1)
98-
val holeContents1 = holeContents.map(transform(_))
99-
PickleQuotes.pickle(quote2, quotes, holeContents1)
98+
val contents1 = contents.map(transform(_)) ::: quote.tags
99+
val pickled = PickleQuotes.pickle(quote2, quotes, contents1)
100+
cpy.Block(tree)(removeHoleContents(quote) :: Nil, pickled)
100101
case tree: DefDef if !tree.rhs.isEmpty && tree.symbol.isInlineMethod =>
101102
tree
102103
case _ =>
103104
super.transform(tree)
104105
}
105106

107+
private def removeHoleContents(tree: Tree)(using Context) =
108+
new TreeMap {
109+
override def transform(tree: Tree)(using Context): Tree = tree match
110+
case tree: Hole => cpy.Hole(tree)(content = EmptyTree)
111+
case _ => super.transform(tree)
112+
}.transform(tree)
113+
106114
private def extractHolesContents(quote: tpd.Quote)(using Context): (List[Tree], tpd.Quote) =
107115
class HoleContentExtractor extends Transformer:
108116
private val holeContents = List.newBuilder[Tree]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import dotty.tools.dotc.ast.tpd
5+
import dotty.tools.dotc.core.Contexts._
6+
import dotty.tools.dotc.core.Decorators.*
7+
import dotty.tools.dotc.core.Symbols._
8+
import dotty.tools.dotc.inlines.Inlines
9+
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
10+
import dotty.tools.dotc.staging.StagingLevel.level
11+
12+
/** This phase removes all non-pickled quotes */
13+
class PruneQuotes extends MiniPhase { thisTransform =>
14+
import tpd._
15+
import PruneQuotes._
16+
17+
override def phaseName: String = PruneQuotes.name
18+
19+
override def description: String = PruneQuotes.description
20+
21+
override def transformQuote(tree: Quote)(using Context): Tree =
22+
if Inlines.inInlineMethod || level > 0 then tree
23+
else Thicket()
24+
}
25+
26+
object PruneQuotes {
27+
import tpd._
28+
29+
val name: String = "pruneQuotes"
30+
val description: String = "Drop non-pickled copies of the quotes. Only keep the pickled version."
31+
}

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

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ object TreeChecker {
664664
assert(tree.tags.isEmpty, i"unexpected tags in Quote before staging phase: ${tree.tags}")
665665
else
666666
assert(!tree.body.isInstanceOf[untpd.Splice] || inInlineMethod, i"missed quote cancellation in $tree")
667-
assert(!tree.body.isInstanceOf[untpd.Hole] || inInlineMethod, i"missed quote cancellation in $tree")
667+
// assert(!tree.body.isInstanceOf[untpd.Hole] || inInlineMethod, i"missed quote cancellation in $tree")
668668
if StagingLevel.level != 0 then
669669
assert(tree.tags.isEmpty, i"unexpected tags in Quote at staging level ${StagingLevel.level}: ${tree.tags}")
670670

@@ -677,7 +677,7 @@ object TreeChecker {
677677

678678
tree1 match
679679
case Quote(body, targ :: Nil) if body.isType =>
680-
assert(!(body.tpe =:= targ.tpe.select(tpnme.Underlying)), i"missed quote cancellation in $tree1")
680+
// assert(!(body.tpe =:= targ.tpe.select(tpnme.Underlying)), i"missed quote cancellation in $tree1")
681681
case _ =>
682682

683683
tree1
@@ -702,26 +702,29 @@ object TreeChecker {
702702
if isTerm then assert(tree1.typeOpt <:< pt)
703703
else assert(tree1.typeOpt =:= pt)
704704

705-
// Check that the types of the args conform to the types of the contents of the hole
706-
val argQuotedTypes = args.map { arg =>
707-
if arg.isTerm then
708-
val tpe = arg.typeOpt.widenTermRefExpr match
709-
case _: MethodicType =>
710-
// Special erasure for captured function references
711-
// See `SpliceTransformer.transformCapturedApplication`
712-
defn.AnyType
713-
case tpe => tpe
714-
defn.QuotedExprClass.typeRef.appliedTo(tpe)
715-
else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr)
716-
}
717-
val expectedResultType =
718-
if isTerm then defn.QuotedExprClass.typeRef.appliedTo(tree1.typeOpt)
719-
else defn.QuotedTypeClass.typeRef.appliedTo(tree1.typeOpt)
720-
val contextualResult =
721-
defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true)
722-
val expectedContentType =
723-
defn.FunctionOf(argQuotedTypes, contextualResult)
724-
assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}")
705+
if pickleQuotesPhase <= ctx.phase then
706+
assert(content.isEmpty)
707+
else
708+
// Check that the types of the args conform to the types of the contents of the hole
709+
val argQuotedTypes = args.map { arg =>
710+
if arg.isTerm then
711+
val tpe = arg.typeOpt.widenTermRefExpr match
712+
case _: MethodicType =>
713+
// Special erasure for captured function references
714+
// See `SpliceTransformer.transformCapturedApplication`
715+
defn.AnyType
716+
case tpe => tpe
717+
defn.QuotedExprClass.typeRef.appliedTo(tpe)
718+
else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr)
719+
}
720+
val expectedResultType =
721+
if isTerm then defn.QuotedExprClass.typeRef.appliedTo(tree1.typeOpt)
722+
else defn.QuotedTypeClass.typeRef.appliedTo(tree1.typeOpt)
723+
val contextualResult =
724+
defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true)
725+
val expectedContentType =
726+
defn.FunctionOf(argQuotedTypes, contextualResult)
727+
assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}")
725728

726729
tree1
727730
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.quoted.*
2+
3+
class B:
4+
@deprecated def dep: Int = 1
5+
6+
def checkDeprecated(using Quotes) =
7+
'{
8+
val b = new B
9+
b.dep // error
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.annotation.experimental
2+
import scala.quoted.*
3+
4+
@experimental class C
5+
6+
def checkExperimental(using Quotes) =
7+
'{
8+
println(new C) // error
9+
}

tests/neg-macros/i15700.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import scala.quoted.*
2+
3+
trait Foo:
4+
def xxx: Int
5+
6+
inline def foo(inline cond: Boolean) = ${ fooImpl('cond) }
7+
8+
def fooImpl(cond: Expr[Boolean])(using Quotes) =
9+
if cond.valueOrAbort then
10+
'{
11+
new Foo {
12+
override def xxx = 2
13+
}
14+
}
15+
else
16+
'{
17+
new Foo { // error: object creation impossible, since def xxx: Int in trait Foo is not defined
18+
override def xxxx = 1 // error: method xxxx overrides nothing
19+
}
20+
}

tests/neg-macros/i17400a.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.quoted.*
2+
3+
class A:
4+
def a: Int = 1
5+
6+
def checkOverride(using Quotes) =
7+
'{
8+
class Sub extends A:
9+
def a: String = "" // error
10+
new Sub
11+
}

tests/pos-macros/i7405b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class Foo {
55
'{
66
trait X extends A {
77
type Y
8-
def y: Y = ???
8+
override def y: Y = ???
99
}
1010
val x: X = ???
1111
type Z = x.Y

0 commit comments

Comments
 (0)