Skip to content

Multiple fixes to @static #1226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Jun 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2e5ab54
Fix #1220. Dont die when having incorect static methods
DarkDimius Apr 18, 2016
7408f14
Test #1220
DarkDimius Apr 18, 2016
bbd48d3
SymDenotations: Allow entering Static symbols.
DarkDimius Apr 18, 2016
806a029
Constructors: do not lift static val initialisation into constructors.
DarkDimius Apr 18, 2016
db6a07d
Getters: do not generate getters for static vals
DarkDimius Apr 18, 2016
4ce8ab0
Allow creating static initialisers.
DarkDimius Apr 18, 2016
27846bb
MoveStatic: Move static methods & fields into companion class
DarkDimius Apr 18, 2016
63eb88d
Drop support for @static lazy vals.
DarkDimius Apr 18, 2016
ce2b964
Fix type in SymDenotations.
DarkDimius Apr 18, 2016
7cacd0f
Fix #1224: static members do not override\implement parent symbols.
DarkDimius Apr 18, 2016
1932b17
Test #1224.
DarkDimius Apr 18, 2016
f650b1e
LazyVals: do not share offsets between companions.
DarkDimius Apr 20, 2016
61fe99b
MoveStatics: fix two bugs.
DarkDimius Apr 20, 2016
d9702d2
Fix Ycheck: allow assigning fields in static constructors.
DarkDimius Apr 20, 2016
e1fcb4c
LazyVals: support debug mode.
DarkDimius Apr 20, 2016
990e962
SymDenotations: fix comment.
DarkDimius Apr 20, 2016
f2cfac5
MoveStatics: survive absence of companions.
DarkDimius Apr 20, 2016
fa6deee
DottyBackendInterface: fix a bug in methodSymbols.
DarkDimius Apr 20, 2016
5f73175
Add tests that were used to reproduce issues with LazyVals.
DarkDimius Apr 20, 2016
49ace48
MoveStatics: fix a bug.
DarkDimius Apr 20, 2016
2c6a9be
CheckStatic: report error position in case of disallowed override
DarkDimius Apr 20, 2016
9899a06
LazyVals: fix leftover moduleClass usage.
DarkDimius Apr 20, 2016
c428e74
LazyVals: do even more verbose debugging.
DarkDimius Jun 7, 2016
de45fa1
MoveStatics: Fix classes without companion not getting static <clinit>
DarkDimius Jun 7, 2016
3c93c5c
Make class initialisers private. Otherwise they break GenBCode.
DarkDimius Jun 7, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions src/dotty/runtime/LazyVals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@ object LazyVals {

final val BITS_PER_LAZY_VAL = 2L
final val LAZY_VAL_MASK = 3L
final val debug = false

@inline def STATE(cur: Long, ord: Int) = (cur >> (ord * BITS_PER_LAZY_VAL)) & LAZY_VAL_MASK
@inline def STATE(cur: Long, ord: Int) = {
val r = (cur >> (ord * BITS_PER_LAZY_VAL)) & LAZY_VAL_MASK
if (debug)
println(s"STATE($cur, $ord) = $r")
r
}
@inline def CAS(t: Object, offset: Long, e: Long, v: Int, ord: Int) = {
if (debug)
println(s"CAS($t, $offset, $e, $v, $ord)")
val mask = ~(LAZY_VAL_MASK << ord * BITS_PER_LAZY_VAL)
val n = (e & mask) | (v << (ord * BITS_PER_LAZY_VAL))
compareAndSet(t, offset, e, n)
}
@inline def setFlag(t: Object, offset: Long, v: Int, ord: Int) = {
if (debug)
println(s"setFlag($t, $offset, $v, $ord)")
var retry = true
while (retry) {
val cur = get(t, offset)
Expand All @@ -35,6 +45,8 @@ object LazyVals {
}
}
@inline def wait4Notification(t: Object, offset: Long, cur: Long, ord: Int) = {
if (debug)
println(s"wait4Notification($t, $offset, $cur, $ord)")
var retry = true
while (retry) {
val cur = get(t, offset)
Expand All @@ -51,7 +63,11 @@ object LazyVals {
}

@inline def compareAndSet(t: Object, off: Long, e: Long, v: Long) = unsafe.compareAndSwapLong(t, off, e, v)
@inline def get(t: Object, off: Long) = unsafe.getLongVolatile(t, off)
@inline def get(t: Object, off: Long) = {
if (debug)
println(s"get($t, $off)")
unsafe.getLongVolatile(t, off)
}

val processors: Int = java.lang.Runtime.getRuntime.availableProcessors()
val base: Int = 8 * processors * processors
Expand All @@ -68,7 +84,12 @@ object LazyVals {
monitors(id)
}

@inline def getOffset(clz: Class[_], name: String) = unsafe.objectFieldOffset(clz.getDeclaredField(name))
@inline def getOffset(clz: Class[_], name: String) = {
val r = unsafe.objectFieldOffset(clz.getDeclaredField(name))
if (debug)
println(s"getOffset($clz, $name) = $r")
r
}

object Names {
final val state = "STATE"
Expand Down
11 changes: 7 additions & 4 deletions src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import scala.collection.generic.Clearable
import scala.collection.mutable
import scala.reflect.ClassTag
import scala.reflect.internal.util.WeakHashSet
import scala.reflect.io.{Directory, PlainDirectory, AbstractFile}
import scala.reflect.io.{AbstractFile, Directory, PlainDirectory}
import scala.tools.asm.{AnnotationVisitor, ClassVisitor, FieldVisitor, MethodVisitor}
import scala.tools.nsc.backend.jvm.{BCodeHelpers, BackendInterface}
import dotty.tools.dotc.core._
Expand All @@ -24,13 +24,16 @@ import Symbols._
import Denotations._
import Phases._
import java.lang.AssertionError
import dotty.tools.dotc.util.{Positions, DotClass}

import dotty.tools.dotc.util.{DotClass, Positions}
import Decorators._
import tpd._

import scala.tools.asm
import NameOps._
import StdNames.nme
import NameOps._
import dotty.tools.dotc.core

class DottyBackendInterface(outputDirectory: AbstractFile)(implicit ctx: Context) extends BackendInterface{
type Symbol = Symbols.Symbol
Expand Down Expand Up @@ -633,7 +636,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile)(implicit ctx: Context
toDenot(sym)(shiftedContext).isStatic(shiftedContext)
}

def isStaticConstructor: Boolean = isStaticMember && isClassConstructor
def isStaticConstructor: Boolean = (isStaticMember && isClassConstructor) || (sym.name eq core.Names.STATIC_CONSTRUCTOR)


// navigation
Expand Down Expand Up @@ -716,7 +719,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile)(implicit ctx: Context
toDenot(sym).info.decls.filter(p => p.isTerm && !p.is(Flags.Method)).toList
}
def methodSymbols: List[Symbol] =
for (f <- toDenot(sym).info.decls.toList if !f.isMethod && f.isTerm && !f.isModule) yield f
for (f <- toDenot(sym).info.decls.toList if f.isMethod && f.isTerm && !f.isModule) yield f
def serialVUID: Option[Long] = None


Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Compiler {
new RestoreScopes), // Repair scopes rendered invalid by moving definitions in prior phases of the group
List(new ExpandPrivate, // Widen private definitions accessed from nested classes
new CollectEntryPoints, // Find classes with main methods
new MoveStatics, // Move static methods to companion classes
new LabelDefs), // Converts calls to labels to jumps
List(new GenSJSIR), // Generate .js code
List(new GenBCode) // Generate JVM bytecode
Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ object NameOps {
(if (name.isTermName) n.toTermName else n.toTypeName).asInstanceOf[N]

def isConstructorName = name == CONSTRUCTOR || name == TRAIT_CONSTRUCTOR
def isStaticConstructorName = name == STATIC_CONSTRUCTOR
def isExceptionResultName = name startsWith EXCEPTION_RESULT_PREFIX
def isImplClassName = name endsWith IMPL_CLASS_SUFFIX
def isLocalDummyName = name startsWith LOCALDUMMY_PREFIX
Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ object Names {

// can't move CONSTRUCTOR/EMPTY_PACKAGE to `nme` because of bootstrap failures in `encode`.
val CONSTRUCTOR = termName("<init>")
val STATIC_CONSTRUCTOR = termName("<clinit>")
val EMPTY_PACKAGE = termName("<empty>")

val dontEncode = Set(CONSTRUCTOR, EMPTY_PACKAGE)
Expand Down
8 changes: 6 additions & 2 deletions src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,10 @@ object SymDenotations {
final def isPrimaryConstructor(implicit ctx: Context) =
isConstructor && owner.primaryConstructor == symbol

/** Does this symbol denote the static constructor of its enclosing class? */
final def isStaticConstructor(implicit ctx: Context) =
name.isStaticConstructorName

/** Is this a subclass of the given class `base`? */
def isSubClass(base: Symbol)(implicit ctx: Context) = false

Expand Down Expand Up @@ -1001,7 +1005,7 @@ object SymDenotations {
if (!canMatchInheritedSymbols) Iterator.empty
else overriddenFromType(owner.info)

/** Returns all all matching symbols defined in parents of the selftype. */
/** Returns all matching symbols defined in parents of the selftype. */
final def extendedOverriddenSymbols(implicit ctx: Context): Iterator[Symbol] =
if (!canMatchInheritedSymbols) Iterator.empty
else overriddenFromType(owner.asClass.classInfo.selfType)
Expand Down Expand Up @@ -1499,7 +1503,7 @@ object SymDenotations {

/** Enter a symbol in given `scope` without potentially replacing the old copy. */
def enterNoReplace(sym: Symbol, scope: MutableScope)(implicit ctx: Context): Unit = {
require((sym.denot.flagsUNSAFE is Private) || !(this is Frozen) || (scope ne this.unforcedDecls))
require((sym.denot.flagsUNSAFE is Private) || !(this is Frozen) || (scope ne this.unforcedDecls) || sym.hasAnnotation(defn.ScalaStaticAnnot))
scope.enter(sym)

if (myMemberFingerPrint != FingerPrint.unknown)
Expand Down
20 changes: 11 additions & 9 deletions src/dotty/tools/dotc/transform/CheckStatic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import TypeUtils._
* is not allowed to inherit classes that define a term member with name `foo`.
* 5. Only `@static` methods and vals are supported in companions of traits.
* Java8 supports those, but not vars, and JavaScript does not have interfaces at all.
* 6. `@static` Lazy vals are currently unsupported.
*/
class CheckStatic extends MiniPhaseTransform { thisTransformer =>
import ast.tpd._
Expand All @@ -57,17 +58,18 @@ class CheckStatic extends MiniPhaseTransform { thisTransformer =>
}

val companion = ctx.owner.companionClass
if (!companion.exists) {
ctx.error("object that conatin @static members should have companion class", defn.pos)
}
def clashes = companion.asClass.membersNamed(defn.name)

val clashes = companion.asClass.membersNamed(defn.name)
if (clashes.exists) {
if (!companion.exists) {
ctx.error("object that contains @static members should have companion class", defn.pos)
} else if (clashes.exists) {
ctx.error("companion classes cannot define members with same name as @static member", defn.pos)
}

if (defn.symbol.is(Flags.Mutable) && companion.is(Flags.Trait)) {
ctx.error("Companions of traits cannot define mutable @static fields")
} else if (defn.symbol.is(Flags.Mutable) && companion.is(Flags.Trait)) {
ctx.error("Companions of traits cannot define mutable @static fields", defn.pos)
} else if (defn.symbol.is(Flags.Lazy)) {
ctx.error("Lazy @static fields are not supported", defn.pos)
} else if (defn.symbol.allOverriddenSymbols.nonEmpty) {
ctx.error("@static members cannot override or implement non-static ones", defn.pos)
}
} else hadNonStaticField = hadNonStaticField || defn.isInstanceOf[ValDef]

Expand Down
4 changes: 2 additions & 2 deletions src/dotty/tools/dotc/transform/Constructors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
*/
override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = {
tree match {
case tree: ValDef if tree.symbol.exists && tree.symbol.owner.isClass && !tree.symbol.is(Lazy) =>
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) =>
assert(!tree.rhs.isEmpty, i"unimplemented: $tree")
Expand Down Expand Up @@ -181,7 +181,7 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
def splitStats(stats: List[Tree]): Unit = stats match {
case stat :: stats1 =>
stat match {
case stat @ ValDef(name, tpt, _) if !stat.symbol.is(Lazy) =>
case stat @ ValDef(name, tpt, _) if !stat.symbol.is(Lazy) && !stat.symbol.hasAnnotation(defn.ScalaStaticAnnot) =>
val sym = stat.symbol
if (isRetained(sym)) {
if (!stat.rhs.isEmpty && !isWildcardArg(stat.rhs))
Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/transform/Getters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Getters extends MiniPhaseTransform with SymTransformer { thisTransform =>
d.is(NoGetterNeeded) ||
d.initial.asInstanceOf[SymDenotation].is(PrivateLocal) && !d.owner.is(Trait) && !isDerivedValueClass(d.owner) && !d.is(Flags.Lazy) ||
d.is(Module) && d.isStatic ||
d.hasAnnotation(defn.ScalaStaticAnnot) ||
d.isSelfSym
if (d.isTerm && (d.is(Lazy) || d.owner.isClass) && d.info.isValueType && !noGetterNeeded) {
val maybeStable = if (d.isStable) Stable else EmptyFlags
Expand Down
21 changes: 9 additions & 12 deletions src/dotty/tools/dotc/transform/LazyVals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import dotty.tools.dotc.core.SymDenotations.SymDenotation
import dotty.tools.dotc.core.DenotTransformers.{SymTransformer, IdentityDenotTransformer, DenotTransformer}
import Erasure.Boxing.adaptToType

class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer with NeedsCompanions {
class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer {
import LazyVals._

import tpd._
Expand All @@ -49,11 +49,6 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer with Nee
* before this phase starts processing same tree */
override def runsAfter = Set(classOf[Mixin])

def isCompanionNeeded(cls: ClassSymbol)(implicit ctx: Context): Boolean = {
def hasLazyVal(cls: ClassSymbol) = cls.info.decls.exists(_.is(Flags.Lazy))
hasLazyVal(cls) || cls.mixins.exists(hasLazyVal)
}

override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree =
transformLazyVal(tree)

Expand Down Expand Up @@ -341,26 +336,28 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer with Nee
val tpe = x.tpe.widen.resultType.widen
val claz = x.symbol.owner.asClass
val thizClass = Literal(Constant(claz.info))
val companion = claz.companionModule
val helperModule = ctx.requiredModule("dotty.runtime.LazyVals")
val getOffset = Select(ref(helperModule), lazyNme.RLazyVals.getOffset)
var offsetSymbol: TermSymbol = null
var flag: Tree = EmptyTree
var ord = 0

def offsetName(id: Int) = (StdNames.nme.LAZY_FIELD_OFFSET + (if(x.symbol.owner.is(Flags.Module)) "_m_" else "") + id.toString).toTermName

// compute or create appropriate offsetSymol, bitmap and bits used by current ValDef
appendOffsetDefs.get(companion.moduleClass) match {
appendOffsetDefs.get(claz) match {
case Some(info) =>
val flagsPerLong = (64 / dotty.runtime.LazyVals.BITS_PER_LAZY_VAL).toInt
info.ord += 1
ord = info.ord % flagsPerLong
val id = info.ord / flagsPerLong
val offsetById = offsetName(id)
if (ord != 0) { // there are unused bits in already existing flag
offsetSymbol = companion.moduleClass.info.decl((StdNames.nme.LAZY_FIELD_OFFSET + id.toString).toTermName)
offsetSymbol = claz.info.decl(offsetById)
.suchThat(sym => (sym is Flags.Synthetic) && sym.isTerm)
.symbol.asTerm
} else { // need to create a new flag
offsetSymbol = ctx.newSymbol(companion.moduleClass, (StdNames.nme.LAZY_FIELD_OFFSET + id.toString).toTermName, Flags.Synthetic, defn.LongType).enteredAfter(this)
offsetSymbol = ctx.newSymbol(claz, offsetById, Flags.Synthetic, defn.LongType).enteredAfter(this)
offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot))
val flagName = (StdNames.nme.BITMAP_PREFIX + id.toString).toTermName
val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this)
Expand All @@ -370,13 +367,13 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer with Nee
}

case None =>
offsetSymbol = ctx.newSymbol(companion.moduleClass, (StdNames.nme.LAZY_FIELD_OFFSET + "0").toTermName, Flags.Synthetic, defn.LongType).enteredAfter(this)
offsetSymbol = ctx.newSymbol(claz, offsetName(0), Flags.Synthetic, defn.LongType).enteredAfter(this)
offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot))
val flagName = (StdNames.nme.BITMAP_PREFIX + "0").toTermName
val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this)
flag = ValDef(flagSymbol, Literal(Constants.Constant(0L)))
val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thizClass, Literal(Constant(flagName.toString))))
appendOffsetDefs += (companion.moduleClass -> new OffsetInfo(List(offsetTree), ord))
appendOffsetDefs += (claz -> new OffsetInfo(List(offsetTree), ord))
}

val containerName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName
Expand Down
77 changes: 77 additions & 0 deletions src/dotty/tools/dotc/transform/MoveStatics.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package dotty.tools.dotc.transform

import dotty.tools.dotc.ast.{Trees, tpd}
import dotty.tools.dotc.core.Annotations.Annotation
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.DenotTransformers.{InfoTransformer, SymTransformer}
import dotty.tools.dotc.core.SymDenotations.SymDenotation
import dotty.tools.dotc.core.Decorators._
import dotty.tools.dotc.core.NameOps._
import dotty.tools.dotc.core.{Flags, Names}
import dotty.tools.dotc.core.Names.Name
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.core.Types.MethodType
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo}

/** Move static methods from companion to the class itself */
class MoveStatics extends MiniPhaseTransform with SymTransformer { thisTransformer =>

import tpd._
override def phaseName = "moveStatic"


def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = {
if (sym.hasAnnotation(defn.ScalaStaticAnnot) && sym.owner.is(Flags.Module) && sym.owner.companionClass.exists) {
sym.owner.asClass.delete(sym.symbol)
sym.owner.companionClass.asClass.enter(sym.symbol)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you need to remove / unlink the symbol from the module's scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm kind of intentionally not doing so.
To make sure it can be found in both places.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, i don't know if that's a good idea (literally - maybe it is..)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note here we're already near the backend, so I'm preparing the tree specifically for GenBCode.
Phases that go in this block are backend-specific and break some assumptions of compiler.
Eg LabelDefs reorders <label> defs in a magical order that no one should touch.

Copy link
Member

@lrytz lrytz Apr 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it's related to exactly that discussion: the current PR creates a static field in both T and T$

import annotation.static
class T
object T {
  @static val x = 99
}
public class T {
  public static I x
}
public final class T$ {
  public static I x
}

the one in the module class is not initialized.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magical

✨ :)

val flags = if (sym.is(Flags.Method)) sym.flags else sym.flags | Flags.Mutable
sym.copySymDenotation(owner = sym.owner.companionClass, initFlags = flags)
}
else sym
}

override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = {
if (ctx.owner.is(Flags.Package)) {
val (classes, others) = trees.partition(x => x.isInstanceOf[TypeDef] && x.symbol.isClass)
val pairs = classes.groupBy(_.symbol.name.stripModuleClassSuffix).asInstanceOf[Map[Name, List[TypeDef]]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need the cast?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static type is Map[Name, List[Tree]].

Copy link
Member

@lrytz lrytz Apr 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh i see now -- since you don't use others, you can write val classes = trees collect { case td: TypeDef if td.symbol.isClass => td } and don't need the cast


def rebuild(orig: TypeDef, newBody: List[Tree]): Tree = {
if (orig eq null) return EmptyTree

val staticFields = newBody.filter(x => x.isInstanceOf[ValDef] && x.symbol.hasAnnotation(defn.ScalaStaticAnnot)).asInstanceOf[List[ValDef]]
val newBodyWithStaticConstr =
if (staticFields.nonEmpty) {
/* do NOT put Flags.JavaStatic here. It breaks .enclosingClass */
val staticCostructor = ctx.newSymbol(orig.symbol, Names.STATIC_CONSTRUCTOR, Flags.Synthetic | Flags.Method | Flags.Private, MethodType(Nil, defn.UnitType))
staticCostructor.addAnnotation(Annotation(defn.ScalaStaticAnnot))
staticCostructor.entered

val staticAssigns = staticFields.map(x => Assign(ref(x.symbol), x.rhs.changeOwner(x.symbol, staticCostructor)))
tpd.DefDef(staticCostructor, Block(staticAssigns, tpd.unitLiteral)) :: newBody
} else newBody

val oldTemplate = orig.rhs.asInstanceOf[Template]
cpy.TypeDef(orig)(rhs = cpy.Template(orig.rhs)(oldTemplate.constr, oldTemplate.parents, oldTemplate.self, newBodyWithStaticConstr))
}

def move(module: TypeDef, companion: TypeDef): List[Tree] = {
if (!module.symbol.is(Flags.Module)) move(companion, module)
else {
val allMembers =
(if(companion ne null) {companion.rhs.asInstanceOf[Template].body} else Nil) ++
module.rhs.asInstanceOf[Template].body
val (newModuleBody, newCompanionBody) = allMembers.partition(x => {assert(x.symbol.exists); x.symbol.owner == module.symbol})
Trees.flatten(rebuild(companion, newCompanionBody) :: rebuild(module, newModuleBody) :: Nil)
}
}
val newPairs =
for ((name, classes) <- pairs)
yield
if (classes.tail.isEmpty)
if (classes.head.symbol.is(Flags.Module)) move(classes.head, null)
else List(rebuild(classes.head, classes.head.rhs.asInstanceOf[Template].body))
else move(classes.head, classes.tail.head)
Trees.flatten(newPairs.toList.flatten ++ others)
} else trees
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe invert the condition and move this case up -- makes it easier to follow

}
}
5 changes: 4 additions & 1 deletion src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import collection.mutable
import ProtoTypes._
import config.Printers
import java.lang.AssertionError

import dotty.tools.dotc.core.Names

import scala.util.control.NonFatal

/** Run by -Ycheck option after a given phase, this class retypes all syntax trees
Expand Down Expand Up @@ -382,7 +385,7 @@ class TreeChecker extends Phase with SymTransformer {
override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) =
withDefinedSyms(ddef.tparams) {
withDefinedSymss(ddef.vparamss) {
if (!sym.isClassConstructor) assert(isValidJVMMethodName(sym.name), s"${sym.fullName} name is invalid on jvm")
if (!sym.isClassConstructor && !(sym.name eq Names.STATIC_CONSTRUCTOR)) assert(isValidJVMMethodName(sym.name), s"${sym.fullName} name is invalid on jvm")
val tpdTree = super.typedDefDef(ddef, sym)
assert(isMethodType(sym.info), i"wrong type, expect a method type for ${sym.fullName}, but found: ${sym.info}")
tpdTree
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
def canAssign(sym: Symbol) = // allow assignments from the primary constructor to class fields
sym.is(Mutable, butNot = Accessor) ||
ctx.owner.isPrimaryConstructor && !sym.is(Method) && sym.owner == ctx.owner.owner ||
ctx.owner.name.isTraitSetterName
ctx.owner.name.isTraitSetterName || ctx.owner.isStaticConstructor
lhsCore.tpe match {
case ref: TermRef if canAssign(ref.symbol) =>
assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, ref.info)))
Expand Down
Loading