[lombok] @Setter support

This commit is contained in:
Andrey Zinovyev
2021-03-25 17:44:12 +03:00
committed by TeamCityServer
parent 70e3877efc
commit 7fd8f7b3bc
10 changed files with 254 additions and 22 deletions
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.lombok.config.LombokConfig
import org.jetbrains.kotlin.lombok.processor.GetterProcessor
import org.jetbrains.kotlin.lombok.processor.Parts
import org.jetbrains.kotlin.lombok.processor.Processor
import org.jetbrains.kotlin.lombok.processor.SetterProcessor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.jvm.SyntheticJavaPartsProvider
import java.util.*
@@ -23,7 +24,8 @@ class LombokSyntheticJavaPartsProvider(private val config: LombokConfig) : Synth
private fun initProcessors(): List<Processor> =
listOf(
GetterProcessor(config)
GetterProcessor(config),
SetterProcessor(config)
)
private val partsCache: MutableMap<ClassDescriptor, Parts> = WeakHashMap()
@@ -36,9 +38,8 @@ class LombokSyntheticJavaPartsProvider(private val config: LombokConfig) : Synth
name: Name,
result: MutableCollection<SimpleFunctionDescriptor>
) {
getSyntheticParts(thisDescriptor).methods.find { it.name == name }?.let {
result.add(it)
}
val methods = getSyntheticParts(thisDescriptor).methods.filter { it.name == name }
result.addAll(methods)
}
private fun extractClass(descriptor: ClassDescriptor): JavaClassImpl? =
@@ -48,3 +48,14 @@ data class Getter(val visibility: DescriptorVisibility) {
)
}
}
data class Setter(val visibility: DescriptorVisibility) {
companion object : AnnotationCompanion<Setter>() {
override val name: FqName = LombokNames.SETTER
override fun extract(annotation: AnnotationDescriptor): Setter =
Setter(
visibility = getVisibility(annotation)
)
}
}
@@ -8,12 +8,13 @@ package org.jetbrains.kotlin.lombok.processor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.lombok.config.Accessors
import org.jetbrains.kotlin.lombok.config.Getter
import org.jetbrains.kotlin.lombok.config.LombokConfig
import org.jetbrains.kotlin.lombok.utils.collectWithNotNull
import org.jetbrains.kotlin.lombok.utils.createFunction
import org.jetbrains.kotlin.lombok.utils.getVariables
import org.jetbrains.kotlin.lombok.utils.toPreparedBase
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.KotlinType
@@ -28,10 +29,9 @@ class GetterProcessor(private val config: LombokConfig) : Processor {
val clAccessors = Accessors.getOrNull(classDescriptor)
val clGetter = Getter.getOrNull(classDescriptor)
val functions = classDescriptor.unsubstitutedMemberScope.getVariableNames()
.map {
classDescriptor.unsubstitutedMemberScope.getContributedVariables(it, NoLookupLocation.FROM_SYNTHETIC_SCOPE).single()
}.collectWithNotNull { Getter.getOrNull(it) ?: clGetter }
val functions = classDescriptor
.getVariables()
.collectWithNotNull { Getter.getOrNull(it) ?: clGetter }
.mapNotNull { (field, annotation) -> createGetter(classDescriptor, field, annotation, clAccessors) }
return Parts(functions)
@@ -52,8 +52,7 @@ class GetterProcessor(private val config: LombokConfig) : Processor {
val prefix = if (field.type.isPrimitiveBoolean() && !noIsPrefix) "is" else "get"
prefix + toPreparedBase(field.name.identifier)
}
return createFunction(
classDescriptor,
return classDescriptor.createFunction(
Name.identifier(functionName),
emptyList(),
field.returnType,
@@ -61,10 +60,6 @@ class GetterProcessor(private val config: LombokConfig) : Processor {
)
}
@Suppress("UNCHECKED_CAST")
internal fun <E, R> Collection<E>.collectWithNotNull(f: (E) -> R?): List<Pair<E, R>> =
map { it to f(it) }.filter { it.second != null } as List<Pair<E, R>>
private fun KotlinType.isPrimitiveBoolean(): Boolean = this is SimpleTypeMarker && isBoolean() //todo
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.lombok.processor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.lombok.config.Accessors
import org.jetbrains.kotlin.lombok.config.LombokConfig
import org.jetbrains.kotlin.lombok.config.Setter
import org.jetbrains.kotlin.lombok.utils.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
class SetterProcessor(private val config: LombokConfig) : Processor {
override fun contribute(classDescriptor: ClassDescriptor, jClass: JavaClassImpl): Parts {
val clAccessors = Accessors.getOrNull(classDescriptor)
val clSetter = Setter.getOrNull(classDescriptor)
val functions = classDescriptor
.getVariables()
.collectWithNotNull { field -> Setter.getOrNull(field) ?: clSetter.takeIf { field.isVar } }
.mapNotNull { (field, setter) -> createSetter(classDescriptor, field, setter, clAccessors) }
return Parts(functions)
}
private fun createSetter(
classDescriptor: ClassDescriptor,
field: PropertyDescriptor,
getter: Setter,
classLevelAccessors: Accessors?
): SimpleFunctionDescriptor? {
val accessors = Accessors.getOrNull(field) ?: classLevelAccessors ?: Accessors.default
val functionName =
if (accessors.fluent) {
field.name.identifier
} else {
"set" + toPreparedBase(field.name.identifier)
}
val returnType = if (accessors.chain) classDescriptor.defaultType else classDescriptor.builtIns.unitType
return classDescriptor.createFunction(
Name.identifier(functionName),
listOf(ValueParameter(field.name, field.type)),
returnType,
visibility = getter.visibility
)
}
}
@@ -8,32 +8,60 @@ package org.jetbrains.kotlin.lombok.utils
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.KotlinType
internal fun createFunction(
containingClass: ClassDescriptor,
internal data class ValueParameter(val name: Name, val type: KotlinType)
internal fun ClassDescriptor.createFunction(
name: Name,
valueParameters: List<ValueParameterDescriptor>,
valueParameters: List<ValueParameter>,
returnType: KotlinType?,
modality: Modality? = Modality.OPEN,
visibility: DescriptorVisibility = DescriptorVisibilities.PUBLIC
): SimpleFunctionDescriptor {
val methodDescriptor = SimpleFunctionDescriptorImpl.create(
containingClass,
this,
Annotations.EMPTY,
name,
CallableMemberDescriptor.Kind.SYNTHESIZED,
containingClass.source
this.source
)
val paramDescriptors = valueParameters.mapIndexed { idx, param -> methodDescriptor.makeValueParameter(param, idx) }
methodDescriptor.initialize(
null,
containingClass.thisAsReceiverParameter,
this.thisAsReceiverParameter,
mutableListOf(),
valueParameters,
paramDescriptors,
returnType,
modality,
visibility
)
return methodDescriptor
}
private fun FunctionDescriptor.makeValueParameter(param: ValueParameter, index: Int): ValueParameterDescriptor {
return ValueParameterDescriptorImpl(
containingDeclaration = this,
original = null,
index = index,
annotations = Annotations.EMPTY,
name = param.name,
outType = param.type,
declaresDefaultValue = false,
isCrossinline = false,
isNoinline = false,
varargElementType = null,
source = this.source
)
}
internal fun ClassDescriptor.getVariables(): List<PropertyDescriptor> =
this.unsubstitutedMemberScope.getVariableNames()
.map {
this.unsubstitutedMemberScope.getContributedVariables(it, NoLookupLocation.FROM_SYNTHETIC_SCOPE).single()
}
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.lombok.utils
@Suppress("UNCHECKED_CAST")
internal fun <E, R> Collection<E>.collectWithNotNull(f: (E) -> R?): List<Pair<E, R>> =
map { it to f(it) }.filter { it.second != null } as List<Pair<E, R>>
@@ -0,0 +1,42 @@
//FILE: SetterTest.java
import lombok.AccessLevel;
import lombok.Setter;
import lombok.Getter;
public class SetterTest {
@Getter @Setter private int age = 10;
@Setter(AccessLevel.PROTECTED) private String name;
@Setter private boolean primitiveBoolean;
void test() {
setAge(12);
setPrimitiveBoolean(true);
}
}
//FILE: test.kt
object Test {
fun usage() {
val obj = SetterTest()
obj.setAge(42)
obj.age = 42
//synthetic property generated only when there is a getter
// obj.primitiveBoolean = false
obj.setPrimitiveBoolean(true)
//shouldn't be accesible from here
// obj.setName("abc")
}
class OverridenGetterTest : SetterTest() {
fun usage() {
setName("abc")
}
}
}
@@ -0,0 +1,38 @@
//FILE: SetterTest.java
import lombok.AccessLevel;
import lombok.Setter;
import lombok.Getter;
@Getter @Setter
public class SetterTest {
private int age = 10;
private final String finalName = "zzz";
private boolean primitiveBoolean;
void test() {
setAge(12);
setPrimitiveBoolean(true);
//no setters generated for final variable
// setFinalName("adsf");
}
}
//FILE: test.kt
object Test {
fun usage() {
val obj = SetterTest()
obj.setAge(42)
obj.age = 42
obj.setPrimitiveBoolean(true)
// no setters generated for final variable
// obj.setFinalName("error")
}
}
@@ -0,0 +1,36 @@
//FILE: SetterTest.java
import lombok.AccessLevel;
import lombok.Setter;
import lombok.Getter;
import lombok.experimental.Accessors;
@Setter
@Getter
public class SetterTest {
@Accessors(fluent = true) private int fluent;
@Accessors(chain = true) private String chained;
@Accessors(chain = true, fluent = true) private String whyNotBoth;
void test() {
fluent(12);
setChained("zz").getChained();
whyNotBoth("zzz").whyNotBoth();
}
}
//FILE: test.kt
object Test {
fun usage() {
val obj = SetterTest()
obj.fluent(12);
obj.setChained("zz").getChained()
obj.whyNotBoth("zzz").whyNotBoth()
}
}
@@ -51,6 +51,21 @@ public class LombokCompileTestGenerated extends AbstractLombokCompileTest {
runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/getters.kt");
}
@TestMetadata("setters.kt")
public void testSetters() throws Exception {
runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/setters.kt");
}
@TestMetadata("settersClassLevel.kt")
public void testSettersClassLevel() throws Exception {
runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/settersClassLevel.kt");
}
@TestMetadata("settersVariations.kt")
public void testSettersVariations() throws Exception {
runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/settersVariations.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/simple.kt");