[Lombok K2] Support @Builder and @Singular annotations

^KT-46959 Fixed
This commit is contained in:
Dmitriy Novozhilov
2022-08-10 10:34:46 +03:00
parent 1f2f6f4067
commit fd4aafdb75
10 changed files with 682 additions and 9 deletions
@@ -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()
}
@@ -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)
@@ -17,5 +17,6 @@ class FirLombokRegistrar(private val lombokConfigFile: File?) : FirExtensionRegi
+::SetterGenerator
+::WithGenerator
+::LombokConstructorsGenerator
+::BuilderGenerator
}
}
@@ -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<Accessors> = cachesFactory.createCache { symbol ->
@@ -77,6 +79,14 @@ class LombokService(session: FirSession, configFile: File?) : FirExtensionSessio
Value.getOrNull(symbol.fir)
}
private val builderCache: Cache<Builder?> = cachesFactory.createCache { symbol ->
Builder.getIfAnnotated(symbol.fir, config)
}
private val singularCache: Cache<Singular?> = 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<T> = FirCache<FirBasedSymbol<*>, T, Nothing?>
@@ -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<Getter>(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<Setter>(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<With>(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<Builder>(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<Singular>(LombokNames.SINGULAR_ID) {
override fun extract(annotation: FirAnnotation): Singular {
return Singular(
singularName = annotation.getStringArgument(VALUE),
allowNull = annotation.getBooleanArgument(IGNORE_NULL_COLLECTIONS) ?: false
)
}
}
}
}
@@ -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"
}
@@ -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<FirClassSymbol<*>, FirJavaClass?, Nothing?> =
session.firCachesFactory.createCache(::createBuilderClass)
private val functionsCache: FirCache<FirClassSymbol<*>, Map<Name, List<FirJavaMethod>>?, Nothing?> =
session.firCachesFactory.createCache(::createFunctions)
override fun getCallableNamesForClass(classSymbol: FirClassSymbol<*>): Set<Name> {
if (!classSymbol.isSuitableJavaClass()) return emptySet()
return functionsCache.getValue(classSymbol)?.keys.orEmpty()
}
override fun getNestedClassifiersNames(classSymbol: FirClassSymbol<*>): Set<Name> {
if (!classSymbol.isSuitableJavaClass()) return emptySet()
val name = builderClassCache.getValue(classSymbol)?.name ?: return emptySet()
return setOf(name)
}
override fun generateFunctions(callableId: CallableId, context: MemberGenerationContext?): List<FirNamedFunctionSymbol> {
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<Name, List<FirJavaMethod>>? {
val builder = lombokService.getBuilder(classSymbol) ?: return null
val functions = mutableListOf<FirJavaMethod>()
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<FirJavaField>()
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<FirDeclaration>
) {
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<FirDeclaration>
) {
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<ConeLombokValueParameter>
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<ConeLombokValueParameter>,
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<FirTypeRef>,
): 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
}
}
}
@@ -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<JavaAnnotation>
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<JavaTypeParameter> = (1..numberOfTypeParameters).map {
DummyJavaTypeParameter(Name.identifier("T_$it"))
}
override val supertypes: Collection<JavaClassifierType>
get() = shouldNotBeCalled()
override val innerClassNames: Collection<Name>
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<JavaClassifierType>
get() = shouldNotBeCalled()
override val lightClassOriginKind: LightClassOriginKind?
get() = shouldNotBeCalled()
override val methods: Collection<JavaMethod>
get() = shouldNotBeCalled()
override val fields: Collection<JavaField>
get() = shouldNotBeCalled()
override val constructors: Collection<JavaConstructor>
get() = shouldNotBeCalled()
override val recordComponents: Collection<JavaRecordComponent>
get() = shouldNotBeCalled()
override fun hasDefaultConstructor(): Boolean {
shouldNotBeCalled()
}
}
class DummyJavaTypeParameter(override val name: Name) : JavaTypeParameter {
override val isFromSource: Boolean
get() = shouldNotBeCalled()
override val annotations: Collection<JavaAnnotation>
get() = shouldNotBeCalled()
override val isDeprecatedInJavaDoc: Boolean
get() = shouldNotBeCalled()
override fun findAnnotation(fqName: FqName): JavaAnnotation? {
shouldNotBeCalled()
}
override val upperBounds: Collection<JavaClassifierType>
get() = emptyList()
}
private fun shouldNotBeCalled(): Nothing = error("should not be called")
@@ -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<T : JavaType>(val original: T, private val ownAnnotations: Collection<JavaAnnotation>?) : JavaType {
override val annotations: Collection<JavaAnnotation>
get() = ownAnnotations ?: original.annotations
override val isDeprecatedInJavaDoc: Boolean
get() = original.isDeprecatedInJavaDoc
}
class WrappedJavaArrayType(
original: JavaArrayType,
ownAnnotations: Collection<JavaAnnotation>?
) : WrappedJavaType<JavaArrayType>(original, ownAnnotations), JavaArrayType {
override val componentType: JavaType
get() = original.componentType
}
class WrappedJavaPrimitiveType(
original: JavaPrimitiveType,
ownAnnotations: Collection<JavaAnnotation>?
) : WrappedJavaType<JavaPrimitiveType>(original, ownAnnotations), JavaPrimitiveType {
override val type: PrimitiveType?
get() = original.type
}
class WrappedJavaWildcardType(
original: JavaWildcardType,
ownAnnotations: Collection<JavaAnnotation>?
) : WrappedJavaType<JavaWildcardType>(original, ownAnnotations), JavaWildcardType {
override val bound: JavaType?
get() = original.bound
override val isExtends: Boolean
get() = original.isExtends
}
class WrappedJavaClassifierType(
original: JavaClassifierType,
ownAnnotations: Collection<JavaAnnotation>?,
) : WrappedJavaType<JavaClassifierType>(original, ownAnnotations), JavaClassifierType {
override val classifier: JavaClassifier?
get() = original.classifier
override val typeArguments: List<JavaType?>
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<JavaAnnotation>): 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<JavaAnnotationArgument>
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<JavaType?>
) : JavaClassifierType {
override val annotations: Collection<JavaAnnotation>
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() }
}
+1
View File
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: User.java
import lombok.Builder;