Skip to content

Commit 3df9139

Browse files
committed
Re-architecture the staging phase.
Fix #6140, fix #6772, fix #7030, fix #7892, fix #7997, fix #8651 and improve #8100. Differences with previous implementation * Only track and check levels within quotes or splices * Track levels of all symbols not at level 0 * Split level checking into specialized variants for types and terms (healType/healTermType) * Detect inconsistent types rather than try to detect consistent ones * Check/heal term inconsistencies only on leaf nodes (TypeTree, RefTree, Ident, This)
1 parent a7ef3e2 commit 3df9139

20 files changed

+292
-172
lines changed

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

+128-148
Large diffs are not rendered by default.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ class ReifyQuotes extends MacroTransform {
301301

302302
/** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */
303303
private def isCaptured(sym: Symbol, level: Int)(implicit ctx: Context): Boolean =
304-
level == 1 && levelOf(sym).contains(1) && capturers.contains(sym)
304+
level == 1 && levelOf(sym) == 1 && capturers.contains(sym)
305305

306306
/** Transform `tree` and return the resulting tree and all `embedded` quotes
307307
* or splices as a pair.

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,19 @@ class Staging extends MacroTransform {
4343
tree match {
4444
case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass =>
4545
val checker = new PCPCheckAndHeal(freshStagingContext) {
46-
override protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[tpd.Tree] = {
46+
override protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): TypeRef = {
4747
def symStr =
4848
if (sym.is(ModuleClass)) sym.sourceModule.show
4949
else i"${sym.name}.this"
5050
val errMsg = s"\nin ${ctx.owner.fullName}"
5151
assert(
5252
ctx.owner.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) ||
53-
(sym.isType && levelOf(sym).getOrElse(0) > 0),
53+
(sym.isType && levelOf(sym) > 0),
5454
em"""access to $symStr from wrong staging level:
55-
| - the definition is at level ${levelOf(sym).getOrElse(0)},
55+
| - the definition is at level ${levelOf(sym)},
5656
| - but the access is at level $level.$errMsg""")
5757

58-
None
58+
tp
5959
}
6060
}
6161
checker.transform(tree)

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

+19-8
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,28 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap
3535
import TreeMapWithStages._
3636

3737
/** A map from locally defined symbols to their definition quotation level */
38-
private val levelOfMap: mutable.HashMap[Symbol, Int] = ictx.property(LevelOfKey).get
38+
private[this] val levelOfMap: mutable.HashMap[Symbol, Int] = ictx.property(LevelOfKey).get
3939

4040
/** A stack of entered symbols, to be unwound after scope exit */
41-
private var enteredSyms: List[Symbol] = Nil
41+
private[this] var enteredSyms: List[Symbol] = Nil
42+
43+
/** If we are inside a quote or a splice */
44+
private[this] var inQuoteOrSplice = false
4245

4346
/** The quotation level of the definition of the locally defined symbol */
44-
protected def levelOf(sym: Symbol): Option[Int] = levelOfMap.get(sym)
47+
protected def levelOf(sym: Symbol): Int = levelOfMap.getOrElse(sym, 0)
4548

4649
/** Localy defined symbols seen so far by `StagingTransformer.transform` */
4750
protected def localSymbols: List[Symbol] = enteredSyms
4851

49-
/** Enter staging level of symbol defined by `tree`, if applicable. */
52+
/** If we are inside a quote or a splice */
53+
protected def isInQuoteOrSplice: Boolean = inQuoteOrSplice
54+
55+
/** Enter staging level of symbol defined by `tree` */
5056
private def markSymbol(sym: Symbol)(implicit ctx: Context): Unit =
51-
if ((sym.isClass || sym.maybeOwner.isTerm) && !levelOfMap.contains(sym)) {
57+
if level != 0 && !levelOfMap.contains(sym) then
5258
levelOfMap(sym) = level
5359
enteredSyms = sym :: enteredSyms
54-
}
5560

5661
/** Enter staging level of symbol defined by `tree`, if applicable. */
5762
private def markDef(tree: Tree)(implicit ctx: Context): Unit = tree match {
@@ -89,19 +94,25 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap
8994
tree match {
9095

9196
case Quoted(quotedTree) =>
92-
dropEmptyBlocks(quotedTree) match {
97+
val old = inQuoteOrSplice
98+
inQuoteOrSplice = true
99+
try dropEmptyBlocks(quotedTree) match {
93100
case Spliced(t) =>
94101
// '{ $x } --> x
95102
// and adapt the refinment of `QuoteContext { type tasty: ... } ?=> Expr[T]`
96103
transform(t).asInstance(tree.tpe)
97104
case _ => transformQuotation(quotedTree, tree)
98105
}
106+
finally inQuoteOrSplice = old
99107

100108
case tree @ Spliced(splicedTree) =>
101-
dropEmptyBlocks(splicedTree) match {
109+
val old = inQuoteOrSplice
110+
inQuoteOrSplice = true
111+
try dropEmptyBlocks(splicedTree) match {
102112
case Quoted(t) => transform(t) // ${ 'x } --> x
103113
case _ => transformSplice(splicedTree, tree)
104114
}
115+
finally inQuoteOrSplice = old
105116

106117
case Block(stats, _) =>
107118
val last = enteredSyms

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

-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import config.Feature._
3838
import config.SourceVersion._
3939
import rewrites.Rewrites.patch
4040
import NavigateAST._
41-
import dotty.tools.dotc.transform.{PCPCheckAndHeal, Staging, TreeMapWithStages}
4241
import transform.SymUtils._
4342
import transform.TypeUtils._
4443
import reporting.trace

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

+3
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,6 @@ nullable.scala
4040

4141
# parameter untupling with overloaded functions (see comment in Applications.normArg)
4242
i7757.scala
43+
44+
# splice type tag dealiased in this reference
45+
i8651b.scala

tests/neg-macros/quote-this-a.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.quoted._
2+
3+
class Foo {
4+
5+
def f(using QuoteContext): Unit = '{
6+
def bar[T](x: T): T = x
7+
bar[
8+
this.type // error
9+
] {
10+
this // error
11+
}
12+
}
13+
14+
}

tests/neg-macros/quote-this.scala renamed to tests/neg-macros/quote-this-c.scala

-9
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,6 @@ import scala.quoted._
22

33
class Foo {
44

5-
def f(using QuoteContext): Unit = '{
6-
def bar[T](x: T): T = x
7-
bar[
8-
this.type // error
9-
] {
10-
this // error
11-
}
12-
}
13-
145
inline def i(): Unit = ${ Foo.impl[Any]('{
156
val x: QuoteContext = ???
167
given x.type = x

tests/neg/i7892.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import scala.quoted._
2+
3+
package x {
4+
class CExprResult1[T]
5+
}
6+
7+
def run(using qctx: QuoteContext): Unit = {
8+
val cpsLeft: x.CExprResult1[?] = ???
9+
run1(cpsLeft) // error
10+
}
11+
12+
def run1[L:Type](cpsLeft: x.CExprResult1[L]): Unit = ???

tests/pos-macros/i6140.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted._
2+
sealed trait Trait[T] {
3+
type t = T
4+
}
5+
6+
object O {
7+
def fn[T:Type](t : Trait[T])(using QuoteContext): Type[T] = '[t.t]
8+
}

tests/pos-macros/i7030/Macros_1.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.quoted._
2+
3+
inline def inner(exprs: Any): Any = ${innerImpl('exprs)}
4+
def innerImpl(exprs: Expr[Any])(using QuoteContext): Expr[Any] =
5+
'{$exprs ; ()}
6+
7+
inline def outer(expr: => Any): Any = ${outerImpl('expr)}
8+
def outerImpl(body: Expr[Any])(using ctx: QuoteContext): Expr[Any] = {
9+
import ctx.tasty._
10+
body.unseal.underlyingArgument.seal
11+
}

tests/pos-macros/i7030/Test_2.scala

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
val x = outer(inner(???))

tests/pos-macros/i8100.scala

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.quoted._
2+
3+
class M {
4+
type E
5+
}
6+
7+
def f[T: Type](using QuoteContext) =
8+
Expr.summon[M] match
9+
case Some('{ $mm : $tt }) =>
10+
'{
11+
val m = $mm
12+
type ME = m.E
13+
${ g[ME](using '[ME]) }
14+
${ g[m.E](using '[ME]) }
15+
${ g[ME](using '[m.E]) }
16+
${ g[m.E](using '[m.E]) }
17+
// ${ g[ME] } // FIXME: issue seems to be in ReifyQuotes
18+
// ${ g[m.E] } // FIXME: issue seems to be in ReifyQuotes
19+
}
20+
21+
def g[T](using Type[T]) = ???

tests/pos/i7997.scala

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import scala.quoted._
2+
def oops(using QuoteContext) = {
3+
val q = '{ class Foo { val x = 3; ${ val v = 'x; '{} } }}
4+
}

tests/pos/i8651a.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted._
2+
def coroutineImpl(using QuoteContext): Expr[Any] =
3+
'{
4+
new {
5+
def state: Int = 0
6+
${identity('state)}
7+
}
8+
}

tests/pos/i8651b.scala

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
abstract class Coroutine[+T] {
2+
def continue: Option[T]
3+
}
4+
5+
object Macros {
6+
7+
import scala.quoted._
8+
import scala.quoted.matching._
9+
10+
11+
inline def coroutine[T](inline body: Any): Coroutine[T] = ${ coroutineImpl('{body}) }
12+
13+
def coroutineImpl[T: Type](expr: Expr[_ <: Any])(implicit qtx: QuoteContext): Expr[Coroutine[T]] = {
14+
import qtx.tasty.{_, given _}
15+
16+
'{
17+
new Coroutine[T] {
18+
var state: Int = 0
19+
20+
def continue: Option[T] = ${
21+
'{
22+
state = 1 //if this line is commented there are no compile errors anymore!!!
23+
None
24+
}
25+
}
26+
27+
}
28+
}
29+
30+
}
31+
}

tests/pos/quote-type-with-param.scala

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import scala.quoted._
2+
3+
def f(using QuoteContext): Unit =
4+
'{ type T[X] = List[X] }

tests/run-macros/i6772/Macro_1.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.quoted._
2+
3+
object Macros {
4+
5+
inline def m() : Any = ${ mImpl() }
6+
7+
def mImpl()(using QuoteContext): Expr[Any] =
8+
List(Expr(1), Expr(2), Expr(3)).toExprOfList
9+
10+
def (list: List[Expr[T]]).toExprOfList[T](using Type[T], QuoteContext): Expr[List[T]] = '{
11+
val buff = List.newBuilder[T]
12+
${ Expr.block(list.map(v => '{ buff += $v }), '{ buff.result() }) }
13+
}
14+
}

tests/run-macros/i6772/Test_2.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted._
2+
import Macros._
3+
4+
object Test {
5+
def main(args: Array[String]): Unit = {
6+
println(m())
7+
}
8+
}

0 commit comments

Comments
 (0)