Skip to content

Commit bc8832c

Browse files
committed
Fix #7405: Allow types references at a lower level
To be able to have references to type tags of types defined in a higher level we need to generalize the concept of type healing. In this case we instead of needing to keep a type tag for the next stage it should be possible to refer to types for a next level. This is already the case inside of top level splices in inline methods (macros) but it should be possible to do in any code.
1 parent 2d2d3c0 commit bc8832c

File tree

5 files changed

+45
-10
lines changed

5 files changed

+45
-10
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
214214
assert(ctx.inInlineMethod)
215215
None
216216
}
217-
else {
217+
else if (levelOf(sym).getOrElse(0) < level) {
218218
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
219219
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
220220
tag.tpe match {
@@ -232,6 +232,8 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
232232
|
233233
| The access would be accepted with an implict $reqType""")
234234
}
235+
} else {
236+
None
235237
}
236238
case _ =>
237239
levelError(sym, tp, pos, "")

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,14 @@ class Staging extends MacroTransform {
4444
case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass =>
4545
val checker = new PCPCheckAndHeal(freshStagingContext) {
4646
override protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[tpd.Tree] = {
47+
4748
def symStr =
4849
if (!tp.isInstanceOf[ThisType]) sym.show
4950
else if (sym.is(ModuleClass)) sym.sourceModule.show
5051
else i"${sym.name}.this"
5152

5253
val errMsg = s"\nin ${ctx.owner.fullName}"
53-
assert(false,
54+
assert(levelOf(sym).getOrElse(0) >= level,
5455
em"""access to $symStr from wrong staging level:
5556
| - the definition is at level ${levelOf(sym).getOrElse(0)},
5657
| - but the access is at level $level.$errMsg""")

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+
}

0 commit comments

Comments
 (0)