Skip to content

Commit 9d0008d

Browse files
committed
Fix quotes with references to path dependent types
1 parent 28676ae commit 9d0008d

File tree

5 files changed

+137
-59
lines changed

5 files changed

+137
-59
lines changed

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

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import dotty.tools.dotc.core.Annotations._
2121
import dotty.tools.dotc.util.Property
2222

2323
import scala.annotation.constructorOnly
24+
import scala.quoted.runtime.Expr.splice
2425

2526
/** Checks that the Phase Consistency Principle (PCP) holds and heals types.
2627
*
@@ -61,24 +62,26 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
6162
super.transform(tree)
6263
else tree match {
6364

64-
case _: TypeTree | _: RefTree if tree.isType =>
65+
case _: TypeTree | _: RefTree if tree.isType =>
6566
val healedType = healType(tree.srcPos)(tree.tpe)
6667
if healedType == tree.tpe then tree
6768
else TypeTree(healedType).withSpan(tree.span)
69+
case tree: Ident if isWildcardArg(tree) =>
70+
tree.withType(healType(tree.srcPos)(tree.tpe))
71+
case tree: Ident => // this is a term Ident
72+
checkLevelConsistency(tree)
73+
tree
74+
case tree: This =>
75+
checkLevelConsistency(tree)
76+
tree
6877
case _: AppliedTypeTree =>
6978
super.transform(tree) match
7079
case tree1: AppliedTypeTree if tree1 ne tree =>
7180
// propagate healed types
7281
tree1.withType(tree1.tpt.tpe.appliedTo(tree1.args.map(_.tpe)))
7382
case tree1 => tree1
74-
75-
case _: Ident | _: This =>
76-
tree.withType(healTypeOfTerm(tree.srcPos)(tree.tpe))
77-
78-
// Remove inline defs in quoted code. Already fully inlined.
7983
case tree: DefDef if tree.symbol.is(Inline) && level > 0 =>
80-
EmptyTree
81-
84+
EmptyTree // Remove inline defs in quoted code. Already fully inlined.
8285
case tree: ValOrDefDef =>
8386
checkAnnotations(tree)
8487
healInfo(tree, tree.tpt.srcPos)
@@ -88,7 +91,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
8891
healInfo(tree, tree.srcPos)
8992
super.transform(tree)
9093
case tree: UnApply =>
91-
super.transform(tree).withType(healTypeOfTerm(tree.srcPos)(tree.tpe))
94+
super.transform(tree).withType(healType(tree.srcPos)(tree.tpe))
9295
case tree: TypeDef if tree.symbol.is(Case) && level > 0 =>
9396
report.error(reporting.CaseClassInInlinedCode(tree), tree)
9497
super.transform(tree)
@@ -115,7 +118,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
115118
if body.isTerm then
116119
// `quoted.runtime.Expr.quote[T](<body>)` --> `quoted.runtime.Expr.quote[T2](<body2>)`
117120
val TypeApply(fun, targs) = quote.fun: @unchecked
118-
val targs2 = targs.map(targ => TypeTree(healTypeOfTerm(quote.fun.srcPos)(targ.tpe)))
121+
val targs2 = targs.map(targ => TypeTree(healType(quote.fun.srcPos)(targ.tpe)))
119122
cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, targs2), body2 :: Nil)
120123
else
121124
val quotes = quote.args.mapConserve(transform)
@@ -190,61 +193,69 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
190193
def apply(tp: Type): Type =
191194
tp match
192195
case tp: TypeRef =>
193-
tp.prefix match
194-
case NoPrefix if level > levelOf(tp.symbol) && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) =>
195-
tryHealTypeOfType(tp.symbol, tp, pos)
196-
case prefix: ThisType if !tp.symbol.isStatic && level > levelOf(prefix.cls) =>
197-
tryHealTypeOfType(tp.symbol, tp, pos)
198-
case prefix: TermRef if tp.symbol.isTypeSplice =>
199-
prefix.symbol.info.argInfos match
200-
case (tb: TypeBounds) :: _ =>
201-
report.error(em"Cannot splice $tp because it is a wildcard type", pos)
202-
case _ =>
203-
// Heal explicit type splice in the code
204-
if level > 0 then getQuoteTypeTags.getTagRef(prefix) else tp
205-
case prefix: TermRef if !prefix.symbol.isStatic && level > levelOf(prefix.symbol) =>
206-
tryHealTypeOfType(prefix.symbol, tp, pos)
207-
case _ =>
208-
mapOver(tp)
209-
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.symbol) =>
210-
levelError(tp.symbol, tp, pos)
211-
case tp: ThisType if level != -1 && level != levelOf(tp.cls) =>
212-
levelError(tp.cls, tp, pos)
196+
healTypeRef(tp)
197+
case tp: TermRef =>
198+
val inconsistentRoot = levelInconsistentRootOfPath(tp)
199+
if inconsistentRoot.exists then levelError(inconsistent, tp, pos)
200+
else tp
213201
case tp: AnnotatedType =>
214202
val newAnnotTree = transform(tp.annot.tree)
215203
derivedAnnotatedType(tp, apply(tp.parent), tp.annot.derivedAnnotation(newAnnotTree))
216204
case _ =>
217205
mapOver(tp)
218206

219-
/** Try to dealias or heal reference to type `T` used in a higher level than its definition.
220-
* Returns a reference to a type tag generated by `QuoteTypeTags` that contains a
221-
* reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`.
222-
* Emits and error if `T` cannot be healed and returns `T`.
223-
*/
224-
private def tryHealTypeOfType(sym: Symbol, tp: TypeRef, pos: SrcPos)(using Context): Type = {
207+
private def healTypeRef(tp: TypeRef): Type =
208+
tp.prefix match
209+
case prefix: TermRef if tp.symbol.isTypeSplice =>
210+
checkNotWildcardSplice(tp)
211+
if level == 0 then tp else getQuoteTypeTags.getTagRef(prefix)
212+
case prefix: TermRef if levelInconsistentRootOfPath(prefix).exists =>
213+
dealiasAndTryHeal(prefix.symbol, tp, pos)
214+
case NoPrefix if level > levelOf(tp.symbol) && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) =>
215+
dealiasAndTryHeal(tp.symbol, tp, pos)
216+
case prefix: ThisType if level > levelOf(prefix.cls) && !tp.symbol.isStatic =>
217+
dealiasAndTryHeal(tp.symbol, tp, pos)
218+
case _ =>
219+
mapOver(tp)
220+
221+
private def dealiasAndTryHeal(sym: Symbol, tp: TypeRef, pos: SrcPos)(using Context): Type =
225222
val tp1 = tp.dealias
226223
if tp1 != tp then apply(tp1)
227224
else tryHeal(tp.symbol, tp, pos)
228-
}
229-
}
230225

