Skip to content

Keep contents of quoted tree after pickling #17480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class Compiler {
new StringInterpolatorOpt, // Optimizes raw and s and f string interpolators by rewriting them to string concatenations or formats
new DropBreaks) :: // Optimize local Break throws by rewriting them
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
new PruneQuotes, // Drop non-pickled copies of the quotes
new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_`
new InlinePatterns, // Remove placeholders of inlined patterns
new VCInlineMethods, // Inlines calls to value class methods
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,11 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
val quoteType = tree.tpe // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]`
exprType.argInfos.head // T

/** Returns scala.quoted.{Expr,Type} depending if this is a term or type quote */
def quoteKind(using Context): Type =
if tree.isTypeQuote then defn.QuotedTypeClass.typeRef
else defn.QuotedExprClass.typeRef
end extension

extension (tree: tpd.QuotePattern)
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ object Phases {
private var myInliningPhase: Phase = _
private var myStagingPhase: Phase = _
private var mySplicingPhase: Phase = _
private var myPickleQuotesPhase: Phase = _
private var myFirstTransformPhase: Phase = _
private var myCollectNullableFieldsPhase: Phase = _
private var myRefChecksPhase: Phase = _
Expand All @@ -238,6 +239,7 @@ object Phases {
final def inliningPhase: Phase = myInliningPhase
final def stagingPhase: Phase = myStagingPhase
final def splicingPhase: Phase = mySplicingPhase
final def pickleQuotesPhase: Phase = myPickleQuotesPhase
final def firstTransformPhase: Phase = myFirstTransformPhase
final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase
final def refchecksPhase: Phase = myRefChecksPhase
Expand Down Expand Up @@ -266,6 +268,7 @@ object Phases {
myInliningPhase = phaseOfClass(classOf[Inlining])
myStagingPhase = phaseOfClass(classOf[Staging])
mySplicingPhase = phaseOfClass(classOf[Splicing])
myPickleQuotesPhase = phaseOfClass(classOf[PickleQuotes])
myFirstTransformPhase = phaseOfClass(classOf[FirstTransform])
myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields])
myRefChecksPhase = phaseOfClass(classOf[RefChecks])
Expand Down Expand Up @@ -452,8 +455,9 @@ object Phases {
def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase
def picklerPhase(using Context): Phase = ctx.base.picklerPhase
def inliningPhase(using Context): Phase = ctx.base.inliningPhase
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
def splicingPhase(using Context): Phase = ctx.base.splicingPhase
def pickleQuotesPhase(using Context): Phase = ctx.base.pickleQuotesPhase
def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase
def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase
def elimRepeatedPhase(using Context): Phase = ctx.base.elimRepeatedPhase
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/FirstTransform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase =>
if (!ctx.mode.is(Mode.Pattern)) constToLiteral(tree) else tree

override def transformBlock(tree: Block)(using Context): Tree =
constToLiteral(tree)
if tree.isType then tree
else constToLiteral(tree)

override def transformIf(tree: If)(using Context): Tree =
tree.cond.tpe match {
Expand Down
22 changes: 15 additions & 7 deletions compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ class PickleQuotes extends MacroTransform {

override def checkPostCondition(tree: Tree)(using Context): Unit =
tree match
case tree: Quote =>
assert(Inlines.inInlineMethod)
case tree: Splice =>
assert(Inlines.inInlineMethod)
// case tree: Quote =>
// assert(Inlines.inInlineMethod)
// case tree: Splice =>
// assert(Inlines.inInlineMethod)
case _ =>

override def run(using Context): Unit =
Expand All @@ -93,16 +93,24 @@ class PickleQuotes extends MacroTransform {
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =
tree match
case Apply(Select(quote: Quote, nme.apply), List(quotes)) =>
val (holeContents, quote1) = extractHolesContents(quote)
val (contents, quote1) = extractHolesContents(quote)
val quote2 = encodeTypeArgs(quote1)
val holeContents1 = holeContents.map(transform(_))
PickleQuotes.pickle(quote2, quotes, holeContents1)
val contents1 = contents.map(transform(_)) ::: quote.tags
val pickled = PickleQuotes.pickle(quote2, quotes, contents1)
cpy.Block(tree)(removeHoleContents(quote) :: Nil, pickled)
case tree: DefDef if !tree.rhs.isEmpty && tree.symbol.isInlineMethod =>
tree
case _ =>
super.transform(tree)
}

private def removeHoleContents(tree: Tree)(using Context) =
new TreeMap {
override def transform(tree: Tree)(using Context): Tree = tree match
case tree: Hole => cpy.Hole(tree)(content = EmptyTree)
case _ => super.transform(tree)
}.transform(tree)

private def extractHolesContents(quote: tpd.Quote)(using Context): (List[Tree], tpd.Quote) =
class HoleContentExtractor extends Transformer:
private val holeContents = List.newBuilder[Tree]
Expand Down
31 changes: 31 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PruneQuotes.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dotty.tools.dotc
package transform

import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Decorators.*
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.inlines.Inlines
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
import dotty.tools.dotc.staging.StagingLevel.level

/** This phase removes all non-pickled quotes */
class PruneQuotes extends MiniPhase { thisTransform =>
import tpd._
import PruneQuotes._

override def phaseName: String = PruneQuotes.name

override def description: String = PruneQuotes.description

override def transformQuote(tree: Quote)(using Context): Tree =
if Inlines.inInlineMethod || level > 0 then tree
else Thicket()
}

object PruneQuotes {
import tpd._

val name: String = "pruneQuotes"
val description: String = "Drop non-pickled copies of the quotes. Only keep the pickled version."
}
47 changes: 25 additions & 22 deletions compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ object TreeChecker {
assert(tree.tags.isEmpty, i"unexpected tags in Quote before staging phase: ${tree.tags}")
else
assert(!tree.body.isInstanceOf[untpd.Splice] || inInlineMethod, i"missed quote cancellation in $tree")
assert(!tree.body.isInstanceOf[untpd.Hole] || inInlineMethod, i"missed quote cancellation in $tree")
// assert(!tree.body.isInstanceOf[untpd.Hole] || inInlineMethod, i"missed quote cancellation in $tree")
if StagingLevel.level != 0 then
assert(tree.tags.isEmpty, i"unexpected tags in Quote at staging level ${StagingLevel.level}: ${tree.tags}")

Expand All @@ -690,7 +690,7 @@ object TreeChecker {

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

tree1
Expand Down Expand Up @@ -734,26 +734,29 @@ object TreeChecker {
if isTerm then assert(tree1.typeOpt <:< pt)
else assert(tree1.typeOpt =:= pt)

// Check that the types of the args conform to the types of the contents of the hole
val argQuotedTypes = args.map { arg =>
if arg.isTerm then
val tpe = arg.typeOpt.widenTermRefExpr match
case _: MethodicType =>
// Special erasure for captured function references
// See `SpliceTransformer.transformCapturedApplication`
defn.AnyType
case tpe => tpe
defn.QuotedExprClass.typeRef.appliedTo(tpe)
else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr)
}
val expectedResultType =
if isTerm then defn.QuotedExprClass.typeRef.appliedTo(tree1.typeOpt)
else defn.QuotedTypeClass.typeRef.appliedTo(tree1.typeOpt)
val contextualResult =
defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true)
val expectedContentType =
defn.FunctionOf(argQuotedTypes, contextualResult)
assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}")
if pickleQuotesPhase <= ctx.phase then
assert(content.isEmpty)
else
// Check that the types of the args conform to the types of the contents of the hole
val argQuotedTypes = args.map { arg =>
if arg.isTerm then
val tpe = arg.typeOpt.widenTermRefExpr match
case _: MethodicType =>
// Special erasure for captured function references
// See `SpliceTransformer.transformCapturedApplication`
defn.AnyType
case tpe => tpe
defn.QuotedExprClass.typeRef.appliedTo(tpe)
else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr)
}
val expectedResultType =
if isTerm then defn.QuotedExprClass.typeRef.appliedTo(tree1.typeOpt)
else defn.QuotedTypeClass.typeRef.appliedTo(tree1.typeOpt)
val contextualResult =
defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true)
val expectedContentType =
defn.FunctionOf(argQuotedTypes, contextualResult)
assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}")

tree1
}
Expand Down
10 changes: 10 additions & 0 deletions tests/neg-custom-args/deprecation/i17400b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import scala.quoted.*

