Skip to content

Commit 2f1bd68

Browse files
committed
Fix #8045: Refine QuoteContext{val tasty} provided by splices
``` given qctx as QuoteContex = ??? '{ ${ myExpr(summon[QuoteContext]) } } ``` is now encoded as ``` given qctx as QuoteContex = ??? exprQuote { exprSplice { (qctx0_$1: QuoteContext { val tasty: qctx.tasty.type }) ?=> myExpr(summon[QuoteContext](qctx0_$1)) } }(given qctx) ``` Before `qctx0_$1` was typed as `QuoteContext`. Intuitively, `qctx0_$1` is a sub-context of `qctx` where both share the same reflection interface
1 parent 7ac765d commit 2f1bd68

File tree

12 files changed

+99
-15
lines changed

12 files changed

+99
-15
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1228,7 +1228,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
12281228
val argType =
12291229
if (baseType != NoType) baseType.argTypesHi.head
12301230
else defn.NothingType
1231-
ref(defn.InternalQuoted_exprSplice).appliedToType(argType).appliedTo(tree)
1231+
ref(defn.InternalQuoted_exprSplice).appliedToTypes(List(argType, defn.QuoteContextClass.typeRef)).appliedTo(tree)
12321232
}
12331233
def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match {
12341234
case Apply(fn, arg :: Nil) if fn.symbol == defn.InternalQuoted_exprSplice => Some(arg)

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

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

33
import dotty.tools.dotc.core.Contexts._
4+
import dotty.tools.dotc.ast.tpd
45
import dotty.tools.dotc.util.Property
56

67
import scala.collection.mutable
@@ -10,6 +11,8 @@ object StagingContext {
1011
/** A key to be used in a context property that tracks the quoteation level */
1112
private val QuotationLevel = new Property.Key[Int]
1213

14+
private val QuotationContexts = new Property.Key[List[tpd.Tree]]
15+
1316
/** All enclosing calls that are currently inlined, from innermost to outermost. */
1417
def level(implicit ctx: Context): Int =
1518
ctx.property(QuotationLevel).getOrElse(0)
@@ -18,8 +21,21 @@ object StagingContext {
1821
def quoteContext(implicit ctx: Context): Context =
1922
ctx.fresh.setProperty(QuotationLevel, level + 1)
2023

24+
/** Context with an incremented quotation level. */
25+
def pushQuoteContext(qctxRef: tpd.Tree)(implicit ctx: Context): Context =
26+
val old = ctx.property(QuotationContexts).getOrElse(List.empty)
27+
ctx.fresh.setProperty(QuotationLevel, level + 1)
28+
.setProperty(QuotationContexts, qctxRef :: old)
29+
2130
/** Context with a decremented quotation level. */
2231
def spliceContext(implicit ctx: Context): Context =
2332
ctx.fresh.setProperty(QuotationLevel, level - 1)
33+
34+
def popQuoteContext()(implicit ctx: Context): (Option[tpd.Tree], Context) =
35+
assert(level >= 0)
36+
val head = ctx.property(QuotationContexts).flatMap(_.headOption)
37+
val ctx1 = ctx.fresh.setProperty(QuotationLevel, level - 1)
38+
.setProperty(QuotationContexts, ctx.property(QuotationContexts).map(_.drop(1)).getOrElse(Nil))
39+
(head, ctx1)
2440
}
2541

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
6868
protected def transformSplice(body: Tree, splice: Tree)(implicit ctx: Context): Tree = {
6969
val body1 = transform(body)(spliceContext)
7070
splice match {
71-
case Apply(fun: TypeApply, _) if splice.isTerm =>
71+
case Apply(fun @ TypeApply(_, _ :: qctx :: Nil), _) if splice.isTerm =>
7272
// Type of the splice itsel must also be healed
7373
// internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
7474
val tp = checkType(splice.sourcePos).apply(splice.tpe.widenTermRefExpr)
75-
cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), body1 :: Nil)
75+
cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: qctx :: Nil), body1 :: Nil)
7676
case splice: Select => cpy.Select(splice)(body1, splice.name)
7777
}
7878
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ class ReifyQuotes extends MacroTransform {
232232
else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen)
233233
val spliceResType =
234234
if (isType) defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)
235-
else defn.FunctionType(1, isContextual = true).appliedTo(defn.QuoteContextClass.typeRef, defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)) | defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)
235+
else defn.AnyType // defn.FunctionType(1, isContextual = true).appliedTo(defn.QuoteContextClass.typeRef, defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)) | defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)
236236
val pickledQuoteStrings = liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType)
237237
val splicesList = liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), spliceResType))
238238
meth.appliedTo(pickledQuoteStrings, splicesList)
@@ -394,7 +394,7 @@ class ReifyQuotes extends MacroTransform {
394394
val body = capturers(tree.symbol).apply(tree)
395395
val splice: Tree =
396396
if (tree.isType) body.select(tpnme.splice)
397-
else ref(defn.InternalQuoted_exprSplice).appliedToType(tree.tpe).appliedTo(body)
397+
else ref(defn.InternalQuoted_exprSplice).appliedToTypes(List(tree.tpe, defn.QuoteContextClass.typeRef)).appliedTo(body)
398398

399399
transformSplice(body, splice)
400400

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap
9090

9191
case Quoted(quotedTree) =>
9292
dropEmptyBlocks(quotedTree) match {
93-
case Spliced(t) => transform(t) // '{ $x } --> x
93+
case Spliced(t) =>
94+
// '{ $x } --> x
95+
// and adapt the refinment of `QuoteContext { type tasty: ... } ?=> Expr[T]`
96+
transform(t).asInstance(tree.tpe)
9497
case _ => transformQuotation(quotedTree, tree)
9598
}
9699

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ trait QuotesAndSplices {
5454
else if (tree.quoted.isType)
5555
typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuote.termRef), tree.quoted :: Nil), pt)(quoteContext)
5656
else
57-
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuote.termRef), tree.quoted), pt)(quoteContext).select(nme.apply).appliedTo(qctx)
57+
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuote.termRef), tree.quoted), pt)(pushQuoteContext(qctx)).select(nme.apply).appliedTo(qctx)
5858
tree1.withSpan(tree.span)
5959
}
6060

