Skip to content

Commit 1b24694

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: qctx.NestedContext) ?=> 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 02f75da commit 1b24694

File tree

14 files changed

+130
-18
lines changed

14 files changed

+130
-18
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: 26 additions & 1 deletion
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,11 @@ 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+
/** A key to be used in a context property that tracks the quoteation stack.
15+
* Stack containing the QuoteContext references recieved by the surrounding quotes.
16+
*/
17+
private val QuoteQontextStack = new Property.Key[List[tpd.Tree]]
18+
1319
/** All enclosing calls that are currently inlined, from innermost to outermost. */
1420
def level(implicit ctx: Context): Int =
1521
ctx.property(QuotationLevel).getOrElse(0)
@@ -18,8 +24,27 @@ object StagingContext {
1824
def quoteContext(implicit ctx: Context): Context =
1925
ctx.fresh.setProperty(QuotationLevel, level + 1)
2026

27+
/** Context with an incremented quotation level and pushes a refecence to a QuoteContext on the quote context stack */
28+
def pushQuoteContext(qctxRef: tpd.Tree)(implicit ctx: Context): Context =
29+
val old = ctx.property(QuoteQontextStack).getOrElse(List.empty)
30+
ctx.fresh.setProperty(QuotationLevel, level + 1)
31+
.setProperty(QuoteQontextStack, qctxRef :: old)
32+
2133
/** Context with a decremented quotation level. */
2234
def spliceContext(implicit ctx: Context): Context =
2335
ctx.fresh.setProperty(QuotationLevel, level - 1)
24-
}
2536

37+
/** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty.
38+
* The quotation stack could be empty if we are in a top level splice or an eroneous splice directly witin a top level splice.
39+
*/
40+
def popQuoteContext()(implicit ctx: Context): (Option[tpd.Tree], Context) =
41+
val ctx1 = ctx.fresh.setProperty(QuotationLevel, level - 1)
42+
val head =
43+
ctx.property(QuoteQontextStack) match
44+
case Some(x :: xs) =>
45+
ctx1.setProperty(QuoteQontextStack, xs)
46+
Some(x)
47+
case _ =>
48+
None // Splice at level 0 or lower
49+
(head, ctx1)
50+
}

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: 11 additions & 7 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,23 @@ trait QuotesAndSplices {
9191
markAsMacro(ctx)
9292
}
9393

94+
val (outerQctx, ctx1) = popQuoteContext()
95+
9496
// Explicitly provide the given QuoteContext of the splice.
9597
// * Avoids leaking implementation details of scala.internal.quoted.CompileTime.exprSplice,
9698
// such as exprSplice taking a ?=> function argument
9799
// * Provide meaningful names for QuoteContext synthesized by within `${ ... }`
98-
// * TODO preserve QuoteContext.tasty path dependent type (see comment below and #8045)
99-
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))
100+
// * If within a quote, provide a QuoteContext is linked typewise with the outer QuoteContext
101+
val qctxParamName = NameKinds.UniqueName.fresh(s"qctx${if level > 0 then level - 1 else ""}_".toTermName)
102+
val qctxParamTpe = outerQctx match {
103+
case Some(qctxRef) => qctxRef.tpe.select("NestedContext".toTypeName)
104+
case _ => defn.QuoteContextClass.typeRef // splice at level 0 (or lower)
105+
}
106+
val qctxParamTpt = untpd.TypedSplice(TypeTree(qctxParamTpe))
103107
val qctxParam = untpd.makeParameter(qctxParamName, qctxParamTpt, untpd.Modifiers(Given))
104108
val expr = untpd.Function(List(qctxParam), tree.expr).withSpan(tree.span)
105109

106-
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprSplice.termRef), expr), pt)(spliceContext).withSpan(tree.span)
110+
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprSplice.termRef), expr), pt)(ctx1).withSpan(tree.span)
107111
}
108112
}
109113

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,

library/src/scala/quoted/QuoteContext.scala

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,32 @@ import scala.quoted.show.SyntaxHighlight
1010
*
1111
* @param tasty Typed AST API. Usage: `def f(qctx: QuoteContext) = { import qctx.tasty.{_, given}; ... }`.
1212
*/
13-
class QuoteContext(val tasty: scala.tasty.Reflection) {
13+
class QuoteContext(val tasty: scala.tasty.Reflection) { self =>
1414

15+
/** Type of a QuoteContext profided by a splice within a quote that took this context.
16+
* It is only required if working with the reflection API.
17+
*
18+
* Usually it is infered by the quotes an splices typing. But sometimes it is necessary
19+
* to explicitly state that a context is nested as in the following example:
20+
*
21+
* ```scala
22+
* def run(using qctx: QuoteContext)(tree: qctx.tasty.Tree): Unit =
23+
* def nested()(using qctx.NestedContext): Expr[Int] = '{ ${ makeExpr(tree) } + 1 }
24+
* '{ ${ nested() } + 2 }
25+
* def makeExpr(using qctx: QuoteContext)(tree: qctx.tasty.Tree): Expr[Int] = ???
26+
* ```
27+
*/
28+
type NestedContext = QuoteContext {
29+
val tasty: self.tasty.type
30+
}
31+
32+
/** Show the fully elaborated source code representation of an expression */
1533
def show(expr: Expr[_], syntaxHighlight: SyntaxHighlight): String = {
1634
import tasty.{_, given}
1735
expr.unseal.showWith(syntaxHighlight)
1836
}
1937

38+
/** Show the fully elaborated source code representation of a type */
2039
def show(tpe: Type[_], syntaxHighlight: SyntaxHighlight): String = {
2140
import tasty.{_, given}
2241
tpe.unseal.showWith(syntaxHighlight)

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] = ???

tests/pos/i8045b.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted._
2+
object Test
3+
def run(using qctx: QuoteContext)(tree: qctx.tasty.Tree): Unit =
4+
def nested()(using qctx.NestedContext): Expr[Int] =
5+
'{ ${ makeExpr(tree) } + 1 }
6+
'{ ${ nested() } + 2 }
7+
8+
def makeExpr(using qctx: QuoteContext)(tree: qctx.tasty.Tree): Expr[Int] = ???
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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: qctx.NestedContext) ?=> a).asInstanceOf[scala.ContextFunction1[scala.quoted.QuoteContext, scala.quoted.Expr[scala.Int]]].apply(using qctx)
44
})
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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: qctx2.NestedContext) ?=> a).asInstanceOf[scala.ContextFunction1[scala.quoted.QuoteContext, scala.quoted.Expr[scala.Int]]].apply(using qctx2)).apply(using qctx)
44
})

0 commit comments

Comments
 (0)