From 3ab2784948d084557e88cd7eb5c55a29613742d0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Jul 2014 14:43:18 +0200 Subject: Added phase: SuperAccessors Rewrote SuperAccessors (more to be done; see comments), and added stuff here and there to make it work smoother. --- test/dotc/tests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test') diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index d25288548..92aa7240a 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:front"//, "-Ystop-before:terminal" + "-Ycheck:super"//, "-Ystop-before:terminal" ) val twice = List("#runs", "2", "-YnoDoubleBindings") -- cgit v1.2.3 From efe4f7e43652a303d16a5253f84316e547f45cca Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Jul 2014 18:57:22 +0200 Subject: Changed PostTyperTransformer scheme 1) We now always generate companion objects for classes. This is done in mini-phase "companions", which also assures that companion-modules appear after companion-classes. 2) PostTyperTransformers is gone; the part which normalizes trees has been rolled into TreeTransform and the part which reordered companion classes and modules is now in Companions. Note: Some tests were deisabled; should be re-enabled by Dmitry where needed. --- src/dotty/tools/dotc/Compiler.scala | 5 +- src/dotty/tools/dotc/core/Phases.scala | 18 ++---- src/dotty/tools/dotc/transform/Companions.scala | 67 ++++++++++++++++++++++ .../dotc/transform/CreateCompanionObjects.scala | 53 ----------------- src/dotty/tools/dotc/transform/LazyVals.scala | 21 +------ .../dotc/transform/PostTyperTransformers.scala | 62 -------------------- src/dotty/tools/dotc/transform/TreeTransform.scala | 6 +- .../transform/CreateCompanionObjectsTest.scala | 6 +- test/test/transform/LazyValsTest.scala | 4 +- test/test/transform/PostTyperTransformerTest.scala | 4 +- 10 files changed, 87 insertions(+), 159 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/Companions.scala delete mode 100644 src/dotty/tools/dotc/transform/CreateCompanionObjects.scala delete mode 100644 src/dotty/tools/dotc/transform/PostTyperTransformers.scala (limited to 'test') diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 55452d6ff..bf8cf4182 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -11,7 +11,6 @@ import reporting.ConsoleReporter import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.transform._ import dotty.tools.dotc.transform.TreeTransforms.{TreeTransform, TreeTransformer} -import dotty.tools.dotc.transform.PostTyperTransformers.PostTyperTransformer import dotty.tools.dotc.core.DenotTransformers.DenotTransformer import dotty.tools.dotc.core.Denotations.SingleDenotation @@ -20,9 +19,9 @@ class Compiler { def phases: List[List[Phase]] = List( List(new FrontEnd), + List(new Companions), List(new SuperAccessors), - List(new LazyValsCreateCompanionObjects, - new TailRec), //force separataion between lazyVals and LVCreateCO + List(new TailRec), List(new PatternMatcher, new LazyValTranformContext().transformer, new Splitter), diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 7bc5f3052..aabde4cf9 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -9,7 +9,6 @@ import Denotations._ import config.Printers._ import scala.collection.mutable.{ListBuffer, ArrayBuffer} import dotty.tools.dotc.transform.TreeTransforms.{TreeTransformer, TreeTransform} -import dotty.tools.dotc.transform.PostTyperTransformers.PostTyperTransformer import dotty.tools.dotc.transform.TreeTransforms import TreeTransforms.Separator import Periods._ @@ -72,12 +71,10 @@ object Phases { /** Squash TreeTransform's beloning to same sublist to a single TreeTransformer * Each TreeTransform gets own period, * whereas a combined TreeTransformer gets period equal to union of periods of it's TreeTransforms - * first TreeTransformer emitted is PostTyperTransformer that simplifies trees, see it's documentation */ private def squashPhases(phasess: List[List[Phase]]): Array[Phase] = { val squashedPhases = ListBuffer[Phase]() var prevPhases: Set[String] = Set.empty - var postTyperEmmited = false var i = 0 while (i < phasess.length) { if (phasess(i).length > 1) { @@ -95,17 +92,10 @@ object Phases { } } val transforms = phasess(i).asInstanceOf[List[TreeTransform]] - val block = - if (!postTyperEmmited) { - postTyperEmmited = true - new PostTyperTransformer { - override def name: String = transformations.map(_.name).mkString("TreeTransform:{", ", ", "}") - override def transformations: Array[TreeTransform] = transforms.toArray - } - } else new TreeTransformer { - override def name: String = transformations.map(_.name).mkString("TreeTransform:{", ", ", "}") - override def transformations: Array[TreeTransform] = transforms.toArray - } + val block = new TreeTransformer { + override def name: String = transformations.map(_.name).mkString("TreeTransform:{", ", ", "}") + override def transformations: Array[TreeTransform] = transforms.toArray + } squashedPhases += block prevPhases ++= phasess(i).map(_.name) block.init(this, phasess(i).head.id, phasess(i).last.id) diff --git a/src/dotty/tools/dotc/transform/Companions.scala b/src/dotty/tools/dotc/transform/Companions.scala new file mode 100644 index 000000000..0e31b511d --- /dev/null +++ b/src/dotty/tools/dotc/transform/Companions.scala @@ -0,0 +1,67 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import ast.Trees.flatten +import Flags._ +import Contexts.Context +import Symbols._ +import scala.collection.mutable +import DenotTransformers._ +import Names.Name +import NameOps._ + + +/** A transformer that provides a convenient way to create companion objects + */ +class Companions extends TreeTransform with IdentityDenotTransformer { thisTransformer => + import ast.tpd._ + + override def name = "companions" + + /** Reorder statements so that module classes always come after their companion classes, add missing companion classes */ + private def reorderAndComplete(stats: List[Tree])(implicit ctx: Context): List[Tree] = { + val moduleClassDefs, singleClassDefs = mutable.Map[Name, Tree]() + + def reorder(stats: List[Tree]): List[Tree] = stats match { + case (stat: TypeDef) :: stats1 if stat.symbol.isClass => + if (stat.symbol is Flags.Module) { + moduleClassDefs += (stat.name -> stat) + singleClassDefs -= stat.name.stripModuleClassSuffix + val stats1r = reorder(stats1) + if (moduleClassDefs contains stat.name) stat :: stats1r else stats1r + } else { + def stats1r = reorder(stats1) + val normalized = moduleClassDefs remove stat.name.moduleClassName match { + case Some(mcdef) => + mcdef :: stats1r + case None => + singleClassDefs += (stat.name -> stat) + stats1r + } + stat :: normalized + } + case stat :: stats1 => stat :: reorder(stats1) + case Nil => Nil + } + + def newCompanion(name: TermName): Thicket = { + val modul = ctx.newCompleteModuleSymbol(ctx.owner, name, Synthetic, Synthetic, + defn.ObjectClass.typeRef :: Nil, Scopes.newScope) + if (ctx.owner.isClass) modul.enteredAfter(thisTransformer) + ModuleDef(modul, Nil) + } + + def addMissingCompanions(stats: List[Tree]): List[Tree] = stats map { + case stat: TypeDef if singleClassDefs contains stat.name => + Thicket(stat :: newCompanion(stat.name.toTermName).trees) + case stat => stat + } + addMissingCompanions(reorder(stats)) + } + + override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = + ast.Trees.flatten(reorderAndComplete(trees)(ctx.withPhase(thisTransformer.next))) +} diff --git a/src/dotty/tools/dotc/transform/CreateCompanionObjects.scala b/src/dotty/tools/dotc/transform/CreateCompanionObjects.scala deleted file mode 100644 index b1cc8ea52..000000000 --- a/src/dotty/tools/dotc/transform/CreateCompanionObjects.scala +++ /dev/null @@ -1,53 +0,0 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts.Context -import scala.collection.mutable.ListBuffer -import dotty.tools.dotc.core.{Scopes, Flags} -import dotty.tools.dotc.core.Symbols.NoSymbol -import scala.annotation.tailrec -import dotty.tools.dotc.core._ -import Symbols._ -import scala.Some -import dotty.tools.dotc.transform.TreeTransforms.{NXTransformations, TransformerInfo, TreeTransform, TreeTransformer} -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts.Context -import scala.collection.mutable -import dotty.tools.dotc.core.Names.Name -import NameOps._ - -/** A transformer that provides a convenient way to create companion objects - */ -abstract class CreateCompanionObjects extends TreeTransform { - - import tpd._ - - /** Given class definition should return true if companion object creation should be enforced - */ - def predicate(cls: TypeDef)(implicit ctx: Context): Boolean - - override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[tpd.Tree] = { - @tailrec - def transformStats0(trees: List[Tree], acc: ListBuffer[Tree]): List[Tree] = { - trees match { - case Nil => acc.toList - case (claz: TypeDef) :: stats if claz.symbol.isClass && !(claz.symbol is Flags.Module) => { - val moduleExists = !(claz.symbol.companionModule eq NoSymbol) - if (moduleExists || !predicate(claz)) transformStats0(stats, acc += claz) - else { - val moduleSymbol = ctx.newCompleteModuleSymbol(claz.symbol.owner, claz.name.toTermName, Flags.Synthetic, Flags.Synthetic, List(defn.ObjectClass.typeRef), Scopes.newScope) - if (moduleSymbol.owner.isClass) moduleSymbol.entered - val companion = tpd.ModuleDef(moduleSymbol, List(EmptyTree)).withPos(claz.pos) - acc += claz - acc += companion - transformStats0(stats, acc) - } - } - case stat :: stats => transformStats0(stats, acc += stat) - } - } - - transformStats0(trees, ListBuffer()) - } -} diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index fe6c3e2e4..ecd94a211 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -19,25 +19,6 @@ import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.SymDenotations.SymDenotation import dotty.tools.dotc.core.DenotTransformers.DenotTransformer - -class LazyValsCreateCompanionObjects extends CreateCompanionObjects { - import tpd._ - - - override def name: String = "lazyValsModules" - - /** Companion classes are required to hold offsets for volatile lazy vals */ - override def predicate(forClass: tpd.TypeDef)(implicit ctx: Context): Boolean = { - (!(forClass.symbol is Flags.Module)) && forClass.rhs.isInstanceOf[Template] && { - val body = forClass.rhs.asInstanceOf[Template].body - body.exists { - case x: ValDef => - (x.mods is Flags.Lazy) && x.symbol.hasAnnotation(defn.VolatileAnnot) - case _ => false - } - } - } -} class LazyValTranformContext { import tpd._ @@ -67,7 +48,7 @@ class LazyValTranformContext { /** List of names of phases that should have finished their processing of all compilation units * before this phase starts */ - override def runsAfterGroupsOf: Set[String] = Set("lazyValsModules") + /** List of names of phases that should have finished their processing of all compilation units * before this phase starts */ diff --git a/src/dotty/tools/dotc/transform/PostTyperTransformers.scala b/src/dotty/tools/dotc/transform/PostTyperTransformers.scala deleted file mode 100644 index 25f122cf5..000000000 --- a/src/dotty/tools/dotc/transform/PostTyperTransformers.scala +++ /dev/null @@ -1,62 +0,0 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.core._ -import Symbols._ -import scala.Some -import dotty.tools.dotc.transform.TreeTransforms.{NXTransformations, TransformerInfo, TreeTransform, TreeTransformer} -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts.Context -import scala.collection.mutable -import dotty.tools.dotc.core.Names.Name -import NameOps._ - -object PostTyperTransformers { - - import tpd._ - - - /** A trait that's assumed by the transformers that run right after typer. - * Ensures that trees are normalized when seen by other transforms. This means: - * (1) All module class definitions appear after their companion class definitions - * (2) There are no import clauses or named arguments - * (3) All trees designating types are instances of TypeTree - */ - abstract class PostTyperTransformer extends TreeTransformer { - - /** Reorder statements so that module classes always come after their companion classes, add missing companion classes */ - def reorder(stats: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = { - val moduleClassDefs = mutable.Map[Name, Tree]() - def reorder0(stats: List[Tree]): List[Tree] = { - stats match { - case (stat: TypeDef) :: stats1 if stat.symbol.isClass => - if (stat.symbol is Flags.Module) { - moduleClassDefs += (stat.name -> stat) - val stats1r = reorder0(stats1) - if (moduleClassDefs contains stat.name) stat :: stats1r else stats1r - } - else { - val mclsName = stat.name.moduleClassName - moduleClassDefs remove mclsName match { - case Some(mcdef) => stat :: mcdef :: reorder0(stats1) - case None => stat :: reorder0(stats1) - } - } - case stat :: stats1 => stat :: reorder0(stats1) - case Nil => Nil - } - } - reorder0(stats) - } - - override def transformStats(trees: List[tpd.Tree], exprOwner: Symbol, info: TransformerInfo, current: Int)(implicit ctx: Context): List[tpd.Tree] = - super.transformStats(reorder(trees)(ctx, info), exprOwner, info, current) - - override def transform(tree: tpd.Tree, info: TransformerInfo, cur: Int)(implicit ctx: Context): tpd.Tree = tree match { - case tree: Import => EmptyTree - case tree: NamedArg => super.transform(tree.arg, info, cur) - case tree: TypeTree => super.transform(tree, info, cur) - case tree => super.transform(if (tree.isType) TypeTree(tree.tpe) else tree, info, cur) - } - } - -} \ No newline at end of file diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index 347762678..2bc733465 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -1124,8 +1124,12 @@ object TreeTransforms { val stats = transformStats(tree.stats, tree.symbol, mutatedInfo, cur)(nestedCtx) goPackageDef(cpy.PackageDef(tree, pid, stats), mutatedInfo.nx.nxTransPackageDef(cur)) } + case tree: Import => EmptyTree + case tree: NamedArg => transform(tree.arg, info, cur) case Thicket(trees) => cpy.Thicket(tree, transformTrees(trees, info, cur)) - case tree => tree + case tree => + if (tree.isType) transform(TypeTree(tree.tpe).withPos(tree.pos), info, cur) + else tree } def transform(tree: Tree, info: TransformerInfo, cur: Int)(implicit ctx: Context): Tree = ctx.traceIndented(s"transforming ${tree.show} at ${ctx.phase}", transforms, show = true) { diff --git a/test/test/transform/CreateCompanionObjectsTest.scala b/test/test/transform/CreateCompanionObjectsTest.scala index 05f4e1062..82830781c 100644 --- a/test/test/transform/CreateCompanionObjectsTest.scala +++ b/test/test/transform/CreateCompanionObjectsTest.scala @@ -14,14 +14,15 @@ import Types._ import Decorators._ import Trees._ import dotty.tools.dotc.transform.TreeTransforms.{TreeTransform, TreeTransformer} -import dotty.tools.dotc.transform.PostTyperTransformers.PostTyperTransformer -import dotty.tools.dotc.transform.CreateCompanionObjects class CreateCompanionObjectsTest extends DottyTest { + /* FIXME: re-enable after adapting to new scheme import tpd._ + type PostTyperTransformer = TreeTransformer // FIXME do without + @Test def shouldCreateNonExistingObjectsInPackage = checkCompile("frontend", "class A{} ") { (tree, context) => @@ -123,4 +124,5 @@ class CreateCompanionObjectsTest extends DottyTest { classPos < modulePos && (notCreatedPos < 0) ) } + */ } diff --git a/test/test/transform/LazyValsTest.scala b/test/test/transform/LazyValsTest.scala index aee6cbb9c..5b8a659fa 100644 --- a/test/test/transform/LazyValsTest.scala +++ b/test/test/transform/LazyValsTest.scala @@ -5,7 +5,7 @@ import test.DottyTest import org.junit.Assert class LazyValsTest extends DottyTest { - + /* FIXME: re-enable after adapting to new scheme @Test def doNotRewriteObjects = { checkCompile("LazyVals", "object O"){ (tree, ctx) => @@ -356,5 +356,5 @@ class LazyValsTest extends DottyTest { Assert.assertTrue("volatile field lazy ref rewritten to class creation", treeS.contains(moduleField) && treeS.contains(reuseFieldPattern)) } - } + }*/ } diff --git a/test/test/transform/PostTyperTransformerTest.scala b/test/test/transform/PostTyperTransformerTest.scala index 0c3f222c7..9886c3023 100644 --- a/test/test/transform/PostTyperTransformerTest.scala +++ b/test/test/transform/PostTyperTransformerTest.scala @@ -14,9 +14,9 @@ import Types._ import Decorators._ import Trees._ import dotty.tools.dotc.transform.TreeTransforms.{TreeTransform, TreeTransformer} -import dotty.tools.dotc.transform.PostTyperTransformers.PostTyperTransformer class PostTyperTransformerTest extends DottyTest { + /* FIXME: re-enable after adapting to new scheme @Test def shouldStripImports = checkCompile("frontend", "class A{ import scala.collection.mutable._; val d = 1}") { @@ -128,5 +128,5 @@ class PostTyperTransformerTest extends DottyTest { Assert.assertTrue("should reorder existing objects in template", classPos < modulePos ) - } + }*/ } -- cgit v1.2.3 From 357003062f994ac8a8ed985248e749297093185f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 3 Jul 2014 19:04:17 +0200 Subject: 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. --- src/dotty/tools/dotc/Compiler.scala | 1 + .../tools/dotc/transform/ExtensionMethods.scala | 170 +++++++++++++++++++++ .../tools/dotc/transform/MacroTransform.scala | 7 +- src/dotty/tools/dotc/transform/TypeUtils.scala | 115 ++++++++++++++ test/dotc/tests.scala | 3 +- 5 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/ExtensionMethods.scala create mode 100644 src/dotty/tools/dotc/transform/TypeUtils.scala (limited to 'test') 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) -- cgit v1.2.3 From 3eabbb77c8f8bd3e08b39e5335e8a67e2d68e659 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 7 Jul 2014 11:52:38 +0200 Subject: Move valueclass functionality into its own ValueClass module. --- src/dotty/tools/dotc/core/SymDenotations.scala | 26 ++++------------ src/dotty/tools/dotc/core/Symbols.scala | 10 ++---- src/dotty/tools/dotc/transform/Erasure.scala | 7 +++-- .../tools/dotc/transform/ExtensionMethods.scala | 5 +-- .../tools/dotc/transform/SuperAccessors.scala | 3 +- src/dotty/tools/dotc/transform/ValueClasses.scala | 36 ++++++++++++++++++++++ test/test/showClass.scala | 1 - 7 files changed, 53 insertions(+), 35 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/ValueClasses.scala (limited to 'test') diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ba57909a0..f55817505 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -329,6 +329,12 @@ object SymDenotations { final def isAnonymousClass(implicit ctx: Context): Boolean = initial.asSymDenotation.name startsWith tpnme.ANON_CLASS + /** Is symbol a primitive value class? */ + def isPrimitiveValueClass(implicit ctx: Context) = defn.ScalaValueClasses contains symbol + + /** Is symbol a phantom class for which no runtime representation exists? */ + def isPhantomClass(implicit ctx: Context) = defn.PhantomClasses contains symbol + /** Is this symbol a class representing a refinement? These classes * are used only temporarily in Typer and Unpickler as an intermediate * step for creating Refinement types. @@ -403,14 +409,6 @@ object SymDenotations { /** Is this a user defined "def" method? Excluded are accessors. */ final def isSourceMethod(implicit ctx: Context) = this is (Method, butNot = Accessor) - /** This this a method in a value class that is implemented as an extension method? */ - final def isMethodWithExtension(implicit ctx: Context) = - isSourceMethod && - owner.isDerivedValueClass && - !isConstructor && - !is(SuperAccessor) && - !is(Macro) - /** Is this a setter? */ final def isGetter(implicit ctx: Context) = (this is Accessor) && !originalName.isSetterName @@ -1335,18 +1333,6 @@ object SymDenotations { decls.denotsNamed(cname).first.symbol } - /** The member that of a derived value class that unboxes it. */ - def valueClassUnbox(implicit ctx: Context): Symbol = - // (info.decl(nme.unbox)).orElse(...) uncomment once we accept unbox methods - classInfo.decls - .find(d => d.isTerm && d.symbol.is(ParamAccessor)) - .map(_.symbol) - .getOrElse(NoSymbol) - - /** The unboxed type that underlies a derived value class */ - def underlyingOfValueClass(implicit ctx: Context): Type = - valueClassUnbox.info.resultType - /** If this class has the same `decls` scope reference in `phase` and * `phase.next`, install a new denotation with a cloned scope in `phase.next`. * @pre Can only be called in `phase.next`. diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index 6421018e5..9d60c9985 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -391,14 +391,8 @@ object Symbols { /** Is this symbol a user-defined value class? */ final def isDerivedValueClass(implicit ctx: Context): Boolean = - false && // value classes are not supported yet - isClass && denot.derivesFrom(defn.AnyValClass) && !isPrimitiveValueClass - - /** Is symbol a primitive value class? */ - def isPrimitiveValueClass(implicit ctx: Context) = defn.ScalaValueClasses contains this - - /** Is symbol a phantom class for which no runtime representation exists? */ - def isPhantomClass(implicit ctx: Context) = defn.PhantomClasses contains this + false // will migrate to ValueClasses.isDerivedValueClass; + // unsupported value class code will continue to use this stub while it exists /** The current name of this symbol */ final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName] diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 44ee5db5a..35742ac8c 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -22,6 +22,7 @@ import dotty.tools.dotc.ast.{Trees, tpd, untpd} import ast.Trees._ import scala.collection.mutable.ListBuffer import dotty.tools.dotc.core.Flags +import ValueClasses._ class Erasure extends Phase with DenotTransformer { @@ -90,7 +91,7 @@ object Erasure { final def box(tree: Tree, target: => String = "")(implicit ctx: Context): Tree = ctx.traceIndented(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") { tree.tpe.widen match { case ErasedValueType(clazz, _) => - New(clazz.typeRef, cast(tree, clazz.underlyingOfValueClass) :: Nil) // todo: use adaptToType? + New(clazz.typeRef, cast(tree, underlyingOfValueClass(clazz)) :: Nil) // todo: use adaptToType? case tp => val cls = tp.classSymbol if (cls eq defn.UnitClass) constant(tree, ref(defn.BoxedUnit_UNIT)) @@ -117,7 +118,7 @@ object Erasure { unbox(tree, underlying) else adaptToType(tree, clazz.typeRef) - .select(clazz.valueClassUnbox) + .select(valueClassUnbox(clazz)) .appliedToNone cast(tree1, pt) case _ => @@ -365,4 +366,4 @@ object Erasure { if (tree.isEmpty) tree else adaptToType(tree, pt) } } -} \ No newline at end of file +} diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 73a0ed458..b64f529bf 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -6,6 +6,7 @@ package dotty.tools.dotc package transform import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} +import ValueClasses._ import dotty.tools.dotc.ast.{Trees, tpd} import scala.collection.{ mutable, immutable } import mutable.ListBuffer @@ -96,7 +97,7 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th if (seen contains clazz) ctx.error("value class may not unbox to itself", pos) else { - val unboxed = clazz.underlyingOfValueClass.typeSymbol + val unboxed = underlyingOfValueClass(clazz).typeSymbol if (unboxed.isDerivedValueClass) checkNonCyclic(pos, seen + clazz, unboxed.asClass) } @@ -122,7 +123,7 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th tree1 } } else tree - case DefDef(mods, name, tparams, vparamss, tpt, rhs) if tree.symbol.isMethodWithExtension => + case DefDef(mods, name, tparams, vparamss, tpt, rhs) if isMethodWithExtension(tree.symbol) => val origMeth = tree.symbol val origClass = ctx.owner.asClass val origTParams = tparams.map(_.symbol) ::: origClass.typeParams // method type params ++ class type params diff --git a/src/dotty/tools/dotc/transform/SuperAccessors.scala b/src/dotty/tools/dotc/transform/SuperAccessors.scala index 0fbf33869..52306956e 100644 --- a/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -4,6 +4,7 @@ package transform import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransform, TreeTransformer} import dotty.tools.dotc.ast.{Trees, tpd} import scala.collection.{ mutable, immutable } +import ValueClasses._ import mutable.ListBuffer import scala.annotation.tailrec import core._ @@ -368,7 +369,7 @@ class SuperAccessors extends MacroTransform with IdentityDenotTransformer { this transformSelect case DefDef(mods, name, tparams, vparamss, tpt, rhs) => - val rhs1 = if (sym.isMethodWithExtension) withInvalidOwner(transform(rhs)) else transform(rhs) + val rhs1 = if (isMethodWithExtension(sym)) withInvalidOwner(transform(rhs)) else transform(rhs) cpy.DefDef(tree, mods, name, tparams, vparamss, tpt, rhs1) case TypeApply(sel @ Select(qual, name), args) => diff --git a/src/dotty/tools/dotc/transform/ValueClasses.scala b/src/dotty/tools/dotc/transform/ValueClasses.scala new file mode 100644 index 000000000..c5cf44552 --- /dev/null +++ b/src/dotty/tools/dotc/transform/ValueClasses.scala @@ -0,0 +1,36 @@ +package dotty.tools.dotc +package transform + +import core._ +import Types._ +import Symbols._ +import SymDenotations._ +import Contexts._ +import Flags._ + +/** Methods that apply to user-defined value classes */ +object ValueClasses { + + def isDerivedValueClass(d: SymDenotation)(implicit ctx: Context) = + d.isClass && d.derivesFrom(defn.AnyValClass) && !d.isPrimitiveValueClass + + def isMethodWithExtension(d: SymDenotation)(implicit ctx: Context) = + d.isSourceMethod && + isDerivedValueClass(d.owner) && + !d.isConstructor && + !d.is(SuperAccessor) && + !d.is(Macro) + + /** The member that of a derived value class that unboxes it. */ + def valueClassUnbox(d: ClassDenotation)(implicit ctx: Context): Symbol = + // (info.decl(nme.unbox)).orElse(...) uncomment once we accept unbox methods + d.classInfo.decls + .find(d => d.isTerm && d.symbol.is(ParamAccessor)) + .map(_.symbol) + .getOrElse(NoSymbol) + + /** The unboxed type that underlies a derived value class */ + def underlyingOfValueClass(d: ClassDenotation)(implicit ctx: Context): Type = + valueClassUnbox(d).info.resultType + +} diff --git a/test/test/showClass.scala b/test/test/showClass.scala index ee71854d1..78751ad6e 100644 --- a/test/test/showClass.scala +++ b/test/test/showClass.scala @@ -1,7 +1,6 @@ package test import dotty.tools.dotc.core.Decorators._ -import dotty.tools.dotc.core.Symbols object showClass extends ShowClassTests { -- cgit v1.2.3 From fc784b8cd338eecc98e1b23ea2f27d0c82e9fe87 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 10 Jul 2014 16:57:59 +0200 Subject: Enabled ExtensionMethods Fixed extension methods so that it now runs and passes the build. Also enables ElimRepeated, which is a prerequistite for ExtensionMethods. Exception: Tailrec is currently disabled, because it needs to run before ExtensionMethods but it fails the -Ycheck test. Therefore the current tests skip this phase. --- src/dotty/tools/dotc/Compiler.scala | 4 +- .../tools/dotc/transform/ExtensionMethods.scala | 51 ++++++---- test/dotc/tests.scala | 2 +- tests/pos/Meter.scala | 109 +++++++++++++++++++++ 4 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 tests/pos/Meter.scala (limited to 'test') diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index b51f383f7..dfed8ce9d 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -19,10 +19,10 @@ class Compiler { def phases: List[List[Phase]] = List( List(new FrontEnd), - List(new Companions), + List(new Companions, new ElimRepeated /*, new ElimLocals*/), + List(new TailRec), List(new SuperAccessors), List(new ExtensionMethods), - List(new TailRec), List(new PatternMatcher, new LazyValTranformContext().transformer, new Splitter), diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index b64f529bf..d0cdf976d 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -12,6 +12,7 @@ import scala.collection.{ mutable, immutable } import mutable.ListBuffer import scala.annotation.tailrec import core._ +import Phases.Phase import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ import TypeUtils._ @@ -29,11 +30,15 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th /** the following two members override abstract members in Transform */ val name: String = "extmethods" + override def runsAfter: Set[String] = Set("elimrepeated") // TODO: add tailrec + def newTransformer(implicit ctx: Context): Transformer = new Extender + override def transformPhase(implicit ctx: Context): Phase = thisTransformer.next + /** 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 + * If the method is not overloaded, this stream consists of just "imeth$extension". + * If the method is overloaded, the stream has as first element "imeth$extenionX", 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 @@ -44,6 +49,8 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = { val decl = imeth.owner.info.decl(imeth.name) + if (imeth.name.toString == "appliedTo") println(i"resolve: $decl") + /** 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. @@ -54,14 +61,15 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe */ decl match { - case decl: SingleDenotation => + case decl: MultiDenotation => val alts = decl.alternatives val index = alts indexOf imeth.denot assert(index >= 0, alts+" does not contain "+imeth) def altName(index: Int) = (imeth.name+"$extension"+index).toTermName + if (imeth.name.toString == "appliedTo") println(i"resolve: $decl ${altName(index)}") 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) + case decl => + assert(decl.exists, imeth.name+" not found in "+imeth.owner+"'s decls: "+imeth.owner.info.decls) Stream((imeth.name+"$extension").toTermName) } } @@ -98,20 +106,20 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th ctx.error("value class may not unbox to itself", pos) else { val unboxed = underlyingOfValueClass(clazz).typeSymbol - if (unboxed.isDerivedValueClass) checkNonCyclic(pos, seen + clazz, unboxed.asClass) + if (isDerivedValueClass(unboxed)) checkNonCyclic(pos, seen + clazz, unboxed.asClass) } override def transform(tree: Tree)(implicit ctx: Context): Tree = { tree match { case tree: Template => - if (ctx.owner.isDerivedValueClass) { + if (isDerivedValueClass(ctx.owner)) { /* 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] + extensionDefs(ctx.owner.linkedClass) = 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)) + val paramAccessors = ctx.owner.info.decls.filter(_.is(TermParamAccessor)) paramAccessors.foreach(_.makeNotPrivateAfter(ctx.owner, thisTransformer)) super.transform(tree) } else if (ctx.owner.isStaticOwner) { @@ -128,8 +136,8 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th 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 staticClass = origClass.linkedClass + assert(staticClass.exists, s"$origClass lacks companion, ${origClass.owner.definedPeriodsString} ${origClass.owner.info.decls} ${origClass.owner.info.decls}") val extensionMeth = ctx.atPhase(thisTransformer.next) { implicit ctx => val extensionName = extensionNames(origMeth).head.toTermName @@ -144,15 +152,18 @@ class ExtensionMethods extends MacroTransform with IdentityDenotTransformer { th 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) + val thisRef :: argRefs = vrefss.flatten + new TreeTypeMap( + typeMap = _ + .subst(origTParams, trefs) + .subst(origVParams, argRefs.map(_.tpe)) + .substThis(origClass, thisRef.tpe), + ownerMap = (sym => if (sym eq origMeth) extensionMeth else sym), + treeMap = { + case tree: This if tree.symbol == origClass => thisRef + case tree => tree + } + ).transform(rhs) }) // These three lines are assembling Foo.bar$extension[T1, T2, ...]($this) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index b1e0a6efe..ac4e915c2 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:extmethods"//, "-Ystop-before:terminal" + "-Yskip:tailrec, -Ycheck:extmethods"//, "-Ystop-before:terminal" ) val twice = List("#runs", "2", "-YnoDoubleBindings") diff --git a/tests/pos/Meter.scala b/tests/pos/Meter.scala new file mode 100644 index 000000000..53be6f9d0 --- /dev/null +++ b/tests/pos/Meter.scala @@ -0,0 +1,109 @@ +package a { + abstract class BoxingConversions[Boxed, Unboxed] { + def box(x: Unboxed): Boxed + def unbox(x: Boxed): Unboxed + } + + class Meter(val underlying: Double) extends AnyVal with _root_.b.Printable { + def + (other: Meter): Meter = + new Meter(this.underlying + other.underlying) + def / (other: Meter)(implicit dummy: Meter.MeterArg = null): Double = this.underlying / other.underlying + def / (factor: Double): Meter = new Meter(this.underlying / factor) + def < (other: Meter): Boolean = this.underlying < other.underlying + def toFoot: Foot = new Foot(this.underlying * 0.3048) + override def print = { Console.print(">>>"); super.print; proprint } + override def toString: String = underlying.toString+"m" + } + + object Meter extends (Double => Meter) { + + private[a] trait MeterArg + + def apply(x: Double): Meter = new Meter(x) + + implicit val boxings: BoxingConversions[Meter, Double] = new BoxingConversions[Meter, Double] { + def box(x: Double) = new Meter(x) + def unbox(m: Meter) = m.underlying + } + } + + class Foot(val unbox: Double) extends AnyVal { + def + (other: Foot): Foot = + new Foot(this.unbox + other.unbox) + override def toString = unbox.toString+"ft" + } + object Foot { + implicit val boxings: BoxingConversions[Foot, Double] = new BoxingConversions[Foot, Double] { + def box(x: Double) = new Foot(x) + def unbox(m: Foot) = m.unbox + } + } + +} +package b { + trait Printable extends Any { + def print: Unit = Console.print(this) + protected def proprint = Console.print("<<<") + } +} +import a._ +import _root_.b._ +object Test extends App { + + { + val x: Meter = new Meter(1) + val a: Object = x.asInstanceOf[Object] + val y: Meter = a.asInstanceOf[Meter] + + val u: Double = 1 + val b: Object = u.asInstanceOf[Object] + val v: Double = b.asInstanceOf[Double] + } + + val x = new Meter(1) + val y = x + //println((x + x) / x) + println((x + x) / 0.5) + println((x < x).toString) + println("x.isInstanceOf[Meter]: "+x.isInstanceOf[Meter]) + + + println("x.hashCode: "+x.hashCode) + println("x == 1: "+(x == 1)) + println("x == y: "+(x == y)) + assert(x.hashCode == (1.0).hashCode) + + val a: Any = x + val b: Any = y + println("a == b: "+(a == b)) + + { println("testing native arrays") + val arr = Array(x, y + x) + println(arr.deep) + def foo[T <: Printable](x: Array[T]) = { + for (i <- 0 until x.length) { x(i).print; println(" "+x(i)) } + } + val m = arr(0) + println(m) + foo(arr) + } + // + // { println("testing wrapped arrays") + // import collection.mutable.FlatArray + // val arr = FlatArray(x, y + x) + // println(arr) + // def foo(x: FlatArray[Meter]) { + // for (i <- 0 until x.length) { x(i).print; println(" "+x(i)) } + // } + // val m = arr(0) + // println(m) + // foo(arr) + // val ys: Seq[Meter] = arr map (_ + new Meter(1)) + // println(ys) + // val zs = arr map (_ / Meter(1)) + // println(zs) + // val fs = arr map (_.toFoot) + // println(fs) + // } + +} -- cgit v1.2.3 From 3f3581c1fdefbebf06d4b3d12c6474c2997ec52d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 13 Jul 2014 14:12:00 +0200 Subject: Make ExtensionMethods not rely on TailRec This acommit allows TailRec to run after ExtensionMethods. This is due to the following changes: 1) Extension methods now "rewire" calls to other extension methods of the same class, so that they go directly to other extension methods instead of to the original method in the class. 2) Tailrec annotations get removed from original method and get added to the extension method instead. With this commit all tests can be re-enabled again. --- src/dotty/tools/dotc/Compiler.scala | 2 +- .../tools/dotc/transform/ExtensionMethods.scala | 64 ++++++++++++++++++---- test/dotc/tests.scala | 4 +- 3 files changed, 56 insertions(+), 14 deletions(-) (limited to 'test') diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index dfed8ce9d..95f74e290 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -20,9 +20,9 @@ class Compiler { List( List(new FrontEnd), List(new Companions, new ElimRepeated /*, new ElimLocals*/), - List(new TailRec), List(new SuperAccessors), List(new ExtensionMethods), + List(new TailRec), List(new PatternMatcher, new LazyValTranformContext().transformer, new Splitter), diff --git a/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 3e5932cfe..917db17a1 100644 --- a/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -23,7 +23,7 @@ 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 InfoTransformer { thisTransformer => +class ExtensionMethods extends MacroTransform with DenotTransformer { thisTransformer => import tpd._ @@ -32,22 +32,30 @@ class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransfo override def runsAfter: Set[String] = Set("elimrepeated") // TODO: add tailrec - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { - case tp: ClassInfo if sym is ModuleClass => - sym.linkedClass match { + override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { + case ref: ClassDenotation if ref is ModuleClass => + ref.linkedClass match { case origClass: ClassSymbol if isDerivedValueClass(origClass) => - val decls1 = tp.decls.cloneScope + val cinfo = ref.classInfo + val decls1 = cinfo.decls.cloneScope ctx.atPhase(thisTransformer.next) { implicit ctx => for (decl <- origClass.classInfo.decls) { if (isMethodWithExtension(decl)) - decls1.enter(createExtensionMethod(decl, sym)) + decls1.enter(createExtensionMethod(decl, ref.symbol)) } } - tp.derivedClassInfo(decls = decls1) - case _ => tp + if (decls1.isEmpty) ref + else ref.copySymDenotation(info = cinfo.derivedClassInfo(decls = decls1)) + case _ => + ref } + case ref: SymDenotation + if isMethodWithExtension(ref) && ref.hasAnnotation(defn.TailrecAnnotationClass) => + val ref1 = ref.copySymDenotation() + ref1.removeAnnotation(defn.TailrecAnnotationClass) + ref1 case _ => - tp + ref } def newTransformer(implicit ctx: Context): Transformer = new Extender @@ -120,7 +128,8 @@ class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransfo imeth.flags | Final &~ (Override | Protected | AbsOverride), imeth.info.toStatic(imeth.owner.asClass), privateWithin = imeth.privateWithin, coord = imeth.coord) - extensionMeth.addAnnotations(from = imeth) + extensionMeth.addAnnotations(from = imeth)(ctx.withPhase(thisTransformer)) + // need to change phase to add tailrec annotation which gets removed from original method in the same phase. extensionMeth } @@ -169,8 +178,41 @@ class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransfo ctx.log(s"Value class $origClass spawns extension method.\n Old: ${origMeth.showDcl}\n New: ${extensionMeth.showDcl}") + def needsRewiring(sym: Symbol) = isMethodWithExtension(sym) && sym.owner == origClass + extensionDefs(staticClass) += polyDefDef(extensionMeth, trefs => vrefss => { val thisRef :: argRefs = vrefss.flatten + def rewireToStatic(tree: Tree, targs: List[Tree])(implicit ctx: Context): Tree = { + def rewireCall(thisArg: Tree): Tree = { + val sym = tree.symbol + if (needsRewiring(sym)) { + val base = thisArg.tpe.baseTypeWithArgs(origClass) + assert(base.exists) + ref(extensionMethod(sym).termRef) + .appliedToTypeTrees(targs ++ base.argInfos.map(TypeTree(_))) + .appliedTo(thisArg) + } else EmptyTree + } + tree match { + case Ident(_) => rewireCall(thisRef) + case Select(qual, _) => rewireCall(qual) + case tree @ TypeApply(fn, targs1) => + assert(targs.isEmpty) + rewireToStatic(fn, targs1) + case Block(stats, expr) => + val expr1 = rewireToStatic(expr, targs) + if (expr1.isEmpty) EmptyTree else Block(stats, expr1) withPos tree.pos + case _ => EmptyTree + } + } + val rewireType = new TypeMap { + def apply(tp: Type) = tp match { + case ref: TermRef if needsRewiring(ref.symbol) => + extensionMethod(ref.symbol).termRef + case _ => + mapOver(tp) + } + } new TreeTypeMap( typeMap = _ .subst(origTParams, trefs) @@ -179,7 +221,7 @@ class ExtensionMethods extends MacroTransform with InfoTransformer { thisTransfo ownerMap = (sym => if (sym eq origMeth) extensionMeth else sym), treeMap = { case tree: This if tree.symbol == origClass => thisRef - case tree => tree + case tree => rewireToStatic(tree, Nil) orElse tree } ).transform(rhs) }) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index ac4e915c2..f1896cbdf 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( - "-Yskip:tailrec, -Ycheck:extmethods"//, "-Ystop-before:terminal" + "-Ycheck:extmethods"//, "-Ystop-before:terminal" ) val twice = List("#runs", "2", "-YnoDoubleBindings") @@ -78,7 +78,7 @@ class tests extends CompilerTest { @Test def neg_t1192_legalPrefix = compileFile(negDir, "t1192", xerrors = 1) @Test def neg_tailcall_t1672b = compileFile(negDir, "tailcall/t1672b", xerrors = 6) @Test def neg_tailcall_t3275 = compileFile(negDir, "tailcall/t3275", xerrors = 1) - @Test def neg_tailcall_t6574 = compileFile(negDir, "tailcall/t6574", xerrors = 4) + @Test def neg_tailcall_t6574 = compileFile(negDir, "tailcall/t6574", xerrors = 2) @Test def neg_tailcall = compileFile(negDir, "tailcall/tailrec", xerrors = 7) @Test def neg_tailcall2 = compileFile(negDir, "tailcall/tailrec-2", xerrors = 2) @Test def neg_tailcall3 = compileFile(negDir, "tailcall/tailrec-3", xerrors = 2) -- cgit v1.2.3