Skip to content

Add tasty reflect members for definitions #4714

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 1 commit into from
Jun 25, 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
149 changes: 110 additions & 39 deletions compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s

type Id = untpd.Ident

def IdDeco(x: Id): IdAPI = new IdAPI {
def pos(implicit ctx: Context): Position = x.pos
def IdDeco(id: Id): IdAPI = new IdAPI {
def pos(implicit ctx: Context): Position = id.pos
def name(implicit ctx: Context): String = id.name.toString
}

def idClassTag: ClassTag[Id] = implicitly[ClassTag[Id]]
Expand Down Expand Up @@ -82,8 +83,8 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
}
}

def PackageClauseDeco(x: PackageClause): PackageClauseAPI = new PackageClauseAPI {
def definition(implicit ctx: Context): Definition = FromSymbol.packageDef(x.symbol)
def PackageClauseDeco(pack: PackageClause): PackageClauseAPI = new PackageClauseAPI {
def definition(implicit ctx: Context): Definition = FromSymbol.packageDef(pack.symbol)
}

// ----- Statements -----------------------------------------------
Expand All @@ -101,6 +102,11 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
}
}

def ImportDeco(imp: Import): ImportAPI = new ImportAPI {
def expr(implicit ctx: Context): Tree = imp.expr
def selector(implicit ctx: Context): List[ImportSelector] = imp.selectors
}

type ImportSelector = untpd.Tree

def importSelectorClassTag: ClassTag[ImportSelector] = implicitly[ClassTag[ImportSelector]]
Expand Down Expand Up @@ -135,34 +141,36 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
x.isInstanceOf[Trees.MemberDef[_]]
}

def DefinitionDeco(x: Definition): DefinitionAPI = new DefinitionAPI {
def DefinitionDeco(definition: Definition): DefinitionAPI = new DefinitionAPI {

def owner(implicit ctx: Context): Definition = FromSymbol.definition(x.symbol.owner)
def name(implicit ctx: Context): String = definition.symbol.name.toString

def owner(implicit ctx: Context): Definition = FromSymbol.definition(definition.symbol.owner)

def flags(implicit ctx: Context): FlagSet =
new FlagSet(x.symbol.flags)
new FlagSet(definition.symbol.flags)

def privateWithin(implicit ctx: Context): Option[Type] = {
val within = x.symbol.privateWithin
if (within.exists && !x.symbol.is(core.Flags.Protected)) Some(within.typeRef)
val within = definition.symbol.privateWithin
if (within.exists && !definition.symbol.is(core.Flags.Protected)) Some(within.typeRef)
else None
}

def protectedWithin(implicit ctx: Context): Option[Type] = {
val within = x.symbol.privateWithin
if (within.exists && x.symbol.is(core.Flags.Protected)) Some(within.typeRef)
val within = definition.symbol.privateWithin
if (within.exists && definition.symbol.is(core.Flags.Protected)) Some(within.typeRef)
else None
}

def annots(implicit ctx: Context): List[Term] = {
x.symbol.annotations.flatMap {
definition.symbol.annotations.flatMap {
case _: core.Annotations.LazyBodyAnnotation => Nil
case annot => annot.tree :: Nil
}
}

def localContext(implicit ctx: Context): Context =
if (x.hasType && x.symbol.exists) ctx.withOwner(x.symbol)
if (definition.hasType && definition.symbol.exists) ctx.withOwner(definition.symbol)
else ctx
}

Expand All @@ -177,13 +185,20 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
object ClassDef extends ClassDefExtractor {
def unapply(x: ClassDef)(implicit ctx: Context): Option[(String, DefDef, List[Parent], Option[ValDef], List[Statement])] = x match {
case x: tpd.TypeDef @unchecked if x.isClassDef =>
val temp @ Trees.Template(constr, parents, self, _) = x.rhs
val selfVal = if (self.isEmpty) None else Some(self)
Some((x.name.toString, constr, parents, selfVal, temp.body))
val deco = ClassDefDeco(x)
Some((x.name.toString, deco.constructor, deco.parents, deco.self, deco.body))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling ClassDefDeco seems to create unnecessary indirection here.

Another thought irrelevant to this PR but about the design: ClassDef and ClassDefDeco seem to have some duplication (that is why ClassDefDeco is called above). It seems possible to simplify the protocol between tasty API and concrete implementation such that extractors are implemented in the abstract API? Just a thought, no need to address in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a simple way to reduce code duplication. I'm considering extending the ClassDefAPI to make it the result of the extractor (with name-based pattern matching). This way there will be only one instantiation and maybe the contents can be lazily extracted.

case _ => None
}
}

def ClassDefDeco(cdef: ClassDef): ClassDefAPI = new ClassDefAPI {
private[this] val rhs = cdef.rhs.asInstanceOf[tpd.Template]
def constructor(implicit ctx: Context): DefDef = rhs.constr
def parents(implicit ctx: Context): List[tpd.Tree] = rhs.parents
def self(implicit ctx: Context): Option[tpd.ValDef] = optional(rhs.self)
def body(implicit ctx: Context): List[tpd.Tree] = rhs.body
}

// DefDef

type DefDef = tpd.DefDef
Expand All @@ -193,11 +208,18 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
object DefDef extends DefDefExtractor {
def unapply(x: DefDef)(implicit ctx: Context): Option[(String, List[TypeDef], List[List[ValDef]], TypeTree, Option[Term])] = x match {
case x: tpd.DefDef @unchecked =>
Some((x.name.toString, x.tparams, x.vparamss, x.tpt, if (x.rhs.isEmpty) None else Some(x.rhs)))
Some((x.name.toString, x.tparams, x.vparamss, x.tpt, optional(x.rhs)))
case _ => None
}
}

def DefDefDeco(ddef: DefDef): DefDefAPI = new DefDefAPI {
def typeParams(implicit ctx: Context): List[TypeDef] = ddef.tparams
def paramss(implicit ctx: Context): List[List[ValDef]] = ddef.vparamss
def returnTpt(implicit ctx: Context): TypeTree = ddef.tpt
def rhs(implicit ctx: Context): Option[Tree] = optional(ddef.rhs)
}

// ValDef

type ValDef = tpd.ValDef
Expand All @@ -207,11 +229,16 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
object ValDef extends ValDefExtractor {
def unapply(x: ValDef)(implicit ctx: Context): Option[(String, TypeTree, Option[Term])] = x match {
case x: tpd.ValDef @unchecked =>
Some((x.name.toString, x.tpt, if (x.rhs.isEmpty) None else Some(x.rhs)))
Some((x.name.toString, x.tpt, optional(x.rhs)))
case _ => None
}
}

def ValDefDeco(vdef: ValDef): ValDefAPI = new ValDefAPI {
def tpt(implicit ctx: Context): TypeTree = vdef.tpt
def rhs(implicit ctx: Context): Option[Tree] = optional(vdef.rhs)
}

// TypeDef

type TypeDef = tpd.TypeDef
Expand All @@ -225,12 +252,21 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
}
}

