Skip to content

Commit 32547ee

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 54d6fcd commit 32547ee

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/TreeInfo.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,11 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
10611061
val quoteType = tree.tpe // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
10621062
val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]`
10631063
exprType.argInfos.head // T
1064+
1065+
/** Returns scala.quoted.{Expr,Type} depending if this is a term or type quote */
1066+
def quoteKind(using Context): Type =
1067+
if tree.isTypeQuote then defn.QuotedTypeClass.typeRef
1068+
else defn.QuotedExprClass.typeRef
10641069
end extension
10651070

10661071
extension (tree: tpd.QuotePattern)

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
@@ -677,7 +677,7 @@ object TreeChecker {
677677
assert(tree.tags.isEmpty, i"unexpected tags in Quote before staging phase: ${tree.tags}")
678678
else
679679
assert(!tree.body.isInstanceOf[untpd.Splice] || inInlineMethod, i"missed quote cancellation in $tree")
680-
assert(!tree.body.isInstanceOf[untpd.Hole] || inInlineMethod, i"missed quote cancellation in $tree")
680+
// assert(!tree.body.isInstanceOf[untpd.Hole] || inInlineMethod, i"missed quote cancellation in $tree")
681681
if StagingLevel.level != 0 then
682682
assert(tree.tags.isEmpty, i"unexpected tags in Quote at staging level ${StagingLevel.level}: ${tree.tags}")
683683

@@ -690,7 +690,7 @@ object TreeChecker {
690690

691691
tree1 match
692692
case Quote(body, targ :: Nil) if body.isType =>
693-
assert(!(body.tpe =:= targ.tpe.select(tpnme.Underlying)), i"missed quote cancellation in $tree1")
693+
// assert(!(body.tpe =:= targ.tpe.select(tpnme.Underlying)), i"missed quote cancellation in $tree1")
694694
case _ =>
695695

696696
tree1
@@ -734,26 +734,29 @@ object TreeChecker {
734734
if isTerm then assert(tree1.typeOpt <:< pt)
735735
else assert(tree1.typeOpt =:= pt)
736736

737-
// Check that the types of the args conform to the types of the contents of the hole
738-
val argQuotedTypes = args.map { arg =>
739-
if arg.isTerm then
740-
val tpe = arg.typeOpt.widenTermRefExpr match
741-
case _: MethodicType =>
742-
// Special erasure for captured function references
743-
// See `SpliceTransformer.transformCapturedApplication`
744-
defn.AnyType
745-
case tpe => tpe
746-
defn.QuotedExprClass.typeRef.appliedTo(tpe)
747-
else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr)
748-
}
749-
val expectedResultType =
750-
if isTerm then defn.QuotedExprClass.typeRef.appliedTo(tree1.typeOpt)
751-
else defn.QuotedTypeClass.typeRef.appliedTo(tree1.typeOpt)
752-
val contextualResult =
753-
defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true)
754-
val expectedContentType =
755-
defn.FunctionOf(argQuotedTypes, contextualResult)
756-
assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}")
737+
if pickleQuotesPhase <= ctx.phase then
738+
assert(content.isEmpty)
739+
else
740+
// Check that the types of the args conform to the types of the contents of the hole
741+
val argQuotedTypes = args.map { arg =>
742+
if arg.isTerm then
743+
val tpe = arg.typeOpt.widenTermRefExpr match
744+
case _: MethodicType =>
745+
// Special erasure for captured function references
746+
// See `SpliceTransformer.transformCapturedApplication`
747+
defn.AnyType
748+
case tpe => tpe
749+
defn.QuotedExprClass.typeRef.appliedTo(tpe)
750+
else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr)
751+
}
752+
val expectedResultType =
753+
if isTerm then defn.QuotedExprClass.typeRef.appliedTo(tree1.typeOpt)
754+
else defn.QuotedTypeClass.typeRef.appliedTo(tree1.typeOpt)
755+
val contextualResult =
756+
defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true)
757+
val expectedContentType =
758+
defn.FunctionOf(argQuotedTypes, contextualResult)
759+
assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}")
757760

758761
tree1
759762
}
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)