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:
committed by
Space Team
parent
b28b0e70b6
commit
111bb461a9
@@ -12,8 +12,8 @@ import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.isAdvanced
|
||||
import org.jetbrains.kotlin.cli.common.arguments.resolvedDelimiter
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@JvmOverloads
|
||||
@@ -33,7 +33,7 @@ internal fun <T : CommonToolArguments> toArgumentStrings(
|
||||
): List<String> = ArrayList<String>().apply {
|
||||
val defaultArguments = type.newArgumentsInstance()
|
||||
type.memberProperties.forEach { property ->
|
||||
val argumentAnnotation = property.findAnnotation<Argument>() ?: return@forEach
|
||||
val argumentAnnotation = property.javaField?.getAnnotation(Argument::class.java) ?: return@forEach
|
||||
val rawPropertyValue = property.get(thisArguments)
|
||||
val rawDefaultValue = property.get(defaultArguments)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.config.JvmTarget
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
//used by IJ facet import
|
||||
@SuppressWarnings("unused")
|
||||
@@ -24,7 +25,8 @@ sealed class ExplicitDefaultSubstitutor {
|
||||
abstract fun isSubstitutable(args: List<String>): Boolean
|
||||
|
||||
protected val argument: Argument by lazy {
|
||||
substitutedProperty.findAnnotation() ?: error("Property \"${substitutedProperty.name}\" has no Argument annotation")
|
||||
substitutedProperty.javaField?.getAnnotation(Argument::class.java)
|
||||
?: error("Property \"${substitutedProperty.name}\" has no Argument annotation")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -14,10 +14,10 @@ import org.junit.jupiter.params.provider.MethodSource
|
||||
import java.util.Base64.getEncoder
|
||||
import kotlin.random.Random
|
||||
import kotlin.reflect.*
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.isSubtypeOf
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.full.withNullability
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
@@ -72,7 +72,7 @@ class CompilerArgumentParsingTest {
|
||||
private fun assertEqualArguments(expected: CommonToolArguments, actual: CommonToolArguments) {
|
||||
if (expected::class != actual::class) fail("Expected class '${expected::class}', found: '${actual::class}'")
|
||||
expected::class.memberProperties
|
||||
.filter { it.findAnnotation<Argument>() != null }
|
||||
.filter { it.javaField?.getAnnotation(Argument::class.java) != null }
|
||||
.ifEmpty { fail("No members with ${Argument::class} annotation") }
|
||||
.map { property ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.test.fail
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class CompilerArgumentsImplementationTest {
|
||||
@MethodSource("implementations")
|
||||
fun `test - all properties with Argument annotation - are public`(implementation: KClass<out CommonToolArguments>) {
|
||||
implementation.memberProperties.forEach { property ->
|
||||
if (property.findAnnotation<Argument>() != null) {
|
||||
if (property.javaField?.getAnnotation(Argument::class.java) != null) {
|
||||
if (property.visibility != KVisibility.PUBLIC) {
|
||||
fail(
|
||||
"Property '${property.name}: ${property.returnType}' " +
|
||||
|
||||
@@ -32,6 +32,7 @@ dependencies {
|
||||
compileOnly(toolsJarApi())
|
||||
compileOnly(intellijCore())
|
||||
compileOnly(commonDependency("org.jetbrains.intellij.deps:trove4j"))
|
||||
compileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
|
||||
|
||||
testApi(project(":compiler:backend"))
|
||||
testApi(project(":compiler:cli"))
|
||||
|
||||
+31
-21
@@ -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}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
package org.jetbrains.kotlin.cli.common;
|
||||
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import kotlin.jvm.JvmClassMappingKt;
|
||||
import kotlin.reflect.KCallable;
|
||||
import kotlin.reflect.KClass;
|
||||
import kotlin.reflect.KProperty1;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
import kotlin.text.StringsKt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.Argument;
|
||||
@@ -29,6 +29,8 @@ import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.ParseCommandLineArgumentsKt;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.PreprocessCommandLineArgumentsKt;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class Usage {
|
||||
public static final String BAT_DELIMITER_CHARACTERS_NOTE =
|
||||
"Note: on Windows, arguments that contain delimiter characters (whitespace, =, ;, ,) need to be surrounded with double quotes (\").";
|
||||
@@ -71,7 +73,8 @@ public class Usage {
|
||||
}
|
||||
|
||||
private static void propertyUsage(@NotNull StringBuilder sb, @NotNull KProperty1<?, ?> property, boolean extraHelp) {
|
||||
Argument argument = ContainerUtil.findInstance(property.getAnnotations(), Argument.class);
|
||||
Field field = ReflectJvmMapping.getJavaField(property);
|
||||
Argument argument = field.getAnnotation(Argument.class);
|
||||
if (argument == null) return;
|
||||
|
||||
if (extraHelp != ParseCommandLineArgumentsKt.isAdvanced(argument)) return;
|
||||
|
||||
@@ -16,17 +16,18 @@ import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.reflect.full.withNullability
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
// Additional properties that should be included in interface
|
||||
// Additional properties that should be included
|
||||
@Suppress("unused")
|
||||
interface AdditionalGradleProperties {
|
||||
private class AdditionalGradleProperties {
|
||||
@GradleOption(
|
||||
value = DefaultValue.EMPTY_STRING_LIST_DEFAULT,
|
||||
gradleInputType = GradleInputTypes.INPUT,
|
||||
shouldGenerateDeprecatedKotlinOptions = true,
|
||||
)
|
||||
@Argument(value = "", description = "A list of additional compiler arguments")
|
||||
var freeCompilerArgs: List<String>
|
||||
var freeCompilerArgs = listOf<String>()
|
||||
}
|
||||
|
||||
private data class GeneratedOptions(
|
||||
@@ -869,7 +870,7 @@ private fun Printer.generateOptionDeprecation(property: KProperty1<*, *>) {
|
||||
}
|
||||
|
||||
private fun Printer.generateDoc(property: KProperty1<*, *>) {
|
||||
val description = property.findAnnotation<Argument>()!!.description
|
||||
val description = property.javaField!!.getAnnotation(Argument::class.java).description
|
||||
val possibleValues = property.gradleValues.possibleValues
|
||||
val defaultValue = property.gradleValues.defaultValue
|
||||
|
||||
@@ -896,7 +897,7 @@ private fun generateMarkdown(properties: List<KProperty1<*, *>>) {
|
||||
if (name == "includeRuntime") continue // This option has no effect in Gradle builds
|
||||
val renderName = listOfNotNull("`$name`", property.findAnnotation<GradleDeprecatedOption>()?.let { "__(Deprecated)__" })
|
||||
.joinToString(" ")
|
||||
val description = property.findAnnotation<Argument>()!!.description
|
||||
val description = property.javaField!!.getAnnotation(Argument::class.java).description
|
||||
val possibleValues = property.gradleValues.possibleValues
|
||||
val defaultValue = when (property.gradleDefaultValue) {
|
||||
"null" -> ""
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
object CompilerArgumentsContentProspector {
|
||||
private val argumentPropertiesCache: MutableMap<KClass<out CommonToolArguments>, Collection<KProperty1<out CommonToolArguments, *>>> =
|
||||
@@ -24,7 +25,7 @@ object CompilerArgumentsContentProspector {
|
||||
mutableMapOf()
|
||||
|
||||
private fun getCompilerArgumentsProperties(kClass: KClass<out CommonToolArguments>) = argumentPropertiesCache.getOrPut(kClass) {
|
||||
kClass.memberProperties.filter { prop -> prop.annotations.any { it is Argument } }
|
||||
kClass.memberProperties.filter { prop -> prop.javaField?.getAnnotation(Argument::class.java) != null }
|
||||
}
|
||||
|
||||
private inline fun <reified R : Any?> Collection<KProperty1<out CommonToolArguments, *>>.filterByReturnType(predicate: (KType?) -> Boolean) =
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
|
||||
import org.jetbrains.kotlin.utils.DescriptionAware
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
@Deprecated("Use IdePlatformKind instead.", level = DeprecationLevel.ERROR)
|
||||
sealed class TargetPlatformKind<out Version : TargetPlatformVersion>(
|
||||
@@ -206,7 +206,7 @@ class KotlinFacetSettings {
|
||||
val isEnabledByCompilerArgument = compilerArguments?.safeAs<A>()?.let(settingReference::get)
|
||||
if (isEnabledByCompilerArgument == true) return true
|
||||
val isEnabledByAdditionalSettings = run {
|
||||
val stringArgumentName = settingReference.findAnnotation<Argument>()?.value ?: return@run null
|
||||
val stringArgumentName = settingReference.javaField?.getAnnotation(Argument::class.java)?.value ?: return@run null
|
||||
compilerSettings?.additionalArguments?.contains(stringArgumentName, ignoreCase = true)
|
||||
}
|
||||
return isEnabledByAdditionalSettings ?: false
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ import org.jetbrains.kotlin.gradle.util.assertNotNull
|
||||
import org.jetbrains.kotlin.gradle.util.buildProjectWithJvm
|
||||
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
|
||||
import org.jetbrains.kotlin.gradle.util.main
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ class KotlinCompileArgumentsTest {
|
||||
val arguments = mainCompilationTask.createCompilerArguments(lenient)
|
||||
|
||||
val argumentsString = ArgumentUtils.convertArgumentsToStringList(arguments)
|
||||
val jvmTargetArgument = K2JVMCompilerArguments::jvmTarget.findAnnotation<Argument>()!!.value
|
||||
val jvmTargetArgument = K2JVMCompilerArguments::jvmTarget.javaField!!.getAnnotation(Argument::class.java)!!.value
|
||||
if (jvmTargetArgument !in argumentsString) fail("Missing '$jvmTargetArgument' in argument list")
|
||||
val indexOfJvmTargetArgument = argumentsString.indexOf(jvmTargetArgument)
|
||||
val jvmTargetTargetArgumentValue = argumentsString.getOrNull(indexOfJvmTargetArgument + 1)
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticsCollector
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
|
||||
import org.jetbrains.kotlin.psi
|
||||
import org.jetbrains.kotlin.scripting.definitions.MessageReporter
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||
import kotlin.script.experimental.api.ScriptDiagnostic
|
||||
import kotlin.script.experimental.api.SourceCode
|
||||
@@ -194,7 +194,7 @@ private fun reportInvalidArguments(
|
||||
): Boolean {
|
||||
val invalidArgKeys = toIgnore.mapNotNull { argProperty ->
|
||||
if (argProperty.get(arguments) != argProperty.get(reportingState.currentArguments)) {
|
||||
argProperty.annotations.firstIsInstanceOrNull<Argument>()?.value
|
||||
argProperty.javaField?.getAnnotation(Argument::class.java)?.value
|
||||
?: throw IllegalStateException("unknown compiler argument property: $argProperty: no Argument annotation found")
|
||||
} else null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user