From bad3a2d65d47d5d18d6fa19a23995d9a123048a9 Mon Sep 17 00:00:00 2001 From: sakno Date: Tue, 24 Apr 2018 15:58:31 +0300 Subject: [PATCH 1/5] Template for new proposal --- proposals/lambdas-in-annotations.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 proposals/lambdas-in-annotations.md diff --git a/proposals/lambdas-in-annotations.md b/proposals/lambdas-in-annotations.md new file mode 100644 index 000000000..61e02d60b --- /dev/null +++ b/proposals/lambdas-in-annotations.md @@ -0,0 +1,9 @@ +# Lambda expression in annotations + +* **Type**: Design proposal +* **Author**: Roman Sakno +* **Contributors**: Roman Sakno +* **Status**: Under consideration +* **Prototype**: Not started + +## Synopsis \ No newline at end of file From e601bbdc0b0fe508be346d5315811655c35eac0a Mon Sep 17 00:00:00 2001 From: SRV Date: Tue, 24 Apr 2018 17:56:08 +0300 Subject: [PATCH 2/5] First version of proposal --- proposals/lambdas-in-annotations.md | 163 +++++++++++++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/proposals/lambdas-in-annotations.md b/proposals/lambdas-in-annotations.md index 61e02d60b..89c170c6d 100644 --- a/proposals/lambdas-in-annotations.md +++ b/proposals/lambdas-in-annotations.md @@ -6,4 +6,165 @@ * **Status**: Under consideration * **Prototype**: Not started -## Synopsis \ No newline at end of file +## Synopsis +Spring Framework and Java EE has many annotations used for validation of bean properties. For strings regexp is a proven way to validate the content: +```java +public class Person{ + private String mailAddress; + + @Validation("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$") + public String getMailAddress(){ + return mailAddress; + } +} +``` + +For validation of user data types these frameworks ask programmer to specify class which implements functional interface with validation logic: + +```java +@FunctionalInterface +interface Validator{ + boolean validate(T value) +} + +public @interface Validation{ + Class> value(); +} +``` + +To use this annotation, it is necessary to create implementation of functional interface as class and then specify it in annotation: +```java +class PersonValidator implements Validator{ + public boolean validate(Person value){ + return value != null && value.getPhoneNumber != null; + } +} + +public Company{ + private Person value; + + @Validation(PersonValidator::class) + public Person getOwner(){ + return value; + } +} +``` + +The same situation in Kotlin. Programmer have to write custom class with implementation of functional interface. This proposal describes syntax for describing implementation of functional interface inside of annotation in the form of lambda expression. + +## Language syntax +The first extension to language grammar should allow to use lambda expression as an argument for annotation. Previous example can be rewritten in Kotlin as follows: +```kotlin +class Company{ + @Validation({it is Person && it.phoneNumber != null}) + var owner: Person? = null +} +``` + +Lambda expression can see only statis and companion members of enclosing class and other classes. +Lambda expression syntax is applicable to annotation argument if the following conditions are met: +1. Type of annotation parameter is `Class`. +1. Type `T` is functional interface in terms of Java functional interfaces (can contain many methods but only one abstract method without default implementation) **-or-** T is an abstract class with single abstract method (**protected** or **public**) and other methods with default implementation. + +The second extension to language grammar allows to use functional type in annotation class parameters: +```kotlin +annotation class Validation(val validator: noinline (Any) -> Bool) +``` +Functional type can have **noinline** modifier which instructs compiler to generate **Class** actual type instead of Kotlin intrinsic, which means that lambda cannot be inlined. This keyword is needed to separate two types of lambda expression support for annotation arguments: +1. **noinline** lambda has actual type **Class<out F>** which allows to be compatible and interoperable with Java code easily. +1. If **noinline** lambda is not used then annotation argument will have special annotation type *MethodReference* from Kotlin runtime (should be also added). + +In simple word, ability to call annotation argument depends on this modifier. If annotation argument has **noinline** functional type then it can't be called as regular lambda. If annotation argument has functional type without **noinline** then this member can be called as follows: + +```kotlin +val validation = method.findAnnotation() +validation?.validator(Person()) +``` + +## Compiler implementation +There are two different implementations of this proposal depending on **noinline** modifier. + +### noinline functional type +Compiler translates lambda expression into separated class with implementation of appropriate method so Java code still can work with such annotation value. + +Given: +```kotlin +annotation class Validation(val validator: noinline (Any) -> Bool) + +class Company{ + @Validation({it is Person && it.phoneNumber != null}) + var owner: Person? = null +} +``` +Translation result: +```kotlin +class Company{ + @JvmSynthetic //yes, classes in JVM can be synthetic + class $PersonValidation1$: Validator{ + @JvmSynthetic + override fun validate(obj: Any?) = obj is Person && it.phoneNumber != null + } + + @Validation($PersonValidation1$::class) + var owner: Person? = null +} +``` + +Invocation of annotation member _validator_ declared by _Validation_ annotation is not supported by compiler as stated above. + +### Regular functional type +For this type of declaration Kotlin runtime must have two special annotations: *MethodReference* and *MethodSignature*. Description of these annotation demonstrated in Java as a low-level language in comparison to Kotlin to be more clean: +```java +public @interface MethodSignature{ //describes method signature for compiler control + Class[] value(); + Class returnType(); +} + +public @interface MethodReference{ //describes method reference to the actual lambda implementation + String methodName(); + MethodSignature signature(); + Class declaringClass(); +} +``` +Also, we need additional extension method that can be used to resolve a method: +```kotlin +fun MethodReference.resolveMethod(): java.lang.reflect.Method = declaringClass.getDeclaredMethod(methodName, signature) +``` + +Now, annotation parameter declaration can be translated easily: +```kotlin +annotation class Validation(val validator: (Any) -> Bool) + +class Company{ + @Validation({it is Person && it.phoneNumber != null}) + var owner: Person? = null +} +``` +into +```kotlin +annotation class Validation(@MethodSignature(Any::class, returnType = Bool::class) val validator: MethodReference) + +class Company{ + @JvmSythetic + @JvmStatic + fun $personValidation$(obj: Any?) = obj is Person && obj.phoneNumber != null + + @Validation(validator = @MethodReference(methodName = "$personValidation$", signature = @MethodSignature(Any::class, Bool::class), declaringClass = Company::class) + fun owner: Person? = null +} +``` +That's why in this version of declaration (without **noinline**) calling of annotation member *validator* is applicable because compiler has enough guarantees about accessibility of lambda implementation: +```kotlin +val validation = method.findAnnotation() +val isValid = validation?.validator(Person()) +``` +will be translated into +```kotlin +val validation = method.findAnnotation() +val method = validation?.validator?.resolveMethod() //resolveMethod() is an extension in runtime library described above +val isValid = method.invoke(Person()) as Bool +``` +Call safety is guaranteed because compiler recognizes *MethodSignature* annotation and can infer proper parameter types. + +#### Optimization of call site +If reflection is too slow we can always replace this code and turn it into **invokedynamic** invocation with *MutableCallSite*. Such optimization gives JVM a chance to recognize it as monomorphic or polymorphic call and inline referenced method. From 515e711d5fd22e41810247aa90ee5017354f0635 Mon Sep 17 00:00:00 2001 From: SRV Date: Tue, 24 Apr 2018 18:20:54 +0300 Subject: [PATCH 3/5] Change noinline keyword to reified Change noinline keyword to reified which is more informative --- proposals/lambdas-in-annotations.md | 38 ++++++++++++++++++----------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/proposals/lambdas-in-annotations.md b/proposals/lambdas-in-annotations.md index 89c170c6d..3ca4317a6 100644 --- a/proposals/lambdas-in-annotations.md +++ b/proposals/lambdas-in-annotations.md @@ -68,28 +68,30 @@ Lambda expression syntax is applicable to annotation argument if the following c The second extension to language grammar allows to use functional type in annotation class parameters: ```kotlin -annotation class Validation(val validator: noinline (Any) -> Bool) +annotation class Validation(val validator: (Any) -> Bool) +``` +Also, **reified** keyword can be applied to functional type: +```kotlin +annotation class Validation(val validator: reified (Any) -> Bool) ``` -Functional type can have **noinline** modifier which instructs compiler to generate **Class** actual type instead of Kotlin intrinsic, which means that lambda cannot be inlined. This keyword is needed to separate two types of lambda expression support for annotation arguments: -1. **noinline** lambda has actual type **Class<out F>** which allows to be compatible and interoperable with Java code easily. -1. If **noinline** lambda is not used then annotation argument will have special annotation type *MethodReference* from Kotlin runtime (should be also added). - -In simple word, ability to call annotation argument depends on this modifier. If annotation argument has **noinline** functional type then it can't be called as regular lambda. If annotation argument has functional type without **noinline** then this member can be called as follows: +This keyword significantly changes generated code and **reified** declaration is not compatible with Java annotation parameters typed as *Class*. It means that lambda expression signature can be reified at compile time: ```kotlin val validation = method.findAnnotation() -validation?.validator(Person()) +validation?.validator(Person()) //because validator has actual type (Any) -> Bool, not Class ``` +Details will be explained in the next section. + ## Compiler implementation There are two different implementations of this proposal depending on **noinline** modifier. -### noinline functional type +### Regular functional type Compiler translates lambda expression into separated class with implementation of appropriate method so Java code still can work with such annotation value. Given: ```kotlin -annotation class Validation(val validator: noinline (Any) -> Bool) +annotation class Validation(val validator: (Any) -> Bool) class Company{ @Validation({it is Person && it.phoneNumber != null}) @@ -112,7 +114,7 @@ class Company{ Invocation of annotation member _validator_ declared by _Validation_ annotation is not supported by compiler as stated above. -### Regular functional type +### Reified functional type For this type of declaration Kotlin runtime must have two special annotations: *MethodReference* and *MethodSignature*. Description of these annotation demonstrated in Java as a low-level language in comparison to Kotlin to be more clean: ```java public @interface MethodSignature{ //describes method signature for compiler control @@ -133,7 +135,7 @@ fun MethodReference.resolveMethod(): java.lang.reflect.Method = declaringClass.g Now, annotation parameter declaration can be translated easily: ```kotlin -annotation class Validation(val validator: (Any) -> Bool) +annotation class Validation(val validator: reified (Any) -> Bool) class Company{ @Validation({it is Person && it.phoneNumber != null}) @@ -153,7 +155,7 @@ class Company{ fun owner: Person? = null } ``` -That's why in this version of declaration (without **noinline**) calling of annotation member *validator* is applicable because compiler has enough guarantees about accessibility of lambda implementation: +That's why reified declaration gives enough guarantees about signature of lambda implementation: ```kotlin val validation = method.findAnnotation() val isValid = validation?.validator(Person()) @@ -162,9 +164,17 @@ will be translated into ```kotlin val validation = method.findAnnotation() val method = validation?.validator?.resolveMethod() //resolveMethod() is an extension in runtime library described above -val isValid = method.invoke(Person()) as Bool +val lambda = //do invokedynamic magic in conjunction with LambdaMetafactory to translate Method into functional interface instance +val isValid = lambda(Person()) +``` +Call safety is guaranteed because compiler recognizes *MethodSignature* annotation and can infer proper parameter types. It is possible to pass *validator* as a lambda function to another function: +```kotlin +fun bar(action: (Any?) -> Bool){ +} + +val validation = method.findAnnotation() +bar(validation?.validator!!) ``` -Call safety is guaranteed because compiler recognizes *MethodSignature* annotation and can infer proper parameter types. #### Optimization of call site If reflection is too slow we can always replace this code and turn it into **invokedynamic** invocation with *MutableCallSite*. Such optimization gives JVM a chance to recognize it as monomorphic or polymorphic call and inline referenced method. From 5fa4076cb6b7de614ec501909330196b7b91f02c Mon Sep 17 00:00:00 2001 From: SRV Date: Tue, 24 Apr 2018 18:22:40 +0300 Subject: [PATCH 4/5] Remove last occurence of noinline --- proposals/lambdas-in-annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/lambdas-in-annotations.md b/proposals/lambdas-in-annotations.md index 3ca4317a6..7d8bf9b0a 100644 --- a/proposals/lambdas-in-annotations.md +++ b/proposals/lambdas-in-annotations.md @@ -84,7 +84,7 @@ validation?.validator(Person()) //because validator has actual type (Any) -> Boo Details will be explained in the next section. ## Compiler implementation -There are two different implementations of this proposal depending on **noinline** modifier. +There are two different implementations of this proposal depending on **reified** modifier. ### Regular functional type Compiler translates lambda expression into separated class with implementation of appropriate method so Java code still can work with such annotation value. From c2e6164640833996d151983acfa53034ce099f1f Mon Sep 17 00:00:00 2001 From: SRV Date: Tue, 24 Apr 2018 18:23:19 +0300 Subject: [PATCH 5/5] Add spread operator --- proposals/lambdas-in-annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/lambdas-in-annotations.md b/proposals/lambdas-in-annotations.md index 7d8bf9b0a..c914bf066 100644 --- a/proposals/lambdas-in-annotations.md +++ b/proposals/lambdas-in-annotations.md @@ -130,7 +130,7 @@ public @interface MethodReference{ //describes method reference to the actual la ``` Also, we need additional extension method that can be used to resolve a method: ```kotlin -fun MethodReference.resolveMethod(): java.lang.reflect.Method = declaringClass.getDeclaredMethod(methodName, signature) +fun MethodReference.resolveMethod(): java.lang.reflect.Method = declaringClass.getDeclaredMethod(methodName, *signature) ``` Now, annotation parameter declaration can be translated easily: