Support synthetic accessors for @JvmDefault members

This commit is contained in:
Mikhael Bogdanov
2018-02-23 13:19:48 +01:00
parent 1acd5642ce
commit 308283e52e
13 changed files with 214 additions and 21 deletions
@@ -30,7 +30,8 @@ class CallableMethod(
override val extensionReceiverKotlinType: KotlinType?,
override val generateCalleeType: Type?,
override val returnKotlinType: KotlinType?,
private val isInterfaceMethod: Boolean = Opcodes.INVOKEINTERFACE == invokeOpcode
private val isInterfaceMethod: Boolean = Opcodes.INVOKEINTERFACE == invokeOpcode,
private val isDefaultMethodInInterface: Boolean = false
) : Callable {
fun getValueParameters(): List<JvmMethodParameterSignature> =
signature.valueParameters
@@ -67,7 +68,7 @@ class CallableMethod(
} else {
v.visitMethodInsn(
INVOKESTATIC, defaultImplOwner.internalName,
method.name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, defaultMethodDesc, false
method.name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, defaultMethodDesc, isDefaultMethodInInterface
)
StackValue.coerce(Type.getReturnType(defaultMethodDesc), Type.getReturnType(signature.asmMethod.descriptor), v)
@@ -400,9 +400,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
generateDelegates(delegationFieldsInfo);
if (!isInterface(descriptor) || kind == OwnerKind.DEFAULT_IMPLS) {
generateSyntheticAccessors();
}
generateSyntheticAccessors();
generateEnumMethods();
@@ -695,9 +695,16 @@ public abstract class MemberCodegen<T extends KtPureElement/* TODO: & KtDeclarat
}
}
protected void generateSyntheticAccessors() {
protected final void generateSyntheticAccessors() {
for (AccessorForCallableDescriptor<?> accessor : ((CodegenContext<?>) context).getAccessors()) {
generateSyntheticAccessor(accessor);
boolean hasJvmDefaultAnnotation = CodegenUtilKt.hasJvmDefaultAnnotation(accessor.getCalleeDescriptor());
OwnerKind kind = context.getContextKind();
if (!isInterface(context.getContextDescriptor()) ||
(hasJvmDefaultAnnotation && kind == OwnerKind.IMPLEMENTATION) ||
(!hasJvmDefaultAnnotation && kind == OwnerKind.DEFAULT_IMPLS)) {
generateSyntheticAccessor(accessor);
}
}
}
@@ -423,4 +423,8 @@ fun MethodNode.textifyMethodNode(): String {
return "$sw"
}
fun CallableMemberDescriptor.hasJvmDefaultAnnotation() = getDirectMember(this).annotations.hasAnnotation(JvmDefaultChecker.JVM_DEFAULT_FQ_NAME)
fun CallableMemberDescriptor.hasJvmDefaultAnnotation() =
getDirectMember(this).annotations.hasAnnotation(JvmDefaultChecker.JVM_DEFAULT_FQ_NAME)
fun DeclarationDescriptor.isCallableMemberWithJvmDefaultAnnotation() =
(this as? CallableMemberDescriptor)?.hasJvmDefaultAnnotation() ?: false
@@ -569,10 +569,13 @@ public abstract class CodegenContext<T extends DeclarationDescriptor> {
CodegenContext properContext = getFirstCrossInlineOrNonInlineContext();
DeclarationDescriptor enclosing = descriptor.getContainingDeclaration();
boolean isInliningContext = properContext.isInlineMethodContext();
boolean sameJvmDefault = CodegenUtilKt.hasJvmDefaultAnnotation(descriptor) ==
CodegenUtilKt.isCallableMemberWithJvmDefaultAnnotation(properContext.contextDescriptor) ||
properContext.contextDescriptor instanceof AccessorForCallableDescriptor;
if (!isInliningContext && (
!properContext.hasThisDescriptor() ||
enclosing == properContext.getThisDescriptor() ||
enclosing == properContext.getClassOrPackageParentContext().getContextDescriptor())) {
((enclosing == properContext.getThisDescriptor()) && sameJvmDefault) ||
((enclosing == properContext.getClassOrPackageParentContext().getContextDescriptor()) && sameJvmDefault))) {
return descriptor;
}
return (D) properContext.accessibleDescriptorIfNeeded(descriptor, superCallTarget, isInliningContext);
@@ -623,6 +626,11 @@ public abstract class CodegenContext<T extends DeclarationDescriptor> {
if (descriptorContext == null) {
return descriptor;
}
if (CodegenUtilKt.hasJvmDefaultAnnotation(descriptor) && descriptorContext instanceof DefaultImplsClassContext) {
descriptorContext = ((DefaultImplsClassContext) descriptorContext).getInterfaceContext();
}
if (descriptor instanceof PropertyDescriptor) {
PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor;
int propertyAccessFlag = getVisibilityAccessFlag(descriptor);
@@ -23,20 +23,21 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
class DefaultImplsClassContext(
typeMapper: KotlinTypeMapper,
contextDescriptor: ClassDescriptor,
contextKind: OwnerKind,
parentContext: CodegenContext<*>?,
localLookup: ((DeclarationDescriptor) -> Boolean)?,
private val interfaceContext: ClassContext
typeMapper: KotlinTypeMapper,
contextDescriptor: ClassDescriptor,
contextKind: OwnerKind,
parentContext: CodegenContext<*>?,
localLookup: ((DeclarationDescriptor) -> Boolean)?,
val interfaceContext: ClassContext
) : ClassContext(typeMapper, contextDescriptor, contextKind, parentContext, localLookup) {
override fun getCompanionObjectContext(): CodegenContext<*>? = interfaceContext.companionObjectContext
override fun getAccessors(): Collection<AccessorForCallableDescriptor<*>> {
val accessors = super.getAccessors()
val alreadyExistKeys = accessors.map ({ Pair(it.calleeDescriptor, it.superCallTarget) })
val filtered = interfaceContext.accessors.associateByTo(linkedMapOf()) { Pair(it.calleeDescriptor, it.superCallTarget) }.apply { keys -= alreadyExistKeys }
val alreadyExistKeys = accessors.map({ Pair(it.calleeDescriptor, it.superCallTarget) })
val filtered = interfaceContext.accessors.associateByTo(linkedMapOf()) { Pair(it.calleeDescriptor, it.superCallTarget) }
.apply { keys -= alreadyExistKeys }
return accessors + filtered.values
}
}
@@ -746,7 +746,7 @@ public class KotlinTypeMapper {
String defaultImplDesc = mapDefaultMethod(originalDescriptor, OwnerKind.IMPLEMENTATION).getDescriptor();
return new CallableMethod(
owner, owner, defaultImplDesc, method, INVOKESPECIAL,
null, null, null, null, null, originalDescriptor.getReturnType(), false
null, null, null, null, null, originalDescriptor.getReturnType(), false, false
);
}
@@ -770,6 +770,7 @@ public class KotlinTypeMapper {
Type thisClass;
KotlinType dispatchReceiverKotlinType;
boolean isInterfaceMember = false;
boolean isDefaultMethodInInterface = false;
if (functionParent instanceof ClassDescriptor) {
FunctionDescriptor declarationFunctionDescriptor = findAnyDeclaration(functionDescriptor);
@@ -784,6 +785,7 @@ public class KotlinTypeMapper {
baseMethodDescriptor = findBaseDeclaration(functionDescriptor).getOriginal();
ClassDescriptor ownerForDefault = (ClassDescriptor) baseMethodDescriptor.getContainingDeclaration();
isDefaultMethodInInterface = isJvmInterface(ownerForDefault) && CodegenUtilKt.hasJvmDefaultAnnotation(baseMethodDescriptor);
ownerForDefaultImpl =
isJvmInterface(ownerForDefault) && !CodegenUtilKt.hasJvmDefaultAnnotation(baseMethodDescriptor) ?
mapDefaultImpls(ownerForDefault) : mapClass(ownerForDefault);
@@ -803,7 +805,14 @@ public class KotlinTypeMapper {
FunctionDescriptor originalDescriptor = descriptor.getOriginal();
signature = mapSignatureSkipGeneric(originalDescriptor, OwnerKind.DEFAULT_IMPLS);
returnKotlinType = originalDescriptor.getReturnType();
owner = mapDefaultImpls(currentOwner);
if (descriptor instanceof AccessorForCallableDescriptor &&
CodegenUtilKt.hasJvmDefaultAnnotation(((AccessorForCallableDescriptor) descriptor).getCalleeDescriptor())) {
owner = mapClass(currentOwner);
isInterfaceMember = true;
}
else {
owner = mapDefaultImpls(currentOwner);
}
}
}
else {
@@ -893,7 +902,7 @@ public class KotlinTypeMapper {
return new CallableMethod(
owner, ownerForDefaultImpl, defaultImplDesc, signature, invokeOpcode,
thisClass, dispatchReceiverKotlinType, receiverParameterType, extensionReceiverKotlinType, calleeType, returnKotlinType,
isJvm8Target ? isInterfaceMember : invokeOpcode == INVOKEINTERFACE
isJvm8Target ? isInterfaceMember : invokeOpcode == INVOKEINTERFACE, isDefaultMethodInInterface
);
}
@@ -0,0 +1,36 @@
// !API_VERSION: 1.3
// JVM_TARGET: 1.8
// WITH_RUNTIME
var storage = "fail"
interface Test {
@kotlin.annotations.JvmDefault
private var foo: String
get() = storage
set(value) {
storage = value
}
@kotlin.annotations.JvmDefault
private fun bar(): String {
return "K"
}
@kotlin.annotations.JvmDefault
fun call(): String {
return {
foo = "O"
foo + bar()
} ()
}
}
class TestClass : Test {
}
fun box(): String {
return TestClass().call()
}
@@ -0,0 +1,26 @@
// !API_VERSION: 1.3
// JVM_TARGET: 1.8
// WITH_RUNTIME
interface Test {
@kotlin.annotations.JvmDefault
private val foo: String
get() = "O"
@kotlin.annotations.JvmDefault
private fun bar(): String {
return "K"
}
companion object {
fun call(test: Test): String {
return test.foo + test.bar()
}
}
}
class TestClass : Test
fun box(): String {
return Test.call(TestClass())
}
@@ -0,0 +1,26 @@
// !API_VERSION: 1.3
// JVM_TARGET: 1.8
// WITH_RUNTIME
interface Test {
@kotlin.annotations.JvmDefault
private val foo: String
get() = "O"
@kotlin.annotations.JvmDefault
private fun bar(): String {
return "K"
}
fun call(): String {
return { foo + bar()} ()
}
}
class TestClass : Test {
}
fun box(): String {
return TestClass().call()
}
@@ -0,0 +1,24 @@
// !API_VERSION: 1.3
// JVM_TARGET: 1.8
// WITH_RUNTIME
interface Test {
fun test(): String {
return privateFun() + privateProp
}
@kotlin.annotations.JvmDefault
private fun privateFun(): String {
return "O"
}
@kotlin.annotations.JvmDefault
private val privateProp: String
get() = "K"
}
class TestImpl: Test
fun box(): String {
return TestImpl().test()
}
@@ -0,0 +1,23 @@
// !API_VERSION: 1.3
// JVM_TARGET: 1.8
// WITH_RUNTIME
interface Test {
@kotlin.annotations.JvmDefault
fun test(): String {
return privateFun() + privateProp
}
private fun privateFun(): String {
return "O"
}
private val privateProp: String
get() = "K"
}
class TestImpl: Test
fun box(): String {
return TestImpl().test()
}
@@ -337,6 +337,24 @@ public class BlackBoxWithJava8CodegenTestGenerated extends AbstractBlackBoxCodeg
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Defaults extends AbstractBlackBoxCodegenTest {
@TestMetadata("accessor.kt")
public void testAccessor() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/jvm8/defaults/accessor.kt");
doTest(fileName);
}
@TestMetadata("accessorFromCompanion.kt")
public void testAccessorFromCompanion() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/jvm8/defaults/accessorFromCompanion.kt");
doTest(fileName);
}
@TestMetadata("accessorsFromDefaultImpls.kt")
public void testAccessorsFromDefaultImpls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/jvm8/defaults/accessorsFromDefaultImpls.kt");
doTest(fileName);
}
public void testAllFilesPresentInDefaults() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/java8/box/jvm8/defaults"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@@ -413,6 +431,18 @@ public class BlackBoxWithJava8CodegenTestGenerated extends AbstractBlackBoxCodeg
doTest(fileName);
}
@TestMetadata("privateDefaultFromDefaultImpl.kt")
public void testPrivateDefaultFromDefaultImpl() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/jvm8/defaults/privateDefaultFromDefaultImpl.kt");
doTest(fileName);
}
@TestMetadata("privateInDefaultImpls.kt")
public void testPrivateInDefaultImpls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/jvm8/defaults/privateInDefaultImpls.kt");
doTest(fileName);
}
@TestMetadata("simpleCall.kt")
public void testSimpleCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/jvm8/defaults/simpleCall.kt");