class B:
@deprecated def dep: Int = 1

def checkDeprecated(using Quotes) =
'{
val b = new B
b.dep // error
}
9 changes: 9 additions & 0 deletions tests/neg-custom-args/no-experimental/i17400c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import scala.annotation.experimental
import scala.quoted.*

@experimental class C

def checkExperimental(using Quotes) =
'{
println(new C) // error
}
20 changes: 20 additions & 0 deletions tests/neg-macros/i15700.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import scala.quoted.*

trait Foo:
def xxx: Int

inline def foo(inline cond: Boolean) = ${ fooImpl('cond) }

def fooImpl(cond: Expr[Boolean])(using Quotes) =
if cond.valueOrAbort then
'{
new Foo {
override def xxx = 2
}
}
else
'{
new Foo { // error: object creation impossible, since def xxx: Int in trait Foo is not defined
override def xxxx = 1 // error: method xxxx overrides nothing
}
}
11 changes: 11 additions & 0 deletions tests/neg-macros/i17400a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import scala.quoted.*

class A:
def a: Int = 1

def checkOverride(using Quotes) =
'{
class Sub extends A:
def a: String = "" // error
new Sub
}
2 changes: 1 addition & 1 deletion tests/pos-macros/i7405b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Foo {
'{
trait X extends A {
type Y
def y: Y = ???
override def y: Y = ???
}
val x: X = ???
type Z = x.Y
Expand Down