Skip to content

Commit

Permalink
Fix quotes with references to path dependent types
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasstucki committed Mar 10, 2023
1 parent 28676ae commit 9d0008d
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 59 deletions.
113 changes: 62 additions & 51 deletions compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import dotty.tools.dotc.core.Annotations._
import dotty.tools.dotc.util.Property

import scala.annotation.constructorOnly
import scala.quoted.runtime.Expr.splice

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

case _: TypeTree | _: RefTree if tree.isType =>
case _: TypeTree | _: RefTree if tree.isType =>
val healedType = healType(tree.srcPos)(tree.tpe)
if healedType == tree.tpe then tree
else TypeTree(healedType).withSpan(tree.span)
case tree: Ident if isWildcardArg(tree) =>
tree.withType(healType(tree.srcPos)(tree.tpe))
case tree: Ident => // this is a term Ident
checkLevelConsistency(tree)
tree
case tree: This =>
checkLevelConsistency(tree)
tree
case _: AppliedTypeTree =>
super.transform(tree) match
case tree1: AppliedTypeTree if tree1 ne tree =>
// propagate healed types
tree1.withType(tree1.tpt.tpe.appliedTo(tree1.args.map(_.tpe)))
case tree1 => tree1

case _: Ident | _: This =>
tree.withType(healTypeOfTerm(tree.srcPos)(tree.tpe))

// Remove inline defs in quoted code. Already fully inlined.
case tree: DefDef if tree.symbol.is(Inline) && level > 0 =>
EmptyTree

EmptyTree // Remove inline defs in quoted code. Already fully inlined.
case tree: ValOrDefDef =>
checkAnnotations(tree)
healInfo(tree, tree.tpt.srcPos)
Expand All @@ -88,7 +91,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
healInfo(tree, tree.srcPos)
super.transform(tree)
case tree: UnApply =>
super.transform(tree).withType(healTypeOfTerm(tree.srcPos)(tree.tpe))
super.transform(tree).withType(healType(tree.srcPos)(tree.tpe))
case tree: TypeDef if tree.symbol.is(Case) && level > 0 =>
report.error(reporting.CaseClassInInlinedCode(tree), tree)
super.transform(tree)
Expand All @@ -115,7 +118,7 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
if body.isTerm then
// `quoted.runtime.Expr.quote[T](<body>)` --> `quoted.runtime.Expr.quote[T2](<body2>)`
val TypeApply(fun, targs) = quote.fun: @unchecked
val targs2 = targs.map(targ => TypeTree(healTypeOfTerm(quote.fun.srcPos)(targ.tpe)))
val targs2 = targs.map(targ => TypeTree(healType(quote.fun.srcPos)(targ.tpe)))
cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, targs2), body2 :: Nil)
else
val quotes = quote.args.mapConserve(transform)
Expand Down Expand Up @@ -190,61 +193,69 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
def apply(tp: Type): Type =
tp match
case tp: TypeRef =>
tp.prefix match
case NoPrefix if level > levelOf(tp.symbol) && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) =>
tryHealTypeOfType(tp.symbol, tp, pos)
case prefix: ThisType if !tp.symbol.isStatic && level > levelOf(prefix.cls) =>
tryHealTypeOfType(tp.symbol, tp, pos)
case prefix: TermRef if tp.symbol.isTypeSplice =>
prefix.symbol.info.argInfos match
case (tb: TypeBounds) :: _ =>
report.error(em"Cannot splice $tp because it is a wildcard type", pos)
case _ =>
// Heal explicit type splice in the code
if level > 0 then getQuoteTypeTags.getTagRef(prefix) else tp
case prefix: TermRef if !prefix.symbol.isStatic && level > levelOf(prefix.symbol) =>
tryHealTypeOfType(prefix.symbol, tp, pos)
case _ =>
mapOver(tp)
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.symbol) =>
levelError(tp.symbol, tp, pos)
case tp: ThisType if level != -1 && level != levelOf(tp.cls) =>
levelError(tp.cls, tp, pos)
healTypeRef(tp)
case tp: TermRef =>
val inconsistentRoot = levelInconsistentRootOfPath(tp)
if inconsistentRoot.exists then levelError(inconsistent, tp, pos)
else tp
case tp: AnnotatedType =>
val newAnnotTree = transform(tp.annot.tree)
derivedAnnotatedType(tp, apply(tp.parent), tp.annot.derivedAnnotation(newAnnotTree))
case _ =>
mapOver(tp)

