Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9c96268

Browse files
authoredFeb 3, 2021
Merge pull request #10557 from Kordyjan/property-beans/synthetic
Implement bean property generation
2 parents a113884 + d3741a9 commit 9c96268

File tree

11 files changed

+131
-3
lines changed

11 files changed

+131
-3
lines changed
 

‎compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,8 @@ class Definitions {
890890

891891
// Annotation classes
892892
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AnnotationDefault")
893+
@tu lazy val BeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BeanProperty")
894+
@tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty")
893895
@tu lazy val BodyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Body")
894896
@tu lazy val ChildAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Child")
895897
@tu lazy val ContextResultCountAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ContextResultCount")

‎compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -582,12 +582,15 @@ class TreeUnpickler(reader: TastyReader,
582582
else
583583
newSymbol(ctx.owner, name, flags, completer, privateWithin, coord)
584584
}
585-
sym.annotations = annotFns.map(_(sym.owner))
585+
val annots = annotFns.map(_(sym.owner))
586+
sym.annotations = annots
586587
if sym.isOpaqueAlias then sym.setFlag(Deferred)
588+
val isSyntheticBeanAccessor = flags.isAllOf(Method | Synthetic) &&
589+
annots.exists(a => a.matches(defn.BeanPropertyAnnot) || a.matches(defn.BooleanBeanPropertyAnnot))
587590
val isScala2MacroDefinedInScala3 = flags.is(Macro, butNot = Inline) && flags.is(Erased)
588591
ctx.owner match {
589-
case cls: ClassSymbol if !isScala2MacroDefinedInScala3 || cls == defn.StringContextClass =>
590-
// Enter all members of classes that are not Scala 2 macros.
592+
case cls: ClassSymbol if (!isScala2MacroDefinedInScala3 || cls == defn.StringContextClass) && !isSyntheticBeanAccessor =>
593+
// Enter all members of classes that are not Scala 2 macros or synthetic bean accessors.
591594
//
592595
// For `StringContext`, enter `s`, `f` and `raw`
593596
// These definitions will be entered when defined in Scala 2. It is fine to enter them
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import ast.tpd._
6+
import Annotations._
7+
import Contexts._
8+
import SymDenotations._
9+
import Symbols.newSymbol
10+
import Decorators._
11+
import Flags._
12+
import Names._
13+
import Types._
14+
import util.Spans._
15+
16+
import DenotTransformers._
17+
18+
class BeanProperties(thisPhase: DenotTransformer):
19+
def addBeanMethods(impl: Template)(using Context): Template =
20+
val origBody = impl.body
21+
cpy.Template(impl)(body = impl.body.flatMap {
22+
case v: ValDef => generateAccessors(v)
23+
case _ => Nil
24+
} ::: origBody)
25+
26+
def generateAccessors(valDef: ValDef)(using Context): List[Tree] =
27+
import Symbols.defn
28+
29+
def generateGetter(valDef: ValDef, annot: Annotation)(using Context) : Tree =
30+
val prefix = if annot matches defn.BooleanBeanPropertyAnnot then "is" else "get"
31+
val meth = newSymbol(
32+
owner = ctx.owner,
33+
name = prefixedName(prefix, valDef.name),
34+
flags = Method | Synthetic,
35+
info = MethodType(Nil, valDef.denot.info),
36+
coord = annot.tree.span
37+
).enteredAfter(thisPhase).asTerm
38+
meth.addAnnotations(valDef.symbol.annotations)
39+
val body: Tree = ref(valDef.symbol)
40+
DefDef(meth, body)
41+
42+
def maybeGenerateSetter(valDef: ValDef, annot: Annotation)(using Context): Option[Tree] =
43+
Option.when(valDef.denot.asSymDenotation.flags.is(Mutable)) {
44+
val owner = ctx.owner
45+
val meth = newSymbol(
46+
owner,
47+
name = prefixedName("set", valDef.name),
48+
flags = Method | Synthetic,
49+
info = MethodType(valDef.name :: Nil, valDef.denot.info :: Nil, defn.UnitType),
50+
coord = annot.tree.span
51+
).enteredAfter(thisPhase).asTerm
52+
meth.addAnnotations(valDef.symbol.annotations)
53+
def body(params: List[List[Tree]]): Tree = Assign(ref(valDef.symbol), params.head.head)
54+
DefDef(meth, body)
55+
}
56+
57+
def prefixedName(prefix: String, valName: Name) =
58+
(prefix + valName.lastPart.toString.capitalize).toTermName
59+
60+
val symbol = valDef.denot.symbol
61+
symbol.getAnnotation(defn.BeanPropertyAnnot)
62+
.orElse(symbol.getAnnotation(defn.BooleanBeanPropertyAnnot))
63+
.toList.flatMap { annot =>
64+
generateGetter(valDef, annot) +: maybeGenerateSetter(valDef, annot) ++: Nil
65+
}
66+
end generateAccessors

‎compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
7575

7676
val superAcc: SuperAccessors = new SuperAccessors(thisPhase)
7777
val synthMbr: SyntheticMembers = new SyntheticMembers(thisPhase)
78+
val beanProps: BeanProperties = new BeanProperties(thisPhase)
7879

7980
private def newPart(tree: Tree): Option[New] = methPart(tree) match {
8081
case Select(nu: New, _) => Some(nu)
@@ -322,8 +323,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
322323
withNoCheckNews(templ.parents.flatMap(newPart)) {
323324
forwardParamAccessors(templ)
324325
synthMbr.addSyntheticMembers(
326+
beanProps.addBeanMethods(
325327
superAcc.wrapTemplate(templ)(
326328
super.transform(_).asInstanceOf[Template]))
329+
)
327330
}
328331
case tree: ValDef =>
329332
val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class A:
2+
@scala.beans.BeanProperty val x = 6
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class B(val a: A):
2+
def x = a.getX() // error

‎tests/run/beans.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
4
2+
true
3+
10
4+
[@beans.LibraryAnnotation_1()]
5+
some text
6+
other text

‎tests/run/beans/A_2.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class A:
2+
@scala.beans.BeanProperty val x = 4
3+
@scala.beans.BooleanBeanProperty val y = true
4+
@scala.beans.BeanProperty var mutableOneWithLongName = "some text"
5+
6+
@scala.beans.BeanProperty
7+
@beans.LibraryAnnotation_1
8+
val retainingAnnotation = 5
9+
10+
trait T:
11+
@scala.beans.BeanProperty val x: Int
12+
13+
class T1 extends T:
14+
override val x = 5
15+
16+
class T2 extends T1:
17+
override val x = 10
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package beans;
2+
3+
import java.lang.annotation.Retention;
4+
import java.lang.annotation.RetentionPolicy;
5+
6+
@Retention(RetentionPolicy.RUNTIME)
7+
public @interface LibraryAnnotation_1 {}

‎tests/run/beans/Test_3.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import java.util.Arrays;
2+
3+
class JavaTest {
4+
public A run() throws ReflectiveOperationException{
5+
A a = new A();
6+
System.out.println(a.getX());
7+
System.out.println(a.isY());
8+
System.out.println(new T2().getX());
9+
10+
System.out.println(Arrays.asList(a.getClass().getMethod("getRetainingAnnotation").getAnnotations()));
11+
12+
System.out.println(a.getMutableOneWithLongName());
13+
a.setMutableOneWithLongName("other text");
14+
return a;
15+
}
16+
}

‎tests/run/beans/Test_4.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Test:
2+
def main(args: Array[String]) =
3+
val a = JavaTest().run()
4+
println(a.mutableOneWithLongName)

0 commit comments

Comments
 (0)
Please sign in to comment.