Skip to content

Commit 4fd5f04

Browse files
committed
Move quote tagging to Staging
Fix #8302, fix #8052 and fix #7521.
1 parent 98103a2 commit 4fd5f04

File tree

18 files changed

+173
-75
lines changed

18 files changed

+173
-75
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
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.util.Property
9+
import dotty.tools.dotc.transform.PCPCheckAndHeal
510

611
import scala.collection.mutable
712

@@ -10,6 +15,8 @@ object StagingContext {
1015
/** A key to be used in a context property that tracks the quoteation level */
1116
private val QuotationLevel = new Property.Key[Int]
1217

18+
private val TaggedTypes = new Property.Key[PCPCheckAndHeal.QuoteTypeTags]
19+
1320
/** All enclosing calls that are currently inlined, from innermost to outermost. */
1421
def level(implicit ctx: Context): Int =
1522
ctx.property(QuotationLevel).getOrElse(0)
@@ -21,5 +28,12 @@ object StagingContext {
2128
/** Context with a decremented quotation level. */
2229
def spliceContext(implicit ctx: Context): Context =
2330
ctx.fresh.setProperty(QuotationLevel, level - 1)
31+
32+
def contextWithQuoteTypeTags(taggedTypes: PCPCheckAndHeal.QuoteTypeTags)(implicit ctx: Context) =
33+
ctx.fresh.setProperty(TaggedTypes, taggedTypes)
34+
35+
def getQuoteTypeTags(implicit ctx: Context): PCPCheckAndHeal.QuoteTypeTags =
36+
ctx.property(TaggedTypes).get
37+
2438
}
2539

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: 63 additions & 8 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,26 @@ 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+
given Context =
60+
if level(ctx) == 0 then contextWithQuoteTypeTags(taggedTypes)(ctx)
61+
else ctx
62+
5763
if (ctx.property(InAnnotation).isDefined)
5864
ctx.error("Cannot have a quote in an annotation", quote.sourcePos)
59-
val body1 = transform(body)(quoteContext)
60-
super.transformQuotation(body1, quote)
65+
66+
val body1 =
67+
transform(body)(quoteContext)
68+
69+
val body2 =
70+
if level != 0 then body1
71+
else
72+
val tags = taggedTypes.getTypeTags
73+
if tags.isEmpty then body1
74+
else tpd.Block(taggedTypes.getTypeTags, body1).withSpan(body.span)
75+
76+
super.transformQuotation(body2, quote)
77+
6178
}
6279

6380
/** Transform splice
@@ -73,7 +90,9 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
7390
// internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
7491
val tp = checkType(splice.sourcePos).apply(splice.tpe.widenTermRefExpr)
7592
cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), body1 :: Nil)
76-
case splice: Select => cpy.Select(splice)(body1, splice.name)
93+
case splice: Select =>
94+
val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf[TermRef])
95+
ref(tagRef).withSpan(splice.span)
7796
}
7897
}
7998

@@ -120,11 +139,14 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
120139
case tp: TypeRef if tp.symbol.isSplice =>
121140
if (tp.isTerm)
122141
ctx.error(i"splice outside quotes", pos)
123-
tp
142+
if level > 0 then getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf[TermRef])
143+
else tp
124144
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)
145+
if level > 0 then
146+
// Adapt direct references to the type of the type parameter T of a quoted.Type[T].
147+
// Replace it with a properly encoded type splice. This is the normal for expected for type splices.
148+
getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf[TermRef])
149+
else tp
128150
case tp: NamedType =>
129151
checkSymLevel(tp.symbol, tp, pos) match {
130152
case Some(tpRef) => tpRef.tpe
@@ -201,6 +223,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
201223
sym.isClass // reference to this in inline methods
202224
)
203225
case None =>
226+
(sym.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) && level > 0) ||
204227
sym.is(Package) || sym.owner.isStaticOwner || levelOK(sym.owner)
205228
}
206229

@@ -212,10 +235,11 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
212235
protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = {
213236
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
214237
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
238+
215239
tag.tpe match
216240
case tp: TermRef =>
217241
checkStable(tp, pos)
218-
Some(tag.select(tpnme.splice))
242+
Some(ref(getQuoteTypeTags.getTagRef(tp)))
219243
case _: SearchFailureType =>
220244
levelError(sym, tp, pos,
221245
i"""
@@ -242,3 +266,34 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
242266
}
243267
}
244268

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

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

Lines changed: 13 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 spliceResType =
234183
if (isType) defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)
235184
else defn.FunctionType(1, isContextual = true).appliedTo(defn.QuoteContextClass.typeRef, defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)) | defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)
@@ -365,10 +314,10 @@ class ReifyQuotes extends MacroTransform {
365314
level == 1 && levelOf(sym).contains(1) && capturers.contains(sym)
366315

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

@@ -377,6 +326,16 @@ class ReifyQuotes extends MacroTransform {
377326
(tree1, embedded.getTrees)
378327
}
379328

329+
private def stipTypeAnnotations(tree: Tree)(using Context): Tree = {
330+
def typeTagMap = new TypeMap() {
331+
def apply(tp: Type): Type =
332+
tp match
333+
case AnnotatedType(parent, _) => apply(parent) // Only keep the Annotated tree
334+
case _ => mapOver(tp)
335+
}
336+
new TreeTypeMap(typeMap = typeTagMap).apply(tree)
337+
}
338+
380339
/** Register `body` as an `embedded` quote or splice
381340
* and return a hole with `splices` as arguments and the given type `tpe`.
382341
*/

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
@@ -81,7 +81,7 @@ trait QuotesAndSplices {
8181
if (c.owner eq c.outer.owner) markAsMacro(c.outer)
8282
else if (c.owner.isInlineMethod) c.owner.setFlag(Macro)
8383
else if (!c.outer.owner.is(Package)) markAsMacro(c.outer)
84-
else assert(false) // Did not find inline def to mark as macro
84+
else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro
8585
markAsMacro(ctx)
8686
}
8787

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+

0 commit comments

Comments
 (0)