Skip to content

Commit f3f96c4

Browse files
Fix quotes with references to path dependent types (#17081)
2 parents f97f3ce + f035f10 commit f3f96c4

File tree

5 files changed

+107
-18
lines changed

5 files changed

+107
-18
lines changed

compiler/src/dotty/tools/dotc/staging/HealType.scala

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,39 +31,52 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
3131
*/
3232
def apply(tp: Type): Type =
3333
tp match
34-
case tp: TypeRef =>
35-
tp.underlying match
36-
case TypeAlias(alias)
37-
if !tp.symbol.isTypeSplice && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) =>
38-
this.apply(alias)
39-
case _ =>
40-
healTypeRef(tp)
41-
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.symbol) =>
42-
levelError(tp.symbol, tp, pos)
34+
case NonSpliceAlias(aliased) => this.apply(aliased)
35+
case tp: TypeRef => healTypeRef(tp)
36+
case tp: TermRef =>
37+
val inconsistentRoot = levelInconsistentRootOfPath(tp)
38+
if inconsistentRoot.exists then levelError(inconsistentRoot, tp, pos)
39+
else tp
4340
case tp: AnnotatedType =>
4441
derivedAnnotatedType(tp, apply(tp.parent), tp.annot)
4542
case _ =>
4643
mapOver(tp)
4744

4845
private def healTypeRef(tp: TypeRef): Type =
4946
tp.prefix match
47+
case NoPrefix if tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) =>
48+
tp
5049
case prefix: TermRef if tp.symbol.isTypeSplice =>
5150
checkNotWildcardSplice(tp)
5251
if level == 0 then tp else getQuoteTypeTags.getTagRef(prefix)
53-
case prefix: TermRef if !prefix.symbol.isStatic && level > levelOf(prefix.symbol) =>
54-
tryHeal(prefix.symbol, tp, pos)
55-
case NoPrefix if level > levelOf(tp.symbol) && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) =>
56-
tryHeal(tp.symbol, tp, pos)
57-
case prefix: ThisType if level > levelOf(prefix.cls) && !tp.symbol.isStatic =>
58-
tryHeal(tp.symbol, tp, pos)
52+
case _: NamedType | _: ThisType | NoPrefix =>
53+
if levelInconsistentRootOfPath(tp).exists then
54+
tryHeal(tp.symbol, tp, pos)
55+
else
56+
tp
5957
case _ =>
6058
mapOver(tp)
6159

60+
private object NonSpliceAlias:
61+
def unapply(tp: TypeRef)(using Context): Option[Type] = tp.underlying match
62+
case TypeAlias(alias) if !tp.symbol.isTypeSplice && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => Some(alias)
63+
case _ => None
64+
6265
private def checkNotWildcardSplice(splice: TypeRef): Unit =
6366
splice.prefix.termSymbol.info.argInfos match
6467
case (tb: TypeBounds) :: _ => report.error(em"Cannot splice $splice because it is a wildcard type", pos)
6568
case _ =>
6669

70+
/** Return the root of this path if it is a variable defined in a previous level.
71+
* If the path is consistent, return NoSymbol.
72+
*/
73+
private def levelInconsistentRootOfPath(tp: Type)(using Context): Symbol =
74+
tp match
75+
case tp @ NamedType(NoPrefix, _) if level > levelOf(tp.symbol) => tp.symbol
76+
case tp: NamedType if !tp.symbol.isStatic => levelInconsistentRootOfPath(tp.prefix)
77+
case tp: ThisType if level > levelOf(tp.cls) => tp.cls
78+
case _ => NoSymbol
79+
6780
/** Try to heal reference to type `T` used in a higher level than its definition.
6881
* Returns a reference to a type tag generated by `QuoteTypeTags` that contains a
6982
* reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`.

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,18 @@ class PickleQuotes extends MacroTransform {
157157
override def apply(tp: Type): Type = tp match
158158
case tp: TypeRef if tp.typeSymbol.isTypeSplice =>
159159
apply(tp.dealias)
160-
case tp @ TypeRef(pre, _) if pre == NoPrefix || pre.termSymbol.isLocal =>
160+
case tp @ TypeRef(pre, _) if isLocalPath(pre) =>
161161
val hiBound = tp.typeSymbol.info match
162162
case info: ClassInfo => info.parents.reduce(_ & _)
163163
case info => info.hiBound
164164
apply(hiBound)
165165
case tp =>
166166
mapOver(tp)
167+
168+
private def isLocalPath(tp: Type): Boolean = tp match
169+
case NoPrefix => true
170+
case tp: TermRef if !tp.symbol.is(Package) => isLocalPath(tp.prefix)
171+
case tp => false
167172
}
168173

169174
/** Remove references to local types that will not be defined in this quote */

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ object TreeChecker {
679679
defn.AnyType
680680
case tpe => tpe
681681
defn.QuotedExprClass.typeRef.appliedTo(tpe)
682-
else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt)
682+
else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr)
683683
}
684684
val expectedResultType =
685685
if isTermHole then defn.QuotedExprClass.typeRef.appliedTo(tpt.typeOpt)
@@ -688,7 +688,7 @@ object TreeChecker {
688688
defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true)
689689
val expectedContentType =
690690
defn.FunctionOf(argQuotedTypes, contextualResult)
691-
assert(content.typeOpt =:= expectedContentType, i"expected content of the hole to be ${expectedContentType} but got ${content.typeOpt}")
691+
assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}")
692692

693693
tree1
694694
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import scala.quoted.*
2+
3+
trait A:
4+
type T
5+
val b: B
6+
7+
trait B:
8+
type T
9+
def f: Unit
10+
11+
trait C0:
12+
type U
13+
val d: D0
14+
trait D0:
15+
type U
16+
def h: Unit
17+
object Macro:
18+
inline def generateCode: Unit = ${ generateCodeExpr }
19+
20+
def generateCodeExpr(using Quotes): Expr[Unit] =
21+
'{
22+
$testLocalPathsGlobalClasses
23+
$testLocalPathsLocalClasses
24+
}
25+
26+
def testLocalPathsGlobalClasses(using Quotes): Expr[Unit] =
27+
'{
28+
type T
29+
val a: A = ???
30+
${
31+
val expr = '{
32+
val t: T = ???
33+
val aT: a.T = ???
34+
val abT: a.b.T = ???
35+
val aRef: a.type = ???
36+
aRef.b
37+
aRef.b.f
38+
val abRef: a.b.type = ???
39+
abRef.f
40+
()
41+
}
42+
expr
43+
}
44+
}
45+
46+
def testLocalPathsLocalClasses(using Quotes): Expr[Unit] =
47+
'{
48+
type U
49+
trait C extends C0:
50+
type U
51+
val d: D
52+
trait D extends D0:
53+
type U
54+
def h: Unit
55+
val c: C = ???
56+
${
57+
val expr = '{
58+
val u: U = ???
59+
val cU: c.U = ???
60+
val cdU: c.d.U = ???
61+
val cRef: c.type = ???
62+
cRef.d
63+
cRef.d.h
64+
val cdRef: c.d.type = ???
65+
cdRef.h
66+
()
67+
}
68+
expr
69+
}
70+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def test = Macro.generateCode

0 commit comments

Comments
 (0)