From e6e81d6780e7e658184c4e94248c72c67ca5882d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Dec 2017 13:47:31 +0100 Subject: [PATCH 1/2] Fix #2081: Don't issue an $init$ method for NoInits traits - Don't issue an initializer is a trait does not need initialization, as signalled by NoInits - Don't overwrite NoInits in erasure since that gives the wrong information - Don't issue supercalls in Mixin to NoInits traits --- .../tools/backend/jvm/DottyBackendInterface.scala | 4 +++- .../dotty/tools/dotc/transform/Constructors.scala | 15 ++++++++++++--- .../src/dotty/tools/dotc/transform/Mixin.scala | 3 ++- compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- tests/run/junitForwarders/C_1.scala | 8 ++++++++ tests/run/traitNoInit.scala | 12 ++++++++++++ 6 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 tests/run/traitNoInit.scala diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 115c5359ebbb..5328fcd9afbb 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -1171,7 +1171,9 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma object Template extends TemplateDeconstructor { def _1: List[Tree] = field.parents def _2: ValDef = field.self - def _3: List[Tree] = field.constr :: field.body + def _3: List[Tree] = + if (field.constr.rhs.isEmpty) field.body + else field.constr :: field.body } object Bind extends BindDeconstructor { diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index 4954c143590f..341f916d4593 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -98,10 +98,12 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = * in this phase and for other methods in memoize). */ override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = { + def emptyRhsOK(sym: Symbol) = + sym.is(LazyOrDeferred) || sym.isConstructor && sym.owner.is(NoInitsTrait) tree match { case tree: ValDef if tree.symbol.exists && tree.symbol.owner.isClass && !tree.symbol.is(Lazy) && !tree.symbol.hasAnnotation(defn.ScalaStaticAnnot) => assert(tree.rhs.isEmpty, i"$tree: initializer should be moved to constructors") - case tree: DefDef if !tree.symbol.is(LazyOrDeferred) => + case tree: DefDef if !emptyRhsOK(tree.symbol) => assert(!tree.rhs.isEmpty, i"unimplemented: $tree") case _ => } @@ -269,9 +271,16 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = case _ => false } + val finalConstrStats = copyParams ::: mappedSuperCalls ::: lazyAssignments ::: stats + val expandedConstr = + if (cls.is(NoInitsTrait)) { + assert(finalConstrStats.isEmpty) + constr + } + else cpy.DefDef(constr)(rhs = Block(finalConstrStats, unitLiteral)) + cpy.Template(tree)( - constr = cpy.DefDef(constr)( - rhs = Block(copyParams ::: mappedSuperCalls ::: lazyAssignments ::: stats, unitLiteral)), + constr = expandedConstr, body = clsStats.toList) } } diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index e17976984a07..712eafc6a3eb 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -179,7 +179,8 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => def superCallOpt(baseCls: Symbol): List[Tree] = superCalls.get(baseCls) match { case Some(call) => - if (defn.NotRuntimeClasses.contains(baseCls)) Nil else call :: Nil + if (defn.NotRuntimeClasses.contains(baseCls) || baseCls.is(NoInitsTrait)) Nil + else call :: Nil case None => if (baseCls.is(NoInitsTrait) || defn.NoInitClasses.contains(baseCls)) Nil else { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2648c2d183d3..c2ae5cf53e80 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1469,7 +1469,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } else { val dummy = localDummy(cls, impl) val body1 = typedStats(impl.body, dummy)(inClassContext(self1.symbol)) - cls.setNoInitsFlags((NoInitsInterface /: body1) ((fs, stat) => fs & defKind(stat))) + if (!ctx.isAfterTyper) + cls.setNoInitsFlags((NoInitsInterface /: body1) ((fs, stat) => fs & defKind(stat))) // Expand comments and type usecases cookComments(body1.map(_.symbol), self1.symbol)(localContext(cdef, cls).setNewScope) diff --git a/tests/run/junitForwarders/C_1.scala b/tests/run/junitForwarders/C_1.scala index 6246e9436866..82ff9d3600c4 100644 --- a/tests/run/junitForwarders/C_1.scala +++ b/tests/run/junitForwarders/C_1.scala @@ -1,7 +1,14 @@ trait T { @org.junit.Test def foo = 0 + println("hi") // to force an $init method } +trait U { + @org.junit.Test def bar = 0 + // don't issue a $init method +} + + class C extends T object Test extends App { @@ -12,4 +19,5 @@ object Test extends App { check(classOf[C], "foo - @org.junit.Test()") // TODO scala-dev#213: should `foo$` really carry the @Test annotation? check(classOf[T], "$init$ - ;foo - @org.junit.Test()") + check(classOf[U], "bar - @org.junit.Test()") } diff --git a/tests/run/traitNoInit.scala b/tests/run/traitNoInit.scala new file mode 100644 index 000000000000..3ef8c55ca69c --- /dev/null +++ b/tests/run/traitNoInit.scala @@ -0,0 +1,12 @@ +trait Foo { + //println("hello") + def meth(x: Int): Int +} + +trait Bar(x: Int) + +class C extends Foo() with Bar(1) { + def meth(x: Int) = x +} + +object Test extends C with App From 81efdfaa2f56f13c24ed50aec29b8b5673d25035 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sun, 31 Dec 2017 17:46:40 +0100 Subject: [PATCH 2/2] Improve traitNoInit test to actually test if $init$ is present --- tests/run/traitNoInit.scala | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/run/traitNoInit.scala b/tests/run/traitNoInit.scala index 3ef8c55ca69c..a5f75dc02610 100644 --- a/tests/run/traitNoInit.scala +++ b/tests/run/traitNoInit.scala @@ -1,12 +1,29 @@ -trait Foo { - //println("hello") +trait NoInit { + def meth(x: Int): Int +} + +trait WithInit { + val i = 1 def meth(x: Int): Int } trait Bar(x: Int) -class C extends Foo() with Bar(1) { +class NoInitClass extends NoInit() with Bar(1) { + def meth(x: Int) = x +} + +class WithInitClass extends WithInit() with Bar(1) { def meth(x: Int) = x } -object Test extends C with App +object Test { + def hasInit(cls: Class[_]) = cls.getMethods.map(_.toString).exists(_.contains("$init$")) + def main(args: Array[String]): Unit = { + val noInit = new NoInitClass {} + val withInit = new WithInitClass {} + + assert(!hasInit(noInit.getClass)) + assert(hasInit(withInit.getClass)) + } +}