From 6927c1aa76c19c1232609adb53b17e008ea04030 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Fri, 24 Apr 2015 09:59:21 +0200 Subject: [PATCH 1/6] Save TASTY in attribute of classfiles. --- src/dotty/tools/backend/jvm/GenBCode.scala | 12 +++++++++++- src/dotty/tools/dotc/ast/tpd.scala | 1 + src/dotty/tools/dotc/core/StdNames.scala | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/backend/jvm/GenBCode.scala b/src/dotty/tools/backend/jvm/GenBCode.scala index a53e910ae7a2..8312a72e7965 100644 --- a/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/src/dotty/tools/backend/jvm/GenBCode.scala @@ -6,7 +6,7 @@ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Phases.Phase import scala.collection.mutable -import scala.tools.asm.{ClassVisitor, MethodVisitor, FieldVisitor} +import scala.tools.asm.{CustomAttr, ClassVisitor, MethodVisitor, FieldVisitor} import scala.tools.nsc.Settings import scala.tools.nsc.backend.jvm._ import dotty.tools.dotc @@ -27,6 +27,7 @@ import scala.tools.asm import scala.tools.asm.tree._ import dotty.tools.dotc.util.{Positions, DotClass} import tpd._ +import StdNames._ import scala.tools.nsc.backend.jvm.opt.LocalOpt @@ -174,6 +175,15 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName) else null; val plainC = pcb.cnode + if ((!claszSymbol.companionClass.exists) || !claszSymbol.is(Flags.Module)) { + // generate TASTY on class if it is there, or on module if it has no companion class + + val binary = ctx.compilationUnit.pickler.assembleParts() + val dataAttr = new CustomAttr(nme.DottyTASTYATTR.toString, binary) + plainC.visitAttribute(dataAttr) + } + + // -------------- bean info class, if needed -------------- val beanC = if (claszSymbol hasAnnotation int.BeanInfoAttr) { diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 955439413ebb..b856e3190d05 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -383,6 +383,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else if (tpw isRef defn.ShortClass) Literal(Constant(0.toShort)) else Literal(Constant(null)).select(defn.Any_asInstanceOf).appliedToType(tpe) } + private class FindLocalDummyAccumulator(cls: ClassSymbol)(implicit ctx: Context) extends TreeAccumulator[Symbol] { def apply(sym: Symbol, tree: Tree)(implicit ctx: Context) = if (sym.exists) sym diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index a4471ebb4f72..1ecc78eb9f85 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -215,6 +215,7 @@ object StdNames { final val RuntimeParamAnnotationATTR: N = "RuntimeVisibleParameterAnnotations" // RetentionPolicy.RUNTIME (annotations on parameters) final val ScalaATTR: N = "Scala" final val ScalaSignatureATTR: N = "ScalaSig" + final val DottyTASTYATTR: N = "DottyTASTY" final val SignatureATTR: N = "Signature" final val SourceFileATTR: N = "SourceFile" final val SyntheticATTR: N = "Synthetic" From 2cab739534ba259efbfabffd417250930e7700a2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 24 Apr 2015 16:30:37 +0200 Subject: [PATCH 2/6] Produce one TASTY pickling per top-level class. If a unit has several top-level classes or object (which are not linked classes of each other) each gets its own pickle information, which contains any enclosing package clauses and imports and then just the top-level class/object and its companion object. --- src/dotty/tools/backend/jvm/GenBCode.scala | 14 ++--- src/dotty/tools/dotc/CompilationUnit.scala | 4 +- src/dotty/tools/dotc/ast/TreeInfo.scala | 28 ++++++++++ src/dotty/tools/dotc/transform/Pickler.scala | 59 ++++++++++++-------- 4 files changed, 73 insertions(+), 32 deletions(-) diff --git a/src/dotty/tools/backend/jvm/GenBCode.scala b/src/dotty/tools/backend/jvm/GenBCode.scala index 8312a72e7965..999cd4137f16 100644 --- a/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/src/dotty/tools/backend/jvm/GenBCode.scala @@ -175,14 +175,12 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName) else null; val plainC = pcb.cnode - if ((!claszSymbol.companionClass.exists) || !claszSymbol.is(Flags.Module)) { - // generate TASTY on class if it is there, or on module if it has no companion class - - val binary = ctx.compilationUnit.pickler.assembleParts() - val dataAttr = new CustomAttr(nme.DottyTASTYATTR.toString, binary) - plainC.visitAttribute(dataAttr) - } - + if (claszSymbol.isClass) // @DarkDimius is this test needed here? + for (pickler <- ctx.compilationUnit.picklers.get(claszSymbol.asClass)) { + val binary = pickler.assembleParts() + val dataAttr = new CustomAttr(nme.DottyTASTYATTR.toString, binary) + plainC.visitAttribute(dataAttr) + } // -------------- bean info class, if needed -------------- val beanC = diff --git a/src/dotty/tools/dotc/CompilationUnit.scala b/src/dotty/tools/dotc/CompilationUnit.scala index 60e16ec3ed22..4f8c30aab7e7 100644 --- a/src/dotty/tools/dotc/CompilationUnit.scala +++ b/src/dotty/tools/dotc/CompilationUnit.scala @@ -19,11 +19,11 @@ class CompilationUnit(val source: SourceFile) { def isJava = source.file.name.endsWith(".java") /** - * Pickler used to create TASTY sections. + * Picklers used to create TASTY sections, indexed by toplevel class to which they belong. * Sections: Header, ASTs and Positions are populated by `pickler` phase. * Subsequent phases can add new sections. */ - lazy val pickler: TastyPickler = new TastyPickler() + var picklers: Map[ClassSymbol, TastyPickler] = Map() /** * Addresses in TASTY file of trees, stored by pickling. diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index a7f89337c4b9..c6d7e10f73c4 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -461,6 +461,34 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => accum(Nil, root) } + + /** The top level classes in this tree, including only those module classes that + * are not a linked class of some other class in the result. + */ + def topLevelClasses(tree: Tree)(implicit ctx: Context): List[ClassSymbol] = tree match { + case PackageDef(_, stats) => stats.flatMap(topLevelClasses) + case tdef: TypeDef if tdef.symbol.isClass => tdef.symbol.asClass :: Nil + case _ => Nil + } + + /** The tree containing only the top-level classes and objects matching either `cls` or its companion object */ + def sliceTopLevel(tree: Tree, cls: ClassSymbol)(implicit ctx: Context): List[Tree] = tree match { + case PackageDef(pid, stats) => + cpy.PackageDef(tree)(pid, stats.flatMap(sliceTopLevel(_, cls))) :: Nil + case tdef: TypeDef => + val sym = tdef.symbol + assert(sym.isClass) + if (cls == sym || cls == sym.linkedClass) tdef :: Nil + else Nil + case vdef: ValDef => + val sym = vdef.symbol + assert(sym is Module) + if (cls == sym.companionClass || cls == sym.moduleClass) vdef :: Nil + else Nil + case tree => + tree :: Nil + } + /** The statement sequence that contains a definition of `sym`, or Nil * if none was found. * For a tree to be found, The symbol must have a position and its definition diff --git a/src/dotty/tools/dotc/transform/Pickler.scala b/src/dotty/tools/dotc/transform/Pickler.scala index 515dad2aa490..c09ba6889415 100644 --- a/src/dotty/tools/dotc/transform/Pickler.scala +++ b/src/dotty/tools/dotc/transform/Pickler.scala @@ -9,6 +9,8 @@ import config.Printers.{noPrinter, pickling} import java.io.PrintStream import Periods._ import Phases._ +import Symbols._ +import Flags.Module import collection.mutable /** This phase pickles trees */ @@ -23,28 +25,41 @@ class Pickler extends Phase { s.close } - private val beforePickling = new mutable.HashMap[CompilationUnit, String] + private val beforePickling = new mutable.HashMap[ClassSymbol, String] + + /** Drop any elements of this list that are linked module classes of other elements in the list */ + private def dropCompanionModuleClasses(clss: List[ClassSymbol])(implicit ctx: Context): List[ClassSymbol] = { + val companionModuleClasses = + clss.filterNot(_ is Module).map(_.linkedClass).filterNot(_.isAbsent) + clss.filterNot(companionModuleClasses.contains) + } override def run(implicit ctx: Context): Unit = { val unit = ctx.compilationUnit - val tree = unit.tpdTree pickling.println(i"unpickling in run ${ctx.runId}") - if (ctx.settings.YtestPickler.value) beforePickling(unit) = tree.show - val pickler = unit.pickler - val treePkl = new TreePickler(pickler) - treePkl.pickle(tree :: Nil) - unit.addrOfTree = treePkl.buf.addrOfTree - unit.addrOfSym = treePkl.addrOfSym - if (tree.pos.exists) - new PositionPickler(pickler, treePkl.buf.addrOfTree).picklePositions(tree :: Nil, tree.pos) + for { cls <- dropCompanionModuleClasses(topLevelClasses(unit.tpdTree)) + tree <- sliceTopLevel(unit.tpdTree, cls) } { + if (ctx.settings.YtestPickler.value) beforePickling(cls) = tree.show + val pickler = new TastyPickler() + unit.picklers += (cls -> pickler) + val treePkl = new TreePickler(pickler) + treePkl.pickle(tree :: Nil) + unit.addrOfTree = treePkl.buf.addrOfTree + unit.addrOfSym = treePkl.addrOfSym + if (tree.pos.exists) + new PositionPickler(pickler, treePkl.buf.addrOfTree).picklePositions(tree :: Nil, tree.pos) - def rawBytes = // not needed right now, but useful to print raw format. - unit.pickler.assembleParts().iterator.grouped(10).toList.zipWithIndex.map { - case (row, i) => s"${i}0: ${row.mkString(" ")}" + def rawBytes = // not needed right now, but useful to print raw format. + pickler.assembleParts().iterator.grouped(10).toList.zipWithIndex.map { + case (row, i) => s"${i}0: ${row.mkString(" ")}" + } + // println(i"rawBytes = \n$rawBytes%\n%") // DEBUG + if (pickling ne noPrinter) { + println(i"**** pickled info of $cls") + new TastyPrinter(pickler.assembleParts()).printContents() } - // println(i"rawBytes = \n$rawBytes%\n%") // DEBUG - if (pickling ne noPrinter) new TastyPrinter(pickler.assembleParts()).printContents() + } } override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { @@ -58,23 +73,23 @@ class Pickler extends Phase { pickling.println(i"testing unpickler at run ${ctx.runId}") ctx.definitions.init val unpicklers = - for (unit <- units) yield { - val unpickler = new DottyUnpickler(unit.pickler.assembleParts()) + for (unit <- units; (cls, pickler) <- unit.picklers) yield { + val unpickler = new DottyUnpickler(pickler.assembleParts()) unpickler.enter(roots = Set()) - unpickler + cls -> unpickler } pickling.println("************* entered toplevel ***********") - for ((unpickler, unit) <- unpicklers zip units) { + for ((cls, unpickler) <- unpicklers) { val unpickled = unpickler.body(readPositions = false) - testSame(i"$unpickled%\n%", beforePickling(unit), unit) + testSame(i"$unpickled%\n%", beforePickling(cls), cls) } } - private def testSame(unpickled: String, previous: String, unit: CompilationUnit)(implicit ctx: Context) = + private def testSame(unpickled: String, previous: String, cls: ClassSymbol)(implicit ctx: Context) = if (previous != unpickled) { output("before-pickling.txt", previous) output("after-pickling.txt", unpickled) - ctx.error(s"""pickling difference for $unit, for details: + ctx.error(s"""pickling difference for ${cls.fullName}, for details: | | diff before-pickling.txt after-pickling.txt""".stripMargin) } From d94f0b8d7b2bc06c0f0642a714f7940d2f2a9348 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 25 Apr 2015 10:08:51 +0200 Subject: [PATCH 3/6] Change name of TASTY attribute to TASTY It does not matter whether it comes from Dotty or elsewhere. We can record the name of the producer in a section of the format itself. --- src/dotty/tools/backend/jvm/GenBCode.scala | 2 +- src/dotty/tools/dotc/core/StdNames.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/backend/jvm/GenBCode.scala b/src/dotty/tools/backend/jvm/GenBCode.scala index 999cd4137f16..22184388146f 100644 --- a/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/src/dotty/tools/backend/jvm/GenBCode.scala @@ -178,7 +178,7 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter if (claszSymbol.isClass) // @DarkDimius is this test needed here? for (pickler <- ctx.compilationUnit.picklers.get(claszSymbol.asClass)) { val binary = pickler.assembleParts() - val dataAttr = new CustomAttr(nme.DottyTASTYATTR.toString, binary) + val dataAttr = new CustomAttr(nme.TASTYATTR.toString, binary) plainC.visitAttribute(dataAttr) } diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index 1ecc78eb9f85..7f66b5a9ec89 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -215,7 +215,7 @@ object StdNames { final val RuntimeParamAnnotationATTR: N = "RuntimeVisibleParameterAnnotations" // RetentionPolicy.RUNTIME (annotations on parameters) final val ScalaATTR: N = "Scala" final val ScalaSignatureATTR: N = "ScalaSig" - final val DottyTASTYATTR: N = "DottyTASTY" + final val TASTYATTR: N = "TASTY" final val SignatureATTR: N = "Signature" final val SourceFileATTR: N = "SourceFile" final val SyntheticATTR: N = "Synthetic" From 7ebc9e2f74e5452749a39f9423de0ba4bd91d7c2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 25 Apr 2015 13:39:53 +0200 Subject: [PATCH 4/6] Better error message constructor applications If a constructor for class C was called with wrong number of parameters, the previous error message referred to `method `. Now it is `constructor C`. --- src/dotty/tools/dotc/typer/Applications.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index c5bd70c1ed71..2ba27d0f4068 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -192,7 +192,7 @@ trait Applications extends Compatibility { self: Typer => def success = ok protected def methodType = methType.asInstanceOf[MethodType] - private def methString: String = s"method ${methRef.name}: ${methType.show}" + private def methString: String = i"${methRef.symbol}: ${methType.show}" /** Re-order arguments to correctly align named arguments */ def reorder[T >: Untyped](args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = { From 8cec943e961c4d82c132855e56d1747cf968830b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 25 Apr 2015 13:41:22 +0200 Subject: [PATCH 5/6] Allow separate compilation of Dotty using TASTY Classfile parser now reads TASTY attributes or annotations and unpickles them in order to allow for separate compilation. --- src/dotty/tools/dotc/core/Definitions.scala | 2 ++ .../dotc/core/pickling/ClassfileParser.scala | 29 ++++++++++++++----- .../internal/TASTYLongSignature.java | 12 ++++++++ .../annotation/internal/TASTYSignature.java | 12 ++++++++ tests/pos/sepComp/A_1.scala | 13 +++++++++ tests/pos/sepComp/B_2.scala | 13 +++++++++ 6 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 src/scala/annotation/internal/TASTYLongSignature.java create mode 100644 src/scala/annotation/internal/TASTYSignature.java create mode 100644 tests/pos/sepComp/A_1.scala create mode 100644 tests/pos/sepComp/B_2.scala diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 7accf9148417..7de254008d13 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -335,6 +335,8 @@ class Definitions { lazy val ContravariantBetweenClass = ctx.requiredClass("dotty.annotation.internal.ContravariantBetween") lazy val ScalaSignatureAnnot = ctx.requiredClass("scala.reflect.ScalaSignature") lazy val ScalaLongSignatureAnnot = ctx.requiredClass("scala.reflect.ScalaLongSignature") + lazy val TASTYSignatureAnnot = ctx.requiredClass("scala.annotation.internal.TASTYSignature") + lazy val TASTYLongSignatureAnnot = ctx.requiredClass("scala.annotation.internal.TASTYLongSignature") lazy val DeprecatedAnnot = ctx.requiredClass("scala.deprecated") lazy val MigrationAnnot = ctx.requiredClass("scala.annotation.migration") lazy val AnnotationDefaultAnnot = ctx.requiredClass("dotty.annotation.internal.AnnotationDefault") diff --git a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala index 3d47678b7d92..21c9aa84de76 100644 --- a/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala +++ b/src/dotty/tools/dotc/core/pickling/ClassfileParser.scala @@ -664,11 +664,17 @@ class ClassfileParser( i < attrs } - def unpickle(bytes: Array[Byte]): Boolean = { + def unpickleScala(bytes: Array[Byte]): Boolean = { new UnPickler(bytes, classRoot, moduleRoot)(ctx).run() true } + def unpickleTASTY(bytes: Array[Byte]): Boolean = { + new DottyUnpickler(bytes) + .enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule)) + true + } + def parseScalaSigBytes: Array[Byte] = { val tag = in.nextByte.toChar assert(tag == STRING_TAG, tag) @@ -688,6 +694,11 @@ class ClassfileParser( pool.getBytes(entries.toList) } + if (scan(tpnme.TASTYATTR)) { + val attrLen = in.nextInt + return unpickleTASTY(in.nextBytes(attrLen)) + } + if (scan(tpnme.RuntimeAnnotationATTR)) { val attrLen = in.nextInt val nAnnots = in.nextChar @@ -698,12 +709,16 @@ class ClassfileParser( var j = 0 while (j < nArgs) { val argName = pool.getName(in.nextChar) - if (attrClass == defn.ScalaSignatureAnnot && argName == nme.bytes) - return unpickle(parseScalaSigBytes) - else if (attrClass == defn.ScalaLongSignatureAnnot && argName == nme.bytes) - return unpickle(parseScalaLongSigBytes) - else - parseAnnotArg(skip = true) + if (argName == nme.bytes) + if (attrClass == defn.ScalaSignatureAnnot) + return unpickleScala(parseScalaSigBytes) + else if (attrClass == defn.ScalaLongSignatureAnnot) + return unpickleScala(parseScalaLongSigBytes) + else if (attrClass == defn.TASTYSignatureAnnot) + return unpickleTASTY(parseScalaSigBytes) + else if (attrClass == defn.TASTYLongSignatureAnnot) + return unpickleTASTY(parseScalaLongSigBytes) + parseAnnotArg(skip = true) j += 1 } i += 1 diff --git a/src/scala/annotation/internal/TASTYLongSignature.java b/src/scala/annotation/internal/TASTYLongSignature.java new file mode 100644 index 000000000000..2278da258a5d --- /dev/null +++ b/src/scala/annotation/internal/TASTYLongSignature.java @@ -0,0 +1,12 @@ +package scala.annotation.internal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface TASTYLongSignature { + public String[] bytes(); +} diff --git a/src/scala/annotation/internal/TASTYSignature.java b/src/scala/annotation/internal/TASTYSignature.java new file mode 100644 index 000000000000..a6372f008397 --- /dev/null +++ b/src/scala/annotation/internal/TASTYSignature.java @@ -0,0 +1,12 @@ +package scala.annotation.internal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface TASTYSignature { + public String bytes(); +} diff --git a/tests/pos/sepComp/A_1.scala b/tests/pos/sepComp/A_1.scala new file mode 100644 index 000000000000..03d574705242 --- /dev/null +++ b/tests/pos/sepComp/A_1.scala @@ -0,0 +1,13 @@ +package sepComp + +class A(y: Int) { + + val x: Int = y + +} + +object A { + + def apply(x: Int) = new A(22) + +} diff --git a/tests/pos/sepComp/B_2.scala b/tests/pos/sepComp/B_2.scala new file mode 100644 index 000000000000..3a503b352aab --- /dev/null +++ b/tests/pos/sepComp/B_2.scala @@ -0,0 +1,13 @@ +package sepComp + +class B extends A(22) { + + val y: Int = this.x + + val a = A(33) + + println(a.x) + +} + + From 3639cf84389860be14f428d33a80eee9aeea6efc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 25 Apr 2015 13:43:20 +0200 Subject: [PATCH 6/6] Update .gitignore Add test-classes to .gitignore. (The change is produced persistently by my Eclipse IDE). --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b882858b301d..69d86527a270 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ classes/ # Partest tests/partest-generated/ +/test-classes/