231-
/** Check phase consistency of terms and heal inconsistent type references. */
232-
private def healTypeOfTerm(pos: SrcPos)(using Context) = new TypeMap {
233-
def apply(tp: Type): Type =
226+
/** Return the root of this path if it is a variable defined in a previous level.
227+
* If the path is consistent, return NoSymbol.
228+
*/
229+
private def levelInconsistentRootOfPath(tp: Type)(using Context): Symbol =
234230
tp match
235-
case tp @ TypeRef(NoPrefix, _) if level > levelOf(tp.symbol) =>
236-
tryHeal(tp.symbol, tp, pos)
237-
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level != levelOf(tp.symbol) =>
238-
levelError(tp.symbol, tp, pos)
239-
case tp: ThisType if level != -1 && level != levelOf(tp.cls) =>
240-
levelError(tp.cls, tp, pos)
241-
case tp: AnnotatedType =>
242-
derivedAnnotatedType(tp, apply(tp.parent), tp.annot)
231+
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.termSymbol) => tp.termSymbol
232+
case tp @ TermRef(prefix, _) if !tp.symbol.isStatic => levelInconsistentRootOfPath(prefix)
233+
case tp: ThisType if level > levelOf(tp.cls) => tp.cls
234+
case _ => NoSymbol
235+
236+
private def checkNotWildcardSplice(splice: TypeRef)(using Context): Unit =
237+
splice.prefix.termSymbol.info.argInfos match
238+
case (tb: TypeBounds) :: _ => report.error(em"Cannot splice $splice because it is a wildcard type", pos)
243239
case _ =>
244-
if tp.typeSymbol.is(Package) then tp
245-
else mapOver(tp)
246240
}
247241

