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..a5f75dc02610 --- /dev/null +++ b/tests/run/traitNoInit.scala @@ -0,0 +1,29 @@ +trait NoInit { + def meth(x: Int): Int +} + +trait WithInit { + val i = 1 + def meth(x: Int): Int +} + +trait Bar(x: Int) + +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 { + 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)) + } +}