diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/LombokSyntheticJavaPartsProvider.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/LombokSyntheticJavaPartsProvider.kt index 149734ddf3f..5259aa26c3c 100644 --- a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/LombokSyntheticJavaPartsProvider.kt +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/LombokSyntheticJavaPartsProvider.kt @@ -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 = listOf( - GetterProcessor(config) + GetterProcessor(config), + SetterProcessor(config) ) private val partsCache: MutableMap = WeakHashMap() @@ -36,9 +38,8 @@ class LombokSyntheticJavaPartsProvider(private val config: LombokConfig) : Synth name: Name, result: MutableCollection ) { - 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? = diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/config/annotationConfig.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/config/annotationConfig.kt index f905d124268..b1729d57883 100644 --- a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/config/annotationConfig.kt +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/config/annotationConfig.kt @@ -48,3 +48,14 @@ data class Getter(val visibility: DescriptorVisibility) { ) } } + +data class Setter(val visibility: DescriptorVisibility) { + companion object : AnnotationCompanion() { + override val name: FqName = LombokNames.SETTER + + override fun extract(annotation: AnnotationDescriptor): Setter = + Setter( + visibility = getVisibility(annotation) + ) + } +} diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/GetterProcessor.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/GetterProcessor.kt index 168da1ba190..dd98793e070 100644 --- a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/GetterProcessor.kt +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/GetterProcessor.kt @@ -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 Collection.collectWithNotNull(f: (E) -> R?): List> = - map { it to f(it) }.filter { it.second != null } as List> - private fun KotlinType.isPrimitiveBoolean(): Boolean = this is SimpleTypeMarker && isBoolean() //todo } diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/SetterProcessor.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/SetterProcessor.kt new file mode 100644 index 00000000000..05b23f4bd09 --- /dev/null +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/SetterProcessor.kt @@ -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 + ) + } +} diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/descriptorUtils.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/descriptorUtils.kt index 780265e8e2d..318ed08d388 100644 --- a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/descriptorUtils.kt +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/descriptorUtils.kt @@ -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, + valueParameters: List, 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 = + this.unsubstitutedMemberScope.getVariableNames() + .map { + this.unsubstitutedMemberScope.getContributedVariables(it, NoLookupLocation.FROM_SYNTHETIC_SCOPE).single() + } diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/utils.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/utils.kt new file mode 100644 index 00000000000..36cde4220ea --- /dev/null +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/utils.kt @@ -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 Collection.collectWithNotNull(f: (E) -> R?): List> = + map { it to f(it) }.filter { it.second != null } as List> diff --git a/plugins/lombok/lombok-compiler-plugin/testData/compile/setters.kt b/plugins/lombok/lombok-compiler-plugin/testData/compile/setters.kt new file mode 100644 index 00000000000..add9462fcde --- /dev/null +++ b/plugins/lombok/lombok-compiler-plugin/testData/compile/setters.kt @@ -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") + } + } +} diff --git a/plugins/lombok/lombok-compiler-plugin/testData/compile/settersClassLevel.kt b/plugins/lombok/lombok-compiler-plugin/testData/compile/settersClassLevel.kt new file mode 100644 index 00000000000..2d63c9afda5 --- /dev/null +++ b/plugins/lombok/lombok-compiler-plugin/testData/compile/settersClassLevel.kt @@ -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") + } +} diff --git a/plugins/lombok/lombok-compiler-plugin/testData/compile/settersVariations.kt b/plugins/lombok/lombok-compiler-plugin/testData/compile/settersVariations.kt new file mode 100644 index 00000000000..52dcdaa774b --- /dev/null +++ b/plugins/lombok/lombok-compiler-plugin/testData/compile/settersVariations.kt @@ -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() + } + +} diff --git a/plugins/lombok/lombok-compiler-plugin/tests/org/jetbrains/kotlin/lombok/LombokCompileTestGenerated.java b/plugins/lombok/lombok-compiler-plugin/tests/org/jetbrains/kotlin/lombok/LombokCompileTestGenerated.java index 0d83480107b..53a026b2443 100644 --- a/plugins/lombok/lombok-compiler-plugin/tests/org/jetbrains/kotlin/lombok/LombokCompileTestGenerated.java +++ b/plugins/lombok/lombok-compiler-plugin/tests/org/jetbrains/kotlin/lombok/LombokCompileTestGenerated.java @@ -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");