CLI: Change kotlin reflection to java reflection

The command line argument parser is using between 0.25s and 0.5s
(depending on platform) on finding annotated properties. This fix
replaces the slow kotlin reflection with java reflection, which is an
order of magnitude faster.

 #KT-58183 Fixed
This commit is contained in:
Troels Bjerre Lund
2023-04-29 07:55:23 +02:00
committed by Space Team
parent b28b0e70b6
commit 111bb461a9
12 changed files with 61 additions and 43 deletions
@@ -18,13 +18,13 @@ package org.jetbrains.kotlin.cli.common.arguments
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.utils.SmartList
import java.lang.reflect.Method
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.cast
import kotlin.reflect.full.memberProperties
@Target(AnnotationTarget.PROPERTY)
@Target(AnnotationTarget.FIELD)
annotation class Argument(
val value: String,
val shortName: String = "",
@@ -120,13 +120,22 @@ private fun <A : CommonToolArguments> parsePreprocessedCommandLineArguments(
errors: Lazy<ArgumentParseErrors>,
overrideArguments: Boolean
) {
data class ArgumentField(val property: KMutableProperty1<A, Any?>, val argument: Argument)
data class ArgumentField(val getter: Method, val setter: Method, val argument: Argument)
@Suppress("UNCHECKED_CAST")
val properties = result::class.memberProperties.mapNotNull { property ->
if (property !is KMutableProperty1<*, *>) return@mapNotNull null
val argument = property.annotations.firstOrNull { it is Argument } as Argument? ?: return@mapNotNull null
ArgumentField(property as KMutableProperty1<A, Any?>, argument)
val superClasses = mutableListOf<Class<*>>(result::class.java)
while (superClasses.last() != Any::class.java) {
superClasses.add(superClasses.last().superclass)
}
val resultClass = result::class.java
val properties = superClasses.flatMap {
it.declaredFields.mapNotNull { field ->
field.getAnnotation(Argument::class.java)?.let { argument ->
val getter = resultClass.getMethod(JvmAbi.getterName(field.name))
val setter = resultClass.getMethod(JvmAbi.setterName(field.name), field.type)
ArgumentField(getter, setter, argument)
}
}
}
val visitedArgs = mutableSetOf<String>()
@@ -144,7 +153,7 @@ private fun <A : CommonToolArguments> parsePreprocessedCommandLineArguments(
}
if (argument.value == arg) {
if (argument.isAdvanced && property.returnType.classifier != Boolean::class) {
if (argument.isAdvanced && getter.returnType.kotlin != Boolean::class) {
errors.value.extraArgumentsPassedInObsoleteForm.add(arg)
}
return true
@@ -200,9 +209,9 @@ private fun <A : CommonToolArguments> parsePreprocessedCommandLineArguments(
continue
}
val (property, argument) = argumentField
val (getter, setter, argument) = argumentField
val value: Any = when {
argumentField.property.returnType.classifier == Boolean::class -> {
getter.returnType.kotlin == Boolean::class -> {
if (arg.startsWith(argument.value + "=")) {
// Can't use toBooleanStrict yet because this part of the compiler is used in Gradle and needs API version 1.4.
when (arg.substring(argument.value.length + 1)) {
@@ -227,13 +236,12 @@ private fun <A : CommonToolArguments> parsePreprocessedCommandLineArguments(
}
}
if ((argumentField.property.returnType.classifier as? KClass<*>)?.java?.isArray == false
&& !visitedArgs.add(argument.value) && value is String && property.get(result) != value
if (!getter.returnType.isArray && !visitedArgs.add(argument.value) && value is String && getter(result) != value
) {
errors.value.duplicateArguments[argument.value] = value
}
updateField(property, result, value, argument.resolvedDelimiter, overrideArguments)
updateField(getter, setter, result, value, argument.resolvedDelimiter, overrideArguments)
}
result.freeArgs += freeArgs
@@ -257,25 +265,27 @@ private fun <A : CommonToolArguments> A.updateInternalArguments(
}
private fun <A : CommonToolArguments> updateField(
property: KMutableProperty1<A, Any?>,
getter: Method,
setter: Method,
result: A,
value: Any,
delimiter: String?,
overrideArguments: Boolean
) {
when (property.returnType.classifier) {
Boolean::class, String::class -> property.set(result, value)
when (getter.returnType.kotlin) {
Boolean::class, String::class -> setter(result, value)
Array<String>::class -> {
val newElements = if (delimiter.isNullOrEmpty()) {
arrayOf(value as String)
} else {
(value as String).split(delimiter).toTypedArray()
}
@Suppress("UNCHECKED_CAST")
val oldValue = property.get(result) as Array<String>?
property.set(result, if (oldValue != null && !overrideArguments) arrayOf(*oldValue, *newElements) else newElements)
val oldValue = getter(result) as Array<String>?
setter(result, if (oldValue != null && !overrideArguments) arrayOf(*oldValue, *newElements) else newElements)
}
else -> throw IllegalStateException("Unsupported argument type: ${property.returnType}")
else -> throw IllegalStateException("Unsupported argument type: ${getter.returnType}")
}
}