[Commonizer] Support generic/simple annotation commonization (to support kotlinx.cinterop.* annotations)
^KT-59302 Verification Pending
This commit is contained in:
committed by
Space Team
parent
c2ad475153
commit
d510c93241
+1
@@ -1,3 +1,4 @@
|
|||||||
fun c(){
|
fun c(){
|
||||||
|
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
||||||
dummy.max(2,3)
|
dummy.max(2,3)
|
||||||
}
|
}
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
fun sn(){
|
fun sn(){
|
||||||
|
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
||||||
dummy.max(1,2)
|
dummy.max(1,2)
|
||||||
}
|
}
|
||||||
+1
@@ -1,5 +1,6 @@
|
|||||||
package includedLib
|
package includedLib
|
||||||
|
|
||||||
|
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
||||||
fun linuxArm64(
|
fun linuxArm64(
|
||||||
a: cinterop.a.StructA
|
a: cinterop.a.StructA
|
||||||
) {
|
) {
|
||||||
|
|||||||
+1
@@ -4,6 +4,7 @@ import kotlinx.cinterop.ExperimentalForeignApi
|
|||||||
import kotlinx.cinterop.alloc
|
import kotlinx.cinterop.alloc
|
||||||
import kotlinx.cinterop.memScoped
|
import kotlinx.cinterop.memScoped
|
||||||
|
|
||||||
|
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
||||||
fun linuxMain() {
|
fun linuxMain() {
|
||||||
cinterop.a.a()
|
cinterop.a.a()
|
||||||
@OptIn(ExperimentalForeignApi::class)
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
|
|||||||
+1
@@ -1,5 +1,6 @@
|
|||||||
package includedLib
|
package includedLib
|
||||||
|
|
||||||
|
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
||||||
fun linuxX64(
|
fun linuxX64(
|
||||||
a: cinterop.a.StructA
|
a: cinterop.a.StructA
|
||||||
) {
|
) {
|
||||||
|
|||||||
+1
@@ -1,5 +1,6 @@
|
|||||||
package lib
|
package lib
|
||||||
|
|
||||||
|
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
||||||
fun linuxArm64(
|
fun linuxArm64(
|
||||||
a: cinterop.lib.a.StructA
|
a: cinterop.lib.a.StructA
|
||||||
) {
|
) {
|
||||||
|
|||||||
+1
@@ -4,6 +4,7 @@ import kotlinx.cinterop.ExperimentalForeignApi
|
|||||||
import kotlinx.cinterop.alloc
|
import kotlinx.cinterop.alloc
|
||||||
import kotlinx.cinterop.memScoped
|
import kotlinx.cinterop.memScoped
|
||||||
|
|
||||||
|
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
||||||
fun linuxMain() {
|
fun linuxMain() {
|
||||||
cinterop.lib.a.a()
|
cinterop.lib.a.a()
|
||||||
@OptIn(ExperimentalForeignApi::class)
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
|
|||||||
+1
@@ -1,5 +1,6 @@
|
|||||||
package lib
|
package lib
|
||||||
|
|
||||||
|
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
||||||
fun linuxX64(
|
fun linuxX64(
|
||||||
a: cinterop.lib.a.StructA
|
a: cinterop.lib.a.StructA
|
||||||
) {
|
) {
|
||||||
|
|||||||
+36
-239
@@ -5,69 +5,48 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.commonizer.core
|
package org.jetbrains.kotlin.commonizer.core
|
||||||
|
|
||||||
import org.jetbrains.kotlin.commonizer.cir.*
|
import org.jetbrains.kotlin.commonizer.cir.CirAnnotation
|
||||||
import org.jetbrains.kotlin.commonizer.cir.CirConstantValue.*
|
import org.jetbrains.kotlin.commonizer.cir.CirClassType
|
||||||
import org.jetbrains.kotlin.commonizer.core.AnnotationsCommonizer.Companion.FALLBACK_MESSAGE
|
import org.jetbrains.kotlin.commonizer.cir.CirEntityId
|
||||||
import org.jetbrains.kotlin.commonizer.utils.*
|
import org.jetbrains.kotlin.commonizer.utils.COMMONIZER_OBJC_INTEROP_CALLABLE_ANNOTATION_ID
|
||||||
import kotlin.DeprecationLevel.WARNING
|
import org.jetbrains.kotlin.commonizer.utils.DEPRECATED_ANNOTATION_CLASS_ID
|
||||||
|
import org.jetbrains.kotlin.commonizer.utils.isObjCInteropCallableAnnotation
|
||||||
|
|
||||||
/**
|
private typealias Annotations = Map<CirEntityId, CirAnnotation>
|
||||||
* This is limited implementation of annotations commonizer. It helps to commonize only [kotlin.Deprecated] annotations.
|
|
||||||
*/
|
|
||||||
class AnnotationsCommonizer : AbstractStandardCommonizer<List<CirAnnotation>, List<CirAnnotation>>() {
|
|
||||||
private var deprecatedAnnotationCommonizer: DeprecatedAnnotationCommonizer? = null
|
|
||||||
private var deprecatedAnnotationCommonizerHasResult: Boolean = true
|
|
||||||
|
|
||||||
private val objCInteropCallableAnnotationCommonizer = ObjCInteropCallableAnnotationCommonizer.asCommonizer()
|
object AnnotationsCommonizer : AssociativeCommonizer<List<CirAnnotation>> {
|
||||||
private var objCInteropCallableAnnotationCommonizerHasResult = true
|
|
||||||
|
|
||||||
override fun commonizationResult(): List<CirAnnotation> {
|
|
||||||
val deprecatedAnnotation = if (deprecatedAnnotationCommonizerHasResult)
|
|
||||||
deprecatedAnnotationCommonizer?.result else null
|
|
||||||
|
|
||||||
val objCInteropCallableAnnotations = if (objCInteropCallableAnnotationCommonizerHasResult)
|
|
||||||
objCInteropCallableAnnotationCommonizer.result else emptyList()
|
|
||||||
|
|
||||||
return if (deprecatedAnnotation != null) {
|
|
||||||
objCInteropCallableAnnotations.plus(deprecatedAnnotation)
|
|
||||||
} else objCInteropCallableAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initialize(first: List<CirAnnotation>) = Unit
|
|
||||||
|
|
||||||
override fun doCommonizeWith(next: List<CirAnnotation>): Boolean {
|
|
||||||
if (deprecatedAnnotationCommonizerHasResult) {
|
|
||||||
deprecatedAnnotationCommonizerHasResult = doCommonizeDeprecatedAnnotation(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (objCInteropCallableAnnotationCommonizerHasResult) {
|
|
||||||
objCInteropCallableAnnotationCommonizerHasResult = objCInteropCallableAnnotationCommonizer.commonizeWith(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doCommonizeDeprecatedAnnotation(next: List<CirAnnotation>): Boolean {
|
|
||||||
val nextDeprecatedAnnotation = next.firstOrNull { it.type.classifierId == DEPRECATED_ANNOTATION_CLASS_ID } ?: return true
|
|
||||||
|
|
||||||
val deprecatedAnnotationCommonizer = deprecatedAnnotationCommonizer
|
|
||||||
?: DeprecatedAnnotationCommonizer().also { this.deprecatedAnnotationCommonizer = it }
|
|
||||||
|
|
||||||
return deprecatedAnnotationCommonizer.commonizeWith(nextDeprecatedAnnotation)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
internal const val FALLBACK_MESSAGE = "See concrete deprecation messages in actual declarations"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object ObjCInteropCallableAnnotationCommonizer : AssociativeCommonizer<List<CirAnnotation>> {
|
|
||||||
override fun commonize(first: List<CirAnnotation>, second: List<CirAnnotation>): List<CirAnnotation> {
|
override fun commonize(first: List<CirAnnotation>, second: List<CirAnnotation>): List<CirAnnotation> {
|
||||||
|
val firstAnnotations = first.associateBy { it.type.classifierId }
|
||||||
|
val secondAnnotations = second.associateBy { it.type.classifierId }
|
||||||
|
|
||||||
|
return setOfNotNull(
|
||||||
|
commonizedObjcCallableAnnotation(first, second),
|
||||||
|
commonizedDeprecatedAnnotation(firstAnnotations, secondAnnotations)
|
||||||
|
).plus(commonizedSimpleAnnotations(firstAnnotations, secondAnnotations)).toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun commonizedObjcCallableAnnotation(first: List<CirAnnotation>, second: List<CirAnnotation>): CirAnnotation? {
|
||||||
return if (first.any { it.type.classifierId.isObjCInteropCallableAnnotation } &&
|
return if (first.any { it.type.classifierId.isObjCInteropCallableAnnotation } &&
|
||||||
second.any { it.type.classifierId.isObjCInteropCallableAnnotation }
|
second.any { it.type.classifierId.isObjCInteropCallableAnnotation }
|
||||||
) {
|
) objCCallableAnnotation else null
|
||||||
objCCallableAnnotationList
|
}
|
||||||
} else emptyList()
|
|
||||||
|
private fun commonizedSimpleAnnotations(first: Annotations, second: Annotations): List<CirAnnotation> {
|
||||||
|
fun CirAnnotation.isSimple() = type.arguments.isEmpty() && !type.classifierId.isObjCInteropCallableAnnotation &&
|
||||||
|
annotationValueArguments.isEmpty() && constantValueArguments.isEmpty()
|
||||||
|
|
||||||
|
return first.values.filter { annotation ->
|
||||||
|
annotation.isSimple() && second[annotation.type.classifierId].let { secondAnnotation ->
|
||||||
|
secondAnnotation != null && secondAnnotation.isSimple()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun commonizedDeprecatedAnnotation(first: Annotations, second: Annotations): CirAnnotation? {
|
||||||
|
val firstDeprecation = first[DEPRECATED_ANNOTATION_CLASS_ID] ?: return null
|
||||||
|
val secondDeprecation = second[DEPRECATED_ANNOTATION_CLASS_ID] ?: return null
|
||||||
|
return DeprecationAnnotationCommonizer.commonize(firstDeprecation, secondDeprecation)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val objCCallableAnnotation = CirAnnotation.createInterned(
|
private val objCCallableAnnotation = CirAnnotation.createInterned(
|
||||||
@@ -78,186 +57,4 @@ object ObjCInteropCallableAnnotationCommonizer : AssociativeCommonizer<List<CirA
|
|||||||
constantValueArguments = emptyMap(),
|
constantValueArguments = emptyMap(),
|
||||||
annotationValueArguments = emptyMap()
|
annotationValueArguments = emptyMap()
|
||||||
)
|
)
|
||||||
|
|
||||||
private val objCCallableAnnotationList = listOf(objCCallableAnnotation)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DeprecatedAnnotationCommonizer : Commonizer<CirAnnotation, CirAnnotation> {
|
|
||||||
private var level: DeprecationLevel? = null // null level means that state is empty
|
|
||||||
private var message: String? = null // null -> message is not equal
|
|
||||||
private lateinit var replaceWithExpression: String
|
|
||||||
private lateinit var replaceWithImports: List<String>
|
|
||||||
|
|
||||||
override val result: CirAnnotation
|
|
||||||
get() {
|
|
||||||
val level: DeprecationLevel = level ?: failInEmptyState()
|
|
||||||
val messageValue: StringValue = message.toDeprecationMessageValue()
|
|
||||||
|
|
||||||
val constantValueArguments: Map<CirName, CirConstantValue> =
|
|
||||||
if (level == WARNING) {
|
|
||||||
// don't populate with the default level value
|
|
||||||
compactMapOf(PROPERTY_NAME_MESSAGE, messageValue)
|
|
||||||
} else
|
|
||||||
compactMapOf(
|
|
||||||
PROPERTY_NAME_MESSAGE, messageValue,
|
|
||||||
PROPERTY_NAME_LEVEL, level.toDeprecationLevelValue()
|
|
||||||
)
|
|
||||||
|
|
||||||
val annotationValueArguments: Map<CirName, CirAnnotation> =
|
|
||||||
if (replaceWithExpression.isEmpty() && replaceWithImports.isEmpty()) {
|
|
||||||
// don't populate with empty (default) ReplaceWith
|
|
||||||
emptyMap()
|
|
||||||
} else
|
|
||||||
compactMapOf(PROPERTY_NAME_REPLACE_WITH, replaceWithExpression.toReplaceWithValue(replaceWithImports))
|
|
||||||
|
|
||||||
return CirAnnotation.createInterned(
|
|
||||||
type = DEPRECATED_ANNOTATION_TYPE,
|
|
||||||
constantValueArguments = constantValueArguments,
|
|
||||||
annotationValueArguments = annotationValueArguments
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun commonizeWith(next: CirAnnotation): Boolean {
|
|
||||||
val nextLevel: DeprecationLevel = next.getDeprecationLevel() ?: WARNING
|
|
||||||
val nextMessage: String = next.getDeprecationMessage().orEmpty()
|
|
||||||
val nextReplaceWith: CirAnnotation? = next.getReplaceWith()
|
|
||||||
val nextReplaceWithExpression: String = nextReplaceWith?.getReplaceWithExpression().orEmpty()
|
|
||||||
val nextReplaceWithImports: List<String> = nextReplaceWith?.getReplaceWithImports().orEmpty()
|
|
||||||
|
|
||||||
return if (level != null) {
|
|
||||||
doCommonizeWith(nextLevel, nextMessage, nextReplaceWithExpression, nextReplaceWithImports)
|
|
||||||
} else {
|
|
||||||
// empty, just fill in
|
|
||||||
initialize(nextLevel, nextMessage, nextReplaceWithExpression, nextReplaceWithImports)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initialize(
|
|
||||||
nextLevel: DeprecationLevel,
|
|
||||||
nextMessage: String?,
|
|
||||||
nextReplaceWithExpression: String,
|
|
||||||
nextReplaceWithImports: List<String>
|
|
||||||
) {
|
|
||||||
level = nextLevel
|
|
||||||
message = nextMessage
|
|
||||||
replaceWithExpression = nextReplaceWithExpression
|
|
||||||
replaceWithImports = nextReplaceWithImports
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doCommonizeWith(
|
|
||||||
nextLevel: DeprecationLevel,
|
|
||||||
nextMessage: String?,
|
|
||||||
nextReplaceWithExpression: String,
|
|
||||||
nextReplaceWithImports: List<String>
|
|
||||||
): Boolean {
|
|
||||||
if (nextLevel.ordinal > level!!.ordinal)
|
|
||||||
level = nextLevel
|
|
||||||
|
|
||||||
if (nextMessage != message)
|
|
||||||
message = null
|
|
||||||
|
|
||||||
if (nextReplaceWithExpression != replaceWithExpression || nextReplaceWithImports != replaceWithImports) {
|
|
||||||
replaceWithExpression = ""
|
|
||||||
replaceWithImports = emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
companion object {
|
|
||||||
private val PROPERTY_NAME_MESSAGE = CirName.create(Deprecated::message.name)
|
|
||||||
private val PROPERTY_NAME_REPLACE_WITH = CirName.create(Deprecated::replaceWith.name)
|
|
||||||
private val PROPERTY_NAME_LEVEL = CirName.create(Deprecated::level.name)
|
|
||||||
|
|
||||||
private val PROPERTY_NAME_EXPRESSION = CirName.create(ReplaceWith::expression.name)
|
|
||||||
private val PROPERTY_NAME_IMPORTS = CirName.create(ReplaceWith::imports.name)
|
|
||||||
|
|
||||||
// Optimization: Keep most frequently used message constants.
|
|
||||||
private val FREQUENTLY_USED_MESSAGE_VALUES: Map<String, StringValue> = listOf(
|
|
||||||
"Use constructor instead",
|
|
||||||
"Use factory method instead"
|
|
||||||
).associateWith { StringValue(it) }
|
|
||||||
private val FALLBACK_MESSAGE_VALUE = StringValue(FALLBACK_MESSAGE)
|
|
||||||
|
|
||||||
private val DEPRECATED_ANNOTATION_TYPE = buildAnnotationType(DEPRECATED_ANNOTATION_CLASS_ID)
|
|
||||||
private val REPLACE_WITH_ANNOTATION_TYPE = buildAnnotationType(CirEntityId.create("kotlin/ReplaceWith"))
|
|
||||||
|
|
||||||
private val DEPRECATION_LEVEL_CLASS_ID = CirEntityId.create("kotlin/DeprecationLevel")
|
|
||||||
|
|
||||||
// Optimization: Keep DeprecationLevel enum constants.
|
|
||||||
private val DEPRECATION_LEVEL_ENUM_ENTRY_VALUES: Map<String, EnumValue> = DeprecationLevel.values().associate {
|
|
||||||
it.name to EnumValue(DEPRECATION_LEVEL_CLASS_ID, CirName.create(it.name))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildAnnotationType(classId: CirEntityId) = CirClassType.createInterned(
|
|
||||||
classId = classId,
|
|
||||||
outerType = null,
|
|
||||||
arguments = emptyList(),
|
|
||||||
isMarkedNullable = false
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun CirAnnotation.getDeprecationMessage(): String? = constantValueArguments.getString(PROPERTY_NAME_MESSAGE)
|
|
||||||
|
|
||||||
private fun String?.toDeprecationMessageValue(): StringValue =
|
|
||||||
if (this == null)
|
|
||||||
FALLBACK_MESSAGE_VALUE
|
|
||||||
else
|
|
||||||
FREQUENTLY_USED_MESSAGE_VALUES[this] ?: StringValue(this)
|
|
||||||
|
|
||||||
private fun CirAnnotation.getDeprecationLevel(): DeprecationLevel? {
|
|
||||||
val enumEntryName = constantValueArguments.getEnumEntryName(PROPERTY_NAME_LEVEL) ?: return null
|
|
||||||
return DeprecationLevel.values().firstOrNull { it.name == enumEntryName }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun DeprecationLevel.toDeprecationLevelValue(): EnumValue =
|
|
||||||
DEPRECATION_LEVEL_ENUM_ENTRY_VALUES.getValue(name)
|
|
||||||
|
|
||||||
private fun CirAnnotation.getReplaceWith(): CirAnnotation? =
|
|
||||||
annotationValueArguments.getAnnotation(PROPERTY_NAME_REPLACE_WITH)
|
|
||||||
|
|
||||||
private fun CirAnnotation.getReplaceWithExpression(): String? =
|
|
||||||
constantValueArguments.getString(PROPERTY_NAME_EXPRESSION)
|
|
||||||
|
|
||||||
private fun CirAnnotation.getReplaceWithImports(): List<String>? =
|
|
||||||
constantValueArguments.getStringArray(PROPERTY_NAME_IMPORTS)
|
|
||||||
|
|
||||||
private fun String.toReplaceWithValue(imports: List<String>): CirAnnotation =
|
|
||||||
createReplaceWithAnnotation(this, imports)
|
|
||||||
|
|
||||||
private inline fun Map<CirName, CirConstantValue>.getString(name: CirName): String? =
|
|
||||||
(this[name] as? StringValue)?.value
|
|
||||||
|
|
||||||
private inline fun Map<CirName, CirConstantValue>.getEnumEntryName(name: CirName): String? =
|
|
||||||
(this[name] as? EnumValue)?.enumEntryName?.name
|
|
||||||
|
|
||||||
private inline fun Map<CirName, CirAnnotation>.getAnnotation(name: CirName): CirAnnotation? =
|
|
||||||
this[name]
|
|
||||||
|
|
||||||
private inline fun Map<CirName, CirConstantValue>.getStringArray(name: CirName): List<String>? {
|
|
||||||
val elements: List<CirConstantValue> = (this[name] as? ArrayValue)?.elements ?: return null
|
|
||||||
if (elements.isEmpty()) return emptyList()
|
|
||||||
|
|
||||||
val result = ArrayList<String>(elements.size)
|
|
||||||
for (element in elements) {
|
|
||||||
if (element is StringValue) {
|
|
||||||
result += element.value
|
|
||||||
} else
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun createReplaceWithAnnotation(expression: String, imports: List<String>): CirAnnotation =
|
|
||||||
CirAnnotation.createInterned(
|
|
||||||
type = REPLACE_WITH_ANNOTATION_TYPE,
|
|
||||||
constantValueArguments = compactMapOf(
|
|
||||||
PROPERTY_NAME_EXPRESSION, StringValue(expression),
|
|
||||||
PROPERTY_NAME_IMPORTS, ArrayValue(imports.compactMap(::StringValue))
|
|
||||||
),
|
|
||||||
annotationValueArguments = emptyMap()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ class ClassConstructorCommonizer(
|
|||||||
private val visibility = VisibilityCommonizer.equalizing()
|
private val visibility = VisibilityCommonizer.equalizing()
|
||||||
private val typeParameterListCommonizer = TypeParameterListCommonizer(typeCommonizer)
|
private val typeParameterListCommonizer = TypeParameterListCommonizer(typeCommonizer)
|
||||||
private val valueParametersCommonizer = CallableValueParametersCommonizer(typeCommonizer)
|
private val valueParametersCommonizer = CallableValueParametersCommonizer(typeCommonizer)
|
||||||
private val annotationsCommonizer: AnnotationsCommonizer = AnnotationsCommonizer()
|
private val annotationsCommonizer = AnnotationsCommonizer.asCommonizer()
|
||||||
|
|
||||||
override fun commonizationResult(): CirClassConstructor? {
|
override fun commonizationResult(): CirClassConstructor? {
|
||||||
val valueParameters = valueParametersCommonizer.result ?: return null
|
val valueParameters = valueParametersCommonizer.result ?: return null
|
||||||
|
|||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 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.commonizer.core
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.commonizer.cir.*
|
||||||
|
import org.jetbrains.kotlin.commonizer.utils.DEPRECATED_ANNOTATION_CLASS_ID
|
||||||
|
import org.jetbrains.kotlin.commonizer.utils.compactMap
|
||||||
|
import org.jetbrains.kotlin.commonizer.utils.compactMapOf
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
object DeprecationAnnotationCommonizer : AssociativeCommonizer<CirAnnotation> {
|
||||||
|
override fun commonize(first: CirAnnotation, second: CirAnnotation): CirAnnotation {
|
||||||
|
val deprecationLevel = run {
|
||||||
|
val firstLevel = first.getDeprecationLevel() ?: DeprecationLevel.WARNING
|
||||||
|
val secondLevel = second.getDeprecationLevel() ?: DeprecationLevel.WARNING
|
||||||
|
if (secondLevel.ordinal > firstLevel.ordinal) secondLevel else firstLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
val deprecationMessage = run {
|
||||||
|
val firstMessage = first.getDeprecationMessage() ?: return@run null
|
||||||
|
val secondMessage = second.getDeprecationMessage() ?: return@run null
|
||||||
|
if (firstMessage == secondMessage) firstMessage else null
|
||||||
|
}
|
||||||
|
|
||||||
|
val replaceWith = run {
|
||||||
|
val firstReplaceWith = first.getReplaceWith() ?: return@run null
|
||||||
|
val secondReplaceWith = second.getReplaceWith() ?: return@run null
|
||||||
|
|
||||||
|
val firstReplaceWithExpression = firstReplaceWith.getReplaceWithExpression().orEmpty()
|
||||||
|
val secondReplaceWithExpression = secondReplaceWith.getReplaceWithExpression().orEmpty()
|
||||||
|
|
||||||
|
val firstReplaceWithImports = firstReplaceWith.getReplaceWithImports().orEmpty()
|
||||||
|
val secondReplaceWithImports = secondReplaceWith.getReplaceWithImports().orEmpty()
|
||||||
|
|
||||||
|
if (
|
||||||
|
firstReplaceWithExpression == secondReplaceWithExpression &&
|
||||||
|
firstReplaceWithImports == secondReplaceWithImports &&
|
||||||
|
/* Empty replace with */
|
||||||
|
(firstReplaceWithExpression.isNotEmpty() || firstReplaceWithImports.isNotEmpty())
|
||||||
|
) firstReplaceWithExpression.toReplaceWithValue(firstReplaceWithImports) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val constantValueArguments: Map<CirName, CirConstantValue> = if (deprecationLevel == DeprecationLevel.WARNING) {
|
||||||
|
// don't populate with the default level value
|
||||||
|
compactMapOf(PROPERTY_NAME_MESSAGE, deprecationMessage.toDeprecationMessageValue())
|
||||||
|
} else compactMapOf(
|
||||||
|
PROPERTY_NAME_MESSAGE, deprecationMessage.toDeprecationMessageValue(),
|
||||||
|
PROPERTY_NAME_LEVEL, deprecationLevel.toDeprecationLevelValue()
|
||||||
|
)
|
||||||
|
|
||||||
|
val annotationValueArguments: Map<CirName, CirAnnotation> = if (replaceWith == null) {
|
||||||
|
// don't populate with empty (default) ReplaceWith
|
||||||
|
emptyMap()
|
||||||
|
} else compactMapOf(PROPERTY_NAME_REPLACE_WITH, replaceWith)
|
||||||
|
|
||||||
|
return CirAnnotation.createInterned(
|
||||||
|
type = DEPRECATED_ANNOTATION_TYPE,
|
||||||
|
constantValueArguments = constantValueArguments,
|
||||||
|
annotationValueArguments = annotationValueArguments
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val PROPERTY_NAME_MESSAGE = CirName.create(Deprecated::message.name)
|
||||||
|
private val PROPERTY_NAME_REPLACE_WITH = CirName.create(Deprecated::replaceWith.name)
|
||||||
|
private val PROPERTY_NAME_LEVEL = CirName.create(Deprecated::level.name)
|
||||||
|
|
||||||
|
private val PROPERTY_NAME_EXPRESSION = CirName.create(ReplaceWith::expression.name)
|
||||||
|
private val PROPERTY_NAME_IMPORTS = CirName.create(ReplaceWith::imports.name)
|
||||||
|
|
||||||
|
internal const val FALLBACK_MESSAGE = "See concrete deprecation messages in actual declarations"
|
||||||
|
|
||||||
|
|
||||||
|
// Optimization: Keep most frequently used message constants.
|
||||||
|
private val FREQUENTLY_USED_MESSAGE_VALUES: Map<String, CirConstantValue.StringValue> = listOf(
|
||||||
|
"Use constructor instead",
|
||||||
|
"Use factory method instead"
|
||||||
|
).associateWith { CirConstantValue.StringValue(it) }
|
||||||
|
private val FALLBACK_MESSAGE_VALUE = CirConstantValue.StringValue(FALLBACK_MESSAGE)
|
||||||
|
|
||||||
|
private val DEPRECATED_ANNOTATION_TYPE = buildAnnotationType(DEPRECATED_ANNOTATION_CLASS_ID)
|
||||||
|
private val REPLACE_WITH_ANNOTATION_TYPE = buildAnnotationType(CirEntityId.create("kotlin/ReplaceWith"))
|
||||||
|
|
||||||
|
private val DEPRECATION_LEVEL_CLASS_ID = CirEntityId.create("kotlin/DeprecationLevel")
|
||||||
|
|
||||||
|
// Optimization: Keep DeprecationLevel enum constants.
|
||||||
|
private val DEPRECATION_LEVEL_ENUM_ENTRY_VALUES: Map<String, CirConstantValue.EnumValue> = DeprecationLevel.entries.associate {
|
||||||
|
it.name to CirConstantValue.EnumValue(DEPRECATION_LEVEL_CLASS_ID, CirName.create(it.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildAnnotationType(classId: CirEntityId) = CirClassType.createInterned(
|
||||||
|
classId = classId,
|
||||||
|
outerType = null,
|
||||||
|
arguments = emptyList(),
|
||||||
|
isMarkedNullable = false
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun CirAnnotation.getDeprecationMessage(): String? = constantValueArguments.getString(PROPERTY_NAME_MESSAGE)
|
||||||
|
|
||||||
|
private fun String?.toDeprecationMessageValue(): CirConstantValue.StringValue =
|
||||||
|
if (this == null)
|
||||||
|
FALLBACK_MESSAGE_VALUE
|
||||||
|
else
|
||||||
|
FREQUENTLY_USED_MESSAGE_VALUES[this] ?: CirConstantValue.StringValue(this)
|
||||||
|
|
||||||
|
private fun CirAnnotation.getDeprecationLevel(): DeprecationLevel? {
|
||||||
|
val enumEntryName = constantValueArguments.getEnumEntryName(PROPERTY_NAME_LEVEL) ?: return null
|
||||||
|
return DeprecationLevel.entries.firstOrNull { it.name == enumEntryName }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DeprecationLevel.toDeprecationLevelValue(): CirConstantValue.EnumValue =
|
||||||
|
DEPRECATION_LEVEL_ENUM_ENTRY_VALUES.getValue(name)
|
||||||
|
|
||||||
|
private fun CirAnnotation.getReplaceWith(): CirAnnotation? =
|
||||||
|
annotationValueArguments.getAnnotation(PROPERTY_NAME_REPLACE_WITH)
|
||||||
|
|
||||||
|
private fun CirAnnotation.getReplaceWithExpression(): String? =
|
||||||
|
constantValueArguments.getString(PROPERTY_NAME_EXPRESSION)
|
||||||
|
|
||||||
|
private fun CirAnnotation.getReplaceWithImports(): List<String>? =
|
||||||
|
constantValueArguments.getStringArray(PROPERTY_NAME_IMPORTS)
|
||||||
|
|
||||||
|
private fun String.toReplaceWithValue(imports: List<String>): CirAnnotation =
|
||||||
|
createReplaceWithAnnotation(this, imports)
|
||||||
|
|
||||||
|
private inline fun Map<CirName, CirConstantValue>.getString(name: CirName): String? =
|
||||||
|
(this[name] as? CirConstantValue.StringValue)?.value
|
||||||
|
|
||||||
|
private inline fun Map<CirName, CirConstantValue>.getEnumEntryName(name: CirName): String? =
|
||||||
|
(this[name] as? CirConstantValue.EnumValue)?.enumEntryName?.name
|
||||||
|
|
||||||
|
private inline fun Map<CirName, CirAnnotation>.getAnnotation(name: CirName): CirAnnotation? =
|
||||||
|
this[name]
|
||||||
|
|
||||||
|
private inline fun Map<CirName, CirConstantValue>.getStringArray(name: CirName): List<String>? {
|
||||||
|
val elements: List<CirConstantValue> = (this[name] as? CirConstantValue.ArrayValue)?.elements ?: return null
|
||||||
|
if (elements.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
val result = ArrayList<String>(elements.size)
|
||||||
|
for (element in elements) {
|
||||||
|
if (element is CirConstantValue.StringValue) {
|
||||||
|
result += element.value
|
||||||
|
} else
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun createReplaceWithAnnotation(expression: String, imports: List<String>): CirAnnotation =
|
||||||
|
CirAnnotation.createInterned(
|
||||||
|
type = REPLACE_WITH_ANNOTATION_TYPE,
|
||||||
|
constantValueArguments = compactMapOf(
|
||||||
|
PROPERTY_NAME_EXPRESSION, CirConstantValue.StringValue(expression),
|
||||||
|
PROPERTY_NAME_IMPORTS, CirConstantValue.ArrayValue(imports.compactMap(CirConstantValue::StringValue))
|
||||||
|
),
|
||||||
|
annotationValueArguments = emptyMap()
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ class FunctionCommonizer(
|
|||||||
val functionOrProperty = functionOrPropertyBaseCommonizer(values) ?: return null
|
val functionOrProperty = functionOrPropertyBaseCommonizer(values) ?: return null
|
||||||
val valueParametersResult = CallableValueParametersCommonizer(typeCommonizer).commonize(values) ?: return null
|
val valueParametersResult = CallableValueParametersCommonizer(typeCommonizer).commonize(values) ?: return null
|
||||||
return CirFunction(
|
return CirFunction(
|
||||||
annotations = AnnotationsCommonizer().commonize(values.map { it.annotations })
|
annotations = AnnotationsCommonizer.commonize(values.map { it.annotations.toList() })
|
||||||
?.plus(functionOrProperty.additionalAnnotations)
|
?.plus(functionOrProperty.additionalAnnotations)
|
||||||
?: return null,
|
?: return null,
|
||||||
name = values.first().name,
|
name = values.first().name,
|
||||||
|
|||||||
+4
-4
@@ -42,7 +42,7 @@ class AnnotationsCommonizerTest : AbstractCommonizerTest<List<CirAnnotation>, Li
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun noRelevantAnnotations3() = doTestSuccess(
|
fun noRelevantAnnotations3() = doTestSuccess(
|
||||||
expected = emptyList(),
|
expected = listOf(mockAnnotation("org/sample/Foo")),
|
||||||
listOf(mockAnnotation("org/sample/Foo")),
|
listOf(mockAnnotation("org/sample/Foo")),
|
||||||
listOf(mockAnnotation("org/sample/Foo")),
|
listOf(mockAnnotation("org/sample/Foo")),
|
||||||
listOf(mockAnnotation("org/sample/Foo"))
|
listOf(mockAnnotation("org/sample/Foo"))
|
||||||
@@ -58,7 +58,7 @@ class AnnotationsCommonizerTest : AbstractCommonizerTest<List<CirAnnotation>, Li
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun noRelevantAnnotations5() = doTestSuccess(
|
fun noRelevantAnnotations5() = doTestSuccess(
|
||||||
expected = emptyList(),
|
expected = listOf(mockAnnotation("kotlin/PublishedApi")),
|
||||||
listOf(mockAnnotation("kotlin/PublishedApi")),
|
listOf(mockAnnotation("kotlin/PublishedApi")),
|
||||||
listOf(mockAnnotation("kotlin/PublishedApi")),
|
listOf(mockAnnotation("kotlin/PublishedApi")),
|
||||||
listOf(mockAnnotation("kotlin/PublishedApi"))
|
listOf(mockAnnotation("kotlin/PublishedApi"))
|
||||||
@@ -74,7 +74,7 @@ class AnnotationsCommonizerTest : AbstractCommonizerTest<List<CirAnnotation>, Li
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun differentMessages() = doTestSuccess(
|
fun differentMessages() = doTestSuccess(
|
||||||
expected = listOf(mockDeprecated(message = AnnotationsCommonizer.FALLBACK_MESSAGE)),
|
expected = listOf(mockDeprecated(message = DeprecationAnnotationCommonizer.FALLBACK_MESSAGE)),
|
||||||
listOf(mockDeprecated(message = "please don't use it because ...")),
|
listOf(mockDeprecated(message = "please don't use it because ...")),
|
||||||
listOf(mockDeprecated(message = "it should not be used due to ...")),
|
listOf(mockDeprecated(message = "it should not be used due to ...")),
|
||||||
listOf(mockDeprecated(message = "please don't use it because ..."))
|
listOf(mockDeprecated(message = "please don't use it because ..."))
|
||||||
@@ -276,7 +276,7 @@ class AnnotationsCommonizerTest : AbstractCommonizerTest<List<CirAnnotation>, Li
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun createCommonizer() = AnnotationsCommonizer()
|
override fun createCommonizer() = AnnotationsCommonizer.asCommonizer()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mockAnnotation(
|
private fun mockAnnotation(
|
||||||
|
|||||||
+33
@@ -135,4 +135,37 @@ class HierarchicalFunctionCommonizationTest : AbstractInlineSourcesCommonization
|
|||||||
result.assertCommonized("(a, b)", "expect fun x(): ABCD")
|
result.assertCommonized("(a, b)", "expect fun x(): ABCD")
|
||||||
result.assertCommonized("((a,b), c)", "expect fun x(): ABCD")
|
result.assertCommonized("((a,b), c)", "expect fun x(): ABCD")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun `test function with simple annotation`() {
|
||||||
|
val result = commonize {
|
||||||
|
outputTarget("(a, b)")
|
||||||
|
registerDependency("a", "b", "(a, b)") { source("annotation class FooAnnotation") }
|
||||||
|
simpleSingleSourceTarget("a", "@FooAnnotation fun x() = Unit")
|
||||||
|
simpleSingleSourceTarget("b", "@FooAnnotation fun x() = Unit")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.assertCommonized("(a, b)", "@FooAnnotation expect fun x()")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test function with non-simple annotation - 1`() {
|
||||||
|
val result = commonize {
|
||||||
|
outputTarget("(a, b)")
|
||||||
|
registerDependency("a", "b", "(a, b)") { source("annotation class FooAnnotation(val param: String)") }
|
||||||
|
simpleSingleSourceTarget("a", """@FooAnnotation("a") fun x() = Unit""")
|
||||||
|
simpleSingleSourceTarget("b", """@FooAnnotation("b") fun x() = Unit""")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.assertCommonized("(a, b)", "expect fun x()")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test function with non-simple annotation - 2`() {
|
||||||
|
val result = commonize {
|
||||||
|
outputTarget("(a, b)")
|
||||||
|
registerDependency("a", "b", "(a, b)") { source("annotation class FooAnnotation<T: Any>(val param: String)") }
|
||||||
|
simpleSingleSourceTarget("a", """@FooAnnotation<Unit>("a") fun x() = Unit""")
|
||||||
|
simpleSingleSourceTarget("b", """@FooAnnotation<Unit>("b") fun x() = Unit""")
|
||||||
|
}
|
||||||
|
|
||||||
|
result.assertCommonized("(a, b)", "expect fun x()")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user