diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 2b58ab97a6f..c168ba9fe57 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -3750,7 +3750,9 @@ public class ExpressionCodegen extends KtVisitor impleme Type varType = getVariableTypeNoSharing(variableDescriptor); - StackValue storeTo = sharedVarType == null ? StackValue.local(index, varType) : StackValue.shared(index, varType); + StackValue storeTo = sharedVarType == null ? + StackValue.local(index, varType, variableDescriptor) : + StackValue.shared(index, varType, variableDescriptor); storeTo.putReceiver(v, false); if (variableDescriptor.isLateInit()) { @@ -3765,6 +3767,7 @@ public class ExpressionCodegen extends KtVisitor impleme markLineNumber(variableDeclaration, false); Type resultType = initializer.type; + KotlinType resultKotlinType = initializer.kotlinType; if (isDelegatedLocalVariable(variableDescriptor)) { StackValue metadataValue = getVariableMetadataValue(variableDescriptor); @@ -3772,15 +3775,17 @@ public class ExpressionCodegen extends KtVisitor impleme ResolvedCall provideDelegateCall = bindingContext.get(PROVIDE_DELEGATE_RESOLVED_CALL, variableDescriptor); if (provideDelegateCall != null) { - resultType = generateProvideDelegateCallForLocalVariable(initializer, metadataValue, provideDelegateCall); + StackValue provideDelegateValue = generateProvideDelegateCallForLocalVariable(initializer, metadataValue, provideDelegateCall); + resultType = provideDelegateValue.type; + resultKotlinType = provideDelegateValue.kotlinType; } } - storeTo.storeSelector(resultType, null, v); + storeTo.storeSelector(resultType, resultKotlinType, v); } @NotNull - private Type generateProvideDelegateCallForLocalVariable( + private StackValue generateProvideDelegateCallForLocalVariable( @NotNull StackValue initializer, StackValue metadataValue, ResolvedCall provideDelegateResolvedCall @@ -3808,7 +3813,7 @@ public class ExpressionCodegen extends KtVisitor impleme result.put(result.type, result.kotlinType, v); tempVariables.remove(arguments.get(0).asElement()); tempVariables.remove(arguments.get(1).asElement()); - return result.type; + return result; } @NotNull diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java index ba0fb12e528..5066ea6b8fc 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java @@ -23,9 +23,7 @@ import org.jetbrains.kotlin.load.java.JvmAbi; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.ValueArgument; -import org.jetbrains.kotlin.resolve.BindingContext; -import org.jetbrains.kotlin.resolve.DescriptorUtils; -import org.jetbrains.kotlin.resolve.ImportedFromObjectCallableDescriptor; +import org.jetbrains.kotlin.resolve.*; import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument; @@ -153,7 +151,7 @@ public abstract class StackValue { @NotNull public static Local local(int index, @NotNull Type type, @NotNull VariableDescriptor descriptor) { - return new Local(index, type, descriptor.isLateInit(), descriptor.getName()); + return new Local(index, type, descriptor.getType(), descriptor.isLateInit(), descriptor.getName()); } @NotNull @@ -174,7 +172,7 @@ public abstract class StackValue { @NotNull public static StackValue shared(int index, @NotNull Type type, @NotNull VariableDescriptor descriptor) { - return new Shared(index, type, descriptor.isLateInit(), descriptor.getName()); + return new Shared(index, type, descriptor.getType(), descriptor.isLateInit(), descriptor.getName()); } @NotNull @@ -363,12 +361,81 @@ public abstract class StackValue { v.invokevirtual(methodOwner.getInternalName(), type.getClassName() + "Value", "()" + type.getDescriptor(), false); } + private static void boxInlineClass(@NotNull KotlinType kotlinType, @NotNull InstructionAdapter v) { + Type boxedType = KotlinTypeMapper.mapInlineClassTypeAsDeclaration(kotlinType); + Type owner = KotlinTypeMapper.mapToErasedInlineClassType(kotlinType); + Type underlyingType = KotlinTypeMapper.mapUnderlyingTypeOfInlineClassType(kotlinType); + v.invokestatic( + owner.getInternalName(), + InlineClassDescriptorResolver.BOX_METHOD_NAME.asString(), + Type.getMethodDescriptor(boxedType, underlyingType), + false + ); + } + + private static void unboxInlineClass(@NotNull KotlinType kotlinType, @NotNull InstructionAdapter v) { + Type owner = KotlinTypeMapper.mapInlineClassTypeAsDeclaration(kotlinType); + Type resultType = KotlinTypeMapper.mapUnderlyingTypeOfInlineClassType(kotlinType); + v.invokevirtual( + owner.getInternalName(), + InlineClassDescriptorResolver.UNBOX_METHOD_NAME.asString(), + "()" + resultType.getDescriptor(), + false + ); + } + protected void coerceTo(@NotNull Type toType, @Nullable KotlinType toKotlinType, @NotNull InstructionAdapter v) { - coerce(this.type, toType, v); + coerce(this.type, this.kotlinType, toType, toKotlinType, v); } protected void coerceFrom(@NotNull Type topOfStackType, @Nullable KotlinType topOfStackKotlinType, @NotNull InstructionAdapter v) { - coerce(topOfStackType, this.type, v); + coerce(topOfStackType, topOfStackKotlinType, this.type, this.kotlinType, v); + } + + public static void coerce( + @NotNull Type fromType, + @Nullable KotlinType fromKotlinType, + @NotNull Type toType, + @Nullable KotlinType toKotlinType, + @NotNull InstructionAdapter v + ) { + if (coerceInlineClasses(fromType, fromKotlinType, toType, toKotlinType, v)) return; + coerce(fromType, toType, v); + } + + private static boolean coerceInlineClasses( + @NotNull Type fromType, + @Nullable KotlinType fromKotlinType, + @NotNull Type toType, + @Nullable KotlinType toKotlinType, + @NotNull InstructionAdapter v + ) { + if (fromKotlinType == null || toKotlinType == null) return false; + if (!InlineClassesUtilsKt.isInlineClassType(fromKotlinType)) return false; + + if (fromKotlinType.equals(toKotlinType) && fromType.equals(toType)) return true; + + boolean isFromTypeUnboxed = isUnboxedInlineClass(fromKotlinType, fromType); + if (InlineClassesUtilsKt.isInlineClassType(toKotlinType)) { + boolean isToTypeUnboxed = isUnboxedInlineClass(toKotlinType, toType); + if (isFromTypeUnboxed && !isToTypeUnboxed) { + boxInlineClass(fromKotlinType, v); + } + else if (!isFromTypeUnboxed) { + unboxInlineClass(fromKotlinType, v); + } + } + else { + if (isFromTypeUnboxed) { + boxInlineClass(fromKotlinType, v); + } + } + + return true; + } + + private static boolean isUnboxedInlineClass(@NotNull KotlinType kotlinType, @NotNull Type actualType) { + return KotlinTypeMapper.mapUnderlyingTypeOfInlineClassType(kotlinType).equals(actualType); } public static void coerce(@NotNull Type fromType, @NotNull Type toType, @NotNull InstructionAdapter v) { @@ -674,8 +741,8 @@ public abstract class StackValue { private final boolean isLateinit; private final Name name; - private Local(int index, Type type, boolean isLateinit, Name name) { - super(type, false); + private Local(int index, Type type, KotlinType kotlinType, boolean isLateinit, Name name) { + super(type, kotlinType, false); if (index < 0) { throw new IllegalStateException("local variable index must be non-negative"); @@ -691,7 +758,7 @@ public abstract class StackValue { } private Local(int index, Type type) { - this(index, type, false, null); + this(index, type, null, false, null); } @Override @@ -876,7 +943,7 @@ public abstract class StackValue { private final Type type; public ArrayElement(Type type, StackValue array, StackValue index) { - super(type, false, false, new Receiver(Type.LONG_TYPE, array, index), true); + super(type, null, false, false, new Receiver(Type.LONG_TYPE, array, index), true); this.type = type; } @@ -1069,7 +1136,7 @@ public abstract class StackValue { @Nullable ResolvedCall resolvedSetCall, @NotNull ExpressionCodegen codegen ) { - super(type, false, false, collectionElementReceiver, true); + super(type, null, false, false, collectionElementReceiver, true); this.resolvedGetCall = resolvedGetCall; this.resolvedSetCall = resolvedSetCall; this.setter = resolvedSetCall == null ? null : @@ -1193,7 +1260,7 @@ public abstract class StackValue { @NotNull StackValue receiver, @Nullable DeclarationDescriptor descriptor ) { - super(type, isStatic, isStatic, receiver, receiver.canHaveSideEffects()); + super(type, null, isStatic, isStatic, receiver, receiver.canHaveSideEffects()); this.owner = owner; this.name = name; this.descriptor = descriptor; @@ -1233,7 +1300,7 @@ public abstract class StackValue { @NotNull StackValue receiver, @NotNull ExpressionCodegen codegen, @Nullable ResolvedCall resolvedCall, boolean skipLateinitAssertion ) { - super(type, isStatic(isStaticBackingField, getter), isStatic(isStaticBackingField, setter), receiver, true); + super(type, descriptor.getType(), isStatic(isStaticBackingField, getter), isStatic(isStaticBackingField, setter), receiver, true); this.backingFieldOwner = backingFieldOwner; this.getter = getter; this.setter = setter; @@ -1419,8 +1486,8 @@ public abstract class StackValue { private final boolean isLateinit; private final Name name; - public Shared(int index, Type type, boolean isLateinit, Name name) { - super(type, false, false, local(index, OBJECT_TYPE), false); + public Shared(int index, Type type, KotlinType kotlinType, boolean isLateinit, Name name) { + super(type, kotlinType, false, false, local(index, OBJECT_TYPE), false); this.index = index; if (isLateinit && name == null) { @@ -1432,7 +1499,7 @@ public abstract class StackValue { } public Shared(int index, Type type) { - this(index, type, false, null); + this(index, type, null, false, null); } public int getIndex() { @@ -1491,7 +1558,7 @@ public abstract class StackValue { Type type, Type owner, String name, StackValue.Field receiver, boolean isLateinit, Name variableName ) { - super(type, false, false, receiver, receiver.canHaveSideEffects()); + super(type, null, false, false, receiver, receiver.canHaveSideEffects()); if (isLateinit && variableName == null) { throw new IllegalArgumentException("variableName should be non-null for captured lateinit variable " + name); @@ -1631,12 +1698,13 @@ public abstract class StackValue { public StackValueWithSimpleReceiver( @NotNull Type type, + @Nullable KotlinType kotlinType, boolean isStaticPut, boolean isStaticStore, @NotNull StackValue receiver, boolean canHaveSideEffects ) { - super(type, canHaveSideEffects); + super(type, kotlinType, canHaveSideEffects); this.receiver = receiver; this.isStaticPut = isStaticPut; this.isStaticStore = isStaticStore; @@ -1783,7 +1851,7 @@ public abstract class StackValue { @NotNull StackValueWithSimpleReceiver originalValue, @NotNull ComplexReceiver receiver ) { - super(type, bothReceiverStatic(originalValue), bothReceiverStatic(originalValue), receiver, originalValue.canHaveSideEffects()); + super(type, null, bothReceiverStatic(originalValue), bothReceiverStatic(originalValue), receiver, originalValue.canHaveSideEffects()); this.originalValue = originalValue; } @@ -1868,7 +1936,7 @@ public abstract class StackValue { @Nullable private final Label ifNull; public SafeFallback(@NotNull Type type, @Nullable Label ifNull, StackValue receiver) { - super(type, false, false, receiver, true); + super(type, null, false, false, receiver, true); this.ifNull = ifNull; } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java index 557b507c321..71d13569529 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java @@ -470,11 +470,33 @@ public class KotlinTypeMapper { ); } + @NotNull public static Type mapInlineClassTypeAsDeclaration(@NotNull KotlinType kotlinType) { return mapInlineClassType(kotlinType, TypeMappingMode.CLASS_DECLARATION); } - public static Type mapInlineClassType( + @NotNull + public static Type mapUnderlyingTypeOfInlineClassType(@NotNull KotlinType kotlinType) { + KotlinType underlyingType = InlineClassesUtilsKt.underlyingTypeOfInlineClassType(kotlinType); + if (underlyingType == null) { + throw new IllegalStateException("There should be underlying type for inline class type: " + kotlinType); + } + return mapInlineClassType(underlyingType, TypeMappingMode.DEFAULT); + } + + @NotNull + public static Type mapToErasedInlineClassType(@NotNull KotlinType kotlinType) { + return Type.getObjectType( + mapInlineClassTypeAsDeclaration(kotlinType).getInternalName() + JvmAbi.ERASED_INLINE_CLASS_SUFFIX + ); + } + + @NotNull + public static Type mapInlineClassType(@NotNull KotlinType kotlinType) { + return mapInlineClassType(kotlinType, TypeMappingMode.DEFAULT); + } + + private static Type mapInlineClassType( @NotNull KotlinType kotlinType, @NotNull TypeMappingMode mode ) { diff --git a/compiler/testData/codegen/box/inlineClasses/checkBoxingOnLocalVariableAssignments.kt b/compiler/testData/codegen/box/inlineClasses/checkBoxingOnLocalVariableAssignments.kt new file mode 100644 index 00000000000..2f7b6a58484 --- /dev/null +++ b/compiler/testData/codegen/box/inlineClasses/checkBoxingOnLocalVariableAssignments.kt @@ -0,0 +1,52 @@ +// !LANGUAGE: +InlineClasses + +inline class InlineNotNullPrimitive(val x: Int) +inline class InlineNullablePrimitive(val x: Int?) +inline class InlineNotNullReference(val a: Any) +inline class InlineNullableReference(val a: Any?) + +fun test1(a: InlineNotNullPrimitive) { + val a0 = a + val a1: Any = a // box + val a2: Any? = a // box + val a3: InlineNotNullPrimitive = a + val a4: InlineNotNullPrimitive? = a // box +} + +fun test2(b: InlineNullablePrimitive) { + val b0 = b + val b1: Any = b // box + val b2: Any? = b // box + val b3: InlineNullablePrimitive = b + val b4: InlineNullablePrimitive? = b // box +} + +fun test3(c: InlineNotNullReference) { + val c0 = c + val c1: Any = c // box + val c2: Any? = c // box + val c3: InlineNotNullReference = c + val c4: InlineNotNullReference? = c +} + +fun test4(d: InlineNullableReference) { + val d0 = d + val d1: Any = d // box + val d2: Any? = d // box + val d3: InlineNullableReference = d + val d4: InlineNullableReference? = d // box +} + +fun box(): String { + val a = InlineNotNullPrimitive(1) + val b = InlineNullablePrimitive(1) + val c = InlineNotNullReference("some") + val d = InlineNullableReference("other") + + test1(a) + test2(b) + test3(c) + test4(d) + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassBoxingOnAssignment.kt b/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassBoxingOnAssignment.kt new file mode 100644 index 00000000000..285b10cf9e3 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassBoxingOnAssignment.kt @@ -0,0 +1,45 @@ +// !LANGUAGE: +InlineClasses + +inline class InlineNotNullPrimitive(val x: Int) +inline class InlineNullablePrimitive(val x: Int?) +inline class InlineNotNullReference(val a: Any) +inline class InlineNullableReference(val a: Any?) + +fun test1(a: InlineNotNullPrimitive) { + val a0 = a + val a1: Any = a // box + val a2: Any? = a // box + val a3: InlineNotNullPrimitive = a + val a4: InlineNotNullPrimitive? = a // box +} + +fun test2(b: InlineNullablePrimitive) { + val b0 = b + val b1: Any = b // box + val b2: Any? = b // box + val b3: InlineNullablePrimitive = b + val b4: InlineNullablePrimitive? = b // box +} + +fun test3(c: InlineNotNullReference) { + val c0 = c + val c1: Any = c // box + val c2: Any? = c // box + val c3: InlineNotNullReference = c + val c4: InlineNotNullReference? = c +} + +fun test4(d: InlineNullableReference) { + val d0 = d + val d1: Any = d // box + val d2: Any? = d // box + val d3: InlineNullableReference = d + val d4: InlineNullableReference? = d // box +} + +// 3 INVOKESTATIC InlineNotNullPrimitive\$Erased.box +// 3 INVOKESTATIC InlineNullablePrimitive\$Erased.box +// 2 INVOKESTATIC InlineNotNullReference\$Erased.box +// 3 INVOKESTATIC InlineNullableReference\$Erased.box + +// 0 valueOf \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 27b5762a1bc..d469ea59356 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -1925,6 +1925,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/inlineClasses/checkOuterInlineFunctionCall.kt"); doTest(fileName); } + + @TestMetadata("inlineClassBoxingOnAssignment.kt") + public void testInlineClassBoxingOnAssignment() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/inlineClasses/inlineClassBoxingOnAssignment.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/bytecodeText/interfaces") diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt index 26b523a8baf..471229e37e0 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt @@ -8,10 +8,18 @@ package org.jetbrains.kotlin.resolve import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.utils.addToStdlib.safeAs fun ClassDescriptor.underlyingRepresentation(): ValueParameterDescriptor? { if (!isInline) return null return unsubstitutedPrimaryConstructor?.valueParameters?.singleOrNull() } -fun DeclarationDescriptor.isInlineClass() = this is ClassDescriptor && this.isInline \ No newline at end of file +fun DeclarationDescriptor.isInlineClass() = this is ClassDescriptor && this.isInline + +fun KotlinType.underlyingTypeOfInlineClassType(): KotlinType? { + return constructor.declarationDescriptor.safeAs()?.underlyingRepresentation()?.type +} + +fun KotlinType.isInlineClassType(): Boolean = constructor.declarationDescriptor?.isInlineClass() ?: false \ No newline at end of file