Use descriptors for reflection on extension properties

This commit is contained in:
Alexander Udalov
2015-02-20 18:04:30 +03:00
parent 1a618deeab
commit ff0044f66a
10 changed files with 199 additions and 105 deletions
@@ -3,10 +3,13 @@ import kotlin.test.assertEquals
fun check(expected: String, p: KExtensionProperty<*, *>) {
var s = p.toString()
// Strip "val" or "var"
assert(s.startsWith("val ") || s.startsWith("var ")) { "Fail val/var: $s" }
s = s.substring(4)
// Strip property name, leave only receiver class
s = s.substring(0, s.lastIndexOf('.'))
s = s.substringBeforeLast('.')
assertEquals(expected, s)
}
@@ -34,7 +37,14 @@ val Array<Any>.a2: Any get() = this
val Array<Array<String>>.a3: Any get() = this
val Array<BooleanArray>.a4: Any get() = this
val Any?.n1: Any get() = Any()
val Int?.n2: Any get() = Any()
val Array<Any>?.n3: Any get() = Any()
val Array<Any?>.n4: Any get() = Any()
val Array<Any?>?.n5: Any get() = Any()
val Map<String, Runnable>.m: Any get() = this
val List<MutableSet<Array<CharSequence>>>.l: Any get() = this
fun box(): String {
check("kotlin.Boolean", Boolean::x)
@@ -55,12 +65,19 @@ fun box(): String {
check("kotlin.LongArray", LongArray::x)
check("kotlin.DoubleArray", DoubleArray::x)
check("kotlin.Array<java.lang.Integer>", Array<Int>::a1)
check("kotlin.Array<java.lang.Object>", Array<Any>::a2)
check("kotlin.Array<kotlin.Array<java.lang.String>>", Array<Array<String>>::a3)
check("kotlin.Any?", Any?::n1)
check("kotlin.Int?", Int?::n2)
check("kotlin.Array<kotlin.Any>?", Array<Any>?::n3)
check("kotlin.Array<kotlin.Any?>", Array<Any?>::n4)
check("kotlin.Array<kotlin.Any?>?", Array<Any?>?::n5)
check("kotlin.Array<kotlin.Int>", Array<Int>::a1)
check("kotlin.Array<kotlin.Any>", Array<Any>::a2)
check("kotlin.Array<kotlin.Array<kotlin.String>>", Array<Array<String>>::a3)
check("kotlin.Array<kotlin.BooleanArray>", Array<BooleanArray>::a4)
check("java.util.Map", Map<String, Runnable>::m)
check("kotlin.Map<kotlin.String, java.lang.Runnable>", Map<String, Runnable>::m)
check("kotlin.List<kotlin.MutableSet<kotlin.Array<kotlin.CharSequence>>>", List<MutableSet<Array<CharSequence>>>::l)
return "OK"
}
@@ -18,8 +18,8 @@ fun assertToString(s: String, x: Any) {
fun box(): String {
assertToString("val top", ::top)
assertToString("var top2", ::top2)
assertToString("val java.lang.String.ext", String::ext)
assertToString("var kotlin.IntRange.ext2", IntRange::ext2)
assertToString("val kotlin.String.ext", String::ext)
assertToString("var kotlin.IntRange?.ext2", IntRange::ext2)
assertToString("val test.A.mem", A::mem)
assertToString("var test.B.mem", B::mem)
return "OK"
@@ -56,5 +56,14 @@ public val Class<*>.classId: ClassId
else -> getDeclaringClass()?.classId?.createNestedClassId(Name.identifier(getSimpleName())) ?: ClassId.topLevel(FqName(getName()))
}
public val Class<*>.desc: String
get() {
if (this == Void.TYPE) return "V"
// This is a clever exploitation of a format returned by Class.getName(): for arrays, it's almost an internal name,
// but with '.' instead of '/'
// TODO: ensure there are tests on arrays of nested classes, multi-dimensional arrays, etc.
return createArrayType().getName().substring(1).replace('.', '/')
}
public fun Class<*>.createArrayType(): Class<*> =
Array.newInstance(this, 0).javaClass
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.load.kotlin.reflect
import org.jetbrains.kotlin.load.java.structure.reflect.classId
import org.jetbrains.kotlin.load.java.structure.reflect.desc
import org.jetbrains.kotlin.load.java.structure.reflect.isEnumClassOrSpecializedEnumEntryClass
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
@@ -209,10 +210,10 @@ private object SignatureSerializer {
val sb = StringBuilder()
sb.append("(")
for (parameterType in method.getParameterTypes()) {
sb.append(typeDesc(parameterType))
sb.append(parameterType.desc)
}
sb.append(")")
sb.append(typeDesc(method.getReturnType()))
sb.append(method.getReturnType().desc)
return sb.toString()
}
@@ -220,23 +221,13 @@ private object SignatureSerializer {
val sb = StringBuilder()
sb.append("(")
for (parameterType in constructor.getParameterTypes()) {
sb.append(typeDesc(parameterType))
sb.append(parameterType.desc)
}
sb.append(")V")
return sb.toString()
}
fun fieldDesc(field: Field): String {
return typeDesc(field.getType())
}
suppress("UNCHECKED_CAST")
fun typeDesc(clazz: Class<*>): String {
if (clazz == Void.TYPE) return "V";
// This is a clever exploitation of a format returned by Class.getName(): for arrays, it's almost an internal name,
// but with '.' instead of '/'
// TODO: ensure there are tests on arrays of nested classes, multi-dimensional arrays, etc.
val arrayClass = java.lang.reflect.Array.newInstance(clazz as Class<Any>, 0).javaClass
return arrayClass.getName().substring(1).replace('.', '/')
return field.getType().desc
}
}
@@ -17,6 +17,7 @@
package kotlin.reflect.jvm.internal
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.load.java.structure.reflect.desc
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.scopes.JetScope
import kotlin.reflect.KotlinReflectionInternalError
@@ -26,12 +27,25 @@ abstract class KCallableContainerImpl {
abstract val scope: JetScope
protected fun findPropertyDescriptor(name: String): () -> PropertyDescriptor = {
val properties = scope.getProperties(Name.identifier(name))
protected fun findPropertyDescriptor(name: String, receiverParameterClass: Class<*>? = null): () -> PropertyDescriptor = {
val receiverDesc = receiverParameterClass?.desc
val properties = scope
.getProperties(Name.identifier(name))
.filter { descriptor ->
descriptor is PropertyDescriptor &&
descriptor.getName().asString() == name &&
with(descriptor.getExtensionReceiverParameter()) {
(this == null && receiverDesc == null) ||
(this != null && RuntimeTypeMapper.mapTypeToJvmDesc(getType()) == receiverDesc)
}
}
if (properties.size() != 1) {
val debugText = if (receiverParameterClass == null) name else "${receiverParameterClass.getSimpleName()}.$name"
throw KotlinReflectionInternalError(
if (properties.isEmpty()) "Property $name not resolved in $this"
else "${properties.size()} properties with name $name resolved in $this"
if (properties.isEmpty()) "Property '$debugText' not resolved in $this"
else "${properties.size()} properties '$debugText' resolved in $this"
)
}
@@ -17,7 +17,6 @@
package kotlin.reflect.jvm.internal
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.load.java.structure.reflect.classId
import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
import kotlin.jvm.internal.KotlinClass
@@ -38,7 +37,7 @@ class KClassImpl<T>(override val jClass: Class<T>) : KCallableContainerImpl(), K
val descriptor by ReflectProperties.lazySoft {(): ClassDescriptor ->
val moduleData = jClass.getOrCreateModule()
val classId = jClass.classId
val classId = RuntimeTypeMapper.mapJvmClassToKotlinClassId(jClass)
val descriptor =
if (classId.isLocal()) moduleData.localClassResolver.resolveLocalClass(classId)
@@ -32,21 +32,19 @@ class KPackageImpl(override val jClass: Class<*>) : KCallableContainerImpl(), KP
override val scope: JetScope get() = descriptor.getMemberScope()
fun topLevelVariable(name: String): KTopLevelVariable<*> {
val descriptor = findPropertyDescriptor(name)
return KTopLevelVariableImpl(this, descriptor)
return KTopLevelVariableImpl(this, findPropertyDescriptor(name))
}
fun mutableTopLevelVariable(name: String): KMutableTopLevelVariable<*> {
val descriptor = findPropertyDescriptor(name)
return KMutableTopLevelVariableImpl(this, descriptor)
return KMutableTopLevelVariableImpl(this, findPropertyDescriptor(name))
}
fun <T> topLevelExtensionProperty(name: String, receiver: Class<T>): KTopLevelExtensionProperty<T, *> {
return KTopLevelExtensionPropertyImpl(name, this, receiver)
return KTopLevelExtensionPropertyImpl(this, findPropertyDescriptor(name, receiver))
}
fun <T> mutableTopLevelExtensionProperty(name: String, receiver: Class<T>): KMutableTopLevelExtensionProperty<T, *> {
return KMutableTopLevelExtensionPropertyImpl(name, this, receiver)
return KMutableTopLevelExtensionPropertyImpl(this, findPropertyDescriptor(name, receiver))
}
override fun equals(other: Any?): Boolean =
@@ -16,27 +16,24 @@
package kotlin.reflect.jvm.internal
import java.lang.reflect.*
import kotlin.reflect.*
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import java.lang.reflect.Method
import kotlin.reflect.IllegalPropertyAccessException
import kotlin.reflect.KMutableTopLevelExtensionProperty
import kotlin.reflect.KTopLevelExtensionProperty
open class KTopLevelExtensionPropertyImpl<T, out R>(
override val name: String,
protected val owner: KPackageImpl,
protected val receiverClass: Class<T>
) : KTopLevelExtensionProperty<T, R>, KPropertyImpl<R> {
override val field: Field? get() = null
override val container: KPackageImpl,
computeDescriptor: () -> PropertyDescriptor
) : DescriptorBasedProperty(computeDescriptor), KTopLevelExtensionProperty<T, R>, KPropertyImpl<R> {
override val name: String get() = descriptor.getName().asString()
// TODO: extract, make lazy (weak?), use our descriptors knowledge, support Java fields
override val getter: Method = try {
owner.jClass.getMethod(getterName(name), receiverClass)
}
catch (e: NoSuchMethodException) {
throw NoSuchPropertyException(e)
}
override val getter: Method get() = super<DescriptorBasedProperty>.getter!!
override fun get(receiver: T): R {
try {
return getter(null, receiver) as R
[suppress("UNCHECKED_CAST")]
return getter.invoke(null, receiver) as R
}
catch (e: IllegalAccessException) {
throw IllegalPropertyAccessException(e)
@@ -44,27 +41,20 @@ open class KTopLevelExtensionPropertyImpl<T, out R>(
}
override fun equals(other: Any?): Boolean =
other is KTopLevelExtensionPropertyImpl<*, *> && name == other.name && owner == other.owner && receiverClass == other.receiverClass
other is KTopLevelExtensionPropertyImpl<*, *> && descriptor == other.descriptor
override fun hashCode(): Int =
(name.hashCode() * 31 + owner.hashCode()) * 31 + receiverClass.hashCode()
descriptor.hashCode()
// TODO: include visibility, return type, maybe package
override fun toString(): String =
"val ${mapJavaClassToKotlin(receiverClass.getName())}.$name"
ReflectionObjectRenderer.renderProperty(descriptor)
}
class KMutableTopLevelExtensionPropertyImpl<T, R>(
name: String,
owner: KPackageImpl,
receiverClass: Class<T>
) : KMutableTopLevelExtensionProperty<T, R>, KMutablePropertyImpl<R>, KTopLevelExtensionPropertyImpl<T, R>(name, owner, receiverClass) {
override val setter: Method = try {
owner.jClass.getMethod(setterName(name), receiverClass, getter.getReturnType())
}
catch (e: NoSuchMethodException) {
throw NoSuchPropertyException(e)
}
container: KPackageImpl,
computeDescriptor: () -> PropertyDescriptor
) : KTopLevelExtensionPropertyImpl<T, R>(container, computeDescriptor), KMutableTopLevelExtensionProperty<T, R>, KMutablePropertyImpl<R> {
override val setter: Method get() = super<KTopLevelExtensionPropertyImpl>.setter!!
override fun set(receiver: T, value: R) {
try {
@@ -74,39 +64,4 @@ class KMutableTopLevelExtensionPropertyImpl<T, R>(
throw IllegalPropertyAccessException(e)
}
}
override fun toString(): String =
"var ${mapJavaClassToKotlin(receiverClass.getName())}.$name"
}
private fun mapJavaClassToKotlin(name: String): String {
if (name[0].isLowerCase()) {
return when (name) {
"boolean" -> "kotlin.Boolean"
"char" -> "kotlin.Char"
"byte" -> "kotlin.Byte"
"short" -> "kotlin.Short"
"int" -> "kotlin.Int"
"float" -> "kotlin.Float"
"long" -> "kotlin.Long"
"double" -> "kotlin.Double"
else -> name
}
}
if (name[0] == '[') {
val element = name.substring(1)
return when (element[0]) {
'Z' -> "kotlin.BooleanArray"
'C' -> "kotlin.CharArray"
'B' -> "kotlin.ByteArray"
'S' -> "kotlin.ShortArray"
'I' -> "kotlin.IntArray"
'F' -> "kotlin.FloatArray"
'J' -> "kotlin.LongArray"
'D' -> "kotlin.DoubleArray"
'L' -> "kotlin.Array<${mapJavaClassToKotlin(element.substring(1, element.length() - 1))}>"
else -> "kotlin.Array<${mapJavaClassToKotlin(element)}>"
}
}
return name
}
@@ -0,0 +1,119 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlin.reflect.jvm.internal
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.load.java.structure.reflect.classId
import org.jetbrains.kotlin.load.java.structure.reflect.desc
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.platform.JavaToKotlinClassMapBuilder
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.TypeUtils
object RuntimeTypeMapper : JavaToKotlinClassMapBuilder() {
private val kotlinFqNameToJvmDesc = linkedMapOf<FqName, String>()
private val kotlinFqNameToJvmDescNullable = linkedMapOf<FqName, String>()
private val jvmDescToKotlinClassId = linkedMapOf<String, ClassId>();
{
init()
initPrimitives()
}
private fun initPrimitives() {
val builtIns = KotlinBuiltIns.getInstance()
for (type in JvmPrimitiveType.values()) {
val primitiveType = type.getPrimitiveType()
val primitiveClassDescriptor = builtIns.getPrimitiveClassDescriptor(primitiveType)
recordMapping(primitiveClassDescriptor, type.getDesc())
recordMapping(builtIns.getPrimitiveArrayClassDescriptor(primitiveType), "[" + type.getDesc())
recordNullableMapping(primitiveClassDescriptor, ClassId.topLevel(type.getWrapperFqName()).desc)
}
}
private fun recordMapping(kotlinDescriptor: ClassDescriptor, jvmDesc: String) {
kotlinFqNameToJvmDesc[DescriptorUtils.getFqNameSafe(kotlinDescriptor)] = jvmDesc
jvmDescToKotlinClassId[jvmDesc] = kotlinDescriptor.classId
val defaultObject = kotlinDescriptor.getDefaultObjectDescriptor()
if (defaultObject != null) {
// TODO: see org.jetbrains.kotlin.codegen.intrinsics.IntrinsicObjects, extract that logic to core/
recordMapping(defaultObject, "Lkotlin/jvm/internal/${kotlinDescriptor.getName().asString()}DefaultObject;")
}
}
private fun recordNullableMapping(kotlinDescriptor: ClassDescriptor, jvmDesc: String) {
kotlinFqNameToJvmDescNullable[DescriptorUtils.getFqNameSafe(kotlinDescriptor)] = jvmDesc
jvmDescToKotlinClassId[jvmDesc] = kotlinDescriptor.classId
}
override fun register(javaClass: Class<*>, kotlinDescriptor: ClassDescriptor, direction: JavaToKotlinClassMapBuilder.Direction) {
recordMapping(kotlinDescriptor, javaClass.classId.desc)
}
override fun register(javaClass: Class<*>, kotlinDescriptor: ClassDescriptor, kotlinMutableDescriptor: ClassDescriptor) {
// TODO: readonly collection mapping just rewrites the mutable one, improve readability here
register(javaClass, kotlinMutableDescriptor, JavaToKotlinClassMapBuilder.Direction.BOTH)
register(javaClass, kotlinDescriptor, JavaToKotlinClassMapBuilder.Direction.BOTH)
}
fun mapTypeToJvmDesc(type: JetType): String {
val classifier = type.getConstructor().getDeclarationDescriptor()
if (classifier is TypeParameterDescriptor) return mapTypeToJvmDesc(classifier.getUpperBounds().first())
val classDescriptor = classifier as ClassDescriptor
val fqNameUnsafe = DescriptorUtils.getFqName(classDescriptor)
if (fqNameUnsafe.isSafe()) {
val fqName = fqNameUnsafe.toSafe()
if (TypeUtils.isNullableType(type)) {
kotlinFqNameToJvmDescNullable[fqName]?.let { return it }
}
kotlinFqNameToJvmDesc[fqName]?.let { return it }
}
if (KotlinBuiltIns.isArray(type)) {
var dimension = 0
var elementType = type
while (KotlinBuiltIns.isArray(elementType)) {
elementType = KotlinBuiltIns.getInstance().getArrayElementType(elementType)
dimension++
}
return "[".repeat(dimension) + mapTypeToJvmDesc(TypeUtils.makeNullable(elementType))
}
return classDescriptor.classId.desc
}
fun mapJvmClassToKotlinClassId(klass: Class<*>): ClassId {
return jvmDescToKotlinClassId[klass.desc] ?: klass.classId
}
private val ClassId.desc: String
get() = "L${JvmClassName.byClassId(this).getInternalName()};"
}
@@ -28,14 +28,6 @@ import java.lang.reflect.Field
import java.lang.reflect.Method
import kotlin.reflect.KotlinReflectionInternalError
private fun String.capitalizeWithJavaBeanConvention(): String {
if (length() > 1 && Character.isUpperCase(this[1])) return this
return Character.toUpperCase(this[0]) + substring(1, length())
}
private fun getterName(propertyName: String): String = "get" + propertyName.capitalizeWithJavaBeanConvention()
private fun setterName(propertyName: String): String = "set" + propertyName.capitalizeWithJavaBeanConvention()
private fun Class<*>.getMaybeDeclaredMethod(name: String, vararg parameterTypes: Class<*>): Method {
try {