aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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)