aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2014-07-03 19:04:17 +0200
committerMartin Odersky <odersky@gmail.com>2014-07-17 11:02:00 +0200
commit357003062f994ac8a8ed985248e749297093185f (patch)
treecd39d0ffa87e04c1b2d563ffe7540d4a30a3d1e4
parentdb88bf06958e33ae415ca227808ab1f3e48fed7f (diff)
downloaddotty-357003062f994ac8a8ed985248e749297093185f.tar.gz
dotty-357003062f994ac8a8ed985248e749297093185f.tar.bz2
dotty-357003062f994ac8a8ed985248e749297093185f.zip
ExtensionMethods phase and TypeUtils
New phase for extension methods. Also, split off some type handling functionality that can be used elsewhere in new TypeUtils decorator. The idea is that TypeUtils should contain methods on Type that make sense specifically for transformations. That way, we can keep Types from growing. Might make sense to do similar decorators for Denotations as well. There's a bug fix in MacroTransform: Need to treat selfInfo varDels specially, since they have no symbol.
-rw-r--r--src/dotty/tools/dotc/Compiler.scala1
-rw-r--r--src/dotty/tools/dotc/transform/ExtensionMethods.scala170
-rw-r--r--src/dotty/tools/dotc/transform/MacroTransform.scala7
-rw-r--r--src/dotty/tools/dotc/transform/TypeUtils.scala115
-rw-r--r--test/dotc/tests.scala3
5 files changed, 293 insertions, 3 deletions
diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala
index bf8cf4182..d202f3a52 100644
--- a/src/dotty/tools/dotc/Compiler.scala
+++ b/src/dotty/tools/dotc/Compiler.scala
@@ -21,6 +21,7 @@ class Compiler {
List(new FrontEnd),
List(new Companions),
List(new SuperAccessors),
+ List(new ExtensionMethods),
List(new TailRec),
List(new PatternMatcher,
new LazyValTranformContext().transformer,
diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala
new file mode 100644
index 000000000..55b53ef4d
--- /dev/null
+++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala
@@ -0,0 +1,170 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL
+ * @author Martin Odersky
+ */
+package dotty.tools.dotc
+package transform
+
+import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer}
+import dotty.tools.dotc.ast.{Trees, tpd}
+import scala.collection.{ mutable, immutable }
+import mutable.ListBuffer
+import scala.annotation.tailrec
+import core._
+import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._
+import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._
+import TypeUtils._
+import util.Positions._
+import Decorators._
+
+/**
+ * Perform Step 1 in the inline classes SIP: Creates extension methods for all
+ * methods in a value class, except parameter or super accessors, or constructors.
+ */
+class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { thisTransformer =>
+
+ import tpd._
+
+ /** the following two members override abstract members in Transform */
+ val name: String = "extmethods"
+
+ def newTransformer(implicit ctx: Context): Transformer = new Extender
+
+ /** Generate stream of possible names for the extension version of given instance method `imeth`.
+ * If the method is not overloaded, this stream consists of just "extension$imeth".
+ * If the method is overloaded, the stream has as first element "extensionX$imeth", where X is the
+ * index of imeth in the sequence of overloaded alternatives with the same name. This choice will
+ * always be picked as the name of the generated extension method.
+ * After this first choice, all other possible indices in the range of 0 until the number
+ * of overloaded alternatives are returned. The secondary choices are used to find a matching method
+ * in `extensionMethod` if the first name has the wrong type. We thereby gain a level of insensitivity
+ * of how overloaded types are ordered between phases and picklings.
+ */
+ private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = {
+ val decl = imeth.owner.info.decl(imeth.name)
+
+ /** No longer needed for Dotty, as we are more disciplined with scopes now.
+ // Bridge generation is done at phase `erasure`, but new scopes are only generated
+ // for the phase after that. So bridges are visible in earlier phases.
+ //
+ // `info.member(imeth.name)` filters these out, but we need to use `decl`
+ // to restrict ourselves to members defined in the current class, so we
+ // must do the filtering here.
+ val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe
+ */
+ decl match {
+ case decl: SingleDenotation =>
+ val alts = decl.alternatives
+ val index = alts indexOf imeth
+ assert(index >= 0, alts+" does not contain "+imeth)
+ def altName(index: Int) = (imeth.name+"$extension"+index).toTermName
+ altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName)
+ case tpe =>
+ assert(tpe != NoType, imeth.name+" not found in "+imeth.owner+"'s decls: "+imeth.owner.info.decls)
+ Stream((imeth.name+"$extension").toTermName)
+ }
+ }
+
+ /** Return the extension method that corresponds to given instance method `meth`. */
+ def extensionMethod(imeth: Symbol)(implicit ctx: Context): Symbol =
+ ctx.atPhase(thisTransformer.next) { implicit ctx =>
+ // FIXME use toStatic instead?
+ val companionInfo = imeth.owner.companionModule.info
+ val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists)
+ val matching = candidates filter (alt => alt.info.toDynamic(imeth.owner) matches imeth.info)
+ assert(matching.nonEmpty,
+ sm"""|no extension method found for:
+ |
+ | $imeth:${imeth.info}
+ |
+ | Candidates:
+ |
+ | ${candidates.map(c => c.name + ":" + c.info).mkString("\n")}
+ |
+ | Candidates (signatures normalized):
+ |
+ | ${candidates.map(c => c.name + ":" + c.info.toDynamic(imeth.owner)).mkString("\n")}
+ |
+ | Eligible Names: ${extensionNames(imeth).mkString(",")}""")
+ matching.head
+ }
+
+ class Extender extends Transformer {
+ private val extensionDefs = mutable.Map[Symbol, mutable.ListBuffer[Tree]]()
+
+ def checkNonCyclic(pos: Position, seen: Set[Symbol], clazz: ClassSymbol)(implicit ctx: Context): Unit =
+ if (seen contains clazz)
+ ctx.error("value class may not unbox to itself", pos)
+ else {
+ val unboxed = clazz.underlyingOfValueClass.typeSymbol
+ if (unboxed.isDerivedValueClass) checkNonCyclic(pos, seen + clazz, unboxed.asClass)
+ }
+
+ override def transform(tree: Tree)(implicit ctx: Context): Tree = {
+ tree match {
+ case tree: Template =>
+ if (ctx.owner.isDerivedValueClass) {
+ /* This is currently redundant since value classes may not
+ wrap over other value classes anyway.
+ checkNonCyclic(ctx.owner.pos, Set(), ctx.owner) */
+ extensionDefs(ctx.owner.companionModule) = new mutable.ListBuffer[Tree]
+ ctx.owner.primaryConstructor.makeNotPrivateAfter(NoSymbol, thisTransformer)
+ // SI-7859 make param accessors accessible so the erasure can generate unbox operations.
+ val paramAccessors = ctx.owner.info.decls.filter(_.is(ParamAccessor))
+ paramAccessors.foreach(_.makeNotPrivateAfter(ctx.owner, thisTransformer))
+ super.transform(tree)
+ } else if (ctx.owner.isStaticOwner) {
+ val tree1 @ Template(constr, parents, selfType, body) = super.transform(tree)
+ extensionDefs remove tree1.symbol.owner match {
+ case Some(defns) if defns.nonEmpty =>
+ cpy.Template(tree1, constr, parents, selfType, body ++ defns)
+ case _ =>
+ tree1
+ }
+ } else tree
+ case DefDef(mods, name, tparams, vparamss, tpt, rhs) if tree.symbol.isMethodWithExtension =>
+ val origMeth = tree.symbol
+ val origClass = ctx.owner.asClass
+ val origTParams = tparams.map(_.symbol) ::: origClass.typeParams // method type params ++ class type params
+ val origVParams = vparamss.flatten map (_.symbol)
+ val staticClass = origClass.companionClass
+ assert(staticClass.exists)
+
+ val extensionMeth = ctx.atPhase(thisTransformer.next) { implicit ctx =>
+ val extensionName = extensionNames(origMeth).head.toTermName
+ val extensionMeth = ctx.newSymbol(staticClass, extensionName,
+ origMeth.flags | Final &~ (Override | Protected | AbsOverride),
+ origMeth.info.toStatic(origClass),
+ privateWithin = origMeth.privateWithin, coord = tree.pos)
+ extensionMeth.addAnnotations(from = origMeth)
+ origMeth.removeAnnotation(defn.TailrecAnnotationClass) // it's on the extension method, now.
+ extensionMeth.enteredAfter(thisTransformer)
+ }
+ ctx.log(s"Value class $origClass spawns extension method.\n Old: ${origMeth.showDcl}\n New: ${extensionMeth.showDcl}")
+
+ extensionDefs(staticClass) += polyDefDef(extensionMeth, trefs => vrefss => {
+ def methPart(tp: Type): MethodType = tp match {
+ case tp: PolyType => methPart(tp.resultType)
+ case tp: MethodType => tp
+ }
+ val substitutions: Type => Type = _
+ .subst(origTParams, trefs)
+ .substSym(origVParams, vrefss.flatten.map(_.symbol))
+ .substThis(origClass, MethodParam(methPart(extensionMeth.info), 0))
+ new TreeTypeMap(substitutions, Map(origMeth -> extensionMeth)).transform(rhs)
+ })
+
+ // These three lines are assembling Foo.bar$extension[T1, T2, ...]($this)
+ // which leaves the actual argument application for extensionCall.
+ val forwarder = ref(extensionMeth.termRef)
+ .appliedToTypes(origTParams.map(_.typeRef))
+ .appliedToArg(This(origClass))
+ .appliedToArgss(vparamss.nestedMap(vparam => ref(vparam.symbol)))
+ .withPos(rhs.pos)
+ cpy.DefDef(tree, mods, name, tparams, vparamss, tpt, forwarder)
+ case _ =>
+ super.transform(tree)
+ }
+ }
+ }
+}
diff --git a/src/dotty/tools/dotc/transform/MacroTransform.scala b/src/dotty/tools/dotc/transform/MacroTransform.scala
index eacbd1717..1ed9e68c2 100644
--- a/src/dotty/tools/dotc/transform/MacroTransform.scala
+++ b/src/dotty/tools/dotc/transform/MacroTransform.scala
@@ -45,17 +45,22 @@ abstract class MacroTransform extends Phase {
override def transform(tree: Tree)(implicit ctx: Context): Tree = {
tree match {
+ case EmptyValDef =>
+ tree
case _: PackageDef | _: MemberDef =>
super.transform(tree)(localCtx(tree))
case Template(constr, parents, self, body) =>
cpy.Template(tree,
transformSub(constr),
transform(parents),
- transformSub(self),
+ transformSelf(self),
transformStats(body, tree.symbol))
case _ =>
super.transform(tree)
}
}
+
+ def transformSelf(vd: ValDef)(implicit ctx: Context) =
+ cpy.ValDef(vd, vd.mods, vd.name, transform(vd.tpt), vd.rhs)
}
}
diff --git a/src/dotty/tools/dotc/transform/TypeUtils.scala b/src/dotty/tools/dotc/transform/TypeUtils.scala
new file mode 100644
index 000000000..87d47e0cd
--- /dev/null
+++ b/src/dotty/tools/dotc/transform/TypeUtils.scala
@@ -0,0 +1,115 @@
+package dotty.tools.dotc
+package transform
+
+import core._
+import Types._
+import Contexts._
+import Symbols._
+import Decorators._
+import StdNames.nme
+import language.implicitConversions
+
+object TypeUtils {
+ implicit def decorateTypeUtils(tpe: Type): TypeUtils = new TypeUtils(tpe)
+}
+
+/** A decorator that provides methods for type transformations
+ * that are needed in the transofmer pipeline
+ */
+class TypeUtils(val self: Type) extends AnyVal {
+ import TypeUtils._
+
+ /** Converts the type of a member of class `clazz` to a method type that
+ * takes the `this` of the class and any type parameters of the class
+ * as additional parameters. Example:
+ *
+ * class Foo[+A <: AnyRef](val xs: List[A]) extends AnyVal {
+ * def baz[B >: A](x: B): List[B] = ...
+ * }
+ *
+ * leads to:
+ *
+ * object Foo {
+ * def extension$baz[B >: A <: Any, A >: Nothing <: AnyRef]($this: Foo[A])(x: B): List[B]
+ * }
+ */
+ def toStatic(clazz: ClassSymbol)(implicit ctx: Context): Type = {
+ val (mtparamCount, origResult) = self match {
+ case self @ PolyType(mtnames) => (mtnames.length, self.resultType)
+ case _ => (0, self)
+ }
+ val ctparams = clazz.typeParams
+ val ctnames = ctparams.map(_.name)
+
+ /** The method result type, prior to mapping any type parameters */
+ val resultType = {
+ val thisParamType = clazz.typeRef.appliedTo(ctparams.map(_.typeRef))
+ MethodType(nme.SELF :: Nil, thisParamType :: Nil)(mt =>
+ origResult.substThis(clazz, MethodParam(mt, 0)))
+ }
+
+ /** Replace class type parameters by the added type parameters of the polytype `pt` */
+ def mapClassParams(tp: Type, pt: PolyType): Type = {
+ val classParamsRange = (mtparamCount until mtparamCount + ctparams.length).toList
+ tp.subst(clazz.typeParams, classParamsRange map (PolyParam(pt, _)))
+ }
+
+ /** The bounds for the added type paraneters of the polytype `pt` */
+ def mappedClassBounds(pt: PolyType): List[TypeBounds] =
+ ctparams.map(tparam => mapClassParams(tparam.info, pt).bounds)
+
+ def mappedResultType(pt: PolyType): Type = mapClassParams(resultType, pt)
+
+ self match {
+ case self @ PolyType(mtnames) =>
+ PolyType(mtnames ++ ctnames)(
+ pt => (self.paramBounds ++ mappedClassBounds(pt))
+ .mapConserve(_.subst(self, pt).bounds),
+ pt => mappedResultType(pt).subst(self, pt))
+ case _ =>
+ if (ctparams.isEmpty) resultType
+ else PolyType(ctnames)(mappedClassBounds, mappedResultType)
+ }
+ }
+
+ /** Converts from the result of a `toStatic(clazz)` back to the original type.
+ *
+ * To do this, it removes the `$this` argument from the parameter list a method,
+ * and converts trailing type parameters of the method to the type parameters of
+ * the given `clazz`.
+ *
+ * If `stpe` is a `PolyType`, any parameters corresponding to class type parameters
+ * are remapped and `$this` is removed from the result type.
+ * If `stpe` is a `MethodType`, it may have a curried parameter list with the
+ * `$this` alone in the first parameter list, in which case that parameter list
+ * is dropped. Or, since the curried lists disappear during uncurry, it may have
+ * a single parameter list with `$this` as the first parameter, in which case that
+ * parameter is removed from the list. Note that we do not need to adjust the result
+ * type with substParams because at uncurry there are no more depdendent method types.
+ */
+ def toDynamic(clazz: Symbol)(implicit ctx: Context): Type = self match {
+ case self: PolyType =>
+ // contains method type parameters, followed by class type parameters
+ val nparams = self.paramNames.length - clazz.typeParams.length
+ val (mNames, cNames) = self.paramNames.splitAt(nparams)
+ val (mBounds, cBounds) = self.paramBounds.splitAt(nparams)
+ val mappedParams =
+ (0 until nparams).toList.map(PolyParam(self, _)) ++ clazz.typeParams.map(_.typeRef)
+ def mapParams(tp: Type, pt: PolyType) = {
+ val mapped = (0 until nparams).toList.map(PolyParam(pt, _)) ++ clazz.typeParams.map(_.typeRef)
+ tp.substParams(self, mapped)
+ }
+ val restpe = self.resultType.toDynamic(clazz).substParams(self, mappedParams)
+ if (nparams == 0) mapParams(restpe, self)
+ else PolyType(self.paramNames.take(nparams))(
+ pt => self.paramBounds.mapconserve(mapParams(_, pt).asInstanceOf[TypeBounds]),
+ pt => mapParams(restpe, pt))
+ case mt @ MethodType(nme.SELF :: otherNames, thizType :: otherTypes) =>
+ val remainder =
+ if (otherNames.isEmpty) mt.resultType
+ else MethodType(otherNames, otherTypes, mt.resultType)
+ remainder.substParam(MethodParam(mt, 0), clazz.thisType)
+ case _ =>
+ self
+ }
+} \ No newline at end of file
diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala
index 92aa7240a..b1e0a6efe 100644
--- a/test/dotc/tests.scala
+++ b/test/dotc/tests.scala
@@ -14,7 +14,7 @@ class tests extends CompilerTest {
"-pagewidth", "160")
implicit val defaultOptions = noCheckOptions ++ List(
- "-Ycheck:super"//, "-Ystop-before:terminal"
+ "-Ycheck:extmethods"//, "-Ystop-before:terminal"
)
val twice = List("#runs", "2", "-YnoDoubleBindings")
@@ -47,7 +47,6 @@ class tests extends CompilerTest {
@Test def pos_overloaded() = compileFile(posDir, "overloaded", doErase)
@Test def pos_templateParents() = compileFile(posDir, "templateParents", doErase)
@Test def pos_structural() = compileFile(posDir, "structural", doErase)
- @Test def pos_i39 = compileFile(posDir, "i39", doErase)
@Test def pos_overloadedAccess = compileFile(posDir, "overloadedAccess", doErase)
@Test def pos_approximateUnion = compileFile(posDir, "approximateUnion", doErase)
@Test def pos_tailcall = compileDir(posDir + "tailcall/", doErase)