Support setters for Java-method-based properties in reflection
Also heuristically support the corner case of multiple properties with the same name and signature in a Java class, see the comment in KDeclarationContainerImpl #KT-11258 Fixed
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
// WITH_REFLECT
|
||||
// FILE: J.java
|
||||
|
||||
public class J implements K {
|
||||
private String foo;
|
||||
|
||||
@Override
|
||||
public String getFoo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFoo(String s) {
|
||||
foo = s;
|
||||
}
|
||||
}
|
||||
|
||||
// FILE: K.kt
|
||||
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.reflect.KParameter
|
||||
|
||||
interface K {
|
||||
var foo: String
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val p = J::foo
|
||||
assertEquals("foo", p.name)
|
||||
|
||||
if (p.parameters.size != 1) return "Should have only 1 parameter"
|
||||
if (p.parameters.single().kind != KParameter.Kind.INSTANCE) return "Should have an instance parameter"
|
||||
|
||||
if (J::class.members.none { it == p }) return "No foo in members"
|
||||
|
||||
val j = J()
|
||||
p.setter.call(j, "OK")
|
||||
return p.getter.call(j)
|
||||
}
|
||||
@@ -667,6 +667,12 @@ public class BlackBoxWithJavaCodegenTestGenerated extends AbstractBlackBoxCodege
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("overrideKotlinPropertyByJavaMethod.kt")
|
||||
public void testOverrideKotlinPropertyByJavaMethod() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/reflection/overrideKotlinPropertyByJavaMethod.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("parametersHaveNoNames.kt")
|
||||
public void testParametersHaveNoNames() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/reflection/parametersHaveNoNames.kt");
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import java.lang.reflect.Constructor
|
||||
import java.lang.reflect.Method
|
||||
import java.util.*
|
||||
import kotlin.jvm.internal.ClassBasedDeclarationContainer
|
||||
import kotlin.reflect.KotlinReflectionInternalError
|
||||
|
||||
@@ -106,6 +107,25 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
||||
}
|
||||
|
||||
if (properties.size != 1) {
|
||||
// Try working around the case of a Java class with a field 'foo' and a method 'getFoo' which overrides Kotlin property 'foo'.
|
||||
// Such class has two property descriptors with the name 'foo' in its scope and they may be indistinguishable from each other.
|
||||
// However, it's not possible to write 'A::foo' if they're indistinguishable; overload resolution would not be able to choose
|
||||
// between the two. So we assume that one of the properties must have a greater visibility than the other, and try loading
|
||||
// that one first.
|
||||
// Note that this heuristic may result in _incorrect behavior_ if a KProperty object for a less visible property is obtained
|
||||
// by other means (through reflection API) and then the soft-referenced descriptor instance for that property is invalidated
|
||||
// because there's no more memory left. In that case the KProperty object will now point to another (more visible) property.
|
||||
// TODO: consider writing additional info (besides signature) to property reference objects to distinguish them in this case
|
||||
|
||||
val mostVisibleProperties = properties
|
||||
.groupBy { it.visibility }
|
||||
.toSortedMap(Comparator { first, second ->
|
||||
Visibilities.compare(first, second) ?: 0
|
||||
}).values.last()
|
||||
if (mostVisibleProperties.size == 1) {
|
||||
return mostVisibleProperties.first()
|
||||
}
|
||||
|
||||
val debugText = "'$name' (JVM signature: $signature)"
|
||||
throw KotlinReflectionInternalError(
|
||||
if (properties.isEmpty()) "Property $debugText not resolved in $this"
|
||||
|
||||
@@ -152,8 +152,12 @@ private fun KPropertyImpl.Accessor<*>.computeCallerForAccessor(isGetter: Boolean
|
||||
computeFieldCaller(jvmSignature.field)
|
||||
}
|
||||
is JvmPropertySignature.JavaMethodProperty -> {
|
||||
if (!isGetter) throw KotlinReflectionInternalError("Setter requested for special built-in $this")
|
||||
FunctionCaller.InstanceMethod(jvmSignature.method)
|
||||
val method =
|
||||
if (isGetter) jvmSignature.getterMethod
|
||||
else jvmSignature.setterMethod ?: throw KotlinReflectionInternalError(
|
||||
"No source found for setter of Java method property: ${jvmSignature.getterMethod}"
|
||||
)
|
||||
FunctionCaller.InstanceMethod(method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ internal sealed class JvmPropertySignature {
|
||||
override fun asString(): String = string
|
||||
}
|
||||
|
||||
class JavaMethodProperty(val method: Method) : JvmPropertySignature() {
|
||||
override fun asString(): String = method.signature
|
||||
class JavaMethodProperty(val getterMethod: Method, val setterMethod: Method?) : JvmPropertySignature() {
|
||||
override fun asString(): String = getterMethod.signature
|
||||
}
|
||||
|
||||
class JavaField(val field: Field) : JvmPropertySignature() {
|
||||
@@ -206,7 +206,10 @@ internal object RuntimeTypeMapper {
|
||||
val element = (property.source as? JavaSourceElement)?.javaElement
|
||||
when (element) {
|
||||
is ReflectJavaField -> return JvmPropertySignature.JavaField(element.member)
|
||||
is ReflectJavaMethod -> return JvmPropertySignature.JavaMethodProperty(element.member)
|
||||
is ReflectJavaMethod -> return JvmPropertySignature.JavaMethodProperty(
|
||||
element.member,
|
||||
((property.setter?.source as? JavaSourceElement)?.javaElement as? ReflectJavaMethod)?.member
|
||||
)
|
||||
else -> throw KotlinReflectionInternalError("Incorrect resolution sequence for Java field $property (source = $element)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user