242+
/** Check level consistency of terms references */
243+
private def checkLevelConsistency(tree: Ident | This)(using Context): Unit =
244+
new TypeTraverser {
245+
def traverse(tp: Type): Unit =
246+
tp match
247+
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level != levelOf(tp.symbol) =>
248+
levelError(tp.symbol, tp, tree.srcPos)
249+
case tp: ThisType if level != -1 && level != levelOf(tp.cls) =>
250+
levelError(tp.cls, tp, tree.srcPos)
251+
case tp: AnnotatedType =>
252+
traverse(tp.parent)
253+
case _ if tp.typeSymbol.is(Package) =>
254+
// OK
255+
case _ =>
256+
traverseChildren(tp)
257+
}.traverse(tree.tpe)
258+
248259
/** Try to heal reference to type `T` used in a higher level than its definition.
249260
* Returns a reference to a type tag generated by `QuoteTypeTags` that contains a
250261
* reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`.

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ class Splicing extends MacroTransform:
222222
// Dealias references to captured types
223223
TypeTree(tree.tpe.dealias)
224224
else super.transform(tree)
225-
case tree: TypeTree =>
225+
case _: TypeTree | _: SingletonTypeTree =>
226226
if containsCapturedType(tree.tpe) && level >= 1 then getTagRefFor(tree)
227227
else tree
228228
case tree @ Assign(lhs: RefTree, rhs) =>
@@ -246,7 +246,7 @@ class Splicing extends MacroTransform:
246246
if tree.symbol == defn.QuotedTypeModule_of && containsCapturedType(tpt.tpe) =>
247247
val newContent = capturedPartTypes(tpt)
248248
newContent match
249-
case block: Block =>
249+
case block: Block =>
250250
inContext(ctx.withSource(tree.source)) {
251251
Apply(TypeApply(typeof, List(newContent)), List(quotes)).withSpan(tree.span)
252252
}
@@ -342,7 +342,7 @@ class Splicing extends MacroTransform:
342342
val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (tree, newBinding))._2
343343
ref(bindingSym)
344344

345-
private def newQuotedTypeClassBinding(tpe: Type)(using Context) =
345+
private def newQuotedTypeClassBinding(tpe: Type)(using Context) =
346346
newSymbol(
347347
spliceOwner,
348348
UniqueName.fresh(nme.Type).toTermName,
@@ -353,15 +353,15 @@ class Splicing extends MacroTransform:
353353
private def capturedType(tree: Tree)(using Context): Symbol =
354354
val tpe = tree.tpe.widenTermRefExpr
355355
val bindingSym = refBindingMap
356-
.getOrElseUpdate(tree.symbol, (TypeTree(tree.tpe), newQuotedTypeClassBinding(tpe)))._2
356+
.getOrElseUpdate(tree.symbol, (TypeTree(tpe), newQuotedTypeClassBinding(tpe)))._2
357357
bindingSym
358358

359359
private def capturedPartTypes(tpt: Tree)(using Context): Tree =
360360
val old = healedTypes
361361
healedTypes = PCPCheckAndHeal.QuoteTypeTags(tpt.span)
362362
val capturePartTypes = new TypeMap {
363363
def apply(tp: Type) = tp match {
364-
case typeRef @ TypeRef(prefix, _) if isCaptured(prefix.typeSymbol) || isCaptured(prefix.termSymbol) =>
364+
case typeRef: TypeRef if containsCapturedType(typeRef) =>
365365
val termRef = refBindingMap
366366
.getOrElseUpdate(typeRef.symbol, (TypeTree(typeRef), newQuotedTypeClassBinding(typeRef)))._2.termRef
367367
val tagRef = healedTypes.nn.getTagRef(termRef)
@@ -376,7 +376,7 @@ class Splicing extends MacroTransform:
376376
tpt match
377377
case block: Block =>
378378
cpy.Block(block)(newHealedTypes ::: block.stats, TypeTree(captured))
379-
case _ =>
379+
case _ =>
380380
if newHealedTypes.nonEmpty then
381381
cpy.Block(tpt)(newHealedTypes, TypeTree(captured))
382382
else

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ object TreeChecker {
678678
defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true)
679679
val expectedContentType =
680680
defn.FunctionOf(argQuotedTypes, contextualResult)
681-
assert(content.typeOpt =:= expectedContentType)
681+
assert(content.typeOpt =:= expectedContentType, i"expected content of the hole to be ${expectedContentType} but got ${content.typeOpt}")
682682

683683
tree1
684684
}

tests/pos-macros/i7519b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ inline def quote[T]: Quoted[T] = ${ quoteImpl[T] }
99

1010
def quoteImpl[T: Type](using Quotes): Expr[Quoted[T]] = {
1111
val value: Expr[Int] = '{ 42 }
12-
'{ new Quoted[T @Annot($value)] }
12+
'{ new Quoted[T @Annot($value)]: Quoted[T] } // ascription is added to ensure that the splice of the annotation does not leak outside of the quote
1313
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import scala.quoted.*
2+
3+
trait A:
4+
type T
5+
val b: B
6+
7+
trait B:
8+
type T
9+
10+
class Test:
11+
def testLocalPathsGlobalClasses(using Quotes): Unit =
12+
'{
13+
type T
14+
val a: A = ???
15+
${
16+
'{
17+
val _: T = ???
18+
val _: a.T = ???
19+
// val _: a.b.T = ??? // FIXME pickling
20+
val _: a.type = ???
21+
val _: a.b.type = ???
22+
}
23+
???
24+
}
25+
}
26+
27+
def testLocalPathsLocalClasses(using Quotes): Unit =
28+
'{
29+
type U
30+
trait C:
31+
type U
32+
val d: D
33+
trait D:
34+
type U
35+
val c: C = ???
36+
${
37+
'{
38+
val _: U = ???
39+
val _: c.U = ???
40+
val _: c.d.U = ???
41+
val _: c.type = ???
42+
val _: c.d.type = ???
43+
}
44+
???
45+
}
46+
}
47+
48+
def testThisPaths(using Quotes): Unit =
49+
'{
50+
trait E:
51+
type V
52+
val f: F
53+
${
54+
'{
55+
// val _: this.type = ??? // FIXME pickling E
56+
// val _: V = ??? // FIXME pickling E
57+
// val _: this.V = ??? // FIXME pickling E
58+
val _: this.f.V = ???
59+
// val _: this.type = ??? // FIXME pickling E
60+
val _: this.f.type = ???
61+
}
62+
???
63+
}
64+
trait F:
65+
type V
66+
???
67+
}

0 commit comments

Comments
 (0)