Skip to content

Fix #3866: Add support for typetags #3867

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -615,14 +615,20 @@ class Definitions {
lazy val QuotedExprType = ctx.requiredClassRef("scala.quoted.Expr")
def QuotedExprClass(implicit ctx: Context) = QuotedExprType.symbol.asClass

def QuotedExpr_~(implicit ctx: Context) = QuotedExprClass.requiredMethod(nme.UNARY_~)
def QuotedExpr_run(implicit ctx: Context) = QuotedExprClass.requiredMethod(nme.run)
lazy val QuotedExpr_spliceR = QuotedExprClass.requiredMethod(nme.UNARY_~)
def QuotedExpr_~(implicit ctx: Context) = QuotedExpr_spliceR.symbol
lazy val QuotedExpr_runR = QuotedExprClass.requiredMethodRef(nme.run)
def QuotedExpr_run(implicit ctx: Context) = QuotedExpr_runR.symbol

lazy val QuotedTypeType = ctx.requiredClassRef("scala.quoted.Type")
def QuotedTypeClass(implicit ctx: Context) = QuotedTypeType.symbol.asClass

def QuotedType_~(implicit ctx: Context) =
QuotedTypeClass.info.member(tpnme.UNARY_~).symbol.asType
lazy val QuotedType_spliceR = QuotedTypeClass.requiredType(tpnme.UNARY_~).typeRef
def QuotedType_~ = QuotedType_spliceR.symbol

lazy val QuotedTypeModule = QuotedTypeClass.companionModule
lazy val QuotedType_applyR = QuotedTypeModule.requiredMethodRef(nme.apply)
def QuotedType_apply(implicit ctx: Context) = QuotedType_applyR.symbol

def Unpickler_unpickleExpr = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleExpr")
def Unpickler_unpickleType = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleType")
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ object Denotations {
def requiredClass(name: PreName)(implicit ctx: Context): ClassSymbol =
info.member(name.toTypeName).requiredSymbol(_.isClass).asClass

def requiredType(name: PreName)(implicit ctx: Context): TypeSymbol =
info.member(name.toTypeName).requiredSymbol(_.isType).asType

/** The alternative of this denotation that has a type matching `targetType` when seen
* as a member of type `site`, `NoDenotation` if none exists.
*/
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Standard-Section: "ASTs" TopLevelStat*
REFINEDtpt Length underlying_Term refinement_Stat*
APPLIEDtpt Length tycon_Term arg_Term*
POLYtpt Length TypeParam* body_Term
TYPEBOUNDStpt Length low_Term high_Term
TYPEBOUNDStpt Length low_Term high_Term?
ANNOTATEDtpt Length underlying_Term fullAnnotation_Term
ANDtpt Length left_Term right_Term
ORtpt Length left_Term right_Term
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,10 @@ class TreePickler(pickler: TastyPickler) {
withLength { pickleParams(tparams); pickleTree(body) }
case TypeBoundsTree(lo, hi) =>
writeByte(TYPEBOUNDStpt)
withLength { pickleTree(lo); pickleTree(hi) }
withLength {
pickleTree(lo);
if (hi ne lo) pickleTree(hi)
}
case Hole(idx, args) =>
writeByte(HOLE)
withLength {
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1038,7 +1038,9 @@ class TreeUnpickler(reader: TastyReader,
val body = readTpt()
LambdaTypeTree(tparams, body)
case TYPEBOUNDStpt =>
TypeBoundsTree(readTpt(), readTpt())
val lo = readTpt()
val hi = if (currentAddr == end) lo else readTpt()
TypeBoundsTree(lo, hi)
case HOLE =>
readHole(end)
case _ =>
Expand Down
63 changes: 41 additions & 22 deletions compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import tasty.TreePickler.Hole
import MegaPhase.MiniPhase
import SymUtils._
import NameKinds.OuterSelectName
import typer.Implicits.SearchFailureType

import scala.collection.mutable
import dotty.tools.dotc.core.StdNames._
Expand All @@ -22,7 +23,7 @@ import dotty.tools.dotc.core.quoted._
/** Translates quoted terms and types to `unpickle` method calls.
* Checks that the phase consistency principle (PCP) holds.
*/
class ReifyQuotes extends MacroTransform {
class ReifyQuotes extends MacroTransformWithImplicits {
import ast.tpd._

override def phaseName: String = "reifyQuotes"
Expand Down Expand Up @@ -101,7 +102,7 @@ class ReifyQuotes extends MacroTransform {
* @param level the current level, where quotes add one and splices subtract one level
* @param levels a stacked map from symbols to the levels in which they were defined
*/
private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo) extends Transformer {
private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo) extends ImplicitsTransformer {
import levels._

/** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */
Expand All @@ -114,29 +115,28 @@ class ReifyQuotes extends MacroTransform {
/** A list of embedded quotes (if `inSplice = true`) or splices (if `inQuote = true`) */
val embedded = new mutable.ListBuffer[Tree]

/** A map from type ref T to "expression of type `quoted.Type[T]`".
/** A map from type ref T to expressions of type `quoted.Type[T]`".
* These will be turned into splices using `addTags`
*/
val importedTypes = new mutable.LinkedHashSet[TypeRef]()
val importedTags = new mutable.LinkedHashMap[TypeRef, Tree]()

/** Assuming typeTagOfRef = `Type1 -> tag1, ..., TypeN -> tagN`, the expression
/** Assuming importedTags = `Type1 -> tag1, ..., TypeN -> tagN`, the expression
*
* { type <Type1> = <tag1>.unary_~
* ...
* type <TypeN> = <tagN>.unary.~
* <expr>
* }
*
* where all references to `TypeI` in `expr` are rewired to point to the locally
* references to `TypeI` in `expr` are rewired to point to the locally
* defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN`
* as splices to `buf`.
* as splices to `embedded`.
*/
def addTags(expr: Tree)(implicit ctx: Context): Tree =
if (importedTypes.isEmpty) expr
if (importedTags.isEmpty) expr
else {
val trefs = importedTypes.toList
val typeDefs = for (tref <- trefs) yield {
val tag = New(defn.QuotedTypeType.appliedTo(tref), Nil) // FIXME: should be an implicitly inferred defn.QuotedTypeType.appliedTo(tref)
val itags = importedTags.toList
val typeDefs = for ((tref, tag) <- itags) yield {
val rhs = transform(tag.select(tpnme.UNARY_~))
val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs)
val original = tref.symbol.asType
Expand All @@ -146,9 +146,9 @@ class ReifyQuotes extends MacroTransform {
info = TypeAlias(tag.tpe.select(tpnme.UNARY_~)))
ctx.typeAssigner.assignType(untpd.TypeDef(original.name, alias), local)
}
importedTypes.clear()
importedTags.clear()
Block(typeDefs,
new SubstMap(substFrom = trefs.map(_.symbol), substTo = typeDefs.map(_.symbol))
new SubstMap(substFrom = itags.map(_._1.symbol), substTo = typeDefs.map(_.symbol))
.apply(expr))
}

Expand Down Expand Up @@ -180,6 +180,29 @@ class ReifyQuotes extends MacroTransform {
def spliceOutsideQuotes(pos: Position)(implicit ctx: Context) =
ctx.error(i"splice outside quotes", pos)

/** Try to heal phase-inconsistent reference to type `T` using a local type definition.
* @return None if successful
* @return Some(msg) if unsuccessful where `msg` is a potentially empty error message
* to be added to the "inconsistent phase" message.
*/
def tryHeal(tp: Type, pos: Position)(implicit ctx: Context): Option[String] = tp match {
case tp: TypeRef =>
val reqType = defn.QuotedTypeType.appliedTo(tp)
val tag = ctx.typer.inferImplicitArg(reqType, pos)
tag.tpe match {
case fail: SearchFailureType =>
Some(i"""
|
| The access would be accepted with the right type tag, but
| ${ctx.typer.missingArgMsg(tag, reqType, "")}""")
case _ =>
importedTags(tp) = nested(isQuote = false).transform(tag)
None
}
case _ =>
Some("")
}

/** Check reference to `sym` for phase consistency, where `tp` is the underlying type
* by which we refer to `sym`.
*/
Expand All @@ -192,14 +215,10 @@ class ReifyQuotes extends MacroTransform {
if (!isThis && sym.maybeOwner.isType)
check(sym.owner, sym.owner.thisType, pos)
else if (sym.exists && !sym.isStaticOwner && !levelOK(sym))
tp match {
case tp: TypeRef =>
importedTypes += tp
case _ =>
ctx.error(em"""access to $symStr from wrong staging level:
| - the definition is at level ${levelOf(sym)},
| - but the access is at level $level.""", pos)
}
for (errMsg <- tryHeal(tp, pos))
ctx.error(em"""access to $symStr from wrong staging level:
| - the definition is at level ${levelOf(sym)},
| - but the access is at level $level.$errMsg""", pos)
}

/** Check all named types and this-types in a given type for phase consistency. */
Expand All @@ -209,7 +228,7 @@ class ReifyQuotes extends MacroTransform {
case tp: NamedType if tp.symbol.isSplice =>
if (inQuote) outer.checkType(pos).foldOver(acc, tp)
else {
spliceOutsideQuotes(pos)
if (tp.isTerm) spliceOutsideQuotes(pos)
tp
}
case tp: NamedType =>
Expand Down
64 changes: 43 additions & 21 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -553,34 +553,54 @@ trait Implicits { self: Typer =>
}

/** Find an implicit argument for parameter `formal`.
* @param error An error handler that gets an error message parameter
* which is itself parameterized by another string,
* indicating where the implicit parameter is needed
* Return a failure as a SearchFailureType in the type of the returned tree.
*/
def inferImplicitArg(formal: Type, pos: Position)(implicit ctx: Context): Tree = {

/** If `formal` is of the form ClassTag[T], where `T` is a class type,
* synthesize a class tag for `T`.
*/
def synthesizedClassTag(formal: Type)(implicit ctx: Context): Tree =
formal.argInfos match {
case arg :: Nil =>
fullyDefinedType(arg, "ClassTag argument", pos) match {
case defn.ArrayOf(elemTp) =>
val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), pos)
if (etag.tpe.isError) EmptyTree else etag.select(nme.wrap)
case tp if hasStableErasure(tp) && !defn.isBottomClass(tp.typeSymbol) =>
ref(defn.ClassTagModule)
.select(nme.apply)
.appliedToType(tp)
.appliedTo(clsOf(erasure(tp)))
.withPos(pos)
case tp =>
EmptyTree
def synthesizedClassTag(formal: Type): Tree = formal.argInfos match {
case arg :: Nil =>
fullyDefinedType(arg, "ClassTag argument", pos) match {
case defn.ArrayOf(elemTp) =>
val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), pos)
if (etag.tpe.isError) EmptyTree else etag.select(nme.wrap)
case tp if hasStableErasure(tp) && !defn.isBottomClass(tp.typeSymbol) =>
ref(defn.ClassTagModule)
.select(nme.apply)
.appliedToType(tp)
.appliedTo(clsOf(erasure(tp)))
.withPos(pos)
case tp =>
EmptyTree
}
case _ =>
EmptyTree
}

def synthesizedTypeTag(formal: Type): Tree = formal.argInfos match {
case arg :: Nil =>
object bindFreeVars extends TypeMap {
var ok = true
def apply(t: Type) = t match {
case t @ TypeRef(NoPrefix, _) =>
inferImplicit(defn.QuotedTypeType.appliedTo(t), EmptyTree, pos) match {
case SearchSuccess(tag, _, _) if tag.tpe.isStable =>
tag.tpe.select(defn.QuotedType_~)
case _ =>
ok = false
t
}
case _ => t
}
case _ =>
EmptyTree
}
}
val tag = bindFreeVars(arg)
if (bindFreeVars.ok) ref(defn.typeQuoteMethod).appliedToType(tag)
else EmptyTree
case _ =>
EmptyTree
}

/** If `formal` is of the form Eq[T, U], where no `Eq` instance exists for
* either `T` or `U`, synthesize `Eq.eqAny[T, U]` as solution.
Expand Down Expand Up @@ -644,6 +664,8 @@ trait Implicits { self: Typer =>
tree
else if (formalValue.isRef(defn.ClassTagClass))
synthesizedClassTag(formalValue).orElse(tree)
else if (formalValue.isRef(defn.QuotedTypeClass))
synthesizedTypeTag(formalValue).orElse(tree)
else if (formalValue.isRef(defn.EqClass))
synthesizedEq(formalValue).orElse(tree)
else
Expand Down
Loading