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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user