diff options
-rw-r--r-- | src/dotty/tools/dotc/Compiler.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/ExtensionMethods.scala | 170 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/MacroTransform.scala | 7 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/TypeUtils.scala | 115 | ||||
-rw-r--r-- | test/dotc/tests.scala | 3 |
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) |