Generate all bodies in interfaces as default methods

This commit is contained in:
Michael Bogdanov
2016-09-09 14:52:32 +03:00
parent fb48f70273
commit dfd5be1a33
5 changed files with 106 additions and 15 deletions
@@ -22,6 +22,7 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.Function; import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil;
import kotlin.collections.CollectionsKt; import kotlin.collections.CollectionsKt;
import kotlin.Unit;
import kotlin.jvm.functions.Function1; import kotlin.jvm.functions.Function1;
import kotlin.text.StringsKt; import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -52,6 +53,7 @@ import org.jetbrains.kotlin.resolve.constants.ArrayValue;
import org.jetbrains.kotlin.resolve.constants.ConstantValue; import org.jetbrains.kotlin.resolve.constants.ConstantValue;
import org.jetbrains.kotlin.resolve.constants.KClassValue; import org.jetbrains.kotlin.resolve.constants.KClassValue;
import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.jvm.AsmTypes;
import org.jetbrains.kotlin.resolve.jvm.RuntimeAssertionInfo; import org.jetbrains.kotlin.resolve.jvm.RuntimeAssertionInfo;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind;
@@ -161,7 +163,9 @@ public class FunctionCodegen {
OwnerKind contextKind = methodContext.getContextKind(); OwnerKind contextKind = methodContext.getContextKind();
if (isInterface(functionDescriptor.getContainingDeclaration()) && if (isInterface(functionDescriptor.getContainingDeclaration()) &&
functionDescriptor.getVisibility() == Visibilities.PRIVATE && functionDescriptor.getVisibility() == Visibilities.PRIVATE &&
contextKind != OwnerKind.DEFAULT_IMPLS) { (isJvm8InterfaceMember(functionDescriptor, state)
? contextKind == OwnerKind.DEFAULT_IMPLS
: contextKind != OwnerKind.DEFAULT_IMPLS)) {
return; return;
} }
@@ -195,6 +199,10 @@ public class FunctionCodegen {
generateBridges(functionDescriptor); generateBridges(functionDescriptor);
if (isJvm8InterfaceMember(functionDescriptor, state) && contextKind != OwnerKind.DEFAULT_IMPLS) {
generateDelegateForDefaultImpl(functionDescriptor, origin.getElement());
}
boolean staticInCompanionObject = AnnotationUtilKt.isPlatformStaticInCompanionObject(functionDescriptor); boolean staticInCompanionObject = AnnotationUtilKt.isPlatformStaticInCompanionObject(functionDescriptor);
if (staticInCompanionObject) { if (staticInCompanionObject) {
ImplementationBodyCodegen parentBodyCodegen = (ImplementationBodyCodegen) memberCodegen.getParentCodegen(); ImplementationBodyCodegen parentBodyCodegen = (ImplementationBodyCodegen) memberCodegen.getParentCodegen();
@@ -227,12 +235,40 @@ public class FunctionCodegen {
Method accessorMethod = Method accessorMethod =
typeMapper.mapAsmMethod(memberCodegen.getContext().accessibleDescriptor(staticFunctionDescriptor, null)); typeMapper.mapAsmMethod(memberCodegen.getContext().accessibleDescriptor(staticFunctionDescriptor, null));
Type owningType = typeMapper.mapClass((ClassifierDescriptor) staticFunctionDescriptor.getContainingDeclaration()); Type owningType = typeMapper.mapClass((ClassifierDescriptor) staticFunctionDescriptor.getContainingDeclaration());
generateDelegateToMethodBody(false, mv, accessorMethod, owningType.getInternalName()); generateDelegateToStaticMethodBody(false, mv, accessorMethod, owningType.getInternalName());
} }
endVisit(mv, null, origin.getElement()); endVisit(mv, null, origin.getElement());
} }
private void generateDelegateForDefaultImpl(
@NotNull final FunctionDescriptor functionDescriptor,
@Nullable PsiElement element
) {
Method defaultImplMethod = typeMapper.mapAsmMethod(functionDescriptor, OwnerKind.DEFAULT_IMPLS);
CodegenUtilKt.generateMethod(
v, "Default Impl delegate in interface", Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC,
new Method(defaultImplMethod.getName() + JvmAbi.DEFAULT_IMPLS_DELEGATE_SUFFIX, defaultImplMethod.getDescriptor()),
element, JvmDeclarationOrigin.NO_ORIGIN,
state, new Function1<InstructionAdapter, Unit>() {
@Override
public Unit invoke(InstructionAdapter adapter) {
Method interfaceMethod = typeMapper.mapAsmMethod(functionDescriptor, OwnerKind.IMPLEMENTATION);
Type type = typeMapper.mapOwner(functionDescriptor);
generateDelegateToMethodBody(
-1, adapter,
interfaceMethod,
type.getInternalName(),
Opcodes.INVOKESPECIAL,
true
);
return null;
}
}
);
}
private void generateMethodAnnotations( private void generateMethodAnnotations(
@NotNull FunctionDescriptor functionDescriptor, @NotNull FunctionDescriptor functionDescriptor,
Method asmMethod, Method asmMethod,
@@ -360,13 +396,16 @@ public class FunctionCodegen {
generateFacadeDelegateMethodBody(mv, signature.getAsmMethod(), (MultifileClassFacadeContext) context.getParentContext()); generateFacadeDelegateMethodBody(mv, signature.getAsmMethod(), (MultifileClassFacadeContext) context.getParentContext());
methodEnd = new Label(); methodEnd = new Label();
} }
else if (OwnerKind.IMPLEMENTATION == context.getContextKind() && else if (OwnerKind.DEFAULT_IMPLS == context.getContextKind() && isJvm8InterfaceMember(functionDescriptor, parentCodegen.state)) {
JvmCodegenUtil.isJvmInterface(functionDescriptor.getContainingDeclaration())) { int flags = AsmUtil.getMethodAsmFlags(functionDescriptor, OwnerKind.DEFAULT_IMPLS, context.getState());
int flags = AsmUtil.getMethodAsmFlags(functionDescriptor, OwnerKind.IMPLEMENTATION, context.getState());
assert (flags & Opcodes.ACC_ABSTRACT) == 0 : "Interface method with body should be non-abstract" + functionDescriptor; assert (flags & Opcodes.ACC_ABSTRACT) == 0 : "Interface method with body should be non-abstract" + functionDescriptor;
Type type = typeMapper.mapDefaultImpls((ClassDescriptor) functionDescriptor.getContainingDeclaration()); Type type = typeMapper.mapOwner(functionDescriptor);
Method asmMethod = typeMapper.mapAsmMethod(functionDescriptor, OwnerKind.DEFAULT_IMPLS); Method asmMethod = typeMapper.mapAsmMethod(functionDescriptor, OwnerKind.DEFAULT_IMPLS);
generateDelegateToMethodBody(true, mv, asmMethod, type.getInternalName()); generateDelegateToStaticMethodBody(
true, mv,
new Method(asmMethod.getName() + JvmAbi.DEFAULT_IMPLS_DELEGATE_SUFFIX, asmMethod.getDescriptor()),
type.getInternalName()
);
methodEnd = new Label(); methodEnd = new Label();
} }
else { else {
@@ -525,14 +564,17 @@ public class FunctionCodegen {
@NotNull Method asmMethod, @NotNull Method asmMethod,
@NotNull MultifileClassFacadeContext context @NotNull MultifileClassFacadeContext context
) { ) {
generateDelegateToMethodBody(true, mv, asmMethod, context.getFilePartType().getInternalName()); generateDelegateToStaticMethodBody(true, mv, asmMethod, context.getFilePartType().getInternalName());
} }
private static void generateDelegateToMethodBody( private static void generateDelegateToMethodBody(
boolean isStatic, // -1 means to add additional this parameter on stack
int firstParamIndex,
@NotNull MethodVisitor mv, @NotNull MethodVisitor mv,
@NotNull Method asmMethod, @NotNull Method asmMethod,
@NotNull String classToDelegateTo @NotNull String classToDelegateTo,
int opcode,
boolean isInterface
) { ) {
InstructionAdapter iv = new InstructionAdapter(mv); InstructionAdapter iv = new InstructionAdapter(mv);
Type[] argTypes = asmMethod.getArgumentTypes(); Type[] argTypes = asmMethod.getArgumentTypes();
@@ -543,15 +585,29 @@ public class FunctionCodegen {
iv.visitLabel(label); iv.visitLabel(label);
iv.visitLineNumber(1, label); iv.visitLineNumber(1, label);
int k = isStatic ? 0 : 1; int paramIndex = firstParamIndex;
for (Type argType : argTypes) { if (paramIndex == -1) {
iv.load(k, argType); iv.load(0, AsmTypes.OBJECT_TYPE);
k += argType.getSize(); paramIndex = 1;
} }
iv.invokestatic(classToDelegateTo, asmMethod.getName(), asmMethod.getDescriptor(), false);
for (Type argType : argTypes) {
iv.load(paramIndex, argType);
paramIndex += argType.getSize();
}
iv.visitMethodInsn(opcode, classToDelegateTo, asmMethod.getName(), asmMethod.getDescriptor(), isInterface);
iv.areturn(asmMethod.getReturnType()); iv.areturn(asmMethod.getReturnType());
} }
private static void generateDelegateToStaticMethodBody(
boolean isStatic,
@NotNull MethodVisitor mv,
@NotNull Method asmMethod,
@NotNull String classToDelegateTo
) {
generateDelegateToMethodBody(isStatic ? 0 : 1, mv, asmMethod, classToDelegateTo, Opcodes.INVOKESTATIC, false);
}
private static boolean needIndexForVar(JvmMethodParameterKind kind) { private static boolean needIndexForVar(JvmMethodParameterKind kind) {
return kind == JvmMethodParameterKind.CAPTURED_LOCAL_VARIABLE || return kind == JvmMethodParameterKind.CAPTURED_LOCAL_VARIABLE ||
kind == JvmMethodParameterKind.ENUM_NAME_OR_ORDINAL || kind == JvmMethodParameterKind.ENUM_NAME_OR_ORDINAL ||
@@ -0,0 +1,13 @@
// JVM_TARGET: 1.8
interface Z {
val z: String;
get() = "OK"
}
class Test : Z
fun box() : String {
return Test().z
}
@@ -0,0 +1,15 @@
// JVM_TARGET: 1.8
interface Test {
var z: String
get() = "OK"
set(value) {}
}
// TESTED_OBJECT_KIND: function
// TESTED_OBJECTS: Test, getZ
// FLAGS: ACC_PUBLIC
// TESTED_OBJECT_KIND: function
// TESTED_OBJECTS: Test, setZ
// FLAGS: ACC_PUBLIC
@@ -41,6 +41,12 @@ public class WriteFlagsTestGenerated extends AbstractWriteFlagsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("defaultProperty.kt")
public void testDefaultProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/writeFlags/defaultProperty.kt");
doTest(fileName);
}
@TestMetadata("interfaceAndDefaultImpls.kt") @TestMetadata("interfaceAndDefaultImpls.kt")
public void testInterfaceAndDefaultImpls() throws Exception { public void testInterfaceAndDefaultImpls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/writeFlags/interfaceAndDefaultImpls.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/writeFlags/interfaceAndDefaultImpls.kt");
@@ -34,6 +34,7 @@ import static org.jetbrains.kotlin.resolve.DescriptorUtils.isCompanionObject;
public final class JvmAbi { public final class JvmAbi {
public static final String DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"; public static final String DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls";
public static final String DEFAULT_IMPLS_SUFFIX = "$" + DEFAULT_IMPLS_CLASS_NAME; public static final String DEFAULT_IMPLS_SUFFIX = "$" + DEFAULT_IMPLS_CLASS_NAME;
public static final String DEFAULT_IMPLS_DELEGATE_SUFFIX = "$defaultImpl";
public static final String DEFAULT_PARAMS_IMPL_SUFFIX = "$default"; public static final String DEFAULT_PARAMS_IMPL_SUFFIX = "$default";