/** Try to dealias or 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]]}`.
* Emits and error if `T` cannot be healed and returns `T`.
*/
private def tryHealTypeOfType(sym: Symbol, tp: TypeRef, pos: SrcPos)(using Context): Type = {
private def healTypeRef(tp: TypeRef): Type =
tp.prefix match
case prefix: TermRef if tp.symbol.isTypeSplice =>
checkNotWildcardSplice(tp)
if level == 0 then tp else getQuoteTypeTags.getTagRef(prefix)
case prefix: TermRef if levelInconsistentRootOfPath(prefix).exists =>
dealiasAndTryHeal(prefix.symbol, tp, pos)
case NoPrefix if level > levelOf(tp.symbol) && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) =>
dealiasAndTryHeal(tp.symbol, tp, pos)
case prefix: ThisType if level > levelOf(prefix.cls) && !tp.symbol.isStatic =>
dealiasAndTryHeal(tp.symbol, tp, pos)
case _ =>
mapOver(tp)

private def dealiasAndTryHeal(sym: Symbol, tp: TypeRef, pos: SrcPos)(using Context): Type =
val tp1 = tp.dealias
if tp1 != tp then apply(tp1)
else tryHeal(tp.symbol, tp, pos)
}
}

/** Check phase consistency of terms and heal inconsistent type references. */
private def healTypeOfTerm(pos: SrcPos)(using Context) = new TypeMap {
def apply(tp: Type): Type =
/** 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 @ TypeRef(NoPrefix, _) if level > levelOf(tp.symbol) =>
tryHeal(tp.symbol, tp, pos)
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level != levelOf(tp.symbol) =>
levelError(tp.symbol, tp, pos)
case tp: ThisType if level != -1 && level != levelOf(tp.cls) =>
levelError(tp.cls, tp, pos)
case tp: AnnotatedType =>
derivedAnnotatedType(tp, apply(tp.parent), tp.annot)
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level > levelOf(tp.termSymbol) => tp.termSymbol
case tp @ TermRef(prefix, _) if !tp.symbol.isStatic => levelInconsistentRootOfPath(prefix)
case tp: ThisType if level > levelOf(tp.cls) => tp.cls
case _ => NoSymbol

private def checkNotWildcardSplice(splice: TypeRef)(using Context): Unit =
splice.prefix.termSymbol.info.argInfos match
case (tb: TypeBounds) :: _ => report.error(em"Cannot splice $splice because it is a wildcard type", pos)
case _ =>
if tp.typeSymbol.is(Package) then tp
else mapOver(tp)
}

/** Check level consistency of terms references */
private def checkLevelConsistency(tree: Ident | This)(using Context): Unit =
new TypeTraverser {
def traverse(tp: Type): Unit =
tp match
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level != levelOf(tp.symbol) =>
levelError(tp.symbol, tp, tree.srcPos)
case tp: ThisType if level != -1 && level != levelOf(tp.cls) =>
levelError(tp.cls, tp, tree.srcPos)
case tp: AnnotatedType =>
traverse(tp.parent)
case _ if tp.typeSymbol.is(Package) =>
// OK
case _ =>
traverseChildren(tp)
}.traverse(tree.tpe)

/** 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]]}`.
Expand Down
12 changes: 6 additions & 6 deletions compiler/src/dotty/tools/dotc/transform/Splicing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ class Splicing extends MacroTransform:
// Dealias references to captured types
TypeTree(tree.tpe.dealias)
else super.transform(tree)
case tree: TypeTree =>
case _: TypeTree | _: SingletonTypeTree =>
if containsCapturedType(tree.tpe) && level >= 1 then getTagRefFor(tree)
else tree
case tree @ Assign(lhs: RefTree, rhs) =>
Expand All @@ -246,7 +246,7 @@ class Splicing extends MacroTransform:
if tree.symbol == defn.QuotedTypeModule_of && containsCapturedType(tpt.tpe) =>
val newContent = capturedPartTypes(tpt)
newContent match
case block: Block =>
case block: Block =>
inContext(ctx.withSource(tree.source)) {
Apply(TypeApply(typeof, List(newContent)), List(quotes)).withSpan(tree.span)
}
Expand Down Expand Up @@ -342,7 +342,7 @@ class Splicing extends MacroTransform:
val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (tree, newBinding))._2
ref(bindingSym)

private def newQuotedTypeClassBinding(tpe: Type)(using Context) =
private def newQuotedTypeClassBinding(tpe: Type)(using Context) =
newSymbol(
spliceOwner,
UniqueName.fresh(nme.Type).toTermName,
Expand All @@ -353,15 +353,15 @@ class Splicing extends MacroTransform:
private def capturedType(tree: Tree)(using Context): Symbol =
val tpe = tree.tpe.widenTermRefExpr
val bindingSym = refBindingMap
.getOrElseUpdate(tree.symbol, (TypeTree(tree.tpe), newQuotedTypeClassBinding(tpe)))._2
.getOrElseUpdate(tree.symbol, (TypeTree(tpe), newQuotedTypeClassBinding(tpe)))._2
bindingSym

private def capturedPartTypes(tpt: Tree)(using Context): Tree =
val old = healedTypes
healedTypes = PCPCheckAndHeal.QuoteTypeTags(tpt.span)
val capturePartTypes = new TypeMap {
def apply(tp: Type) = tp match {
case typeRef @ TypeRef(prefix, _) if isCaptured(prefix.typeSymbol) || isCaptured(prefix.termSymbol) =>
case typeRef: TypeRef if containsCapturedType(typeRef) =>
val termRef = refBindingMap
.getOrElseUpdate(typeRef.symbol, (TypeTree(typeRef), newQuotedTypeClassBinding(typeRef)))._2.termRef
val tagRef = healedTypes.nn.getTagRef(termRef)
Expand All @@ -376,7 +376,7 @@ class Splicing extends MacroTransform:
tpt match
case block: Block =>
cpy.Block(block)(newHealedTypes ::: block.stats, TypeTree(captured))
case _ =>
case _ =>
if newHealedTypes.nonEmpty then
cpy.Block(tpt)(newHealedTypes, TypeTree(captured))
else
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ object TreeChecker {
defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true)
val expectedContentType =
defn.FunctionOf(argQuotedTypes, contextualResult)
assert(content.typeOpt =:= expectedContentType)
assert(content.typeOpt =:= expectedContentType, i"expected content of the hole to be ${expectedContentType} but got ${content.typeOpt}")

tree1
}
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-macros/i7519b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ inline def quote[T]: Quoted[T] = ${ quoteImpl[T] }

def quoteImpl[T: Type](using Quotes): Expr[Quoted[T]] = {
val value: Expr[Int] = '{ 42 }
'{ new Quoted[T @Annot($value)] }
'{ 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
}
67 changes: 67 additions & 0 deletions tests/pos-macros/path-dependent-type-capture.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import scala.quoted.*

trait A:
type T
val b: B

trait B:
type T

class Test:
def testLocalPathsGlobalClasses(using Quotes): Unit =
'{
type T
val a: A = ???
${
'{
val _: T = ???
val _: a.T = ???
// val _: a.b.T = ??? // FIXME pickling
val _: a.type = ???
val _: a.b.type = ???
}
???
}
}

def testLocalPathsLocalClasses(using Quotes): Unit =
'{
type U
trait C:
type U
val d: D
trait D:
type U
val c: C = ???
${
'{
val _: U = ???
val _: c.U = ???
val _: c.d.U = ???
val _: c.type = ???
val _: c.d.type = ???
}
???
}
}

def testThisPaths(using Quotes): Unit =
'{
trait E:
type V
val f: F
${
'{
// val _: this.type = ??? // FIXME pickling E
// val _: V = ??? // FIXME pickling E
// val _: this.V = ??? // FIXME pickling E
val _: this.f.V = ???
// val _: this.type = ??? // FIXME pickling E
val _: this.f.type = ???
}
???
}
trait F:
type V
???
}

0 comments on commit 9d0008d

Please sign in to comment.