Implement codegen for bound class reference expressions

- Inline the usage of ExpressionCodegen#generateClassLiteralReference into
  ClosureCodegen, simplify
- Support DoubleColonLHS.Expression in generateClassLiteralReference
- Substantially simplify KClass.java intrinsic by reusing
  generateClassLiteralReference

 #KT-13075 Fixed
 #KT-12995 In Progress
This commit is contained in:
Alexander Udalov
2016-07-14 17:09:33 +03:00
parent 28dc5d788c
commit 495ed13fce
8 changed files with 124 additions and 83 deletions
@@ -59,7 +59,6 @@ import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.codegen.AsmUtil.*;
import static org.jetbrains.kotlin.codegen.ExpressionCodegen.generateClassLiteralReference;
import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.isConst;
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.CLOSURE;
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass;
@@ -390,8 +389,8 @@ public class ClosureCodegen extends MemberCodegen<KtElement> {
DeclarationDescriptor container = descriptor.getContainingDeclaration();
if (container instanceof ClassDescriptor) {
// TODO: getDefaultType() here is wrong and won't work for arrays
StackValue value = generateClassLiteralReference(state.getTypeMapper(), ((ClassDescriptor) container).getDefaultType());
value.put(K_CLASS_TYPE, iv);
putJavaLangClassInstance(iv, state.getTypeMapper().mapType(((ClassDescriptor) container).getDefaultType()));
wrapJavaClassIntoKClass(iv);
}
else if (container instanceof PackageFragmentDescriptor) {
iv.aconst(state.getTypeMapper().mapOwner(descriptor));
@@ -92,6 +92,7 @@ import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeProjection;
import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS;
import org.jetbrains.kotlin.types.typesApproximation.CapturedTypeApproximationKt;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
@@ -3209,13 +3210,11 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
@Override
public StackValue visitClassLiteralExpression(@NotNull KtClassLiteralExpression expression, StackValue data) {
KotlinType type = bindingContext.getType(expression);
assert type != null;
assert state.getReflectionTypes().getKClass().getTypeConstructor().equals(type.getConstructor())
: "::class expression should be type checked to a KClass: " + type;
return generateClassLiteralReference(typeMapper, CollectionsKt.single(type.getArguments()).getType(), this);
KtExpression receiverExpression = expression.getReceiverExpression();
assert receiverExpression != null : "Class literal expression should have a left-hand side";
DoubleColonLHS lhs = bindingContext.get(DOUBLE_COLON_LHS, receiverExpression);
assert lhs != null : "Class literal expression should have LHS resolved";
return generateClassLiteralReference(lhs, receiverExpression, /* wrapIntoKClass = */ true);
}
@Override
@@ -3282,28 +3281,31 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
}
@NotNull
public static StackValue generateClassLiteralReference(@NotNull KotlinTypeMapper typeMapper, @NotNull KotlinType type) {
return generateClassLiteralReference(typeMapper, type, null);
}
@NotNull
private static StackValue generateClassLiteralReference(@NotNull final KotlinTypeMapper typeMapper, @NotNull final KotlinType type, @Nullable final ExpressionCodegen codegen) {
return StackValue.operation(K_CLASS_TYPE, new Function1<InstructionAdapter, Unit>() {
public StackValue generateClassLiteralReference(
@NotNull final DoubleColonLHS lhs,
@Nullable final KtExpression receiverExpression,
final boolean wrapIntoKClass
) {
return StackValue.operation(wrapIntoKClass ? K_CLASS_TYPE : JAVA_CLASS_TYPE, new Function1<InstructionAdapter, Unit>() {
@Override
public Unit invoke(InstructionAdapter v) {
Type classAsmType = typeMapper.mapType(type);
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
if (descriptor instanceof TypeParameterDescriptor) {
TypeParameterDescriptor typeParameterDescriptor = (TypeParameterDescriptor) descriptor;
assert typeParameterDescriptor.isReified() :
"Non-reified type parameter under ::class should be rejected by type checker: " + typeParameterDescriptor;
assert codegen != null :
"Reference to member of reified type should be rejected by type checker " + typeParameterDescriptor;
codegen.putReifiedOperationMarkerIfTypeIsReifiedParameter(type, ReifiedTypeInliner.OperationKind.JAVA_CLASS);
KotlinType type = lhs.getType();
if (lhs instanceof DoubleColonLHS.Expression) {
JavaClassProperty.INSTANCE.generateImpl(v, gen(receiverExpression));
}
else if (lhs instanceof DoubleColonLHS.Type) {
if (TypeUtils.isTypeParameter(type)) {
assert TypeUtils.isReifiedTypeParameter(type) :
"Non-reified type parameter under ::class should be rejected by type checker: " + type;
putReifiedOperationMarkerIfTypeIsReifiedParameter(type, ReifiedTypeInliner.OperationKind.JAVA_CLASS);
}
putJavaLangClassInstance(v, typeMapper.mapType(type));
}
putJavaLangClassInstance(v, classAsmType);
wrapJavaClassIntoKClass(v);
if (wrapIntoKClass) {
wrapJavaClassIntoKClass(v);
}
return Unit.INSTANCE;
}
@@ -16,69 +16,21 @@
package org.jetbrains.kotlin.codegen.intrinsics
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.psi.KtClassLiteralExpression
import org.jetbrains.kotlin.resolve.BindingContext.DOUBLE_COLON_LHS
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class KClassJavaProperty : IntrinsicPropertyGetter() {
override fun generate(resolvedCall: ResolvedCall<*>?, codegen: ExpressionCodegen, returnType: Type, receiver: StackValue): StackValue? {
val receiverType = resolvedCall!!.extensionReceiver!!.type
val type = getKClassTypeArgument(receiverType) ?: return null
val asmType = codegen.state.typeMapper.mapType(type)
return when {
isReifiedTypeParameter(type) -> {
StackValue.operation(returnType) { iv ->
codegen.putReifiedOperationMarkerIfTypeIsReifiedParameter(type, ReifiedTypeInliner.OperationKind.JAVA_CLASS)
AsmUtil.putJavaLangClassInstance(iv, asmType)
coerceToJavaLangClass(iv, returnType)
}
}
isWithClassLiteralArgument(resolvedCall) -> {
StackValue.operation(returnType) { iv ->
if (AsmUtil.isPrimitive(asmType)) {
iv.getstatic(AsmUtil.boxType(asmType).internalName, "TYPE", "Ljava/lang/Class;")
}
else {
iv.tconst(asmType)
}
coerceToJavaLangClass(iv, returnType)
}
}
else -> null
}
}
private fun getKClassTypeArgument(type: KotlinType): KotlinType? {
val typeClassifier = type.constructor.declarationDescriptor
return if (typeClassifier is ClassDescriptor && KotlinBuiltIns.isKClass(typeClassifier))
type.arguments.singleOrNull()?.type
else
null
}
private fun isReifiedTypeParameter(type: KotlinType): Boolean {
val typeDescriptor = type.constructor.declarationDescriptor
return typeDescriptor is TypeParameterDescriptor && typeDescriptor.isReified
}
private fun isWithClassLiteralArgument(resolvedCall: ResolvedCall<*>): Boolean {
val extensionReceiver = resolvedCall.extensionReceiver
return extensionReceiver is ExpressionReceiver && extensionReceiver.expression is KtClassLiteralExpression
}
private fun coerceToJavaLangClass(iv: InstructionAdapter, returnType: Type) {
StackValue.coerce(AsmTypes.JAVA_CLASS_TYPE, returnType, iv)
val receiverValue = resolvedCall!!.extensionReceiver as? ExpressionReceiver ?: return null
val classLiteralExpression = receiverValue.expression as? KtClassLiteralExpression ?: return null
val receiverExpression = classLiteralExpression.receiverExpression ?: return null
val lhs = codegen.bindingContext.get(DOUBLE_COLON_LHS, receiverExpression) ?: return null
val value = codegen.generateClassLiteralReference(lhs, receiverExpression, /* wrapIntoKClass = */ false)
return StackValue.coercion(value, returnType)
}
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
fun box(): String {
var x = 42
val k = (x++)::class.java
if (k != Int::class.java) return "Fail 1: $k"
if (x != 43) return "Fail 2: $x (side effect should have taken place)"
return "OK"
}
@@ -0,0 +1,17 @@
// WITH_RUNTIME
import kotlin.test.assertEquals
fun box(): String {
assertEquals(true::class, Boolean::class)
assertEquals(42.toByte()::class, Byte::class)
assertEquals('z'::class, Char::class)
assertEquals(3.14::class, Double::class)
assertEquals(2.72f::class, Float::class)
// TODO: KT-13110
// assertEquals(42::class, Int::class)
assertEquals(42L::class, Long::class)
assertEquals(42.toShort()::class, Short::class)
return "OK"
}
@@ -0,0 +1,14 @@
fun box(): String {
var x = 42
val k1 = (x++)::class
if (k1 != Int::class) return "Fail 1: $k1"
if (x != 43) return "Fail 2: $x"
val k2 = { x *= 2; x }()::class
// Note that k2 is the class of the wrapper type java.lang.Integer
if (k2 != Integer::class) return "Fail 3: $k2"
if (x != 86) return "Fail 4: $x"
return "OK"
}
@@ -0,0 +1,5 @@
fun box(): String {
val x: CharSequence = ""
val klass = x::class
return if (klass == String::class) "OK" else "Fail: $klass"
}
@@ -2342,6 +2342,48 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
}
}
@TestMetadata("compiler/testData/codegen/box/classLiteral")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ClassLiteral extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInClassLiteral() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/classLiteral"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("compiler/testData/codegen/box/classLiteral/bound")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Bound extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInBound() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/classLiteral/bound"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("javaIntrinsicWithSideEffect.kt")
public void testJavaIntrinsicWithSideEffect() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/classLiteral/bound/javaIntrinsicWithSideEffect.kt");
doTest(fileName);
}
@TestMetadata("primitives.kt")
public void testPrimitives() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/classLiteral/bound/primitives.kt");
doTest(fileName);
}
@TestMetadata("sideEffect.kt")
public void testSideEffect() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/classLiteral/bound/sideEffect.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/classLiteral/bound/simple.kt");
doTest(fileName);
}
}
}
@TestMetadata("compiler/testData/codegen/box/classes")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)