diff options
author | Lukas Rytz <lukas.rytz@epfl.ch> | 2009-06-07 11:13:47 +0000 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@epfl.ch> | 2009-06-07 11:13:47 +0000 |
commit | 8cc477f8b6fd81c45fe30ac454c021a9769911ad (patch) | |
tree | ce574d8e84ea47c453298fe557b49f7d51ddfdf3 | |
parent | a2166dec9d687347a08c9e6a95e07f1a67f0d370 (diff) | |
download | scala-8cc477f8b6fd81c45fe30ac454c021a9769911ad.tar.gz scala-8cc477f8b6fd81c45fe30ac454c021a9769911ad.tar.bz2 scala-8cc477f8b6fd81c45fe30ac454c021a9769911ad.zip |
fixed BeanProperty, added BooleanBeanProperty, ...
fixed BeanProperty, added BooleanBeanProperty, added many tests (#1029,
#1751, #294, #1942, #1782, #1788, #637).
20 files changed, 221 insertions, 84 deletions
diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala index b28cfc859c..ad0efdec11 100644 --- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala +++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala @@ -346,6 +346,7 @@ trait Definitions { lazy val SerializableAttr: Symbol = getClass("scala.serializable") lazy val DeprecatedAttr: Symbol = getClass("scala.deprecated") lazy val BeanPropertyAttr: Symbol = getClass(sn.BeanProperty) + lazy val BooleanBeanPropertyAttr: Symbol = getClass(sn.BooleanBeanProperty) var AnnotationDefaultAttr: Symbol = _ lazy val NativeAttr: Symbol = getClass("scala.native") lazy val VolatileAttr: Symbol = getClass("scala.volatile") diff --git a/src/compiler/scala/tools/nsc/symtab/StdNames.scala b/src/compiler/scala/tools/nsc/symtab/StdNames.scala index ae2f3a39c7..0f60d2796f 100644 --- a/src/compiler/scala/tools/nsc/symtab/StdNames.scala +++ b/src/compiler/scala/tools/nsc/symtab/StdNames.scala @@ -418,6 +418,7 @@ trait StdNames { val ValueType : Name val Serializable : Name val BeanProperty : Name + val BooleanBeanProperty: Name val Delegate : Name val IOOBException: Name // IndexOutOfBoundsException val Code : Name @@ -465,6 +466,7 @@ trait StdNames { final val ValueType = newTermName("System.ValueType") final val Serializable = nme.NOSYMBOL final val BeanProperty = nme.NOSYMBOL + final val BooleanBeanProperty = nme.NOSYMBOL final val Delegate = newTermName("System.MulticastDelegate") final val IOOBException = newTermName("System.IndexOutOfRangeException") final val Code = nme.NOSYMBOL @@ -486,6 +488,7 @@ trait StdNames { private class J2SENames extends JavaNames { final val Serializable = newTermName("java.io.Serializable") final val BeanProperty = newTermName("scala.reflect.BeanProperty") + final val BooleanBeanProperty = newTermName("scala.reflect.BooleanBeanProperty") final val Code = newTermName("scala.reflect.Code") } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index ef2fa32118..157263d9cc 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -372,18 +372,13 @@ trait Namers { self: Analyzer => else mods.flags & ~PRESUPER | STABLE) if (nme.isSetterName(name)) context.error(tree.pos, "Names of vals or vars may not end in `_='") - var getter = owner.newMethod(tree.pos, name).setFlag(accflags) - setPrivateWithin(tree, getter, mods) - getter = enterInScope(getter).asInstanceOf[TermSymbol] - // needs the current context for adding synthetic BeanGetter / -Setter. - // namerOf(getter) has a primaryConstructorContext which cannot be used - // do add definitions to the class - setInfo(getter)(namerOf(getter).getterTypeCompleter(vd, context)) + // .isInstanceOf[..]: probably for (old) IDE hook. is this obsolete? + val getter = enterNewMethod(tree, name, accflags, mods).asInstanceOf[TermSymbol] + setInfo(getter)(namerOf(getter).getterTypeCompleter(vd)) if ((mods.flags & MUTABLE) != 0) { - var setter = owner.newMethod(tree.pos, nme.getterToSetter(name)) - .setFlag(accflags & ~STABLE & ~CASEACCESSOR) - setPrivateWithin(tree, setter, mods) - setter = enterInScope(setter).asInstanceOf[TermSymbol] + val setter = enterNewMethod(tree, nme.getterToSetter(name), + accflags & ~STABLE & ~CASEACCESSOR, + mods).asInstanceOf[TermSymbol] setInfo(setter)(namerOf(setter).setterTypeCompleter(vd)) } tree.symbol = @@ -403,6 +398,7 @@ trait Namers { self: Analyzer => vsym.setLazyAccessor(getter) vsym } else getter + addBeanGetterSetter(vd, getter) } case DefDef(mods, nme.CONSTRUCTOR, tparams, _, _, _) => var sym = owner.newConstructor(tree.pos).setFlag(mods.flags | owner.getFlag(ConstrFlags)) @@ -410,9 +406,7 @@ trait Namers { self: Analyzer => tree.symbol = enterInScope(sym) finishWith(tparams) case DefDef(mods, name, tparams, _, _, _) => - var sym = (owner.newMethod(tree.pos, name)).setFlag(mods.flags) - setPrivateWithin(tree, sym, mods) - tree.symbol = enterInScope(sym) + tree.symbol = enterNewMethod(tree, name, mods.flags, mods) finishWith(tparams) case TypeDef(mods, name, tparams, _) => var flags: Long = mods.flags @@ -444,6 +438,58 @@ trait Namers { self: Analyzer => tree.symbol } + def enterNewMethod(tree: Tree, name: Name, flags: Long, mods: Modifiers) = { + val sym = context.owner.newMethod(tree.pos, name).setFlag(flags) + setPrivateWithin(tree, sym, mods) + enterInScope(sym) + } + + private def addBeanGetterSetter(vd: ValDef, getter: Symbol) { + def isAnn(ann: Tree, demand: String) = ann match { + case Apply(Select(New(Ident(name)), _), _) => + name.toString == demand + case Apply(Select(New(Select(pre, name)), _), _) => + name.toString == demand + case _ => false + } + val ValDef(mods, name, tpt, _) = vd + val hasBP = mods.annotations.exists(isAnn(_, "BeanProperty")) + val hasBoolBP = mods.annotations.exists(isAnn(_, "BooleanBeanProperty")) + if ((hasBP || hasBoolBP) && !forMSIL) { + if (!name(0).isLetter) + context.error(vd.pos, "`BeanProperty' annotation can be applied "+ + "only to fields that start with a letter") + else if (mods hasFlag PRIVATE) + // avoids name clashes with private fields in traits + context.error(vd.pos, "`BeanProperty' annotation can only be applied "+ + "to non-private fields") + else { + val flags = (mods.flags & (DEFERRED | OVERRIDE | STATIC)) | SYNTHETIC + val beanName = name(0).toString.toUpperCase + name.subName(1, name.length) + + val getterName = if (hasBoolBP) "is" + beanName + else "get" + beanName + val getterMods = Modifiers(flags, mods.privateWithin, + mods.annotations map (_.duplicate)) + val beanGetterDef = atPos(vd.pos) { + DefDef(getterMods, getterName, Nil, List(Nil), tpt.duplicate, + if (mods hasFlag DEFERRED) EmptyTree + else Select(This(getter.owner.name), name)) } + enterSyntheticSym(beanGetterDef) + + if (mods hasFlag MUTABLE) { + // can't use "enterSyntheticSym", because the parameter type is not yet + // known. instead, uses the same machinery as for the non-bean setter: + // create and enter the symbol here, add the tree in Typer.addGettterSetter. + val setterName = "set" + beanName + val setter = enterNewMethod(vd, setterName, flags, mods).asInstanceOf[TermSymbol] + setInfo(setter)(namerOf(setter).setterTypeCompleter(vd)) + } + } + } + } + + // --- Lazy Type Assignment -------------------------------------------------- def typeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym => @@ -478,12 +524,10 @@ trait Namers { self: Analyzer => } } - def getterTypeCompleter(vd: ValDef, getterCtx: Context) = mkTypeCompleter(vd) { sym => + def getterTypeCompleter(vd: ValDef) = mkTypeCompleter(vd) { sym => if (settings.debug.value) log("defining " + sym) val tp = typeSig(vd) sym.setInfo(PolyType(List(), tp)) - if (sym.hasAnnotation(BeanPropertyAttr) && sym.owner.isClass && !forMSIL) - addBeanGetterSetter(vd, tp, getterCtx) if (settings.debug.value) log("defined " + sym) validate(sym) } @@ -496,56 +540,6 @@ trait Namers { self: Analyzer => validate(sym) } - private def addBeanGetterSetter(vd: ValDef, tp: Type, getterCtx: Context) { - val ValDef(mods, name, _, _) = vd - val sym = vd.symbol - if (!name(0).isLetter) - context.error(sym.pos, "`BeanProperty' annotation can be applied "+ - "only to fields that start with a letter") - else { - val tmplCtx = getterCtx.nextEnclosing(c => c.scope.toList.contains(sym)) - assert(tmplCtx != NoContext, context) - val tmplNamer = newNamer(tmplCtx) - val flags = (mods.flags & (DEFERRED | OVERRIDE | STATIC)) | SYNTHETIC - val beanName = name(0).toString.toUpperCase + name.subName(1, name.length) - val getterName = if (tp == BooleanClass.tpe) "is" + beanName - else "get" + beanName - val existingGetter = sym.owner.info.decl(getterName) - if (existingGetter != NoSymbol) { - if (!existingGetter.hasFlag(SYNTHETIC)) - context.error(sym.pos, "a defintion of `"+ getterName + - "' already exists in "+ sym.owner) - } else { - val getterMods = Modifiers(flags, mods.privateWithin, - mods.annotations map (_.duplicate)) - val beanGetterDef = atPos(sym.pos) { - DefDef(getterMods, getterName, Nil, List(Nil), TypeTree(tp), - if (mods hasFlag DEFERRED) EmptyTree - else Select(This(sym.owner.name), name)) } - tmplNamer.enterSyntheticSym(beanGetterDef) - } - if (mods hasFlag MUTABLE) { - val setterName = "set" + beanName - val existingSetter = sym.owner.info.decl(setterName) - if (existingSetter != NoSymbol) { - if (!existingSetter.hasFlag(SYNTHETIC)) - context.error(sym.pos, "a defintion of `"+ setterName + - "' already exists in "+ sym.owner) - } else { - val setterMods = Modifiers(flags, mods.privateWithin, - mods.annotations map (_.duplicate)) - val param = ValDef(NoMods, "new" + name, TypeTree(tp), EmptyTree) - val beanSetterDef = atPos(sym.pos) { - DefDef(setterMods, setterName, Nil, List(List(param)), - TypeTree(UnitClass.tpe), - if (mods hasFlag DEFERRED) EmptyTree - else Assign(Select(This(sym.owner.name), name), Ident(param.name))) } - tmplNamer.enterSyntheticSym(beanSetterDef) - } - } - } - } - def selfTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym => var selftpe = typer.typedType(tree).tpe if (!(selftpe.typeSymbol isNonBottomSubClass sym.owner)) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 89cfcb21e2..4255fd54e2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1271,22 +1271,33 @@ trait Typers { self: Analyzer => treeCopy.DefDef(result, result.mods, result.name, result.tparams, result.vparamss, result.tpt, result.rhs) } - def setterDef: DefDef = { - val setr = getter.setter(value.owner) - setr.setAnnotations(value.annotations) + def setterDef(setter: Symbol): DefDef = { + setter.setAnnotations(value.annotations) val result = atPos(vdef)( - DefDef(setr, - if ((mods hasFlag DEFERRED) || (setr hasFlag OVERLOADED)) + DefDef(setter, + if ((mods hasFlag DEFERRED) || (setter hasFlag OVERLOADED)) EmptyTree else typed(Assign(Select(This(value.owner), value), - Ident(setr.paramss.head.head))))) + Ident(setter.paramss.head.head))))) treeCopy.DefDef(result, result.mods, result.name, result.tparams, result.vparamss, result.tpt, result.rhs) } - val gs = if (mods hasFlag MUTABLE) List(getterDef, setterDef) - else List(getterDef) - if (mods hasFlag DEFERRED) gs else vdef :: gs + + val gs = new ListBuffer[DefDef] + gs.append(getterDef) + if (mods hasFlag MUTABLE) { + val setter = getter.setter(value.owner) + gs.append(setterDef(setter)) + if (!forMSIL && (value.hasAnnotation(BeanPropertyAttr) || + value.hasAnnotation(BooleanBeanPropertyAttr))) { + val beanSetterName = "set" + name(0).toString.toUpperCase + + name.subName(1, name.length) + val beanSetter = value.owner.info.decl(beanSetterName) + gs.append(setterDef(beanSetter)) + } + } + if (mods hasFlag DEFERRED) gs.toList else vdef :: gs.toList } case DocDef(comment, defn) => addGetterSetter(defn) map (stat => DocDef(comment, stat)) @@ -1798,7 +1809,7 @@ trait Typers { self: Analyzer => context.unit.synthetics get e.sym match { case Some(tree) => newStats += typedStat(tree) // might add even more synthetics to the scope - context.unit.synthetics -= e.sym + context.unit.synthetics -= e.sym case _ => } diff --git a/src/library/scala/reflect/BeanProperty.scala b/src/library/scala/reflect/BeanProperty.scala index 1c75cb02fc..50c095486f 100644 --- a/src/library/scala/reflect/BeanProperty.scala +++ b/src/library/scala/reflect/BeanProperty.scala @@ -12,22 +12,21 @@ package scala.reflect /** <p> - * This annotation adds a setter and a getter method, following the - * Java Bean convention (first letter of the property is capitalized) - * used by popular Java web frameworks. For example: + * When attached to a field, this annotation adds a setter and a getter + * method following the Java Bean convention. For example: * </p><pre> * @BeanProperty * <b>var</b> status = ""</pre> * <p> - * adds the following methods to the <b>generated</b> code + * adds the following methods to the class: * </p><pre> * <b>def</b> setStatus(s: String) { <b>this</b>.status = s } * <b>def</b> getStatus: String = <b>this</b>.status * </pre> * <p> - * However, you cannot call <code>setStatus</code> from - * <a href="http://scala-lang.org/" target="_top">Scala</a>, - * you should use the normal Scala access and assignment. + * For fields of type <code>Boolean</code>, if you need a getter + * named <code>isStatus</code>, use the + * <code>scala.reflect.BooleanBeanProperty</code> annotation instead. * </p> */ class BeanProperty extends StaticAnnotation diff --git a/src/library/scala/reflect/BooleanBeanProperty.scala b/src/library/scala/reflect/BooleanBeanProperty.scala new file mode 100644 index 0000000000..cd9f2e5e9e --- /dev/null +++ b/src/library/scala/reflect/BooleanBeanProperty.scala @@ -0,0 +1,21 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2009, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// $Id: BeanProperty.scala 17925 2009-05-30 18:10:21Z rytz $ + + +package scala.reflect + +/** <p> + * This annotation has the same functionality as + * <code>scala.reflect.BeanProperty</code>, but the generated + * Bean getter will be named <code>isFieldName</code> instead + * of <code>getFieldName</code>. + * </p> + */ +class BooleanBeanProperty extends StaticAnnotation diff --git a/test/files/pos/annotations.scala b/test/files/pos/annotations.scala index 9f780c0094..5492d89351 100644 --- a/test/files/pos/annotations.scala +++ b/test/files/pos/annotations.scala @@ -22,5 +22,55 @@ object Test { // bug #1070 trait T { @BeanProperty var field = 1 } + + // annotation on annotation constructor + @(ann @ann(100))(200) def foo() = 300 +} + +// test forward references to getters / setters +class BeanPropertyTests { + @scala.reflect.BeanProperty lazy val lv1 = 0 + + def foo() { + val bp1 = new BeanPropertyTests1 + + println(lv1) + println(getLv1()) + println(bp1.getLv2()) + + println(getV1()) + setV1(10) + bp1.setV2(100) + } + + @scala.reflect.BeanProperty var v1 = 0 + } +class BeanPropertyTests1 { + @scala.reflect.BeanProperty lazy val lv2 = "0" + @scala.reflect.BeanProperty var v2 = 0 +} + +// test mixin of getters / setters, and implementing abstract +// methods using @BeanProperty +class C extends T with BeanF { + def foo() { + setF("doch!") + setG(true) + this.getF() + } +} + +trait T { + @scala.reflect.BeanProperty var f = "nei" + @scala.reflect.BooleanBeanProperty var g = false +} + +trait BeanF { + def getF(): String + def setF(n: String): Unit + + def isG(): Boolean + def setG(nb: Boolean): Unit +} diff --git a/test/files/pos/t1029/Test_1.scala b/test/files/pos/t1029/Test_1.scala new file mode 100644 index 0000000000..e828087f2c --- /dev/null +++ b/test/files/pos/t1029/Test_1.scala @@ -0,0 +1,7 @@ +class ann(a: Array[Int]) extends StaticAnnotation + +object Test1 { + // bug #1029 + @ann(Array(10, 2)) def u = () + val v: String @ann(Array(13, 2)) = "-1" +} diff --git a/test/files/pos/t1029/Test_2.scala b/test/files/pos/t1029/Test_2.scala new file mode 100644 index 0000000000..00589052cb --- /dev/null +++ b/test/files/pos/t1029/Test_2.scala @@ -0,0 +1,3 @@ +object Test { + val t = Test1 +} diff --git a/test/files/pos/t1751/A1_2.scala b/test/files/pos/t1751/A1_2.scala new file mode 100644 index 0000000000..354d5eecd0 --- /dev/null +++ b/test/files/pos/t1751/A1_2.scala @@ -0,0 +1,2 @@ +@SuiteClasses(Array(classOf[A2])) +class A1 diff --git a/test/files/pos/t1751/A2_1.scala b/test/files/pos/t1751/A2_1.scala new file mode 100644 index 0000000000..c768062e43 --- /dev/null +++ b/test/files/pos/t1751/A2_1.scala @@ -0,0 +1,2 @@ +@SuiteClasses(Array()) +class A2 diff --git a/test/files/pos/t1751/SuiteClasses.java b/test/files/pos/t1751/SuiteClasses.java new file mode 100644 index 0000000000..9f09021c5a --- /dev/null +++ b/test/files/pos/t1751/SuiteClasses.java @@ -0,0 +1,3 @@ +public @interface SuiteClasses { + public Class<?>[] value(); +}
\ No newline at end of file diff --git a/test/files/pos/t1782/ImplementedBy.java b/test/files/pos/t1782/ImplementedBy.java new file mode 100644 index 0000000000..6aa8b4fa9e --- /dev/null +++ b/test/files/pos/t1782/ImplementedBy.java @@ -0,0 +1,3 @@ +public @interface ImplementedBy { + public Class<?> value(); +} diff --git a/test/files/pos/t1782/Test_1.scala b/test/files/pos/t1782/Test_1.scala new file mode 100644 index 0000000000..de4f0e9886 --- /dev/null +++ b/test/files/pos/t1782/Test_1.scala @@ -0,0 +1,10 @@ +@ImplementedBy(classOf[Provider]) +trait Service { + def someMethod() +} + +class Provider + extends Service +{ + def someMethod() = () +} diff --git a/test/files/pos/t1942/A_1.scala b/test/files/pos/t1942/A_1.scala new file mode 100644 index 0000000000..19a7575a0a --- /dev/null +++ b/test/files/pos/t1942/A_1.scala @@ -0,0 +1,11 @@ +class A { + def foo(x: Int) = 0 + def foo(x: String) = 1 +} + +class ann(x: Int) extends StaticAnnotation + +class t { + val a = new A + @ann(a.foo(1)) def bar = 1 +} diff --git a/test/files/pos/t1942/Test_2.scala b/test/files/pos/t1942/Test_2.scala new file mode 100644 index 0000000000..6c045bbce5 --- /dev/null +++ b/test/files/pos/t1942/Test_2.scala @@ -0,0 +1,3 @@ +class Test { + println(new t) +} diff --git a/test/files/pos/t294/Ann.java b/test/files/pos/t294/Ann.java new file mode 100644 index 0000000000..934ca46297 --- /dev/null +++ b/test/files/pos/t294/Ann.java @@ -0,0 +1,3 @@ +public @interface Ann { + public Ann2[] nested(); +} diff --git a/test/files/pos/t294/Ann2.java b/test/files/pos/t294/Ann2.java new file mode 100644 index 0000000000..958cf1ab76 --- /dev/null +++ b/test/files/pos/t294/Ann2.java @@ -0,0 +1,3 @@ +public @interface Ann2 { + public int value(); +}
\ No newline at end of file diff --git a/test/files/pos/t294/Test_1.scala b/test/files/pos/t294/Test_1.scala new file mode 100644 index 0000000000..ff1f34b10e --- /dev/null +++ b/test/files/pos/t294/Test_1.scala @@ -0,0 +1,7 @@ +// also test pickling of java annotations; Test_2.scala will +// read this class file +@Ann(nested = Array(new Ann2(10))) class Test { + @Ann2(100) var ctx: Object = _ + @Ann(nested = Array()) def foo = 10 + @Ann(nested = Array(new Ann2(10), new Ann2(23))) val bam = -3 +} diff --git a/test/files/pos/t294/Test_2.scala b/test/files/pos/t294/Test_2.scala new file mode 100644 index 0000000000..9fb1c6e175 --- /dev/null +++ b/test/files/pos/t294/Test_2.scala @@ -0,0 +1 @@ +class Test2 extends Test |