@@ -91,19 +91,27 @@ trait QuotesAndSplices {
9191
markAsMacro(ctx)
9292
}
9393

94+
val (topQuoteContext, ctx1) =
95+
if level < 0 then (None, spliceContext)
96+
else popQuoteContext()
97+
9498
// Explicitly provide the given QuoteContext of the splice.
9599
// * Avoids leaking implementation details of scala.internal.quoted.CompileTime.exprSplice,
96100
// such as exprSplice taking a ?=> function argument
97101
// * Provide meaningful names for QuoteContext synthesized by within `${ ... }`
98-
// * TODO preserve QuoteContext.tasty path dependent type (see comment below and #8045)
99102
val qctxParamName = NameKinds.UniqueName.fresh(s"qctx${level - 1}_".toTermName)
100-
// TODO: Refine QuoteContext with the tasty context that the quote received
101-
// If encoloseing quote receives `qctx` then this type should be `QuoteContext { val tasty: qxtx.tasty.type }`
102-
val qctxParamTpt = untpd.TypedSplice(TypeTree(defn.QuoteContextClass.typeRef))
103+
val qctxParamTpe = topQuoteContext match {
104+
case Some(qctxRef) =>
105+
val reflectTpe = qctxRef.tpe.select(defn.QuoteContextClass.requiredValue("tasty"))
106+
RefinedType(defn.QuoteContextClass.typeRef, "tasty".toTermName, reflectTpe)
107+
case _ => defn.QuoteContextClass.typeRef
108+
}
109+
val qctxParamTpt = untpd.TypedSplice(TypeTree(qctxParamTpe))
110+
103111
val qctxParam = untpd.makeParameter(qctxParamName, qctxParamTpt, untpd.Modifiers(Given))
104112
val expr = untpd.Function(List(qctxParam), tree.expr).withSpan(tree.span)
105113

