diff --git a/compiler/src/dotty/tools/dotc/staging/HealType.scala b/compiler/src/dotty/tools/dotc/staging/HealType.scala index 7907c2e47542..4f59e92241fb 100644 --- a/compiler/src/dotty/tools/dotc/staging/HealType.scala +++ b/compiler/src/dotty/tools/dotc/staging/HealType.scala @@ -31,15 +31,12 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap { */ def apply(tp: Type): Type = tp match - case tp: TypeRef => - tp.underlying match - case TypeAlias(alias) - if !tp.symbol.isTypeSplice && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => - this.apply(alias) - case _ => - healTypeRef(tp) - case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.symbol) => - levelError(tp.symbol, tp, pos) + case NonSpliceAlias(aliased) => this.apply(aliased) + case tp: TypeRef => healTypeRef(tp) + case tp: TermRef => + val inconsistentRoot = levelInconsistentRootOfPath(tp) + if inconsistentRoot.exists then levelError(inconsistentRoot, tp, pos) + else tp case tp: AnnotatedType => derivedAnnotatedType(tp, apply(tp.parent), tp.annot) case _ => @@ -47,23 +44,39 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap { private def healTypeRef(tp: TypeRef): Type = tp.prefix match + case NoPrefix if tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => + tp case prefix: TermRef if tp.symbol.isTypeSplice => checkNotWildcardSplice(tp) if level == 0 then tp else getQuoteTypeTags.getTagRef(prefix) - case prefix: TermRef if !prefix.symbol.isStatic && level > levelOf(prefix.symbol) => - tryHeal(prefix.symbol, tp, pos) - case NoPrefix if level > levelOf(tp.symbol) && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => - tryHeal(tp.symbol, tp, pos) - case prefix: ThisType if level > levelOf(prefix.cls) && !tp.symbol.isStatic => - tryHeal(tp.symbol, tp, pos) + case _: NamedType | _: ThisType | NoPrefix => + if levelInconsistentRootOfPath(tp).exists then + tryHeal(tp.symbol, tp, pos) + else + tp case _ => mapOver(tp) + private object NonSpliceAlias: + def unapply(tp: TypeRef)(using Context): Option[Type] = tp.underlying match + case TypeAlias(alias) if !tp.symbol.isTypeSplice && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => Some(alias) + case _ => None + private def checkNotWildcardSplice(splice: TypeRef): Unit = splice.prefix.termSymbol.info.argInfos match case (tb: TypeBounds) :: _ => report.error(em"Cannot splice $splice because it is a wildcard type", pos) case _ => + /** Return the root of this path if it is a variable defined in a previous level. + * If the path is consistent, return NoSymbol. + */ + private def levelInconsistentRootOfPath(tp: Type)(using Context): Symbol = + tp match + case tp @ NamedType(NoPrefix, _) if level > levelOf(tp.symbol) => tp.symbol + case tp: NamedType if !tp.symbol.isStatic => levelInconsistentRootOfPath(tp.prefix) + case tp: ThisType if level > levelOf(tp.cls) => tp.cls + case _ => NoSymbol + /** Try to heal reference to type `T` used in a higher level than its definition. * Returns a reference to a type tag generated by `QuoteTypeTags` that contains a * reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`. diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 728ee9552c81..62174c806f09 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -157,13 +157,18 @@ class PickleQuotes extends MacroTransform { override def apply(tp: Type): Type = tp match case tp: TypeRef if tp.typeSymbol.isTypeSplice => apply(tp.dealias) - case tp @ TypeRef(pre, _) if pre == NoPrefix || pre.termSymbol.isLocal => + case tp @ TypeRef(pre, _) if isLocalPath(pre) => val hiBound = tp.typeSymbol.info match case info: ClassInfo => info.parents.reduce(_ & _) case info => info.hiBound apply(hiBound) case tp => mapOver(tp) + + private def isLocalPath(tp: Type): Boolean = tp match + case NoPrefix => true + case tp: TermRef if !tp.symbol.is(Package) => isLocalPath(tp.prefix) + case tp => false } /** Remove references to local types that will not be defined in this quote */ diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index f9966eac7fad..f9240d6091c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -679,7 +679,7 @@ object TreeChecker { defn.AnyType case tpe => tpe defn.QuotedExprClass.typeRef.appliedTo(tpe) - else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt) + else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr) } val expectedResultType = if isTermHole then defn.QuotedExprClass.typeRef.appliedTo(tpt.typeOpt) @@ -688,7 +688,7 @@ object TreeChecker { defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) val expectedContentType = defn.FunctionOf(argQuotedTypes, contextualResult) - assert(content.typeOpt =:= expectedContentType, i"expected content of the hole to be ${expectedContentType} but got ${content.typeOpt}") + assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}") tree1 } diff --git a/tests/pos-macros/path-dependent-type-capture/Macro_1.scala b/tests/pos-macros/path-dependent-type-capture/Macro_1.scala new file mode 100644 index 000000000000..588e50846eff --- /dev/null +++ b/tests/pos-macros/path-dependent-type-capture/Macro_1.scala @@ -0,0 +1,70 @@ +import scala.quoted.* + +trait A: + type T + val b: B + +trait B: + type T + def f: Unit + +trait C0: + type U + val d: D0 +trait D0: + type U + def h: Unit +object Macro: + inline def generateCode: Unit = ${ generateCodeExpr } + + def generateCodeExpr(using Quotes): Expr[Unit] = + '{ + $testLocalPathsGlobalClasses + $testLocalPathsLocalClasses + } + + def testLocalPathsGlobalClasses(using Quotes): Expr[Unit] = + '{ + type T + val a: A = ??? + ${ + val expr = '{ + val t: T = ??? + val aT: a.T = ??? + val abT: a.b.T = ??? + val aRef: a.type = ??? + aRef.b + aRef.b.f + val abRef: a.b.type = ??? + abRef.f + () + } + expr + } + } + + def testLocalPathsLocalClasses(using Quotes): Expr[Unit] = + '{ + type U + trait C extends C0: + type U + val d: D + trait D extends D0: + type U + def h: Unit + val c: C = ??? + ${ + val expr = '{ + val u: U = ??? + val cU: c.U = ??? + val cdU: c.d.U = ??? + val cRef: c.type = ??? + cRef.d + cRef.d.h + val cdRef: c.d.type = ??? + cdRef.h + () + } + expr + } + } diff --git a/tests/pos-macros/path-dependent-type-capture/Test_2.scala b/tests/pos-macros/path-dependent-type-capture/Test_2.scala new file mode 100644 index 000000000000..c12cd8d2436a --- /dev/null +++ b/tests/pos-macros/path-dependent-type-capture/Test_2.scala @@ -0,0 +1 @@ +@main def test = Macro.generateCode