From fd4aafdb75bf80959a3c2b81d253a2a0c6626560 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Wed, 10 Aug 2022 10:34:46 +0300 Subject: [PATCH] [Lombok K2] Support @Builder and @Singular annotations ^KT-46959 Fixed --- .../fir/java/declarations/FirJavaClass.kt | 4 +- .../kotlin/lombok/utils/LombokNames.kt | 2 + .../kotlin/lombok/k2/FirLombokRegistrar.kt | 1 + .../kotlin/lombok/k2/config/LombokService.kt | 14 +- .../lombok/k2/config/annotationConfig.kt | 59 ++- .../lombok/k2/config/annotationUtils.kt | 14 +- .../lombok/k2/generators/BuilderGenerator.kt | 384 ++++++++++++++++++ .../lombok/k2/java/dummyJavaElements.kt | 104 +++++ .../kotlin/lombok/k2/java/wrappedJavaTypes.kt | 108 +++++ .../testData/diagnostics/builderConfig.kt | 1 + 10 files changed, 682 insertions(+), 9 deletions(-) create mode 100644 plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/generators/BuilderGenerator.kt create mode 100644 plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/java/dummyJavaElements.kt create mode 100644 plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/java/wrappedJavaTypes.kt diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/declarations/FirJavaClass.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/declarations/FirJavaClass.kt index 50cb0c88292..e509a5c76f8 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/declarations/FirJavaClass.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/declarations/FirJavaClass.kt @@ -125,7 +125,7 @@ class FirJavaClass @FirImplementationDetail internal constructor( } @FirBuilderDsl -internal class FirJavaClassBuilder : FirRegularClassBuilder(), FirAnnotationContainerBuilder { +class FirJavaClassBuilder : FirRegularClassBuilder(), FirAnnotationContainerBuilder { lateinit var visibility: Visibility var modality: Modality? = null var isFromSource: Boolean by Delegates.notNull() @@ -174,6 +174,6 @@ internal class FirJavaClassBuilder : FirRegularClassBuilder(), FirAnnotationCont } } -internal inline fun buildJavaClass(init: FirJavaClassBuilder.() -> Unit): FirJavaClass { +inline fun buildJavaClass(init: FirJavaClassBuilder.() -> Unit): FirJavaClass { return FirJavaClassBuilder().apply(init).build() } diff --git a/plugins/lombok/lombok.common/src/org/jetbrains/kotlin/lombok/utils/LombokNames.kt b/plugins/lombok/lombok.common/src/org/jetbrains/kotlin/lombok/utils/LombokNames.kt index 77a770638cb..e92451ba435 100644 --- a/plugins/lombok/lombok.common/src/org/jetbrains/kotlin/lombok/utils/LombokNames.kt +++ b/plugins/lombok/lombok.common/src/org/jetbrains/kotlin/lombok/utils/LombokNames.kt @@ -29,6 +29,8 @@ object LombokNames { val WITH_ID = ClassId.topLevel(WITH) val DATA_ID = ClassId.topLevel(DATA) val VALUE_ID = ClassId.topLevel(VALUE) + val BUILDER_ID = ClassId.topLevel(BUILDER) + val SINGULAR_ID = ClassId.topLevel(SINGULAR) val NO_ARGS_CONSTRUCTOR_ID = ClassId.topLevel(NO_ARGS_CONSTRUCTOR) val ALL_ARGS_CONSTRUCTOR_ID = ClassId.topLevel(ALL_ARGS_CONSTRUCTOR) val REQUIRED_ARGS_CONSTRUCTOR_ID = ClassId.topLevel(REQUIRED_ARGS_CONSTRUCTOR) diff --git a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/FirLombokRegistrar.kt b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/FirLombokRegistrar.kt index 6d82014ae1b..378d89b83e6 100644 --- a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/FirLombokRegistrar.kt +++ b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/FirLombokRegistrar.kt @@ -17,5 +17,6 @@ class FirLombokRegistrar(private val lombokConfigFile: File?) : FirExtensionRegi +::SetterGenerator +::WithGenerator +::LombokConstructorsGenerator + +::BuilderGenerator } } diff --git a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/LombokService.kt b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/LombokService.kt index 4b3b09a3983..6cd0c0bcb2b 100644 --- a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/LombokService.kt +++ b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/LombokService.kt @@ -17,11 +17,13 @@ import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.lombok.config.LombokConfig import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Accessors import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.AllArgsConstructor +import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Builder import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Data import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Getter import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.NoArgsConstructor import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.RequiredArgsConstructor import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Setter +import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Singular import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Value import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.With import java.io.File @@ -34,7 +36,7 @@ class LombokService(session: FirSession, configFile: File?) : FirExtensionSessio } } - private val config = configFile?.let(LombokConfig::parse) ?: LombokConfig.Empty + val config = configFile?.let(LombokConfig::parse) ?: LombokConfig.Empty private val cachesFactory = session.firCachesFactory private val accessorsCache: Cache = cachesFactory.createCache { symbol -> @@ -77,6 +79,14 @@ class LombokService(session: FirSession, configFile: File?) : FirExtensionSessio Value.getOrNull(symbol.fir) } + private val builderCache: Cache = cachesFactory.createCache { symbol -> + Builder.getIfAnnotated(symbol.fir, config) + } + + private val singularCache: Cache = cachesFactory.createCache { symbol -> + Singular.getOrNull(symbol.fir) + } + fun getAccessors(symbol: FirBasedSymbol<*>): Accessors = accessorsCache.getValue(symbol) fun getAccessorsIfAnnotated(symbol: FirBasedSymbol<*>): Accessors? = accessorsIfAnnotatedCache.getValue(symbol) fun getGetter(symbol: FirBasedSymbol<*>): Getter? = getterCache.getValue(symbol) @@ -87,6 +97,8 @@ class LombokService(session: FirSession, configFile: File?) : FirExtensionSessio fun getRequiredArgsConstructor(symbol: FirBasedSymbol<*>): RequiredArgsConstructor? = requiredArgsConstructorCache.getValue(symbol) fun getData(symbol: FirBasedSymbol<*>): Data? = dataCache.getValue(symbol) fun getValue(symbol: FirBasedSymbol<*>): Value? = valueCache.getValue(symbol) + fun getBuilder(symbol: FirBasedSymbol<*>): Builder? = builderCache.getValue(symbol) + fun getSingular(symbol: FirBasedSymbol<*>): Singular? = singularCache.getValue(symbol) } private typealias Cache = FirCache, T, Nothing?> diff --git a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/annotationConfig.kt b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/annotationConfig.kt index ea2f656c3f4..880c266c521 100644 --- a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/annotationConfig.kt +++ b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/annotationConfig.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.fir.FirAnnotationContainer import org.jetbrains.kotlin.fir.declarations.getBooleanArgument +import org.jetbrains.kotlin.fir.declarations.getStringArgument import org.jetbrains.kotlin.fir.declarations.getStringArrayArgument import org.jetbrains.kotlin.fir.expressions.FirAnnotation import org.jetbrains.kotlin.fir.types.ConeClassLikeType @@ -16,15 +17,23 @@ import org.jetbrains.kotlin.fir.types.coneTypeSafe import org.jetbrains.kotlin.lombok.config.AccessLevel import org.jetbrains.kotlin.lombok.config.LombokConfig import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.ACCESS +import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.BUILDER_CLASS_NAME +import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.BUILDER_CLASS_NAME_CONFIG +import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.BUILDER_METHOD_NAME +import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.BUILD_METHOD_NAME import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.CHAIN import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.CHAIN_CONFIG import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.FLUENT import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.FLUENT_CONFIG +import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.IGNORE_NULL_COLLECTIONS import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.NO_IS_PREFIX_CONFIG import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.PREFIX import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.PREFIX_CONFIG +import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.SETTER_PREFIX import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.STATIC_CONSTRUCTOR import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.STATIC_NAME +import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.TO_BUILDER +import org.jetbrains.kotlin.lombok.k2.config.LombokConfigNames.VALUE import org.jetbrains.kotlin.lombok.utils.LombokNames import org.jetbrains.kotlin.name.ClassId @@ -97,7 +106,7 @@ object ConeLombokAnnotations { class Getter(val visibility: AccessLevel = AccessLevel.PUBLIC) { companion object : ConeAnnotationCompanion(LombokNames.GETTER_ID) { override fun extract(annotation: FirAnnotation): Getter = Getter( - visibility = getAccessLevel(annotation) + visibility = annotation.getAccessLevel() ) } } @@ -105,7 +114,7 @@ object ConeLombokAnnotations { class Setter(val visibility: AccessLevel = AccessLevel.PUBLIC) { companion object : ConeAnnotationCompanion(LombokNames.SETTER_ID) { override fun extract(annotation: FirAnnotation): Setter = Setter( - visibility = getAccessLevel(annotation) + visibility = annotation.getAccessLevel() ) } } @@ -113,7 +122,7 @@ object ConeLombokAnnotations { class With(val visibility: AccessLevel = AccessLevel.PUBLIC) { companion object : ConeAnnotationCompanion(LombokNames.WITH_ID) { override fun extract(annotation: FirAnnotation): With = With( - visibility = getAccessLevel(annotation) + visibility = annotation.getAccessLevel() ) } } @@ -188,6 +197,50 @@ object ConeLombokAnnotations { ) } } + + class Builder( + val builderClassName: String, + val buildMethodName: String, + val builderMethodName: String, + val requiresToBuilder: Boolean, + val visibility: AccessLevel, + val setterPrefix: String? + ) { + companion object : ConeAnnotationAndConfigCompanion(LombokNames.BUILDER_ID) { + private const val DEFAULT_BUILDER_CLASS_NAME = "*Builder" + private const val DEFAULT_BUILD_METHOD_NAME = "build" + private const val DEFAULT_BUILDER_METHOD_NAME = "builder" + private const val DEFAULT_REQUIRES_TO_BUILDER = false + + + override fun extract(annotation: FirAnnotation?, config: LombokConfig): Builder { + return Builder( + builderClassName = annotation?.getStringArgument(BUILDER_CLASS_NAME) + ?: config.getString(BUILDER_CLASS_NAME_CONFIG) + ?: DEFAULT_BUILDER_CLASS_NAME, + buildMethodName = annotation?.getStringArgument(BUILD_METHOD_NAME) ?: DEFAULT_BUILD_METHOD_NAME, + builderMethodName = annotation?.getStringArgument(BUILDER_METHOD_NAME) ?: DEFAULT_BUILDER_METHOD_NAME, + requiresToBuilder = annotation?.getBooleanArgument(TO_BUILDER) ?: DEFAULT_REQUIRES_TO_BUILDER, + visibility = annotation?.getAccessLevel(ACCESS) ?: AccessLevel.PUBLIC, + setterPrefix = annotation?.getStringArgument(SETTER_PREFIX) + ) + } + } + } + + class Singular( + val singularName: String?, + val allowNull: Boolean, + ) { + companion object : ConeAnnotationCompanion(LombokNames.SINGULAR_ID) { + override fun extract(annotation: FirAnnotation): Singular { + return Singular( + singularName = annotation.getStringArgument(VALUE), + allowNull = annotation.getBooleanArgument(IGNORE_NULL_COLLECTIONS) ?: false + ) + } + } + } } diff --git a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/annotationUtils.kt b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/annotationUtils.kt index f09cded42f6..2769854b900 100644 --- a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/annotationUtils.kt +++ b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/config/annotationUtils.kt @@ -17,8 +17,8 @@ import org.jetbrains.kotlin.lombok.config.AccessLevel import org.jetbrains.kotlin.lombok.utils.trimToNull import org.jetbrains.kotlin.name.Name -fun getAccessLevel(annotation: FirAnnotation, field: Name = LombokConfigNames.VALUE): AccessLevel { - val value = annotation.getArgumentAsString(field) ?: return AccessLevel.PUBLIC +fun FirAnnotation.getAccessLevel(field: Name = LombokConfigNames.VALUE): AccessLevel { + val value = getArgumentAsString(field) ?: return AccessLevel.PUBLIC return AccessLevel.valueOf(value) } @@ -39,7 +39,7 @@ private fun FirAnnotation.getArgumentAsString(field: Name): String? { } fun getVisibility(annotation: FirAnnotation, field: Name = LombokConfigNames.VALUE): Visibility { - return getAccessLevel(annotation, field).toVisibility() + return annotation.getAccessLevel(field).toVisibility() } fun FirAnnotation.getNonBlankStringArgument(name: Name): String? = getStringArgument(name)?.trimToNull() @@ -53,9 +53,17 @@ object LombokConfigNames { val STATIC_NAME = Name.identifier("staticName") val STATIC_CONSTRUCTOR = Name.identifier("staticConstructor") + val BUILDER_CLASS_NAME = Name.identifier("builderClassName") + val BUILD_METHOD_NAME = Name.identifier("buildMethodName") + val BUILDER_METHOD_NAME = Name.identifier("builderMethodName") + val TO_BUILDER = Name.identifier("toBuilder") + val SETTER_PREFIX = Name.identifier("setterPrefix") + val IGNORE_NULL_COLLECTIONS = Name.identifier("ignoreNullCollections") + const val FLUENT_CONFIG = "lombok.accessors.fluent" const val CHAIN_CONFIG = "lombok.accessors.chain" const val PREFIX_CONFIG = "lombok.accessors.prefix" const val NO_IS_PREFIX_CONFIG = "lombok.getter.noIsPrefix" + const val BUILDER_CLASS_NAME_CONFIG = "lombok.builder.className" } diff --git a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/generators/BuilderGenerator.kt b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/generators/BuilderGenerator.kt new file mode 100644 index 00000000000..588034df26a --- /dev/null +++ b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/generators/BuilderGenerator.kt @@ -0,0 +1,384 @@ +/* + * Copyright 2010-2022 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.k2.generators + +import com.intellij.openapi.util.text.StringUtil +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.caches.FirCache +import org.jetbrains.kotlin.fir.caches.createCache +import org.jetbrains.kotlin.fir.caches.firCachesFactory +import org.jetbrains.kotlin.fir.caches.getValue +import org.jetbrains.kotlin.fir.containingClassForStaticMemberAttr +import org.jetbrains.kotlin.fir.declarations.FirDeclaration +import org.jetbrains.kotlin.fir.declarations.builder.buildConstructedClassTypeParameterRef +import org.jetbrains.kotlin.fir.declarations.builder.buildOuterClassTypeParameterRef +import org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl +import org.jetbrains.kotlin.fir.declarations.utils.classId +import org.jetbrains.kotlin.fir.declarations.utils.effectiveVisibility +import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension +import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext +import org.jetbrains.kotlin.fir.java.JavaScopeProvider +import org.jetbrains.kotlin.fir.java.declarations.* +import org.jetbrains.kotlin.fir.resolve.defaultType +import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider +import org.jetbrains.kotlin.fir.symbols.SymbolInternals +import org.jetbrains.kotlin.fir.symbols.impl.* +import org.jetbrains.kotlin.fir.toEffectiveVisibility +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef +import org.jetbrains.kotlin.fir.types.jvm.FirJavaTypeRef +import org.jetbrains.kotlin.load.java.structure.JavaClass +import org.jetbrains.kotlin.load.java.structure.JavaClassifierType +import org.jetbrains.kotlin.load.java.structure.JavaType +import org.jetbrains.kotlin.lombok.config.LombokConfig +import org.jetbrains.kotlin.lombok.k2.* +import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Builder +import org.jetbrains.kotlin.lombok.k2.config.ConeLombokAnnotations.Singular +import org.jetbrains.kotlin.lombok.k2.config.LombokService +import org.jetbrains.kotlin.lombok.k2.config.lombokService +import org.jetbrains.kotlin.lombok.k2.java.* +import org.jetbrains.kotlin.lombok.utils.LombokNames +import org.jetbrains.kotlin.lombok.utils.capitalize +import org.jetbrains.kotlin.name.CallableId +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name + +class BuilderGenerator(session: FirSession) : FirDeclarationGenerationExtension(session) { + companion object { + private const val TO_BUILDER = "toBuilder" + } + + private val lombokService: LombokService + get() = session.lombokService + + private val builderClassCache: FirCache, FirJavaClass?, Nothing?> = + session.firCachesFactory.createCache(::createBuilderClass) + + private val functionsCache: FirCache, Map>?, Nothing?> = + session.firCachesFactory.createCache(::createFunctions) + + override fun getCallableNamesForClass(classSymbol: FirClassSymbol<*>): Set { + if (!classSymbol.isSuitableJavaClass()) return emptySet() + return functionsCache.getValue(classSymbol)?.keys.orEmpty() + } + + override fun getNestedClassifiersNames(classSymbol: FirClassSymbol<*>): Set { + if (!classSymbol.isSuitableJavaClass()) return emptySet() + val name = builderClassCache.getValue(classSymbol)?.name ?: return emptySet() + return setOf(name) + } + + override fun generateFunctions(callableId: CallableId, context: MemberGenerationContext?): List { + val classSymbol = context?.owner ?: return emptyList() + return functionsCache.getValue(classSymbol)?.get(callableId.callableName).orEmpty().map { it.symbol } + } + + override fun generateClassLikeDeclaration(classId: ClassId): FirClassLikeSymbol<*>? { + val outerClassId = classId.outerClassId ?: return null + val outerClassSymbol = session.symbolProvider.getClassLikeSymbolByClassId(outerClassId) as? FirClassSymbol<*> ?: return null + if (!outerClassSymbol.isSuitableJavaClass()) return null + return builderClassCache.getValue(outerClassSymbol)?.symbol + } + + private fun createFunctions(classSymbol: FirClassSymbol<*>): Map>? { + val builder = lombokService.getBuilder(classSymbol) ?: return null + val functions = mutableListOf() + val classId = classSymbol.classId + val builderClassName = builder.builderClassName.replace("*", classId.shortClassName.asString()) + val builderClassId = classId.createNestedClassId(Name.identifier(builderClassName)) + + val builderType = builderClassId.constructClassLikeType(emptyArray(), isNullable = false) + val visibility = builder.visibility.toVisibility() + functions += classSymbol.createJavaMethod( + Name.identifier(builder.builderMethodName), + valueParameters = emptyList(), + returnTypeRef = builderType.toFirResolvedTypeRef(), + visibility = visibility, + modality = Modality.FINAL, + dispatchReceiverType = null, + isStatic = true + ) + + if (builder.requiresToBuilder) { + functions += classSymbol.createJavaMethod( + Name.identifier(TO_BUILDER), + valueParameters = emptyList(), + returnTypeRef = builderType.toFirResolvedTypeRef(), + visibility = visibility, + modality = Modality.FINAL, + ) + } + + return functions.groupBy { it.name } + } + + @OptIn(SymbolInternals::class) + private fun createBuilderClass(classSymbol: FirClassSymbol<*>): FirJavaClass? { + val javaClass = classSymbol.fir as? FirJavaClass ?: return null + val builder = lombokService.getBuilder(classSymbol) ?: return null + val builderName = Name.identifier(builder.builderClassName.replace("*", classSymbol.name.asString())) + val visibility = builder.visibility.toVisibility() + val builderClass = classSymbol.createJavaClass( + session, + builderName, + visibility, + Modality.FINAL, + isStatic = true, + superTypeRefs = listOf(session.builtinTypes.anyType) + )?.apply { + declarations += symbol.createDefaultJavaConstructor(visibility) + declarations += symbol.createJavaMethod( + Name.identifier(builder.buildMethodName), + valueParameters = emptyList(), + returnTypeRef = classSymbol.defaultType().toFirResolvedTypeRef(), + visibility = visibility, + modality = Modality.FINAL + ) + val fields = javaClass.declarations.filterIsInstance() + for (field in fields) { + when (val singular = lombokService.getSingular(field.symbol)) { + null -> createSetterMethod(builder, field, symbol, declarations) + else -> createMethodsForSingularFields(builder, singular, field, symbol, declarations) + } + } + + } ?: return null + + + return builderClass + } + + private fun createSetterMethod( + builder: Builder, + field: FirJavaField, + builderClassSymbol: FirRegularClassSymbol, + destination: MutableList + ) { + val fieldName = field.name + val setterName = fieldName.toMethodName(builder) + destination += builderClassSymbol.createJavaMethod( + name = setterName, + valueParameters = listOf(ConeLombokValueParameter(fieldName, field.returnTypeRef)), + returnTypeRef = builderClassSymbol.defaultType().toFirResolvedTypeRef(), + modality = Modality.FINAL, + visibility = builder.visibility.toVisibility() + ) + } + + private fun createMethodsForSingularFields( + builder: Builder, + singular: Singular, + field: FirJavaField, + builderClassSymbol: FirRegularClassSymbol, + destination: MutableList + ) { + val fieldJavaTypeRef = field.returnTypeRef as? FirJavaTypeRef ?: return + val javaClassifierType = fieldJavaTypeRef.type as? JavaClassifierType ?: return + val typeName = (javaClassifierType.classifier as? JavaClass)?.fqName?.asString() ?: return + + val nameInSingularForm = (singular.singularName ?: field.name.identifier.singularForm)?.let(Name::identifier) ?: return + + val useGuava = lombokService.config.useGuava + val supportedCollections = LombokNames.getSupportedCollectionsForSingular(useGuava) + val supportedMaps = LombokNames.getSupportedMapsForSingular(useGuava) + + val addMultipleParameterType: FirTypeRef + val valueParameters: List + + when (typeName) { + in supportedCollections -> { + val parameterType = javaClassifierType.parameterType(0, singular.allowNull) ?: return + valueParameters = listOf( + ConeLombokValueParameter(nameInSingularForm, parameterType.toRef()) + ) + + addMultipleParameterType = DummyJavaClassType(JavaClasses.Collection, typeArguments = listOf(parameterType)).toRef() + } + + in supportedMaps -> { + val keyType = javaClassifierType.parameterType(0, singular.allowNull) ?: return + val valueType = javaClassifierType.parameterType(1, singular.allowNull) ?: return + valueParameters = listOf( + ConeLombokValueParameter(Name.identifier("key"), keyType.toRef()), + ConeLombokValueParameter(Name.identifier("value"), valueType.toRef()), + ) + + addMultipleParameterType = DummyJavaClassType(JavaClasses.Map, typeArguments = listOf(keyType, valueType)).toRef() + } + + else -> return + } + + val builderType = builderClassSymbol.defaultType().toFirResolvedTypeRef() + val visibility = builder.visibility.toVisibility() + + destination += builderClassSymbol.createJavaMethod( + name = nameInSingularForm.toMethodName(builder), + valueParameters, + returnTypeRef = builderType, + modality = Modality.FINAL, + visibility = visibility + ) + + destination += builderClassSymbol.createJavaMethod( + name = field.name.toMethodName(builder), + valueParameters = listOf(ConeLombokValueParameter(field.name, addMultipleParameterType)), + returnTypeRef = builderType, + modality = Modality.FINAL, + visibility = visibility + ) + + destination += builderClassSymbol.createJavaMethod( + name = Name.identifier("clear${field.name.identifier.capitalize()}"), + valueParameters = listOf(), + returnTypeRef = builderType, + modality = Modality.FINAL, + visibility = visibility + ) + } + + private fun Name.toMethodName(builder: Builder): Name { + val prefix = builder.setterPrefix + return if (prefix.isNullOrBlank()) { + this + } else { + Name.identifier("${prefix}${identifier.capitalize()}") + } + } + + private val String.singularForm: String? + get() = StringUtil.unpluralize(this) + + private val LombokConfig.useGuava: Boolean + get() = getBoolean("lombok.singular.useGuava") ?: false + + private fun JavaClassifierType.parameterType(index: Int, allowNull: Boolean): JavaType? { + val type = typeArguments.getOrNull(index) ?: return null + return if (allowNull) type.makeNullable() else type.makeNotNullable() + } +} + +fun JavaType.makeNullable(): JavaType = withAnnotations(annotations + NullabilityJavaAnnotation.Nullable) +fun JavaType.makeNotNullable(): JavaType = withAnnotations(annotations + NullabilityJavaAnnotation.NotNull) + +fun FirClassSymbol<*>.createJavaMethod( + name: Name, + valueParameters: List, + returnTypeRef: FirTypeRef, + visibility: Visibility, + modality: Modality, + dispatchReceiverType: ConeSimpleKotlinType? = this.defaultType(), + isStatic: Boolean = false +): FirJavaMethod { + return buildJavaMethod { + moduleData = this@createJavaMethod.moduleData + this.returnTypeRef = returnTypeRef + this.dispatchReceiverType = dispatchReceiverType + this.name = name + symbol = FirNamedFunctionSymbol(CallableId(classId, name)) + status = FirResolvedDeclarationStatusImpl(visibility, modality, visibility.toEffectiveVisibility(this@createJavaMethod)).apply { + this.isStatic = isStatic + } + isFromSource = true + annotationBuilder = { emptyList() } + for (valueParameter in valueParameters) { + this.valueParameters += buildJavaValueParameter { + moduleData = this@createJavaMethod.moduleData + this.returnTypeRef = valueParameter.typeRef + this.name = valueParameter.name + annotationBuilder = { emptyList() } + isVararg = false + isFromSource = true + } + } + }.apply { + if (isStatic) { + containingClassForStaticMemberAttr = this@createJavaMethod.toLookupTag() + } + } +} + +fun FirClassSymbol<*>.createDefaultJavaConstructor( + visibility: Visibility, +): FirJavaConstructor { + val outerClassSymbol = this + return buildJavaConstructor { + moduleData = outerClassSymbol.moduleData + isFromSource = true + symbol = FirConstructorSymbol(classId) + isInner = outerClassSymbol.rawStatus.isInner + status = FirResolvedDeclarationStatusImpl( + visibility, + Modality.FINAL, + visibility.toEffectiveVisibility(outerClassSymbol) + ).apply { + isExpect = false + isActual = false + isOverride = false + isInner = this@buildJavaConstructor.isInner + } + this.visibility = visibility + isPrimary = false + returnTypeRef = buildResolvedTypeRef { + type = outerClassSymbol.defaultType() + } + dispatchReceiverType = if (isInner) outerClassSymbol.defaultType() else null + typeParameters += outerClassSymbol.typeParameterSymbols.map { buildConstructedClassTypeParameterRef { symbol = it } } + annotationBuilder = { emptyList() } + } +} + +class ConeLombokValueParameter(val name: Name, val typeRef: FirTypeRef) + +@OptIn(SymbolInternals::class) +fun FirClassSymbol<*>.createJavaClass( + session: FirSession, + name: Name, + visibility: Visibility, + modality: Modality, + isStatic: Boolean, + superTypeRefs: List, +): FirJavaClass? { + val containingClass = this.fir as? FirJavaClass ?: return null + val classId = containingClass.classId.createNestedClassId(name) + return buildJavaClass { + moduleData = containingClass.moduleData + symbol = FirRegularClassSymbol(classId) + this.name = name + isFromSource = true + this.visibility = visibility + this.modality = modality + this.isStatic = isStatic + classKind = ClassKind.CLASS + javaTypeParameterStack = containingClass.javaTypeParameterStack + scopeProvider = JavaScopeProvider + if (!isStatic) { + typeParameters += containingClass.typeParameters.map { + buildOuterClassTypeParameterRef { symbol = it.symbol } + } + } + this.superTypeRefs += superTypeRefs + val effectiveVisibility = containingClass.effectiveVisibility.lowerBound( + visibility.toEffectiveVisibility(this@createJavaClass, forClass = true), + session.typeContext + ) + isTopLevel = false + status = FirResolvedDeclarationStatusImpl( + visibility, + modality, + effectiveVisibility + ).apply { + this.isInner = !isTopLevel && !this@buildJavaClass.isStatic + isCompanion = false + isData = false + isInline = false + isFun = classKind == ClassKind.INTERFACE + } + } +} diff --git a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/java/dummyJavaElements.kt b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/java/dummyJavaElements.kt new file mode 100644 index 00000000000..3fb5dfcea4a --- /dev/null +++ b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/java/dummyJavaElements.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2010-2022 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.k2.java + +import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.load.java.structure.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +object JavaClasses { + val Collection = DummyJavaClass("Collection", javaUtilName("Collection"), numberOfTypeParameters = 1) + val Map = DummyJavaClass("Map", javaUtilName("Map"), numberOfTypeParameters = 2) + + + private fun javaUtilName(name: String): FqName { + return FqName.fromSegments(listOf("java", "util", name)) + } +} + +class DummyJavaClass(name: String, override val fqName: FqName, numberOfTypeParameters: Int) : JavaClass { + override val name: Name = Name.identifier(name) + + override val isFromSource: Boolean + get() = shouldNotBeCalled() + override val annotations: Collection + get() = shouldNotBeCalled() + override val isDeprecatedInJavaDoc: Boolean + get() = shouldNotBeCalled() + + override fun findAnnotation(fqName: FqName): JavaAnnotation? { + return null + } + + override val isAbstract: Boolean + get() = shouldNotBeCalled() + override val isStatic: Boolean + get() = shouldNotBeCalled() + override val isFinal: Boolean + get() = shouldNotBeCalled() + override val visibility: Visibility + get() = shouldNotBeCalled() + override val typeParameters: List = (1..numberOfTypeParameters).map { + DummyJavaTypeParameter(Name.identifier("T_$it")) + } + + override val supertypes: Collection + get() = shouldNotBeCalled() + override val innerClassNames: Collection + get() = shouldNotBeCalled() + + override fun findInnerClass(name: Name): JavaClass? { + shouldNotBeCalled() + } + + override val outerClass: JavaClass? + get() = null + override val isInterface: Boolean + get() = shouldNotBeCalled() + override val isAnnotationType: Boolean + get() = shouldNotBeCalled() + override val isEnum: Boolean + get() = shouldNotBeCalled() + override val isRecord: Boolean + get() = shouldNotBeCalled() + override val isSealed: Boolean + get() = shouldNotBeCalled() + override val permittedTypes: Collection + get() = shouldNotBeCalled() + override val lightClassOriginKind: LightClassOriginKind? + get() = shouldNotBeCalled() + override val methods: Collection + get() = shouldNotBeCalled() + override val fields: Collection + get() = shouldNotBeCalled() + override val constructors: Collection + get() = shouldNotBeCalled() + override val recordComponents: Collection + get() = shouldNotBeCalled() + + override fun hasDefaultConstructor(): Boolean { + shouldNotBeCalled() + } +} + +class DummyJavaTypeParameter(override val name: Name) : JavaTypeParameter { + override val isFromSource: Boolean + get() = shouldNotBeCalled() + override val annotations: Collection + get() = shouldNotBeCalled() + override val isDeprecatedInJavaDoc: Boolean + get() = shouldNotBeCalled() + + override fun findAnnotation(fqName: FqName): JavaAnnotation? { + shouldNotBeCalled() + } + + override val upperBounds: Collection + get() = emptyList() +} + +private fun shouldNotBeCalled(): Nothing = error("should not be called") diff --git a/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/java/wrappedJavaTypes.kt b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/java/wrappedJavaTypes.kt new file mode 100644 index 00000000000..b0fee53ad45 --- /dev/null +++ b/plugins/lombok/lombok.k2/src/org/jetbrains/kotlin/lombok/k2/java/wrappedJavaTypes.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2010-2022 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.k2.java + +import org.jetbrains.kotlin.builtins.PrimitiveType +import org.jetbrains.kotlin.fir.types.jvm.FirJavaTypeRef +import org.jetbrains.kotlin.fir.types.jvm.buildJavaTypeRef +import org.jetbrains.kotlin.load.java.structure.* +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.SpecialNames + +abstract class WrappedJavaType(val original: T, private val ownAnnotations: Collection?) : JavaType { + override val annotations: Collection + get() = ownAnnotations ?: original.annotations + + override val isDeprecatedInJavaDoc: Boolean + get() = original.isDeprecatedInJavaDoc +} + +class WrappedJavaArrayType( + original: JavaArrayType, + ownAnnotations: Collection? +) : WrappedJavaType(original, ownAnnotations), JavaArrayType { + override val componentType: JavaType + get() = original.componentType +} + +class WrappedJavaPrimitiveType( + original: JavaPrimitiveType, + ownAnnotations: Collection? +) : WrappedJavaType(original, ownAnnotations), JavaPrimitiveType { + override val type: PrimitiveType? + get() = original.type +} + +class WrappedJavaWildcardType( + original: JavaWildcardType, + ownAnnotations: Collection? +) : WrappedJavaType(original, ownAnnotations), JavaWildcardType { + override val bound: JavaType? + get() = original.bound + override val isExtends: Boolean + get() = original.isExtends +} + +class WrappedJavaClassifierType( + original: JavaClassifierType, + ownAnnotations: Collection?, +) : WrappedJavaType(original, ownAnnotations), JavaClassifierType { + override val classifier: JavaClassifier? + get() = original.classifier + override val typeArguments: List + get() = original.typeArguments + override val isRaw: Boolean + get() = original.isRaw + override val classifierQualifiedName: String + get() = original.classifierQualifiedName + override val presentableText: String + get() = original.presentableText +} + +fun JavaType.withAnnotations(annotations: Collection): JavaType = when (this) { + is JavaArrayType -> WrappedJavaArrayType(this, annotations) + is JavaClassifierType -> WrappedJavaClassifierType(this, annotations) + is JavaPrimitiveType -> WrappedJavaPrimitiveType(this, annotations) + is JavaWildcardType -> WrappedJavaWildcardType(this, annotations) + else -> this +} + +abstract class NullabilityJavaAnnotation(override val classId: ClassId) : JavaAnnotation { + override val arguments: Collection + get() = emptyList() + + override fun resolve(): JavaClass? = null + + object NotNull : NullabilityJavaAnnotation(ClassId(ORG_JETBRAINS_ANNOTATIONS, Name.identifier("NotNull"))) + object Nullable : NullabilityJavaAnnotation(ClassId(ORG_JETBRAINS_ANNOTATIONS, Name.identifier("Nullable"))) + + companion object { + private val ORG_JETBRAINS_ANNOTATIONS = FqName.fromSegments(listOf("org", "jetbrains", "annotations")) + } +} + +class DummyJavaClassType( + override val classifier: JavaClass, + override val typeArguments: List +) : JavaClassifierType { + override val annotations: Collection + get() = emptyList() + override val isDeprecatedInJavaDoc: Boolean + get() = false + override val isRaw: Boolean + get() = false + override val classifierQualifiedName: String + get() = classifier.fqName?.asString() ?: SpecialNames.NO_NAME_PROVIDED.asString() + override val presentableText: String + get() = classifierQualifiedName +} + +fun JavaType.toRef(): FirJavaTypeRef = buildJavaTypeRef { + type = this@toRef + annotationBuilder = { emptyList() } +} diff --git a/plugins/lombok/testData/diagnostics/builderConfig.kt b/plugins/lombok/testData/diagnostics/builderConfig.kt index ef7b094652d..e8a2721c5da 100644 --- a/plugins/lombok/testData/diagnostics/builderConfig.kt +++ b/plugins/lombok/testData/diagnostics/builderConfig.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // FILE: User.java import lombok.Builder;