106-
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprSplice.termRef), expr), pt)(spliceContext).withSpan(tree.span)
114+
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprSplice.termRef), expr), pt)(ctx1).withSpan(tree.span)
107115
}
108116
}
109117

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package scala.internal.quoted
2+
3+
import scala.annotation.{Annotation, compileTimeOnly}
4+
import scala.quoted._
5+
6+
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime`")
7+
object CompileTime {
8+
9+
/** A term quote is desugared by the compiler into a call to this method */
10+
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.exprQuote`")
11+
def exprQuote[T](x: T): QuoteContext ?=> Expr[T] = ???
12+
13+
/** A term splice is desugared by the compiler into a call to this method */
14+
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.exprSplice`")
15+
def exprSplice[T, QCtx <: QuoteContext](x: QCtx ?=> Expr[T]): T = ???
16+
17+
/** A type quote is desugared by the compiler into a call to this method */
18+
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.typeQuote`")
19+
def typeQuote[T <: AnyKind]: Type[T] = ???
20+
21+
/** A splice in a quoted pattern is desugared by the compiler into a call to this method */
22+
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternHole`")
23+
def patternHole[T]: T = ???
24+
25+
/** A splice of a name in a quoted pattern is desugared by wrapping getting this annotation */
26+
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternBindHole`")
27+
class patternBindHole extends Annotation
28+
29+
/** A splice of a name in a quoted pattern is that marks the definition of a type splice */
30+
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.patternType`")
31+
class patternType extends Annotation
32+
33+
/** A type pattern that must be aproximated from above */
34+
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.fromAbove`")
35+
class fromAbove extends Annotation
36+
37+
/** Artifact of pickled type splices
38+
*
39+
* During quote reification a quote `'{ ... F[$t] ... }` will be transformed into
40+
* `'{ @quoteTypeTag type T$1 = $t ... F[T$1] ... }` to have a tree for `$t`.
41+
* This artifact is removed during quote unpickling.
42+
*
43+
* See ReifyQuotes.scala and PickledQuotes.scala
44+
*/
45+
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.quoteTypeTag`")
46+
class quoteTypeTag extends Annotation
47+
48+
}

library/src/scala/internal/quoted/Unpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import scala.quoted.{Expr, QuoteContext, Type}
66
object Unpickler {
77

88
type PickledQuote = List[String]
9-
type PickledExprArgs = Seq[Seq[Any] => ((QuoteContext ?=> Expr[Any]) | Type[_])]
9+
type PickledExprArgs = Seq[Seq[Any] => Any]
1010
type PickledTypeArgs = Seq[Seq[Any] => Type[_]]
1111

1212
/** Unpickle `repr` which represents a pickled `Expr` tree,

tests/pos/i8045.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import scala.quoted._
2+
object Test
3+
def run(using qctx: QuoteContext)(tree: qctx.tasty.Tree): Unit =
4+
'{ ${ makeExpr(tree) } + 1 }
5+
def makeExpr(using qctx: QuoteContext)(tree: qctx.tasty.Tree): Expr[Int] = ???
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
((qctx: scala.quoted.QuoteContext) ?=> {
22
val a: scala.quoted.Expr[scala.Int] = scala.internal.quoted.CompileTime.exprQuote[scala.Int](4).apply(using qctx)
3-
((qctx1_$1: scala.quoted.QuoteContext) ?=> a).apply(using qctx)
3+
((qctx1_$1: scala.quoted.QuoteContext {
4+
val tasty: qctx.tasty
5+
}) ?=> a).asInstanceOf[scala.ContextFunction1[scala.quoted.QuoteContext, scala.quoted.Expr[scala.Int]]].apply(using qctx)
46
})
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
((qctx: scala.quoted.QuoteContext) ?=> {
22
val a: scala.quoted.Expr[scala.Int] = scala.internal.quoted.CompileTime.exprQuote[scala.Int](4).apply(using qctx)
3-
((qctx2: scala.quoted.QuoteContext) ?=> ((qctx1_$1: scala.quoted.QuoteContext) ?=> a).apply(using qctx2)).apply(using qctx)
3+
((qctx2: scala.quoted.QuoteContext) ?=> ((qctx1_$1: scala.quoted.QuoteContext {
4+
val tasty: qctx2.tasty
5+
}) ?=> a).asInstanceOf[scala.ContextFunction1[scala.quoted.QuoteContext, scala.quoted.Expr[scala.Int]]].apply(using qctx2)).apply(using qctx)
46
})

0 commit comments

Comments
 (0)