Skip to content

Commit 0e847de

Browse files
Merge pull request #8342 from dotty-staging/fix-#8302
Move quote tagging to Staging
2 parents 57efb73 + e1b2378 commit 0e847de

File tree

18 files changed

+162
-76
lines changed

18 files changed

+162
-76
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package dotty.tools.dotc.core
22

3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core.Contexts._
5+
import dotty.tools.dotc.core.Types._
6+
import dotty.tools.dotc.core.Symbols._
37
import dotty.tools.dotc.core.Contexts._
48
import dotty.tools.dotc.ast.tpd
59
import dotty.tools.dotc.util.Property
10+
import dotty.tools.dotc.transform.PCPCheckAndHeal
611

712
import scala.collection.mutable
813

@@ -16,6 +21,8 @@ object StagingContext {
1621
*/
1722
private val QuoteContextStack = new Property.Key[List[tpd.Tree]]
1823

24+
private val TaggedTypes = new Property.Key[PCPCheckAndHeal.QuoteTypeTags]
25+
1926
/** All enclosing calls that are currently inlined, from innermost to outermost. */
2027
def level(implicit ctx: Context): Int =
2128
ctx.property(QuotationLevel).getOrElse(0)
@@ -34,6 +41,12 @@ object StagingContext {
3441
def spliceContext(implicit ctx: Context): Context =
3542
ctx.fresh.setProperty(QuotationLevel, level - 1)
3643

44+
def contextWithQuoteTypeTags(taggedTypes: PCPCheckAndHeal.QuoteTypeTags)(implicit ctx: Context) =
45+
ctx.fresh.setProperty(TaggedTypes, taggedTypes)
46+
47+
def getQuoteTypeTags(implicit ctx: Context): PCPCheckAndHeal.QuoteTypeTags =
48+
ctx.property(TaggedTypes).get
49+
3750
/** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty.
3851
* The quotation stack could be empty if we are in a top level splice or an eroneous splice directly witin a top level splice.
3952
*/

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ object PickledQuotes {
5252
private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() {
5353
override def apply(tp: Type): Type = {
5454
val tp1 = tp match {
55-
case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => tp.dealias
55+
case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) =>
56+
tp.symbol.info.hiBound
5657
case _ => tp
5758
}
5859
mapOver(tp1)

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

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import dotty.tools.dotc.transform.TreeMapWithStages._
2121
import dotty.tools.dotc.typer.Checking
2222
import dotty.tools.dotc.typer.Implicits.SearchFailureType
2323
import dotty.tools.dotc.typer.Inliner
24+
import dotty.tools.dotc.core.Annotations._
2425

2526
import scala.collection.mutable
2627
import dotty.tools.dotc.util.SourcePosition
@@ -54,10 +55,21 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
5455

5556
/** Transform quoted trees while maintaining phase correctness */
5657
override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = {
58+
val taggedTypes = new PCPCheckAndHeal.QuoteTypeTags(quote.span)(using ctx)
59+
5760
if (ctx.property(InAnnotation).isDefined)
5861
ctx.error("Cannot have a quote in an annotation", quote.sourcePos)
59-
val body1 = transform(body)(quoteContext)
60-
super.transformQuotation(body1, quote)
62+
63+
val contextWithQuote =
64+
if level == 0 then contextWithQuoteTypeTags(taggedTypes)(quoteContext)
65+
else quoteContext
66+
val body1 = transform(body)(contextWithQuote)
67+
val body2 =
68+
taggedTypes.getTypeTags match
69+
case Nil => body1
70+
case tags => tpd.Block(tags, body1).withSpan(body.span)
71+
72+
super.transformQuotation(body2, quote)
6173
}
6274

6375
/** Transform splice
@@ -73,7 +85,9 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
7385
// internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
7486
val tp = checkType(splice.sourcePos).apply(splice.tpe.widenTermRefExpr)
7587
cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: qctx :: Nil), body1 :: Nil)
76-
case splice: Select => cpy.Select(splice)(body1, splice.name)
88+
case splice: Select =>
89+
val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf[TermRef])
90+
ref(tagRef).withSpan(splice.span)
7791
}
7892
}
7993

@@ -120,11 +134,14 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
120134
case tp: TypeRef if tp.symbol.isSplice =>
121135
if (tp.isTerm)
122136
ctx.error(i"splice outside quotes", pos)
123-
tp
137+
if level > 0 then getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf[TermRef])
138+
else tp
124139
case tp: TypeRef if tp.symbol == defn.QuotedTypeClass.typeParams.head =>
125-
// Adapt direct references to the type of the type parameter T of a quoted.Type[T].
126-
// Replace it with a properly encoded type splice. This is the normal for expected for type splices.
127-
tp.prefix.select(tpnme.splice)
140+
if level > 0 then
141+
// Adapt direct references to the type of the type parameter T of a quoted.Type[T].
142+
// Replace it with a properly encoded type splice. This is the normal form expected for type splices.
143+
getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf[TermRef])
144+
else tp
128145
case tp: NamedType =>
129146
checkSymLevel(tp.symbol, tp, pos) match {
130147
case Some(tpRef) => tpRef.tpe
@@ -201,7 +218,9 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
201218
sym.isClass // reference to this in inline methods
202219
)
203220
case None =>
204-
sym.is(Package) || sym.owner.isStaticOwner || levelOK(sym.owner)
221+
sym.is(Package) || sym.owner.isStaticOwner ||
222+
(sym.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) && level > 0) ||
223+
levelOK(sym.owner)
205224
}
206225

207226
/** Try to heal reference to type `T` used in a higher level than its definition.
@@ -212,10 +231,11 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
212231
protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = {
213232
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
214233
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
234+
215235
tag.tpe match
216236
case tp: TermRef =>
217237
checkStable(tp, pos)
218-
Some(tag.select(tpnme.splice))
238+
Some(ref(getQuoteTypeTags.getTagRef(tp)))
219239
case _: SearchFailureType =>
220240
levelError(sym, tp, pos,
221241
i"""
@@ -242,3 +262,34 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
242262
}
243263
}
244264

265+
object PCPCheckAndHeal {
266+
import tpd._
267+
268+
class QuoteTypeTags(span: Span)(using ctx: Context) {
269+
270+
private val tags = collection.mutable.LinkedHashMap.empty[Symbol, TypeDef]
271+
272+
def getTagRef(spliced: TermRef): TypeRef = {
273+
val typeDef = tags.getOrElseUpdate(spliced.symbol, mkTagSymbolAndAssignType(spliced))
274+
typeDef.symbol.typeRef
275+
}
276+
277+
def getTypeTags: List[TypeDef] = tags.valuesIterator.toList
278+
279+
private def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = {
280+
val splicedTree = tpd.ref(spliced).withSpan(span)
281+
val rhs = splicedTree.select(tpnme.splice).withSpan(span)
282+
val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree)
283+
val local = ctx.newSymbol(
284+
owner = ctx.owner,
285+
name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName,
286+
flags = Synthetic,
287+
info = TypeAlias(splicedTree.tpe.select(tpnme.splice)),
288+
coord = span).asType
289+
local.addAnnotation(Annotation(defn.InternalQuoted_QuoteTypeTagAnnot))
290+
ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local)
291+
}
292+
293+
}
294+
295+
}

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

Lines changed: 6 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -119,57 +119,6 @@ class ReifyQuotes extends MacroTransform {
119119
new QuoteReifier(this, capturers, nestedEmbedded, ctx.owner)(ctx)
120120
}
121121

122-
/** Assuming <expr> contains types `${<tag1>}, ..., ${<tagN>}`, the expression
123-
*
124-
* { @quoteTypeTag type <Type1> = ${<tag1>}
125-
* ...
126-
* @quoteTypeTag type <TypeN> = ${<tagN>}
127-
* <expr>
128-
* }
129-
*
130-
* references to `TypeI` in `expr` are rewired to point to the locally
131-
* defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN`
132-
* as splices.
133-
*/
134-
private def addTags(expr: Tree)(implicit ctx: Context): Tree = {
135-
136-
def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = {
137-
val splicedTree = tpd.ref(spliced).withSpan(expr.span)
138-
val rhs = transform(splicedTree.select(tpnme.splice))
139-
val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree)
140-
val local = ctx.newSymbol(
141-
owner = ctx.owner,
142-
name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName,
143-
flags = Synthetic,
144-
info = TypeAlias(splicedTree.tpe.select(tpnme.splice)),
145-
coord = spliced.termSymbol.coord).asType
146-
local.addAnnotation(Annotation(defn.InternalQuoted_QuoteTypeTagAnnot))
147-
ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local)
148-
}
149-
150-
val tagDefCache = new mutable.LinkedHashMap[Symbol, TypeDef]()
151-
152-
def typeTagMap = new TypeMap() {
153-
def apply(tp: Type): Type = tp match {
154-
case tp: TypeRef if tp.symbol.isSplice =>
155-
tp.prefix match {
156-
case prefix: TermRef =>
157-
val tagDef = tagDefCache.getOrElseUpdate(prefix.symbol, mkTagSymbolAndAssignType(prefix))
158-
tagDef.symbol.typeRef
159-
}
160-
case AnnotatedType(parent, _) =>
161-
apply(parent) // Only keep the Annotated tree
162-
case _ =>
163-
mapOver(tp)
164-
}
165-
}
166-
167-
val tagedTree = new TreeTypeMap(typeMap = typeTagMap).apply(expr)
168-
169-
if (tagDefCache.isEmpty) expr
170-
else Block(tagDefCache.valuesIterator.toList, tagedTree)
171-
}
172-
173122
/** Split `body` into a core and a list of embedded splices.
174123
* Then if inside a splice, make a hole from these parts.
175124
* If outside a splice, generate a call tp `scala.quoted.Unpickler.unpickleType` or
@@ -229,7 +178,7 @@ class ReifyQuotes extends MacroTransform {
229178
def pickleAsTasty() = {
230179
val meth =
231180
if (isType) ref(defn.Unpickler_unpickleType).appliedToType(originalTp)
232-
else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen)
181+
else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen.dealias)
233182
val pickledQuoteStrings = liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType)
234183
val splicesList = liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.AnyType))
235184
meth.appliedTo(pickledQuoteStrings, splicesList)
@@ -362,10 +311,10 @@ class ReifyQuotes extends MacroTransform {
362311
level == 1 && levelOf(sym).contains(1) && capturers.contains(sym)
363312

364313
/** Transform `tree` and return the resulting tree and all `embedded` quotes
365-
* or splices as a pair, after performing the `addTags` transform.
314+
* or splices as a pair.
366315
*/
367316
private def splitQuote(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = {
368-
val tree1 = addTags(transform(tree))
317+
val tree1 = stipTypeAnnotations(transform(tree))
369318
(tree1, embedded.getTrees)
370319
}
371320

@@ -374,6 +323,9 @@ class ReifyQuotes extends MacroTransform {
374323
(tree1, embedded.getTrees)
375324
}
376325

326+
private def stipTypeAnnotations(tree: Tree)(using Context): Tree =
327+
new TreeTypeMap(typeMap = _.stripAnnots).apply(tree)
328+
377329
/** Register `body` as an `embedded` quote or splice
378330
* and return a hole with `splices` as arguments and the given type `tpe`.
379331
*/

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ class Staging extends MacroTransform {
4848
if (sym.is(ModuleClass)) sym.sourceModule.show
4949
else i"${sym.name}.this"
5050
val errMsg = s"\nin ${ctx.owner.fullName}"
51-
assert(false,
51+
assert(
52+
ctx.owner.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) ||
53+
(sym.isType && levelOf(sym).getOrElse(0) > 0),
5254
em"""access to $symStr from wrong staging level:
5355
| - the definition is at level ${levelOf(sym).getOrElse(0)},
5456
| - but the access is at level $level.$errMsg""")

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ trait QuotesAndSplices {
8787
if (c.owner eq c.outer.owner) markAsMacro(c.outer)
8888
else if (c.owner.isInlineMethod) c.owner.setFlag(Macro)
8989
else if (!c.outer.owner.is(Package)) markAsMacro(c.outer)
90-
else assert(false) // Did not find inline def to mark as macro
90+
else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro
9191
markAsMacro(ctx)
9292
}
9393

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ t3612.scala
1111
reference
1212
scala-days-2019-slides
1313
i7048e.scala
14+
i8052.scala
1415

1516
# Stale symbol: package object scala
1617
seqtype-cycle

library/src/scala/quoted/util/ExprMap.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ trait ExprMap {
6464
val tp = tpt.tpe match
6565
// TODO improve code
6666
case AppliedType(TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "<repeated>"), List(tp0: Type)) =>
67+
// TODO rewrite without using quotes
6768
type T
68-
val a = tp0.seal.asInstanceOf[quoted.Type[T]]
69-
'[Seq[$a]].unseal.tpe
69+
val qtp: quoted.Type[T] = tp0.seal.asInstanceOf[quoted.Type[T]]
70+
given qtp.type = qtp
71+
'[Seq[T]].unseal.tpe
7072
case tp => tp
7173
Typed.copy(tree)(transformTerm(expr, tp), transformTypeTree(tpt))
7274
case tree: NamedArg =>

tests/neg/i7048e.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ abstract class Test {
99

1010
def foo(using QuoteContext): Expr[Any] = {
1111

12-
val r = '{Option.empty[T]} // error
12+
val r = '{Option.empty[T]} // error: is not stable
1313

1414
{
1515
val t: Test = this
@@ -22,14 +22,14 @@ abstract class Test {
2222
{
2323
val r1 = '{Option.empty[${T}]} // works
2424
val r2 = '{Option.empty[List[${T}]]} // works
25-
// val r3 = '{summon[Type[${T}]]} // access to Test.this from wrong staging level
26-
val r4 = '{summon[${T} <:< Any]} // error
25+
val r3 = '{summon[Type[${T}]]} // error: is not stable
26+
val r4 = '{summon[${T} <:< Any]} // error: is not stable
2727
}
2828

2929
{
30-
val s = '{Option.empty[${T}]}
30+
val s = '{Option.empty[${T}]} // works
3131
val r = '{identity($s)} // works
32-
val r2 = '{identity(${s: Expr[Option[T]]})} // error // error
32+
val r2 = '{identity(${s: Expr[Option[T]]})} // error // error : is not stable
3333
}
3434

3535
r

tests/neg/i8052.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import scala.deriving._
2+
import scala.quoted._
3+
import scala.quoted.matching._
4+
5+
object Macro2 {
6+
trait TC[T] {
7+
def test(): Unit
8+
}
9+
10+
object TC {
11+
def derived[T: Type](ev: Expr[Mirror.Of[T]])(using qctx: QuoteContext): Expr[TC[T]] = '{
12+
new TC[T] {
13+
def encode(): Unit = $ev match {
14+
case '{ $m: Mirror.ProductOf[T] } => ??? // error
15+
}
16+
}
17+
}
18+
}
19+
}

tests/pos/i7521.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.quoted._
2+
import scala.annotation.StaticAnnotation
3+
4+
object Test {
5+
inline def quote[T]: Unit = ${ quoteImpl[T] }
6+
def quoteImpl[T: Type](using qctx: QuoteContext): Expr[Unit] = '{
7+
class Annot extends StaticAnnotation
8+
var test: T @Annot = ???
9+
}
10+
}

tests/pos/i8052.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.deriving._
2+
import scala.quoted._
3+
import scala.quoted.matching._
4+
5+
object Macro2 {
6+
trait TC[T] {
7+
def test(): Unit
8+
}
9+
10+
object TC {
11+
def derived[T: Type](ev: Expr[Mirror.Of[T]])(using qctx: QuoteContext): Expr[TC[T]] = '{
12+
new TC[T] {
13+
def encode(): Unit = ${
14+
ev match {
15+
case '{ $m: Mirror.ProductOf[T] } => ???
16+
}
17+
}
18+
}
19+
}
20+
}
21+
}

tests/pos/i8302.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted._
2+
def foo[T](using qctx: QuoteContext, tpe: Type[T]): Expr[Any] =
3+
'{
4+
type TT = T
5+
val t = '[TT]
6+
???
7+
}
8+

tests/pos/using-quote-context.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import scala.quoted._
2+
3+
class Test {
4+
def fold[W: Type](s: Expr[W]): QuoteContext ?=> Expr[W] =
5+
'{ ???; $s }
6+
}

tests/run-staging/i5247.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
null.asInstanceOf[java.lang.Object]
2-
null.asInstanceOf[scala.List[java.lang.Object]]
2+
null.asInstanceOf[scala.collection.immutable.List[java.lang.Object]]

0 commit comments

Comments
 (0)