summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
authorAleksandar Prokopec <axel22@gmail.com>2012-07-10 20:54:23 +0200
committerAleksandar Prokopec <axel22@gmail.com>2012-07-18 17:11:59 +0200
commit892ee3df93a10ffe24fb11b37ad7c3a9cb93d5de (patch)
treebad301c983fb868fbaf696fddd2afbc4c11a82a0 /src/compiler
parent79161ec9e2c8511f3b6d22c0e2f5b96da9565144 (diff)
downloadscala-892ee3df93a10ffe24fb11b37ad7c3a9cb93d5de.tar.gz
scala-892ee3df93a10ffe24fb11b37ad7c3a9cb93d5de.tar.bz2
scala-892ee3df93a10ffe24fb11b37ad7c3a9cb93d5de.zip
Implement @static annotation on singleton object fields.
This commit introduces the `@static` annotation on fields of static singleton objects. A static singleton object is a top-level singleton object or an object nested within a static singleton object. The field annotated with `@static` is generated as a true static field in the companion class of the object. The initialization of the field is done in the static initializer of the companion class, instead of the object itself. Here's an example. This: object Foo { @static val FIELD = 123 } class Foo generates : object Foo { def FIELD(): Int = Foo.FIELD } class Foo { <static> val FIELD = 123 } The accessor in `object Foo` is changed to return the static field in `class Foo` (the companion class is generated if it is not already present, and the same is done for getters if `FIELD` is a `var`). Furthermore, all the callsites to accessor `Foo.FIELD` are rewritten to access the static field directly. It is illegal to annotate a field in the singleton object as `@static` if there is already a member of the same name in `class Foo`. This allows better Java interoperability with frameworks which rely on static fields being present in the class. The `AtomicReferenceFieldUpdater`s are one such example. Instead of having a Java base class holding the `volatile` mutable field `f` and a static field containing a reference to the `AtomicReferenceFieldUpdater` that mutates `f` atomically, and extending this Java base class from Scala, we can now simply do: object Foo { @static val arfu = AtomicReferenceUpdater.newUpdater( classOf[Foo], classOf[String], "f" ) } class Foo { @volatile var f = null import Foo._ def CAS(ov: String, nv: String) = arfu.compareAndSet(this, null, "") } In summary, this commit introduces the following: - adds the `@static` annotation - for objects without a companion class and `@static` members, creates a companion class (this is done in `CleanUp`) - checks for conflicting names and if `@static` is used on static singleton object member - creates the `static` field in the companion class for `@static` members - moves the field initialization from the companion object to the static initializer of the class (the initialization of `@static` members is bypassed in the `Constructors` phase, and added to static ctors in `CleanUp`) - all callsites to the accessors of `@static` are rewritten to access the static fields directly (this is done in `GenICode`) - getters and setters to `@static` fields access the static field directly, and the field in the singleton object is not emitted (this is done in `GenICode`) - changes in `GenJVM`, `GenASM` - now computing local var indices in static initializers as well - this was an oversight before, now is necessary Future work: allow the `@static` annotation on methods as well - this will be added in a future commit. Review by @odersky, @dragos, @paulp, @heathermiller.
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala83
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala5
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala12
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala138
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala7
5 files changed, 208 insertions, 37 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
index b638745327..9ec212b084 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
@@ -113,26 +113,42 @@ abstract class GenICode extends SubComponent {
m.native = m.symbol.hasAnnotation(definitions.NativeAttr)
if (!m.isAbstractMethod && !m.native) {
- ctx1 = genLoad(rhs, ctx1, m.returnType);
-
- // reverse the order of the local variables, to match the source-order
- m.locals = m.locals.reverse
-
- rhs match {
- case Block(_, Return(_)) => ()
- case Return(_) => ()
- case EmptyTree =>
- globalError("Concrete method has no definition: " + tree + (
- if (settings.debug.value) "(found: " + m.symbol.owner.info.decls.toList.mkString(", ") + ")"
- else "")
- )
- case _ => if (ctx1.bb.isEmpty)
- ctx1.bb.closeWith(RETURN(m.returnType), rhs.pos)
- else
+ if (m.symbol.isAccessor && m.symbol.accessed.hasStaticAnnotation) {
+ // in companion object accessors to @static fields, we access the static field directly
+ val hostClass = m.symbol.owner.companionClass
+ val staticfield = hostClass.info.decls.find(_.name.toString.trim == m.symbol.accessed.name.toString.trim)
+
+ if (m.symbol.isGetter) {
+ ctx1.bb.emit(LOAD_FIELD(staticfield.get, true) setHostClass hostClass, tree.pos)
ctx1.bb.closeWith(RETURN(m.returnType))
+ } else if (m.symbol.isSetter) {
+ ctx1.bb.emit(LOAD_LOCAL(m.locals.head), tree.pos)
+ ctx1.bb.emit(STORE_FIELD(staticfield.get, true), tree.pos)
+ ctx1.bb.closeWith(RETURN(m.returnType))
+ } else assert(false, "unreachable")
+ } else {
+ ctx1 = genLoad(rhs, ctx1, m.returnType);
+
+ // reverse the order of the local variables, to match the source-order
+ m.locals = m.locals.reverse
+
+ rhs match {
+ case Block(_, Return(_)) => ()
+ case Return(_) => ()
+ case EmptyTree =>
+ globalError("Concrete method has no definition: " + tree + (
+ if (settings.debug.value) "(found: " + m.symbol.owner.info.decls.toList.mkString(", ") + ")"
+ else "")
+ )
+ case _ =>
+ if (ctx1.bb.isEmpty)
+ ctx1.bb.closeWith(RETURN(m.returnType), rhs.pos)
+ else
+ ctx1.bb.closeWith(RETURN(m.returnType))
+ }
+ if (!ctx1.bb.closed) ctx1.bb.close
+ prune(ctx1.method)
}
- if (!ctx1.bb.closed) ctx1.bb.close
- prune(ctx1.method)
} else
ctx1.method.setCode(NoCode)
ctx1
@@ -854,9 +870,32 @@ abstract class GenICode extends SubComponent {
generatedType = toTypeKind(fun.symbol.tpe.resultType)
ctx1
+ case app @ Apply(fun @ Select(qual, _), args)
+ if !ctx.method.symbol.isStaticConstructor
+ && fun.symbol.isAccessor && fun.symbol.accessed.hasStaticAnnotation =>
+ // bypass the accessor to the companion object and load the static field directly
+ // the only place were this bypass is not done, is the static intializer for the static field itself
+ val sym = fun.symbol
+ generatedType = toTypeKind(sym.accessed.info)
+ val hostClass = qual.tpe.typeSymbol.orElse(sym.owner).companionClass
+ val staticfield = hostClass.info.decls.find(_.name.toString.trim == sym.accessed.name.toString.trim)
+
+ if (sym.isGetter) {
+ ctx.bb.emit(LOAD_FIELD(staticfield.get, true) setHostClass hostClass, tree.pos)
+ ctx
+ } else if (sym.isSetter) {
+ val ctx1 = genLoadArguments(args, sym.info.paramTypes, ctx)
+ ctx1.bb.emit(STORE_FIELD(staticfield.get, true), tree.pos)
+ ctx1.bb.emit(CONSTANT(Constant(false)), tree.pos)
+ ctx1
+ } else {
+ assert(false, "supposedly unreachable")
+ ctx
+ }
+
case app @ Apply(fun, args) =>
val sym = fun.symbol
-
+
if (sym.isLabel) { // jump to a label
val label = ctx.labels.getOrElse(sym, {
// it is a forward jump, scan for labels
@@ -1623,8 +1662,12 @@ abstract class GenICode extends SubComponent {
* backend emits them as static).
* No code is needed for this module symbol.
*/
- for (f <- cls.info.decls ; if !f.isMethod && f.isTerm && !f.isModule)
+ for (
+ f <- cls.info.decls;
+ if !f.isMethod && f.isTerm && !f.isModule && !(f.owner.isModuleClass && f.hasStaticAnnotation)
+ ) {
ctx.clazz addField new IField(f)
+ }
}
/**
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
index 756d90bc53..ade99267f8 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
@@ -1170,7 +1170,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters {
log("No forwarder for " + m + " due to conflict with " + linkedClass.info.member(m.name))
else {
log("Adding static forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass))
- addForwarder(isRemoteClass, jclass, moduleClass, m)
+ if (m.isAccessor && m.accessed.hasStaticAnnotation) {
+ log("@static: accessor " + m + ", accessed: " + m.accessed)
+ } else addForwarder(isRemoteClass, jclass, moduleClass, m)
}
}
}
@@ -1675,6 +1677,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters {
jmethod = clinitMethod
jMethodName = CLASS_CONSTRUCTOR_NAME
jmethod.visitCode()
+ computeLocalVarsIndex(m)
genCode(m, false, true)
jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments
jmethod.visitEnd()
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala
index 912a5b0e90..0ae2adac84 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala
@@ -1021,6 +1021,8 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with
method = m
jmethod = clinitMethod
+
+ computeLocalVarsIndex(m)
genCode(m)
case None =>
legacyStaticInitializer(cls, clinit)
@@ -1114,7 +1116,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with
linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name } toSet
}
debuglog("Potentially conflicting names for forwarders: " + conflictingNames)
-
+
for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) {
if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor)
debuglog("No forwarder for '%s' from %s to '%s'".format(m, className, moduleClass))
@@ -1122,7 +1124,9 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with
log("No forwarder for " + m + " due to conflict with " + linkedClass.info.member(m.name))
else {
log("Adding static forwarder for '%s' from %s to '%s'".format(m, className, moduleClass))
- addForwarder(jclass, moduleClass, m)
+ if (m.isAccessor && m.accessed.hasStaticAnnotation) {
+ log("@static: accessor " + m + ", accessed: " + m.accessed)
+ } else addForwarder(jclass, moduleClass, m)
}
}
}
@@ -1304,7 +1308,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with
jclass.getType())
}
}
-
+
style match {
case Static(true) => dbg("invokespecial"); jcode.emitINVOKESPECIAL(jowner, jname, jtype)
case Static(false) => dbg("invokestatic"); jcode.emitINVOKESTATIC(jowner, jname, jtype)
@@ -1885,7 +1889,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with
*/
def computeLocalVarsIndex(m: IMethod) {
var idx = if (m.symbol.isStaticMember) 0 else 1;
-
+
for (l <- m.params) {
debuglog("Index value for " + l + "{" + l.## + "}: " + idx)
l.index = idx
diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
index bbdf10a021..1d8d58ccf7 100644
--- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala
+++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
@@ -23,9 +23,12 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
new CleanUpTransformer(unit)
class CleanUpTransformer(unit: CompilationUnit) extends Transformer {
- private val newStaticMembers = mutable.Buffer.empty[Tree]
- private val newStaticInits = mutable.Buffer.empty[Tree]
- private val symbolsStoredAsStatic = mutable.Map.empty[String, Symbol]
+ private val newStaticMembers = mutable.Buffer.empty[Tree]
+ private val newStaticInits = mutable.Buffer.empty[Tree]
+ private val symbolsStoredAsStatic = mutable.Map.empty[String, Symbol]
+ private val staticBodies = mutable.Map.empty[(Symbol, Symbol), Tree]
+ private val syntheticClasses = mutable.Map.empty[Symbol, mutable.Set[Tree]] // package and trees
+ private val classNames = mutable.Map.empty[Symbol, Set[Name]]
private def clearStatics() {
newStaticMembers.clear()
newStaticInits.clear()
@@ -45,15 +48,16 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
result
}
private def transformTemplate(tree: Tree) = {
- val Template(parents, self, body) = tree
+ val t @ Template(parents, self, body) = tree
clearStatics()
+
val newBody = transformTrees(body)
val templ = deriveTemplate(tree)(_ => transformTrees(newStaticMembers.toList) ::: newBody)
try addStaticInits(templ) // postprocess to include static ctors
finally clearStatics()
}
private def mkTerm(prefix: String): TermName = unit.freshTermName(prefix)
-
+
/** Kludge to provide a safe fix for #4560:
* If we generate a reference in an implementation class, we
* watch out for embedded This(..) nodes that point to the interface.
@@ -555,7 +559,62 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
else tree
}
-
+
+ case ValDef(mods, name, tpt, rhs) if tree.symbol.hasStaticAnnotation =>
+ log("moving @static valdef field: " + name + ", in: " + tree.symbol.owner)
+ val sym = tree.symbol
+ val owner = sym.owner
+
+ val staticBeforeLifting = atPhase(currentRun.erasurePhase) { owner.isStatic }
+ if (!owner.isModuleClass || !staticBeforeLifting) {
+ if (!sym.isSynthetic) {
+ reporter.error(tree.pos, "Only members of top-level objects and their nested objects can be annotated with @static.")
+ tree.symbol.removeAnnotation(StaticClass)
+ }
+ super.transform(tree)
+ } else if (owner.isModuleClass) {
+ val linkedClass = owner.companionClass match {
+ case NoSymbol =>
+ // create the companion class if it does not exist
+ val enclosing = owner.owner
+ val compclass = enclosing.newClass(newTypeName(owner.name.toString))
+ compclass setInfo ClassInfoType(List(ObjectClass.tpe), newScope, compclass)
+ enclosing.info.decls enter compclass
+
+ val compclstree = ClassDef(compclass, NoMods, List(List()), List(List()), List(), tree.pos)
+
+ syntheticClasses.getOrElseUpdate(enclosing, mutable.Set()) += compclstree
+
+ compclass
+ case comp => comp
+ }
+
+ // create a static field in the companion class for this @static field
+ val stfieldSym = linkedClass.newVariable(newTermName(name), tree.pos, STATIC | SYNTHETIC | FINAL) setInfo sym.tpe
+ stfieldSym.addAnnotation(StaticClass)
+
+ val names = classNames.getOrElseUpdate(linkedClass, linkedClass.info.decls.collect {
+ case sym if sym.name.isTermName => sym.name
+ } toSet)
+ if (names(stfieldSym.name)) {
+ reporter.error(
+ tree.pos,
+ "@static annotated field " + tree.symbol.name + " has the same name as a member of class " + linkedClass.name
+ )
+ } else {
+ linkedClass.info.decls enter stfieldSym
+
+ val initializerBody = rhs
+
+ // static field was previously initialized in the companion object itself, like this:
+ // staticBodies((linkedClass, stfieldSym)) = Select(This(owner), sym.getter(owner))
+ // instead, we move the initializer to the static ctor of the companion class
+ // we save the entire ValDef/DefDef to extract the rhs later
+ staticBodies((linkedClass, stfieldSym)) = tree
+ }
+ }
+ super.transform(tree)
+
/* MSIL requires that the stack is empty at the end of a try-block.
* Hence, we here rewrite all try blocks with a result != {Unit, All} such that they
* store their result in a local variable. The catch blocks are adjusted as well.
@@ -665,6 +724,11 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
if (newStaticInits.isEmpty)
template
else {
+ val ctorBody = newStaticInits.toList flatMap {
+ case Block(stats, expr) => stats :+ expr
+ case t => List(t)
+ }
+
val newCtor = findStaticCtor(template) match {
// in case there already were static ctors - augment existing ones
// currently, however, static ctors aren't being generated anywhere else
@@ -673,22 +737,76 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
deriveDefDef(ctor) {
case block @ Block(stats, expr) =>
// need to add inits to existing block
- treeCopy.Block(block, newStaticInits.toList ::: stats, expr)
+ treeCopy.Block(block, ctorBody ::: stats, expr)
case term: TermTree =>
// need to create a new block with inits and the old term
- treeCopy.Block(term, newStaticInits.toList, term)
+ treeCopy.Block(term, ctorBody, term)
}
case _ =>
// create new static ctor
val staticCtorSym = currentClass.newStaticConstructor(template.pos)
- val rhs = Block(newStaticInits.toList, Literal(Constant(())))
+ val rhs = Block(ctorBody, Literal(Constant(())))
localTyper.typedPos(template.pos)(DefDef(staticCtorSym, rhs))
}
deriveTemplate(template)(newCtor :: _)
}
}
-
+
+ private def addStaticDeclarations(tree: Template, clazz: Symbol) {
+ // add static field initializer statements for each static field in clazz
+ if (!clazz.isModuleClass) for {
+ staticSym <- clazz.info.decls
+ if staticSym.hasStaticAnnotation
+ } staticSym match {
+ case stfieldSym if stfieldSym.isVariable =>
+ val valdef = staticBodies((clazz, stfieldSym))
+ val ValDef(_, _, _, rhs) = valdef
+ val fixedrhs = rhs.changeOwner((valdef.symbol, clazz.info.decl(nme.CONSTRUCTOR)))
+
+ val stfieldDef = localTyper.typedPos(tree.pos)(VAL(stfieldSym) === EmptyTree)
+ val flattenedInit = fixedrhs match {
+ case Block(stats, expr) => Block(stats, safeREF(stfieldSym) === expr)
+ case rhs => safeREF(stfieldSym) === rhs
+ }
+ val stfieldInit = localTyper.typedPos(tree.pos)(flattenedInit)
+
+ // add field definition to new defs
+ newStaticMembers append stfieldDef
+ newStaticInits append stfieldInit
+ }
+ }
+
+
+
+ override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
+ super.transformStats(stats, exprOwner) ++ {
+ // flush pending synthetic classes created in this owner
+ val synthclassdefs = syntheticClasses.get(exprOwner).toList.flatten
+ syntheticClasses -= exprOwner
+ synthclassdefs map {
+ cdef => localTyper.typedPos(cdef.pos)(cdef)
+ }
+ } map {
+ case clsdef @ ClassDef(mods, name, tparams, t @ Template(parent, self, body)) =>
+ // process all classes in the package again to add static initializers
+ clearStatics()
+
+ addStaticDeclarations(t, clsdef.symbol)
+
+ val templ = deriveTemplate(t)(_ => transformTrees(newStaticMembers.toList) ::: body)
+ val ntempl =
+ try addStaticInits(templ)
+ finally clearStatics()
+
+ val derived = deriveClassDef(clsdef)(_ => ntempl)
+ classNames.remove(clsdef.symbol)
+ derived
+
+ case stat => stat
+ }
+ }
+
} // CleanUpTransformer
}
diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala
index e5119eac71..70bd0bd21b 100644
--- a/src/compiler/scala/tools/nsc/transform/Constructors.scala
+++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala
@@ -186,12 +186,15 @@ abstract class Constructors extends Transform with ast.TreeDSL {
// before the superclass constructor call, otherwise it goes after.
// Lazy vals don't get the assignment in the constructor.
if (!stat.symbol.tpe.isInstanceOf[ConstantType]) {
- if (rhs != EmptyTree && !stat.symbol.isLazy) {
+ if (stat.symbol.hasStaticAnnotation) {
+ debuglog("@static annotated field initialization skipped.")
+ defBuf += deriveValDef(stat)(tree => tree)
+ } else if (rhs != EmptyTree && !stat.symbol.isLazy) {
val rhs1 = intoConstructor(stat.symbol, rhs);
(if (canBeMoved(stat)) constrPrefixBuf else constrStatBuf) += mkAssign(
stat.symbol, rhs1)
+ defBuf += deriveValDef(stat)(_ => EmptyTree)
}
- defBuf += deriveValDef(stat)(_ => EmptyTree)
}
case ClassDef(_, _, _, _) =>
// classes are treated recursively, and left in the template