diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/methodsFromAny/extensionPropertyReceiverToString.kt b/compiler/testData/codegen/boxWithStdlib/reflection/methodsFromAny/extensionPropertyReceiverToString.kt index 2fc67171bc3..bb1915fb389 100644 --- a/compiler/testData/codegen/boxWithStdlib/reflection/methodsFromAny/extensionPropertyReceiverToString.kt +++ b/compiler/testData/codegen/boxWithStdlib/reflection/methodsFromAny/extensionPropertyReceiverToString.kt @@ -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.a2: Any get() = this val Array>.a3: Any get() = this val Array.a4: Any get() = this +val Any?.n1: Any get() = Any() +val Int?.n2: Any get() = Any() +val Array?.n3: Any get() = Any() +val Array.n4: Any get() = Any() +val Array?.n5: Any get() = Any() + val Map.m: Any get() = this +val List>>.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", Array::a1) - check("kotlin.Array", Array::a2) - check("kotlin.Array>", Array>::a3) + check("kotlin.Any?", Any?::n1) + check("kotlin.Int?", Int?::n2) + check("kotlin.Array?", Array?::n3) + check("kotlin.Array", Array::n4) + check("kotlin.Array?", Array?::n5) + + check("kotlin.Array", Array::a1) + check("kotlin.Array", Array::a2) + check("kotlin.Array>", Array>::a3) check("kotlin.Array", Array::a4) - check("java.util.Map", Map::m) + check("kotlin.Map", Map::m) + check("kotlin.List>>", List>>::l) return "OK" } diff --git a/compiler/testData/codegen/boxWithStdlib/reflection/methodsFromAny/propertyToString.kt b/compiler/testData/codegen/boxWithStdlib/reflection/methodsFromAny/propertyToString.kt index 2a1e96b7574..81c68d11010 100644 --- a/compiler/testData/codegen/boxWithStdlib/reflection/methodsFromAny/propertyToString.kt +++ b/compiler/testData/codegen/boxWithStdlib/reflection/methodsFromAny/propertyToString.kt @@ -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" diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt index 985a0449155..674a4ef1c55 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt @@ -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 diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt index 355a483d473..263c98ea0ca 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt @@ -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, 0).javaClass - return arrayClass.getName().substring(1).replace('.', '/') + return field.getType().desc } } diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableContainerImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableContainerImpl.kt index 00cc920b40d..ec512ff6357 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableContainerImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableContainerImpl.kt @@ -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" ) } diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt index 944db12ae95..f9b1d9207a2 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt @@ -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(override val jClass: Class) : 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) diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPackageImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPackageImpl.kt index 22ad3b811dd..13ca97f13bf 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPackageImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPackageImpl.kt @@ -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 topLevelExtensionProperty(name: String, receiver: Class): KTopLevelExtensionProperty { - return KTopLevelExtensionPropertyImpl(name, this, receiver) + return KTopLevelExtensionPropertyImpl(this, findPropertyDescriptor(name, receiver)) } fun mutableTopLevelExtensionProperty(name: String, receiver: Class): KMutableTopLevelExtensionProperty { - return KMutableTopLevelExtensionPropertyImpl(name, this, receiver) + return KMutableTopLevelExtensionPropertyImpl(this, findPropertyDescriptor(name, receiver)) } override fun equals(other: Any?): Boolean = diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTopLevelExtensionPropertyImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTopLevelExtensionPropertyImpl.kt index 41e2d712be0..064e8d14a37 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTopLevelExtensionPropertyImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KTopLevelExtensionPropertyImpl.kt @@ -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( - override val name: String, - protected val owner: KPackageImpl, - protected val receiverClass: Class -) : KTopLevelExtensionProperty, KPropertyImpl { - override val field: Field? get() = null + override val container: KPackageImpl, + computeDescriptor: () -> PropertyDescriptor +) : DescriptorBasedProperty(computeDescriptor), KTopLevelExtensionProperty, KPropertyImpl { + 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.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( } 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( - name: String, - owner: KPackageImpl, - receiverClass: Class -) : KMutableTopLevelExtensionProperty, KMutablePropertyImpl, KTopLevelExtensionPropertyImpl(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(container, computeDescriptor), KMutableTopLevelExtensionProperty, KMutablePropertyImpl { + override val setter: Method get() = super.setter!! override fun set(receiver: T, value: R) { try { @@ -74,39 +64,4 @@ class KMutableTopLevelExtensionPropertyImpl( 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 } diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/RuntimeTypeMapper.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/RuntimeTypeMapper.kt new file mode 100644 index 00000000000..f00247bb806 --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/RuntimeTypeMapper.kt @@ -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() + private val kotlinFqNameToJvmDescNullable = linkedMapOf() + private val jvmDescToKotlinClassId = linkedMapOf(); + + { + 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()};" +} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt index d8b7ed0805f..f84c2be43ec 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt @@ -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 {