def TypeDefDeco(tdef: TypeDef): TypeDefAPI = new TypeDefAPI {
def rhs(implicit ctx: Context): TypeOrBoundsTree = tdef.rhs
}

// PackageDef

type PackageDef = PackageDefinition

def PackageDefDeco(t: PackageDef): PackageDefAPI = new PackageDefAPI {
def PackageDefDeco(pdef: PackageDef): PackageDefAPI = new PackageDefAPI {

def owner(implicit ctx: Context): PackageDefinition = FromSymbol.packageDef(pdef.symbol.owner)

def members(implicit ctx: Context): List[Statement] = {
if (t.symbol.is(core.Flags.JavaDefined)) Nil // FIXME should also support java packages
else t.symbol.info.decls.iterator.map(FromSymbol.definition).toList
if (pdef.symbol.is(core.Flags.JavaDefined)) Nil // FIXME should also support java packages
else pdef.symbol.info.decls.iterator.map(FromSymbol.definition).toList
}
}

Expand All @@ -252,15 +288,15 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s

type Term = tpd.Tree

def TermDeco(tree: Term): TermAPI = new TermAPI {
def TermDeco(term: Term): TermAPI = new TermAPI {

def pos(implicit ctx: Context): Position = tree.pos
def pos(implicit ctx: Context): Position = term.pos

def tpe(implicit ctx: Context): Types.Type = tree.tpe
def tpe(implicit ctx: Context): Types.Type = term.tpe

def toExpr[T: quoted.Type](implicit ctx: Context): quoted.Expr[T] = {
typecheck(ctx)
new quoted.Exprs.TastyTreeExpr(tree).asInstanceOf[quoted.Expr[T]]
new quoted.Exprs.TastyTreeExpr(term).asInstanceOf[quoted.Expr[T]]
}

private def typecheck[T: quoted.Type](ctx: Contexts.Context): Unit = {
Expand All @@ -270,15 +306,15 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
def doReport(m: MessageContainer)(implicit ctx: Contexts.Context): Unit = ()
})
val tp = QuotedTypeDeco(implicitly[quoted.Type[T]]).toTasty
ctx0.typer.typed(tree, tp.tpe)
ctx0.typer.typed(term, tp.tpe)
if (ctx0.reporter.hasErrors) {
val stack = new Exception().getStackTrace
def filter(elem: StackTraceElement) =
elem.getClassName.startsWith("dotty.tools.dotc.tasty.TastyImpl") ||
!elem.getClassName.startsWith("dotty.tools.dotc")
throw new scala.tasty.TastyTypecheckError(
s"""Error during tasty reflection while typing term
|term: ${tree.show}
|term: ${term.show}
|with expected type: ${tp.tpe.show}
|
| ${stack.takeWhile(filter).mkString("\n ")}
Expand Down Expand Up @@ -321,7 +357,7 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s

object This extends ThisExtractor {
def unapply(x: Term)(implicit ctx: Context): Option[Option[Id]] = x match {
case Trees.This(qual) => Some(if (qual.isEmpty) None else Some(qual))
case Trees.This(qual) => Some(optional(qual))
case _ => None
}
}
Expand Down Expand Up @@ -414,7 +450,7 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s

object Lambda extends LambdaExtractor {
def unapply(x: Term)(implicit ctx: Context): Option[(Term, Option[TypeTree])] = x match {
case x: tpd.Closure @unchecked => Some((x.meth, if (x.tpt.isEmpty) None else Some(x.tpt)))
case x: tpd.Closure @unchecked => Some((x.meth, optional(x.tpt)))
case _ => None
}
}
Expand All @@ -435,7 +471,7 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s

object Try extends TryExtractor {
def unapply(x: Term)(implicit ctx: Context): Option[(Term, List[CaseDef], Option[Term])] = x match {
case x: tpd.Try @unchecked => Some((x.expr, x.cases, if (x.finalizer.isEmpty) None else Some(x.finalizer)))
case x: tpd.Try @unchecked => Some((x.expr, x.cases, optional(x.finalizer)))
case _ => None
}
}
Expand Down Expand Up @@ -497,12 +533,15 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s

def CaseDefDeco(caseDef: CaseDef): CaseDefAPI = new CaseDefAPI {
def show(implicit ctx: Context, s: Show[TastyImpl.this.type]): String = s.showCaseDef(caseDef)
def pattern(implicit ctx: Context): Pattern = caseDef.pat
def guard(implicit ctx: Context): Option[Term] = optional(caseDef.guard)
def rhs(implicit ctx: Context): Term = caseDef.body
}

object CaseDef extends CaseDefExtractor {
def unapply(x: CaseDef): Option[(Pattern, Option[Term], Term)] = x match {
case x: tpd.CaseDef @unchecked =>
Some(x.pat, if (x.guard.isEmpty) None else Some(x.guard), x.body)
Some(x.pat, optional(x.guard), x.body)
case _ => None
}
}
Expand Down Expand Up @@ -575,9 +614,9 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s

type TypeTree = tpd.Tree

def TypeTreeDeco(x: TypeTree): TypeTreeAPI = new TypeTreeAPI {
def pos(implicit ctx: Context): Position = x.pos
def tpe(implicit ctx: Context): Types.Type = x.tpe.stripTypeVar
def TypeTreeDeco(tpt: TypeTree): TypeTreeAPI = new TypeTreeAPI {
def pos(implicit ctx: Context): Position = tpt.pos
def tpe(implicit ctx: Context): Types.Type = tpt.tpe.stripTypeVar
}

def typeTreeClassTag: ClassTag[TypeTree] = implicitly[ClassTag[TypeTree]]
Expand Down Expand Up @@ -682,8 +721,10 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s

type TypeBoundsTree = tpd.Tree

def TypeBoundsTreeDeco(x: TypeBoundsTree): TypeBoundsTreeAPI = new TypeBoundsTreeAPI {
def tpe(implicit ctx: Context): TypeBounds = x.tpe.asInstanceOf[Types.TypeBounds]
def TypeBoundsTreeDeco(bounds: TypeBoundsTree): TypeBoundsTreeAPI = new TypeBoundsTreeAPI {
def tpe(implicit ctx: Context): TypeBounds = bounds.tpe.asInstanceOf[Types.TypeBounds]
def low(implicit ctx: Context): TypeTree = bounds.asInstanceOf[tpd.TypeBoundsTree].lo
def hi(implicit ctx: Context): TypeTree = bounds.asInstanceOf[tpd.TypeBoundsTree].hi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asInstanceOf[tpd.TypeBoundsTree] is unnecessary. Same for the line before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeBoundsTree here is of type tpd.Tree but guaranteed to be tpd.TypeBoundsTree by the extractor, the cast is necessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, and this has all the usual dangers we've identified in our draft :-|... looking forward to a fix!

}

def typeBoundsTreeClassTag: ClassTag[TypeBoundsTree] = implicitly[ClassTag[TypeBoundsTree]]
Expand Down Expand Up @@ -726,9 +767,24 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
def polyTypeClassTag: ClassTag[PolyType] = implicitly[ClassTag[PolyType]]
def typeLambdaClassTag: ClassTag[TypeLambda] = implicitly[ClassTag[TypeLambda]]

def MethodTypeDeco(x: MethodType): MethodTypeAPI = new MethodTypeAPI {
def isErased: Boolean = x.isErasedMethod
def isImplicit: Boolean = x.isImplicitMethod
def MethodTypeDeco(tpe: MethodType): MethodTypeAPI = new MethodTypeAPI {
def isErased: Boolean = tpe.isErasedMethod
def isImplicit: Boolean = tpe.isImplicitMethod
def paramNames(implicit ctx: Context): List[String] = tpe.paramNames.map(_.toString)
def paramTypes(implicit ctx: Context): List[Type] = tpe.paramInfos
def resultTpe(implicit ctx: Context): Type = tpe.resType
}

def PolyTypeDeco(tpe: Types.PolyType): PolyTypeAPI = new PolyTypeAPI {
def paramNames(implicit ctx: Context): List[String] = tpe.paramNames.map(_.toString)
def paramTypes(implicit ctx: Context): List[TypeBounds] = tpe.paramInfos
def resultTpe(implicit ctx: Context): Type = tpe.resType
}

def TypeLambdaDeco(tpe: Types.TypeLambda): TypeLambdaAPI = new TypeLambdaAPI {
def paramNames(implicit ctx: Context): List[String] = tpe.paramNames.map(_.toString)
def paramTypes(implicit ctx: Context): List[TypeBounds] = tpe.paramInfos
def resultTpe(implicit ctx: Context): Type = tpe.resType
}

object Type extends TypeModule {
Expand Down Expand Up @@ -894,6 +950,11 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
}
}

def TypeBoundsDeco(tpe: TypeBounds): TypeBoundsAPI = new TypeBoundsAPI {
def low(implicit ctx: Context): Type = tpe.lo
def hi(implicit ctx: Context): Type = tpe.hi
}

// ----- NoPrefix --------------------------------------------------

type NoPrefix = Types.NoPrefix.type
Expand Down Expand Up @@ -1015,6 +1076,11 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
}
}

def SignatureDeco(sig: Signature): SignatureAPI = new SignatureAPI {
def paramSigs: List[String] = sig.paramsSig.map(_.toString)
def resultSig: String = sig.resSig.toString
}

// ===== Positions ================================================

type Position = SourcePosition
Expand All @@ -1032,4 +1098,9 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
def endColumn = pos.endColumn
}

// ===== Helpers ==================================================

private def optional[T <: Trees.Tree[_]](tree: T): Option[tree.type] =
if (tree.isEmpty) None else Some(tree)

}
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/tastyreflect/package.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dotty.tools.dotc

import dotty.tools.dotc.ast.Trees.{Tree, Untyped}
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Symbols.Symbol
import dotty.tools.dotc.core.Types.Type

Expand All @@ -9,8 +10,9 @@ package object tastyreflect {
type PackageDefinition = PackageDefinitionImpl[Type]

/** Represents the symbol of a definition in tree form */
case class PackageDefinitionImpl[-T >: Untyped] private[tastyreflect] (symbol: Symbol) extends Tree[T] {
case class PackageDefinitionImpl[-T >: Untyped] private[tastyreflect] (sym: Symbol) extends Tree[T] {
type ThisTree[-T >: Untyped] = PackageDefinitionImpl[T]
override def denot(implicit ctx: Context) = sym.denot
}

}
Loading