Support mapping between Kotlin functions and JVM methods/constructors

This commit is contained in:
Alexander Udalov
2015-07-15 23:33:37 +03:00
parent 93656c93c1
commit 936bede8b1
14 changed files with 323 additions and 38 deletions
@@ -0,0 +1,7 @@
public class javaConstructor {
public final String result;
public javaConstructor(String result) {
this.result = result;
}
}
@@ -0,0 +1,12 @@
import kotlin.reflect.*
import kotlin.reflect.jvm.*
import javaConstructor as J
fun box(): String {
val reference = ::J
val javaConstructor = reference.javaConstructor ?: return "Fail: no Constructor for reference"
val j = javaConstructor.newInstance("OK")
val kotlinConstructor = javaConstructor.kotlinFunction
if (reference != kotlinConstructor) return "Fail: reference != kotlinConstructor"
return j.result
}
@@ -0,0 +1,9 @@
public class javaMethods {
public String f(String s) {
return s;
}
public static String g(String s) {
return s;
}
}
@@ -0,0 +1,19 @@
import kotlin.reflect.*
import kotlin.reflect.jvm.*
import javaMethods as J
fun box(): String {
val f = J::f
val fm = f.javaMethod ?: return "Fail: no Method for f"
if (fm.invoke(J(), "abc") != "abc") return "Fail fm"
val ff = fm.kotlinFunction ?: return "Fail: no KFunction for fm"
if (f != ff) return "Fail f != ff"
val g = J::g
val gm = g.javaMethod ?: return "Fail: no Method for g"
if (gm.invoke(null, "ghi") != "ghi") return "Fail gm"
val gg = gm.kotlinFunction ?: return "Fail: no KFunction for gm"
if (g != gg) return "Fail g != gg"
return "OK"
}
@@ -0,0 +1,35 @@
import kotlin.reflect.*
import kotlin.reflect.jvm.*
class K {
class Nested
inner class Inner
}
class Secondary {
constructor(x: Int) {}
}
fun check(f: KFunction<*>) {
assert(f.javaMethod == null, "Fail f method")
assert(f.javaConstructor != null, "Fail f constructor")
val c = f.javaConstructor!!
assert(c.kotlinFunction != null, "Fail m function")
val ff = c.kotlinFunction!!
assert(f == ff, "Fail f != ff")
}
fun box(): String {
check(::K)
// Workaround KT-8596
val nested = K::Nested
check(nested)
check(K::Inner)
check(::Secondary)
return "OK"
}
@@ -0,0 +1,27 @@
import kotlin.reflect.*
import kotlin.reflect.jvm.*
class K {
fun foo(s: String): Int = s.length()
}
fun bar(s: String): Int = s.length()
fun String.baz(): Int = this.length()
fun check(f: KFunction<Int>) {
assert(f.javaConstructor == null, "Fail f constructor")
assert(f.javaMethod != null, "Fail f method")
val m = f.javaMethod!!
assert(m.kotlinFunction != null, "Fail m function")
val ff = m.kotlinFunction!!
assert(f == ff, "Fail f != ff")
}
fun box(): String {
check(K::foo)
check(::bar)
check(String::baz)
return "OK"
}
@@ -510,11 +510,23 @@ public class BlackBoxAgainstJavaCodegenTestGenerated extends AbstractBlackBoxCod
doTestAgainstJava(fileName);
}
@TestMetadata("javaConstructor.kt")
public void testJavaConstructor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxAgainstJava/reflection/mapping/javaConstructor.kt");
doTestAgainstJava(fileName);
}
@TestMetadata("javaFields.kt")
public void testJavaFields() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxAgainstJava/reflection/mapping/javaFields.kt");
doTestAgainstJava(fileName);
}
@TestMetadata("javaMethods.kt")
public void testJavaMethods() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxAgainstJava/reflection/mapping/javaMethods.kt");
doTestAgainstJava(fileName);
}
}
@TestMetadata("compiler/testData/codegen/boxAgainstJava/reflection/properties")
@@ -3114,12 +3114,24 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/reflection/mapping"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("constructor.kt")
public void testConstructor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/mapping/constructor.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("extensionProperty.kt")
public void testExtensionProperty() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/mapping/extensionProperty.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("functions.kt")
public void testFunctions() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/mapping/functions.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("mappedClassIsEqualToClassLiteral.kt")
public void testMappedClassIsEqualToClassLiteral() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/mapping/mappedClassIsEqualToClassLiteral.kt");
@@ -26,7 +26,7 @@ import java.lang.reflect.Type
import java.util.ArrayList
public abstract class ReflectJavaMember : ReflectJavaElement(), ReflectJavaAnnotationOwner, ReflectJavaModifierListOwner, JavaMember {
protected abstract val member: Member
internal abstract val member: Member
override val element: AnnotatedElement get() = member as AnnotatedElement
@@ -75,14 +75,14 @@ abstract class DescriptorBasedProperty<out R> protected constructor(
open val javaGetter: Method? by ReflectProperties.lazySoft {
val proto = protoData
if (proto == null || !proto.signature.hasGetter()) null
else container.findMethodBySignature(proto.signature.getGetter(), proto.nameResolver,
else container.findMethodBySignature(proto.proto, proto.signature.getGetter(), proto.nameResolver,
descriptor.getGetter()?.getVisibility()?.let { Visibilities.isPrivate(it) } ?: false)
}
open val javaSetter: Method? by ReflectProperties.lazySoft {
val proto = protoData
if (proto == null || !proto.signature.hasSetter()) null
else container.findMethodBySignature(proto.signature.getSetter(), proto.nameResolver,
else container.findMethodBySignature(proto.proto, proto.signature.getSetter(), proto.nameResolver,
descriptor.getSetter()?.getVisibility()?.let { Visibilities.isPrivate(it) } ?: false)
}
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.deserialization.NameResolver
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.JvmType.PrimitiveType.*
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Method
import kotlin.reflect.KCallable
@@ -141,22 +142,55 @@ abstract class KCallableContainerImpl : KDeclarationContainer {
}
// TODO: check resulting method's return type
fun findMethodBySignature(signature: JvmProtoBuf.JvmMethodSignature, nameResolver: NameResolver, declared: Boolean): Method? {
fun findMethodBySignature(
@suppress("UNUSED_PARAMETER") proto: ProtoBuf.Callable,
signature: JvmProtoBuf.JvmMethodSignature,
nameResolver: NameResolver,
declared: Boolean
): Method? {
val name = nameResolver.getString(signature.getName())
val classLoader = jClass.classLoader
val parameterTypes = signature.getParameterTypeList().map { jvmType ->
loadJvmType(jvmType, nameResolver, classLoader)
}.toTypedArray()
if (name == "<init>") return null
val parameterTypes = loadParameterTypes(nameResolver, signature)
// Method for a top level function should be the one from the package facade.
// This is likely to change after the package part reform.
val owner = jClass
return try {
if (declared) jClass.getDeclaredMethod(name, *parameterTypes)
else jClass.getMethod(name, *parameterTypes)
if (declared) owner.getDeclaredMethod(name, *parameterTypes)
else owner.getMethod(name, *parameterTypes)
}
catch (e: NoSuchMethodException) {
null
}
}
fun findConstructorBySignature(
signature: JvmProtoBuf.JvmMethodSignature,
nameResolver: NameResolver,
declared: Boolean
): Constructor<*>? {
if (nameResolver.getString(signature.getName()) != "<init>") return null
val parameterTypes = loadParameterTypes(nameResolver, signature)
return try {
if (declared) jClass.getDeclaredConstructor(*parameterTypes)
else jClass.getConstructor(*parameterTypes)
}
catch (e: NoSuchMethodException) {
null
}
}
private fun loadParameterTypes(nameResolver: NameResolver, signature: JvmProtoBuf.JvmMethodSignature): Array<Class<*>> {
val classLoader = jClass.classLoader
return signature.getParameterTypeList().map { jvmType ->
loadJvmType(jvmType, nameResolver, classLoader)
}.toTypedArray()
}
// TODO: check resulting field's type
fun findFieldBySignature(
proto: ProtoBuf.Callable,
@@ -166,18 +200,11 @@ abstract class KCallableContainerImpl : KDeclarationContainer {
val name = nameResolver.getString(signature.getName())
val owner =
when {
proto.hasExtension(JvmProtoBuf.implClassName) -> {
val implClassName = nameResolver.getName(proto.getExtension(JvmProtoBuf.implClassName))
// TODO: store fq name of impl class name in jvm_descriptors.proto
val classId = ClassId(jClass.classId.getPackageFqName(), implClassName)
jClass.classLoader.loadClass(classId.asSingleFqName().asString())
}
signature.getIsStaticInOuter() -> {
jClass.getEnclosingClass() ?: throw KotlinReflectionInternalError("Inconsistent metadata for field $name in $jClass")
}
else -> jClass
implClassForCallable(nameResolver, proto) ?:
if (signature.getIsStaticInOuter()) {
jClass.getEnclosingClass() ?: throw KotlinReflectionInternalError("Inconsistent metadata for field $name in $jClass")
}
else jClass
return try {
owner.getDeclaredField(name)
@@ -187,6 +214,17 @@ abstract class KCallableContainerImpl : KDeclarationContainer {
}
}
// Returns the JVM class which contains this callable. This class may be different from the one represented by descriptors
// in case of top level functions (their bodies are in package parts), methods with implementations in interfaces, etc.
private fun implClassForCallable(nameResolver: NameResolver, proto: ProtoBuf.Callable): Class<*>? {
if (!proto.hasExtension(JvmProtoBuf.implClassName)) return null
val implClassName = nameResolver.getName(proto.getExtension(JvmProtoBuf.implClassName))
// TODO: store fq name of impl class name in jvm_descriptors.proto
val classId = ClassId(jClass.classId.getPackageFqName(), implClassName)
return jClass.classLoader.loadClass(classId.asSingleFqName().asString())
}
private fun loadJvmType(
type: JvmProtoBuf.JvmType,
nameResolver: NameResolver,
@@ -18,6 +18,17 @@
package kotlin.reflect.jvm.internal
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.reflect.ReflectJavaConstructor
import org.jetbrains.kotlin.load.java.structure.reflect.ReflectJavaMethod
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.deserialization.NameResolver
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import kotlin.jvm.internal.FunctionImpl
import kotlin.reflect.*
@@ -34,10 +45,57 @@ open class KFunctionImpl protected constructor(
container, descriptor.getName().asString(), RuntimeTypeMapper.mapSignature(descriptor), descriptor
)
private data class FunctionProtoData(
val proto: ProtoBuf.Callable,
val nameResolver: NameResolver,
val signature: JvmProtoBuf.JvmMethodSignature
)
override val descriptor: FunctionDescriptor by ReflectProperties.lazySoft<FunctionDescriptor>(descriptorInitialValue) {
container.findFunctionDescriptor(name, signature)
}
// null if this is a function declared in a foreign (Java) class
private val protoData: FunctionProtoData? by ReflectProperties.lazyWeak {
val function = DescriptorUtils.unwrapFakeOverride(descriptor) as? DeserializedCallableMemberDescriptor
if (function != null) {
val proto = function.proto
if (proto.hasExtension(JvmProtoBuf.methodSignature)) {
return@lazyWeak FunctionProtoData(proto, function.nameResolver, proto.getExtension(JvmProtoBuf.methodSignature))
}
}
null
}
internal val javaMethod: Method? by ReflectProperties.lazySoft {
if (name != "<init>") {
val proto = protoData
if (proto != null) {
container.findMethodBySignature(proto.proto, proto.signature, proto.nameResolver,
Visibilities.isPrivate(descriptor.getVisibility()))
}
else {
((descriptor.getOriginal().getSource() as? JavaSourceElement)?.javaElement as? ReflectJavaMethod)?.member
}
}
else null
}
internal val javaConstructor: Constructor<*>? by ReflectProperties.lazySoft {
if (name == "<init>") {
val proto = protoData
if (proto != null) {
return@lazySoft container.findConstructorBySignature(
proto.signature, proto.nameResolver, Visibilities.isPrivate(descriptor.getVisibility())
)
}
else {
((descriptor.getOriginal().getSource() as? JavaSourceElement)?.javaElement as? ReflectJavaConstructor)?.member
}
}
else null
}
override val name: String get() = descriptor.getName().asString()
override fun getArity(): Int {
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.descriptors.JavaConstructorDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
@@ -56,19 +57,31 @@ object RuntimeTypeMapper {
return StringBuilder {
append(method.getName().asString())
append("(")
for (parameter in method.getValueParameters()) {
appendJavaType(parameter.getType())
}
append(")")
appendParameters(method)
appendJavaType(method.getReturnType())
}.toString()
}
else if (function is JavaConstructorDescriptor) {
val constructor = (function.getSource() as? JavaSourceElement)?.javaElement as? JavaConstructor ?:
throw KotlinReflectionInternalError("Incorrect resolution sequence for Java constructor $function")
return StringBuilder {
append("<init>")
appendParameters(constructor)
append("V")
}.toString()
}
else throw KotlinReflectionInternalError("Unknown origin of $function (${function.javaClass})")
}
private fun StringBuilder.appendParameters(callable: JavaCallable) {
append("(")
for (parameter in callable.getValueParameters()) {
appendJavaType(parameter.getType())
}
append(")")
}
// TODO: verify edge cases when it's possible to reference generic functions
private tailRecursive fun StringBuilder.appendJavaType(type: JavaType) {
when (type) {
@@ -16,13 +16,12 @@
package kotlin.reflect.jvm
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import kotlin.reflect.*
import kotlin.reflect.jvm.internal.KClassImpl
import kotlin.reflect.jvm.internal.KMutablePropertyImpl
import kotlin.reflect.jvm.internal.KPackageImpl
import kotlin.reflect.jvm.internal.KPropertyImpl
import kotlin.reflect.jvm.internal.*
// Kotlin reflection -> Java reflection
@@ -64,6 +63,21 @@ public val KMutableProperty<*>.javaSetter: Method?
get() = (this as? KMutablePropertyImpl<*>)?.javaSetter
/**
* Returns a Java [Method] instance corresponding to the given Kotlin function,
* or `null` if this function is a constructor or cannot be represented by a Java [Method].
*/
public val KFunction<*>.javaMethod: Method?
get() = (this as? KFunctionImpl)?.javaMethod
/**
* Returns a Java [Constructor] instance corresponding to the given Kotlin function,
* or `null` if this function is not a constructor or cannot be represented by a Java [Constructor].
*/
@suppress("UNCHECKED_CAST")
public val <T> KFunction<T>.javaConstructor: Constructor<T>?
get() = (this as? KFunctionImpl)?.javaConstructor as Constructor<T>?
// Java reflection -> Kotlin reflection
@@ -82,7 +96,8 @@ public val <T> Class<T>.kotlin: KClass<T>
* for more information.
*/
public val Class<*>.kotlinPackage: KPackage?
get() = if (getAnnotation(javaClass<kotlin.jvm.internal.KotlinPackage>()) != null) KPackageImpl(this) else null
get() = if (getSimpleName().endsWith("Package") &&
getAnnotation(javaClass<kotlin.jvm.internal.KotlinPackage>()) != null) KPackageImpl(this) else null
/**
@@ -94,10 +109,38 @@ public val Field.kotlin: KProperty<*>?
get() {
if (isSynthetic()) return null
val clazz = getDeclaringClass().kotlin as KClassImpl
// TODO: fields in package parts
// TODO: optimize (search by name)
return clazz.properties.firstOrNull { p: KProperty<*> ->
(p as KPropertyImpl<*>).javaField == this
}
return getDeclaringClass().kotlin.properties.firstOrNull { it.javaField == this }
}
/**
* Returns a [KFunction] instance corresponding to the given Java [Method] instance,
* or `null` if this method cannot be represented by a Kotlin function
* (for example, if it is a synthetic method).
*/
public val Method.kotlinFunction: KFunction<*>?
get() {
if (isSynthetic()) return null
if (Modifier.isStatic(getModifiers())) {
getDeclaringClass().kotlinPackage?.let { pkg ->
return pkg.functions.firstOrNull { it.javaMethod == this }
}
}
return getDeclaringClass().kotlin.functions.firstOrNull { it.javaMethod == this }
}
/**
* Returns a [KFunction] instance corresponding to the given Java [Constructor] instance,
* or `null` if this constructor cannot be represented by a Kotlin function
* (for example, if it is a synthetic constructor).
*/
public val <T> Constructor<T>.kotlinFunction: KFunction<T>?
get() {
if (isSynthetic()) return null
return getDeclaringClass().kotlin.constructors.firstOrNull { it.javaConstructor == this }
}