Skip to content

Commit 90a6448

Browse files
authored
Merge pull request #7682 from dotty-staging/fix-#7405
Fix #7405 and #7887: Allow types references at a lower level
2 parents 9a270c1 + 8042c00 commit 90a6448

File tree

8 files changed

+93
-42
lines changed

8 files changed

+93
-42
lines changed

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

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,12 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
176176
if (!sym.exists || levelOK(sym) || isStaticPathOK(tp) || isStaticNew(tp))
177177
None
178178
else if (!sym.isStaticOwner && !isClassRef)
179-
tryHeal(sym, tp, pos)
179+
tp match
180+
case tp: TypeRef =>
181+
if levelOf(sym).getOrElse(0) < level then tryHeal(sym, tp, pos)
182+
else None
183+
case _ =>
184+
levelError(sym, tp, pos, "")
180185
else if (!sym.owner.isStaticOwner) // non-top level class reference that is phase inconsistent
181186
levelError(sym, tp, pos, "")
182187
else
@@ -202,40 +207,29 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
202207
sym.is(Package) || sym.owner.isStaticOwner || levelOK(sym.owner)
203208
}
204209

205-
/** Try to heal phase-inconsistent reference to type `T` using a local type definition.
210+
/** Try to heal reference to type `T` used in a higher level than its definition.
206211
* @return None if successful
207212
* @return Some(msg) if unsuccessful where `msg` is a potentially empty error message
208213
* to be added to the "inconsistent phase" message.
209214
*/
210-
protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] =
211-
tp match {
212-
case tp: TypeRef =>
213-
if (level == -1) {
214-
assert(ctx.inInlineMethod)
215-
None
216-
}
217-
else {
218-
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
219-
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
220-
tag.tpe match {
221-
case _: TermRef =>
222-
Some(tag.select(tpnme.splice))
223-
case _: SearchFailureType =>
224-
levelError(sym, tp, pos,
225-
i"""
226-
|
227-
| The access would be accepted with the right type tag, but
228-
| ${ctx.typer.missingArgMsg(tag, reqType, "")}""")
229-
case _ =>
230-
levelError(sym, tp, pos,
231-
i"""
232-
|
233-
| The access would be accepted with an implict $reqType""")
234-
}
235-
}
215+
protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = {
216+
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
217+
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
218+
tag.tpe match
219+
case _: TermRef =>
220+
Some(tag.select(tpnme.splice))
221+
case _: SearchFailureType =>
222+
levelError(sym, tp, pos,
223+
i"""
224+
|
225+
| The access would be accepted with the right type tag, but
226+
| ${ctx.typer.missingArgMsg(tag, reqType, "")}""")
236227
case _ =>
237-
levelError(sym, tp, pos, "")
238-
}
228+
levelError(sym, tp, pos,
229+
i"""
230+
|
231+
| The access would be accepted with a given $reqType""")
232+
}
239233

240234
private def levelError(sym: Symbol, tp: Type, pos: SourcePosition, errMsg: String)(given Context) = {
241235
def symStr =

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,10 @@ 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: Type, pos: SourcePosition)(implicit ctx: Context): Option[tpd.Tree] = {
46+
override protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[tpd.Tree] = {
4747
def symStr =
48-
if (!tp.isInstanceOf[ThisType]) sym.show
49-
else if (sym.is(ModuleClass)) sym.sourceModule.show
48+
if (sym.is(ModuleClass)) sym.sourceModule.show
5049
else i"${sym.name}.this"
51-
5250
val errMsg = s"\nin ${ctx.owner.fullName}"
5351
assert(false,
5452
em"""access to $symStr from wrong staging level:

docs/docs/reference/metaprogramming/macros.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,15 @@ different directions here for `f` and `x`. The reference to `f` is
161161
legal because it is quoted, then spliced, whereas the reference to `x`
162162
is legal because it is spliced, then quoted.
163163

164-
### Types and the PCP
164+
### Lifting Types
165165

166-
In principle, The phase consistency principle applies to types as well
167-
as for expressions. This might seem too restrictive. Indeed, the
168-
definition of `reflect` above is not phase correct since there is a
166+
Types are not directly affected by the phase consistency principle.
167+
It is possible to use types defined at any level in any other level.
168+
But, if a type is used in a subsequent stage it will need to be lifted to a `Type`.
169+
The resulting value of `Type` will be subject to PCP.
170+
Indeed, the definition of `reflect` above uses `T` in the next stage, there is a
169171
quote but no splice between the parameter binding of `T` and its
170-
usage. But the code can be made phase correct by adding a binding
171-
of a `Type[T]` tag:
172+
usage. But the code can be rewritten by adding a binding of a `Type[T]` tag:
172173
```scala
173174
def reflect[T, U](f: Expr[T] => Expr[U])(given t: Type[T]): Expr[T => U] =
174175
'{ (x: $t) => ${ f('x) } }
@@ -177,8 +178,8 @@ In this version of `reflect`, the type of `x` is now the result of
177178
splicing the `Type` value `t`. This operation _is_ splice correct -- there
178179
is one quote and one splice between the use of `t` and its definition.
179180

180-
To avoid clutter, the Scala implementation tries to convert any phase-incorrect
181-
reference to a type `T` to a type-splice, by rewriting `T` to `${ summon[Type[T]] }`.
181+
To avoid clutter, the Scala implementation tries to convert any type
182+
reference to a type `T` in subsequent phases to a type-splice, by rewriting `T` to `${ summon[Type[T]] }`.
182183
For instance, the user-level definition of `reflect`:
183184

184185
```scala

tests/pos/i7405.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import scala.quoted._
2+
class Foo {
3+
def f(given QuoteContext): Expr[Any] = {
4+
'{
5+
type X = Int // Level 1
6+
val x: X = ???
7+
${
8+
val t: Type[X] = '[X] // Level 0
9+
'{ val y: $t = x }
10+
}
11+
}
12+
}
13+
}

tests/pos/i7405b.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import scala.quoted._
2+
3+
class Foo {
4+
def f(given QuoteContext): Expr[Any] = {
5+
'{
6+
trait X {
7+
type Y
8+
def y: Y = ???
9+
}
10+
val x: X = ???
11+
type Z = x.Y
12+
${
13+
val t: Type[Z] = '[Z]
14+
'{ val y: $t = x.y }
15+
}
16+
}
17+
}
18+
}

tests/pos/i7887.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
def typed[A](given t: quoted.Type[A], qctx: quoted.QuoteContext): Unit = {
2+
import qctx.tasty.{given, _}
3+
'{
4+
type T = $t
5+
${'{???}.cast[T]}
6+
}
7+
}

tests/run-macros/i7887/Macro_1.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
def myMacroImpl(a: quoted.Expr[_])(given qctx: quoted.QuoteContext) = {
2+
import qctx.tasty.{_, given}
3+
def typed[A] = {
4+
implicit val t: quoted.Type[A] = a.unseal.tpe.widen.seal.asInstanceOf[quoted.Type[A]]
5+
'{
6+
type T = $t
7+
${a.unseal.seal.cast[T]}
8+
}
9+
}
10+
11+
typed
12+
}
13+
14+
15+
inline def myMacro(a: => Any) = ${
16+
myMacroImpl('a)
17+
}

tests/run-macros/i7887/Test_2.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test extends App {
2+
assert(myMacro(42) == 42)
3+
}

0 commit comments

Comments
 (0)