Support KClass.simpleName in stdlib-only reflection implementation

#KT-33646 Fixed
This commit is contained in:
Alexander Udalov
2019-10-24 18:33:46 +02:00
parent e146d308db
commit c164745301
15 changed files with 138 additions and 77 deletions
@@ -17,8 +17,8 @@
package org.jetbrains.kotlin.resolve.jvm.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.ReflectionTypes
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.NotFoundClasses
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.resolve.calls.checkers.AbstractReflectionApiCallChecker
@@ -32,10 +32,10 @@ import org.jetbrains.kotlin.storage.getValue
* of reflection API which will fail at runtime.
*/
class JvmReflectionAPICallChecker(
private val module: ModuleDescriptor,
notFoundClasses: NotFoundClasses,
storageManager: StorageManager
) : AbstractReflectionApiCallChecker(module, notFoundClasses, storageManager) {
private val module: ModuleDescriptor,
reflectionTypes: ReflectionTypes,
storageManager: StorageManager
) : AbstractReflectionApiCallChecker(reflectionTypes, storageManager) {
override val isWholeReflectionApiAvailable by storageManager.createLazyValue {
module.findClassAcrossModuleDependencies(JvmAbi.REFLECTION_FACTORY_IMPL) != null
}
@@ -21,8 +21,7 @@ import org.jetbrains.kotlin.builtins.KOTLIN_REFLECT_FQ_NAME
import org.jetbrains.kotlin.builtins.ReflectionTypes
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.NotFoundClasses
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -36,18 +35,21 @@ private val ANY_MEMBER_NAMES = setOf("equals", "hashCode", "toString")
* Checks that there are no usages of reflection API which will fail at runtime.
*/
abstract class AbstractReflectionApiCallChecker(
private val module: ModuleDescriptor,
private val notFoundClasses: NotFoundClasses,
private val reflectionTypes: ReflectionTypes,
storageManager: StorageManager
) : CallChecker {
protected abstract val isWholeReflectionApiAvailable: Boolean
protected abstract fun report(element: PsiElement, context: CallCheckerContext)
private val kPropertyClasses by storageManager.createLazyValue {
val reflectionTypes = ReflectionTypes(module, notFoundClasses)
setOf(reflectionTypes.kProperty0, reflectionTypes.kProperty1, reflectionTypes.kProperty2)
}
private val kClass by storageManager.createLazyValue { reflectionTypes.kClass }
protected open fun isAllowedKClassMember(name: Name): Boolean =
name.asString() == "simpleName"
final override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
if (isWholeReflectionApiAvailable) return
@@ -68,6 +70,7 @@ abstract class AbstractReflectionApiCallChecker(
return name.asString() in ANY_MEMBER_NAMES ||
name == OperatorNameConventions.INVOKE ||
name.asString() == "name" ||
DescriptorUtils.isSubclass(containingClass, kClass) && isAllowedKClassMember(descriptor.name) ||
(name.asString() == "get" || name.asString() == "set") && containingClass.isKPropertyClass()
}
@@ -79,4 +82,3 @@ abstract class AbstractReflectionApiCallChecker(
return fqName == KOTLIN_REFLECT_FQ_NAME.toUnsafe() || fqName.asString().startsWith(KOTLIN_REFLECT_FQ_NAME.asString() + ".")
}
}
@@ -1,8 +1,5 @@
// IGNORE_BACKEND: JS_IR
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS, NATIVE
// WITH_REFLECT
// TARGET_BACKEND: JVM
// WITH_RUNTIME
import kotlin.test.assertEquals
@@ -16,6 +13,10 @@ fun box(): String {
assertEquals("Array", Array<Any>::class.simpleName)
assertEquals("Array", Array<IntArray>::class.simpleName)
assertEquals("Array", Array<Array<String>>::class.simpleName)
assertEquals("IntArray", IntArray::class.simpleName)
assertEquals("DoubleArray", DoubleArray::class.simpleName)
assertEquals("Companion", Int.Companion::class.simpleName)
assertEquals("Companion", Double.Companion::class.simpleName)
@@ -24,9 +25,16 @@ fun box(): String {
assertEquals("IntRange", IntRange::class.simpleName)
assertEquals("List", List::class.simpleName)
assertEquals("Entry", Map.Entry::class.simpleName)
// TODO: this is wrong but should be fixed
// TODO: KT-11754
assertEquals("List", MutableList::class.simpleName)
assertEquals("Entry", MutableMap.MutableEntry::class.simpleName)
assertEquals("Function0", Function0::class.simpleName)
assertEquals("Function1", Function1::class.simpleName)
assertEquals("Function5", Function5::class.simpleName)
assertEquals("FunctionN", Function42::class.simpleName)
return "OK"
}
@@ -1,6 +1,5 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
// WITH_RUNTIME
import kotlin.test.assertEquals
@@ -10,7 +9,6 @@ fun box(): String {
assertEquals("Klass", Klass::class.simpleName)
assertEquals("Date", java.util.Date::class.simpleName)
assertEquals("ObjectRef", kotlin.jvm.internal.Ref.ObjectRef::class.simpleName)
assertEquals("Void", java.lang.Void::class.simpleName)
return "OK"
}
@@ -13,5 +13,7 @@ fun box(): String {
assertEquals(Void.TYPE, Void.TYPE.kotlin.javaPrimitiveType)
assertEquals(Void::class.java, Void.TYPE.kotlin.javaObjectType)
assertEquals("Void", Void::class.simpleName)
return "OK"
}
@@ -1,6 +1,6 @@
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND: NATIVE
// WITH_REFLECT
// WITH_RUNTIME
import kotlin.reflect.KClass
import kotlin.test.assertEquals
@@ -15,11 +15,11 @@ fun box(): String {
assertEquals("Klass", jClass.getSimpleName())
assertEquals("Klass", kjClass.getSimpleName())
assertEquals("Klass", kkClass.java.simpleName)
assertEquals("Klass", kClass.simpleName)
assertEquals(kjClass, jjClass)
try { kClass.simpleName; return "Fail 1" } catch (e: Error) {}
try { kClass.qualifiedName; return "Fail 2" } catch (e: Error) {}
try { kClass.members; return "Fail 3" } catch (e: Error) {}
try { kClass.qualifiedName; return "Fail qualifiedName" } catch (e: Error) {}
try { kClass.members; return "Fail members" } catch (e: Error) {}
val jlError = Error::class.java
val kljError = Error::class
@@ -29,6 +29,7 @@ fun box(): String {
assertEquals("Error", jlError.getSimpleName())
assertEquals("Error", jljError.getSimpleName())
assertEquals("Error", jlkError.java.simpleName)
assertEquals("Error", kljError.simpleName)
return "OK"
}
@@ -13,5 +13,7 @@ fun box(): String {
assertEquals(Void.TYPE, Void.TYPE.kotlin.javaPrimitiveType)
assertEquals(Void::class.java, Void.TYPE.kotlin.javaObjectType)
assertEquals("Void", Void::class.simpleName)
return "OK"
}
@@ -17,11 +17,11 @@ fun n11() = (Foo::func)(Foo(""))
fun y01() = Foo::prop.<!NO_REFLECTION_IN_CLASS_PATH!>getter<!>
fun y02() = Foo::class.<!NO_REFLECTION_IN_CLASS_PATH!>members<!>
fun y03() = Foo::class.<!NO_REFLECTION_IN_CLASS_PATH!>simpleName<!>
fun y03() = Foo::class.simpleName
fun y04() = Foo::class.<!UNRESOLVED_REFERENCE!>properties<!>
fun <T : Any> kclass(k: KClass<*>, kt: KClass<T>) {
k.<!NO_REFLECTION_IN_CLASS_PATH!>simpleName<!>
k.simpleName
k.<!NO_REFLECTION_IN_CLASS_PATH!>qualifiedName<!>
k.<!NO_REFLECTION_IN_CLASS_PATH!>members<!>
k.<!NO_REFLECTION_IN_CLASS_PATH!>constructors<!>
@@ -19,6 +19,9 @@ package kotlin.reflect.jvm.internal
import org.jetbrains.kotlin.builtins.CompanionObjectMapping
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.runtime.components.ReflectKotlinClass
import org.jetbrains.kotlin.descriptors.runtime.structure.functionClassArity
import org.jetbrains.kotlin.descriptors.runtime.structure.wrapperByPrimitive
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
@@ -32,13 +35,11 @@ import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.utils.compact
import kotlin.jvm.internal.ClassReference
import kotlin.jvm.internal.TypeIntrinsics
import kotlin.reflect.*
import kotlin.reflect.jvm.internal.KDeclarationContainerImpl.MemberBelonginess.DECLARED
import kotlin.reflect.jvm.internal.KDeclarationContainerImpl.MemberBelonginess.INHERITED
import org.jetbrains.kotlin.descriptors.runtime.components.ReflectKotlinClass
import org.jetbrains.kotlin.descriptors.runtime.structure.functionClassArity
import org.jetbrains.kotlin.descriptors.runtime.structure.wrapperByPrimitive
internal class KClassImpl<T : Any>(override val jClass: Class<T>) : KDeclarationContainerImpl(), KClass<T>, KClassifierImpl {
inner class Data : KDeclarationContainerImpl.Data() {
@@ -55,16 +56,6 @@ internal class KClassImpl<T : Any>(override val jClass: Class<T>) : KDeclaration
val annotations: List<Annotation> by ReflectProperties.lazySoft { descriptor.computeAnnotations() }
val simpleName: String? by ReflectProperties.lazySoft {
if (jClass.isAnonymousClass) return@lazySoft null
val classId = classId
when {
classId.isLocal -> calculateLocalClassName(jClass)
else -> classId.shortClassName.asString()
}
}
val qualifiedName: String? by ReflectProperties.lazySoft {
if (jClass.isAnonymousClass) return@lazySoft null
@@ -75,17 +66,6 @@ internal class KClassImpl<T : Any>(override val jClass: Class<T>) : KDeclaration
}
}
private fun calculateLocalClassName(jClass: Class<*>): String {
val name = jClass.simpleName
jClass.enclosingMethod?.let { method ->
return name.substringAfter(method.name + "$")
}
jClass.enclosingConstructor?.let { constructor ->
return name.substringAfter(constructor.name + "$")
}
return name.substringAfter('$')
}
@Suppress("UNCHECKED_CAST")
val constructors: Collection<KFunction<T>> by ReflectProperties.lazySoft {
constructorDescriptors.map { descriptor ->
@@ -180,7 +160,14 @@ internal class KClassImpl<T : Any>(override val jClass: Class<T>) : KDeclaration
override val annotations: List<Annotation> get() = data().annotations
private val classId: ClassId get() = RuntimeTypeMapper.mapJvmClassToKotlinClassId(jClass)
private val classId: ClassId
get() = RuntimeTypeMapper.mapJvmClassToKotlinClassId(jClass).also { result ->
if (!jClass.isAnonymousClass && !jClass.isLocalClass) {
assert(result.shortClassName.asString() == simpleName) {
"Incorrect class name computed for class ${jClass.name}. Result: $result. Expected simple name $simpleName"
}
}
}
// Note that we load members from the container's default type, which might be confusing. For example, a function declared in a
// generic class "A<T>" would have "A<T>" as the receiver parameter even if a concrete type like "A<String>" was specified
@@ -228,7 +215,7 @@ internal class KClassImpl<T : Any>(override val jClass: Class<T>) : KDeclaration
}
}
override val simpleName: String? get() = data().simpleName
override val simpleName: String? get() = ClassReference.getClassSimpleName(jClass)
override val qualifiedName: String? get() = data().qualifiedName
@@ -20,18 +20,14 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.ReflectionTypes
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.NotFoundClasses
import org.jetbrains.kotlin.diagnostics.Errors.UNSUPPORTED
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.calls.checkers.AbstractReflectionApiCallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.storage.getValue
private val ALLOWED_KCLASS_MEMBERS = setOf("simpleName", "isInstance")
private val ALLOWED_CLASSES = setOf(
FqName("kotlin.reflect.KType"),
FqName("kotlin.reflect.KTypeProjection"),
@@ -40,11 +36,12 @@ private val ALLOWED_CLASSES = setOf(
)
class JsReflectionAPICallChecker(
module: ModuleDescriptor,
private val reflectionTypes: ReflectionTypes,
notFoundClasses: NotFoundClasses,
reflectionTypes: ReflectionTypes,
storageManager: StorageManager
) : AbstractReflectionApiCallChecker(module, notFoundClasses, storageManager) {
) : AbstractReflectionApiCallChecker(reflectionTypes, storageManager) {
override fun isAllowedKClassMember(name: Name): Boolean =
super.isAllowedKClassMember(name) || name.asString() == "isInstance"
override val isWholeReflectionApiAvailable: Boolean
get() = false
@@ -52,10 +49,6 @@ class JsReflectionAPICallChecker(
context.trace.report(UNSUPPORTED.on(element, "This reflection API is not supported yet in JavaScript"))
}
private val kClass by storageManager.createLazyValue { reflectionTypes.kClass }
override fun isAllowedReflectionApi(descriptor: CallableDescriptor, containingClass: ClassDescriptor): Boolean =
super.isAllowedReflectionApi(descriptor, containingClass) ||
DescriptorUtils.isSubclass(containingClass, kClass) && descriptor.name.asString() in ALLOWED_KCLASS_MEMBERS ||
containingClass.fqNameSafe in ALLOWED_CLASSES
super.isAllowedReflectionApi(descriptor, containingClass) || containingClass.fqNameSafe in ALLOWED_CLASSES
}
@@ -18054,11 +18054,6 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/reflection/classLiterals/annotationClassLiteral.kt");
}
@TestMetadata("builtinClassLiterals.kt")
public void testBuiltinClassLiterals() throws Exception {
runTest("compiler/testData/codegen/box/reflection/classLiterals/builtinClassLiterals.kt");
}
@TestMetadata("genericClass.kt")
public void testGenericClass() throws Exception {
runTest("compiler/testData/codegen/box/reflection/classLiterals/genericClass.kt");
@@ -19164,11 +19164,6 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/reflection/classLiterals/annotationClassLiteral.kt");
}
@TestMetadata("builtinClassLiterals.kt")
public void testBuiltinClassLiterals() throws Exception {
runTest("compiler/testData/codegen/box/reflection/classLiterals/builtinClassLiterals.kt");
}
@TestMetadata("genericClass.kt")
public void testGenericClass() throws Exception {
runTest("compiler/testData/codegen/box/reflection/classLiterals/genericClass.kt");
@@ -9,7 +9,7 @@ import kotlin.reflect.*
public class ClassReference(override val jClass: Class<*>) : KClass<Any>, ClassBasedDeclarationContainer {
override val simpleName: String?
get() = error()
get() = getClassSimpleName(jClass)
override val qualifiedName: String?
get() = error()
@@ -86,4 +86,77 @@ public class ClassReference(override val jClass: Class<*>) : KClass<Any>, ClassB
override fun toString() =
jClass.toString() + Reflection.REFLECTION_NOT_AVAILABLE
companion object {
private val primitiveFqNames = HashMap<String, String>().apply {
put("boolean", "kotlin.Boolean")
put("char", "kotlin.Char")
put("byte", "kotlin.Byte")
put("short", "kotlin.Short")
put("int", "kotlin.Int")
put("float", "kotlin.Float")
put("long", "kotlin.Long")
put("double", "kotlin.Double")
}
private val primitiveWrapperFqNames = HashMap<String, String>().apply {
put("java.lang.Boolean", "kotlin.Boolean")
put("java.lang.Character", "kotlin.Char")
put("java.lang.Byte", "kotlin.Byte")
put("java.lang.Short", "kotlin.Short")
put("java.lang.Integer", "kotlin.Int")
put("java.lang.Float", "kotlin.Float")
put("java.lang.Long", "kotlin.Long")
put("java.lang.Double", "kotlin.Double")
}
// See JavaToKotlinClassMap.
private val classFqNames = HashMap<String, String>().apply {
put("java.lang.Object", "kotlin.Any")
put("java.lang.String", "kotlin.String")
put("java.lang.CharSequence", "kotlin.CharSequence")
put("java.lang.Throwable", "kotlin.Throwable")
put("java.lang.Cloneable", "kotlin.Cloneable")
put("java.lang.Number", "kotlin.Number")
put("java.lang.Comparable", "kotlin.Comparable")
put("java.lang.Enum", "kotlin.Enum")
put("java.lang.annotation.Annotation", "kotlin.Annotation")
put("java.lang.Iterable", "kotlin.collections.Iterable")
put("java.util.Iterator", "kotlin.collections.Iterator")
put("java.util.Collection", "kotlin.collections.Collection")
put("java.util.List", "kotlin.collections.List")
put("java.util.Set", "kotlin.collections.Set")
put("java.util.ListIterator", "kotlin.collections.ListIterator")
put("java.util.Map", "kotlin.collections.Map")
put("java.util.Map\$Entry", "kotlin.collections.Map.Entry")
put("kotlin.jvm.internal.StringCompanionObject", "kotlin.String.Companion")
put("kotlin.jvm.internal.EnumCompanionObject", "kotlin.Enum.Companion")
putAll(primitiveFqNames)
putAll(primitiveWrapperFqNames)
primitiveFqNames.values.associateTo(this) { kotlinName ->
"kotlin.jvm.internal.${kotlinName.substringAfterLast('.')}CompanionObject" to "$kotlinName.Companion"
}
}
private val simpleNames = classFqNames.mapValues { (_, fqName) -> fqName.substringAfterLast('.') }
public fun getClassSimpleName(jClass: Class<*>): String? = when {
jClass.isAnonymousClass -> null
jClass.isLocalClass -> {
val name = jClass.simpleName
jClass.enclosingMethod?.let { method -> name.substringAfter(method.name + "$") }
?: jClass.enclosingConstructor?.let { constructor -> name.substringAfter(constructor.name + "$") }
?: name.substringAfter('$')
}
jClass.isArray -> {
val componentType = jClass.componentType
when {
componentType.isPrimitive -> simpleNames[componentType.name]?.plus("Array")
else -> null
} ?: "Array"
}
else -> simpleNames[jClass.name] ?: jClass.simpleName
}
}
}
@@ -3269,6 +3269,7 @@ public abstract interface class kotlin/jvm/internal/ClassBasedDeclarationContain
}
public final class kotlin/jvm/internal/ClassReference : kotlin/jvm/internal/ClassBasedDeclarationContainer, kotlin/reflect/KClass {
public static final field Companion Lkotlin/jvm/internal/ClassReference$Companion;
public fun <init> (Ljava/lang/Class;)V
public fun equals (Ljava/lang/Object;)Z
public fun getAnnotations ()Ljava/util/List;
@@ -3295,6 +3296,10 @@ public final class kotlin/jvm/internal/ClassReference : kotlin/jvm/internal/Clas
public fun toString ()Ljava/lang/String;
}
public final class kotlin/jvm/internal/ClassReference$Companion {
public final fun getClassSimpleName (Ljava/lang/Class;)Ljava/lang/String;
}
public final class kotlin/jvm/internal/CollectionToArray {
public static final fun toArray (Ljava/util/Collection;)[Ljava/lang/Object;
public static final fun toArray (Ljava/util/Collection;[Ljava/lang/Object;)[Ljava/lang/Object;