Skip to content

Implement bean property generation #10557

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 7 commits into from
Feb 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,8 @@ class Definitions {

// Annotation classes
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AnnotationDefault")
@tu lazy val BeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BeanProperty")
@tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty")
@tu lazy val BodyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Body")
@tu lazy val ChildAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Child")
@tu lazy val ContextResultCountAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ContextResultCount")
Expand Down
9 changes: 6 additions & 3 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -582,12 +582,15 @@ class TreeUnpickler(reader: TastyReader,
else
newSymbol(ctx.owner, name, flags, completer, privateWithin, coord)
}
sym.annotations = annotFns.map(_(sym.owner))
val annots = annotFns.map(_(sym.owner))
sym.annotations = annots
if sym.isOpaqueAlias then sym.setFlag(Deferred)
val isSyntheticBeanAccessor = flags.isAllOf(Method | Synthetic) &&
annots.exists(a => a.matches(defn.BeanPropertyAnnot) || a.matches(defn.BooleanBeanPropertyAnnot))
val isScala2MacroDefinedInScala3 = flags.is(Macro, butNot = Inline) && flags.is(Erased)
ctx.owner match {
case cls: ClassSymbol if !isScala2MacroDefinedInScala3 || cls == defn.StringContextClass =>
// Enter all members of classes that are not Scala 2 macros.
case cls: ClassSymbol if (!isScala2MacroDefinedInScala3 || cls == defn.StringContextClass) && !isSyntheticBeanAccessor =>
// Enter all members of classes that are not Scala 2 macros or synthetic bean accessors.
//
// For `StringContext`, enter `s`, `f` and `raw`
// These definitions will be entered when defined in Scala 2. It is fine to enter them
Expand Down
66 changes: 66 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/BeanProperties.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package dotty.tools.dotc
package transform

import core._
import ast.tpd._
import Annotations._
import Contexts._
import SymDenotations._
import Symbols.newSymbol
import Decorators._
import Flags._
import Names._
import Types._
import util.Spans._

import DenotTransformers._

class BeanProperties(thisPhase: DenotTransformer):
def addBeanMethods(impl: Template)(using Context): Template =
val origBody = impl.body
cpy.Template(impl)(body = impl.body.flatMap {
case v: ValDef => generateAccessors(v)
case _ => Nil
} ::: origBody)

def generateAccessors(valDef: ValDef)(using Context): List[Tree] =
import Symbols.defn

def generateGetter(valDef: ValDef, annot: Annotation)(using Context) : Tree =
val prefix = if annot matches defn.BooleanBeanPropertyAnnot then "is" else "get"
val meth = newSymbol(
owner = ctx.owner,
name = prefixedName(prefix, valDef.name),
flags = Method | Synthetic,
info = MethodType(Nil, valDef.denot.info),
coord = annot.tree.span
).enteredAfter(thisPhase).asTerm
meth.addAnnotations(valDef.symbol.annotations)
val body: Tree = ref(valDef.symbol)
DefDef(meth, body)

def maybeGenerateSetter(valDef: ValDef, annot: Annotation)(using Context): Option[Tree] =
Option.when(valDef.denot.asSymDenotation.flags.is(Mutable)) {
val owner = ctx.owner
val meth = newSymbol(
owner,
name = prefixedName("set", valDef.name),
flags = Method | Synthetic,
info = MethodType(valDef.name :: Nil, valDef.denot.info :: Nil, defn.UnitType),
coord = annot.tree.span
).enteredAfter(thisPhase).asTerm
meth.addAnnotations(valDef.symbol.annotations)
def body(params: List[List[Tree]]): Tree = Assign(ref(valDef.symbol), params.head.head)
DefDef(meth, body)
}

def prefixedName(prefix: String, valName: Name) =
(prefix + valName.lastPart.toString.capitalize).toTermName

val symbol = valDef.denot.symbol
symbol.getAnnotation(defn.BeanPropertyAnnot)
.orElse(symbol.getAnnotation(defn.BooleanBeanPropertyAnnot))
.toList.flatMap { annot =>
generateGetter(valDef, annot) +: maybeGenerateSetter(valDef, annot) ++: Nil
}
end generateAccessors
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase

val superAcc: SuperAccessors = new SuperAccessors(thisPhase)
val synthMbr: SyntheticMembers = new SyntheticMembers(thisPhase)
val beanProps: BeanProperties = new BeanProperties(thisPhase)

private def newPart(tree: Tree): Option[New] = methPart(tree) match {
case Select(nu: New, _) => Some(nu)
Expand Down Expand Up @@ -322,8 +323,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
withNoCheckNews(templ.parents.flatMap(newPart)) {
forwardParamAccessors(templ)
synthMbr.addSyntheticMembers(
beanProps.addBeanMethods(
superAcc.wrapTemplate(templ)(
super.transform(_).asInstanceOf[Template]))
)
}
case tree: ValDef =>
val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
Expand Down
2 changes: 2 additions & 0 deletions tests/neg/beanAccessorsLeaking/A_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class A:
@scala.beans.BeanProperty val x = 6
2 changes: 2 additions & 0 deletions tests/neg/beanAccessorsLeaking/B_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class B(val a: A):
def x = a.getX() // error
6 changes: 6 additions & 0 deletions tests/run/beans.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
4
true
10
[@beans.LibraryAnnotation_1()]
some text
other text
17 changes: 17 additions & 0 deletions tests/run/beans/A_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class A:
@scala.beans.BeanProperty val x = 4
@scala.beans.BooleanBeanProperty val y = true
@scala.beans.BeanProperty var mutableOneWithLongName = "some text"

@scala.beans.BeanProperty
@beans.LibraryAnnotation_1
val retainingAnnotation = 5

trait T:
@scala.beans.BeanProperty val x: Int

class T1 extends T:
override val x = 5

class T2 extends T1:
override val x = 10
7 changes: 7 additions & 0 deletions tests/run/beans/LibraryAnnotation_1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package beans;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface LibraryAnnotation_1 {}
16 changes: 16 additions & 0 deletions tests/run/beans/Test_3.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import java.util.Arrays;

class JavaTest {
public A run() throws ReflectiveOperationException{
A a = new A();
System.out.println(a.getX());
System.out.println(a.isY());
System.out.println(new T2().getX());

System.out.println(Arrays.asList(a.getClass().getMethod("getRetainingAnnotation").getAnnotations()));

System.out.println(a.getMutableOneWithLongName());
a.setMutableOneWithLongName("other text");
return a;
}
}
4 changes: 4 additions & 0 deletions tests/run/beans/Test_4.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
object Test:
def main(args: Array[String]) =
val a = JavaTest().run()
println(a.mutableOneWithLongName)