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 java.util.List;
|
||||||
|
|
||||||
import static org.jetbrains.kotlin.codegen.AsmUtil.*;
|
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.JvmCodegenUtil.isConst;
|
||||||
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.CLOSURE;
|
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.CLOSURE;
|
||||||
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass;
|
import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass;
|
||||||
@@ -390,8 +389,8 @@ public class ClosureCodegen extends MemberCodegen<KtElement> {
|
|||||||
DeclarationDescriptor container = descriptor.getContainingDeclaration();
|
DeclarationDescriptor container = descriptor.getContainingDeclaration();
|
||||||
if (container instanceof ClassDescriptor) {
|
if (container instanceof ClassDescriptor) {
|
||||||
// TODO: getDefaultType() here is wrong and won't work for arrays
|
// TODO: getDefaultType() here is wrong and won't work for arrays
|
||||||
StackValue value = generateClassLiteralReference(state.getTypeMapper(), ((ClassDescriptor) container).getDefaultType());
|
putJavaLangClassInstance(iv, state.getTypeMapper().mapType(((ClassDescriptor) container).getDefaultType()));
|
||||||
value.put(K_CLASS_TYPE, iv);
|
wrapJavaClassIntoKClass(iv);
|
||||||
}
|
}
|
||||||
else if (container instanceof PackageFragmentDescriptor) {
|
else if (container instanceof PackageFragmentDescriptor) {
|
||||||
iv.aconst(state.getTypeMapper().mapOwner(descriptor));
|
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.TypeProjection;
|
||||||
import org.jetbrains.kotlin.types.TypeUtils;
|
import org.jetbrains.kotlin.types.TypeUtils;
|
||||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
||||||
|
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS;
|
||||||
import org.jetbrains.kotlin.types.typesApproximation.CapturedTypeApproximationKt;
|
import org.jetbrains.kotlin.types.typesApproximation.CapturedTypeApproximationKt;
|
||||||
import org.jetbrains.org.objectweb.asm.Label;
|
import org.jetbrains.org.objectweb.asm.Label;
|
||||||
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
||||||
@@ -3209,13 +3210,11 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StackValue visitClassLiteralExpression(@NotNull KtClassLiteralExpression expression, StackValue data) {
|
public StackValue visitClassLiteralExpression(@NotNull KtClassLiteralExpression expression, StackValue data) {
|
||||||
KotlinType type = bindingContext.getType(expression);
|
KtExpression receiverExpression = expression.getReceiverExpression();
|
||||||
assert type != null;
|
assert receiverExpression != null : "Class literal expression should have a left-hand side";
|
||||||
|
DoubleColonLHS lhs = bindingContext.get(DOUBLE_COLON_LHS, receiverExpression);
|
||||||
assert state.getReflectionTypes().getKClass().getTypeConstructor().equals(type.getConstructor())
|
assert lhs != null : "Class literal expression should have LHS resolved";
|
||||||
: "::class expression should be type checked to a KClass: " + type;
|
return generateClassLiteralReference(lhs, receiverExpression, /* wrapIntoKClass = */ true);
|
||||||
|
|
||||||
return generateClassLiteralReference(typeMapper, CollectionsKt.single(type.getArguments()).getType(), this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -3282,28 +3281,31 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static StackValue generateClassLiteralReference(@NotNull KotlinTypeMapper typeMapper, @NotNull KotlinType type) {
|
public StackValue generateClassLiteralReference(
|
||||||
return generateClassLiteralReference(typeMapper, type, null);
|
@NotNull final DoubleColonLHS lhs,
|
||||||
}
|
@Nullable final KtExpression receiverExpression,
|
||||||
|
final boolean wrapIntoKClass
|
||||||
@NotNull
|
) {
|
||||||
private static StackValue generateClassLiteralReference(@NotNull final KotlinTypeMapper typeMapper, @NotNull final KotlinType type, @Nullable final ExpressionCodegen codegen) {
|
return StackValue.operation(wrapIntoKClass ? K_CLASS_TYPE : JAVA_CLASS_TYPE, new Function1<InstructionAdapter, Unit>() {
|
||||||
return StackValue.operation(K_CLASS_TYPE, new Function1<InstructionAdapter, Unit>() {
|
|
||||||
@Override
|
@Override
|
||||||
public Unit invoke(InstructionAdapter v) {
|
public Unit invoke(InstructionAdapter v) {
|
||||||
Type classAsmType = typeMapper.mapType(type);
|
KotlinType type = lhs.getType();
|
||||||
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
|
if (lhs instanceof DoubleColonLHS.Expression) {
|
||||||
if (descriptor instanceof TypeParameterDescriptor) {
|
JavaClassProperty.INSTANCE.generateImpl(v, gen(receiverExpression));
|
||||||
TypeParameterDescriptor typeParameterDescriptor = (TypeParameterDescriptor) descriptor;
|
}
|
||||||
assert typeParameterDescriptor.isReified() :
|
else if (lhs instanceof DoubleColonLHS.Type) {
|
||||||
"Non-reified type parameter under ::class should be rejected by type checker: " + typeParameterDescriptor;
|
if (TypeUtils.isTypeParameter(type)) {
|
||||||
assert codegen != null :
|
assert TypeUtils.isReifiedTypeParameter(type) :
|
||||||
"Reference to member of reified type should be rejected by type checker " + typeParameterDescriptor;
|
"Non-reified type parameter under ::class should be rejected by type checker: " + type;
|
||||||
codegen.putReifiedOperationMarkerIfTypeIsReifiedParameter(type, ReifiedTypeInliner.OperationKind.JAVA_CLASS);
|
putReifiedOperationMarkerIfTypeIsReifiedParameter(type, ReifiedTypeInliner.OperationKind.JAVA_CLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
putJavaLangClassInstance(v, typeMapper.mapType(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
putJavaLangClassInstance(v, classAsmType);
|
if (wrapIntoKClass) {
|
||||||
wrapJavaClassIntoKClass(v);
|
wrapJavaClassIntoKClass(v);
|
||||||
|
}
|
||||||
|
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,69 +16,21 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.intrinsics
|
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.ExpressionCodegen
|
||||||
import org.jetbrains.kotlin.codegen.StackValue
|
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.psi.KtClassLiteralExpression
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext.DOUBLE_COLON_LHS
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
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.resolve.scopes.receivers.ExpressionReceiver
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
|
||||||
|
|
||||||
class KClassJavaProperty : IntrinsicPropertyGetter() {
|
class KClassJavaProperty : IntrinsicPropertyGetter() {
|
||||||
override fun generate(resolvedCall: ResolvedCall<*>?, codegen: ExpressionCodegen, returnType: Type, receiver: StackValue): StackValue? {
|
override fun generate(resolvedCall: ResolvedCall<*>?, codegen: ExpressionCodegen, returnType: Type, receiver: StackValue): StackValue? {
|
||||||
val receiverType = resolvedCall!!.extensionReceiver!!.type
|
val receiverValue = resolvedCall!!.extensionReceiver as? ExpressionReceiver ?: return null
|
||||||
val type = getKClassTypeArgument(receiverType) ?: return null
|
val classLiteralExpression = receiverValue.expression as? KtClassLiteralExpression ?: return null
|
||||||
val asmType = codegen.state.typeMapper.mapType(type)
|
val receiverExpression = classLiteralExpression.receiverExpression ?: return null
|
||||||
|
val lhs = codegen.bindingContext.get(DOUBLE_COLON_LHS, receiverExpression) ?: return null
|
||||||
return when {
|
val value = codegen.generateClassLiteralReference(lhs, receiverExpression, /* wrapIntoKClass = */ false)
|
||||||
isReifiedTypeParameter(type) -> {
|
return StackValue.coercion(value, returnType)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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")
|
@TestMetadata("compiler/testData/codegen/box/classes")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user