diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ErasedInlineClassBodyCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/ErasedInlineClassBodyCodegen.kt index bf92ee98b99..b96752851fc 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ErasedInlineClassBodyCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ErasedInlineClassBodyCodegen.kt @@ -8,7 +8,13 @@ package org.jetbrains.kotlin.codegen import com.intellij.util.ArrayUtil import org.jetbrains.kotlin.codegen.context.ClassContext import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver +import org.jetbrains.kotlin.resolve.jvm.diagnostics.Synthetic +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.org.objectweb.asm.Opcodes class ErasedInlineClassBodyCodegen( @@ -27,6 +33,38 @@ class ErasedInlineClassBodyCodegen( v.visitSource(myClass.containingKtFile.name, null) } + override fun generateSyntheticPartsAfterBody() { + super.generateSyntheticPartsAfterBody() + + val boxMethodDescriptor = InlineClassDescriptorResolver.createBoxFunctionDescriptor(descriptor) ?: return + functionCodegen.generateMethod( + Synthetic(null, boxMethodDescriptor), boxMethodDescriptor, object : FunctionGenerationStrategy.CodegenBased(state) { + override fun mapMethodSignature( + functionDescriptor: FunctionDescriptor, + typeMapper: KotlinTypeMapper, + contextKind: OwnerKind, + hasSpecialBridge: Boolean + ): JvmMethodGenericSignature { + // note that in signatures of functions inside erased inline class only underlying types are using, + // but return type of `box` function should be a wrapper type, therefore we handle this situation here differently + return typeMapper.mapSignatureForBoxMethodOfInlineClass(functionDescriptor) + } + + override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) { + val iv = codegen.v + val wrapperType = signature.returnType + val baseValueType = signature.valueParameters.single().asmType + val constructor = typeMapper.mapToCallableMethod(descriptor.unsubstitutedPrimaryConstructor!!, false) + iv.anew(wrapperType) + iv.dup() + iv.load(0, baseValueType) + constructor.genInvokeInstruction(iv) + iv.areturn(wrapperType) + } + } + ) + } + override fun generateKotlinMetadataAnnotation() { writeSyntheticClassMetadata(v, state) } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java index 23d51c511a1..e0e8b3a1ade 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java @@ -182,7 +182,7 @@ public class FunctionCodegen { } boolean hasSpecialBridge = hasSpecialBridgeMethod(functionDescriptor); - JvmMethodGenericSignature jvmSignature = typeMapper.mapSignatureWithGeneric(functionDescriptor, contextKind, hasSpecialBridge); + JvmMethodGenericSignature jvmSignature = strategy.mapMethodSignature(functionDescriptor, typeMapper, contextKind, hasSpecialBridge); Method asmMethod = jvmSignature.getAsmMethod(); int flags = getMethodAsmFlags(functionDescriptor, contextKind, state); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionGenerationStrategy.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionGenerationStrategy.java index 9c6bd696c00..b3032d50a27 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionGenerationStrategy.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionGenerationStrategy.java @@ -1,17 +1,6 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. */ package org.jetbrains.kotlin.codegen; @@ -19,9 +8,12 @@ package org.jetbrains.kotlin.codegen; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.codegen.context.MethodContext; import org.jetbrains.kotlin.codegen.state.GenerationState; +import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper; +import org.jetbrains.kotlin.descriptors.FunctionDescriptor; import org.jetbrains.kotlin.psi.KtDeclarationWithBody; import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature; import org.jetbrains.org.objectweb.asm.MethodVisitor; @@ -40,6 +32,16 @@ public abstract class FunctionGenerationStrategy { return mv; } + @NotNull + public JvmMethodGenericSignature mapMethodSignature( + @NotNull FunctionDescriptor functionDescriptor, + @NotNull KotlinTypeMapper typeMapper, + @NotNull OwnerKind contextKind, + boolean hasSpecialBridge + ) { + return typeMapper.mapSignatureWithGeneric(functionDescriptor, contextKind, hasSpecialBridge); + } + public static class FunctionDefault extends CodegenBased { private final KtDeclarationWithBody declaration; 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 800372b54ff..557b507c321 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java @@ -373,9 +373,8 @@ public class KotlinTypeMapper { return Type.VOID_TYPE; } else if (descriptor instanceof FunctionDescriptor && forceBoxedReturnType((FunctionDescriptor) descriptor)) { - // GENERIC_TYPE is a hack to automatically box the return type //noinspection ConstantConditions - return mapType(descriptor.getReturnType(), sw, TypeMappingMode.GENERIC_ARGUMENT); + return mapType(descriptor.getReturnType(), sw, TypeMappingMode.RETURN_TYPE_BOXED); } return mapReturnType(descriptor, sw, returnType); @@ -1031,10 +1030,16 @@ public class KotlinTypeMapper { return mapSignatureSkipGeneric(f, OwnerKind.IMPLEMENTATION); } + @NotNull public JvmMethodSignature mapSignatureForInlineErasedClassSkipGeneric(@NotNull FunctionDescriptor f) { return mapSignatureSkipGeneric(f, OwnerKind.ERASED_INLINE_CLASS); } + @NotNull + public JvmMethodGenericSignature mapSignatureForBoxMethodOfInlineClass(@NotNull FunctionDescriptor f) { + return mapSignature(f, OwnerKind.IMPLEMENTATION, true); + } + @NotNull public JvmMethodSignature mapSignatureSkipGeneric(@NotNull FunctionDescriptor f, @NotNull OwnerKind kind) { return mapSignature(f, kind, true); @@ -1231,11 +1236,15 @@ public class KotlinTypeMapper { /** * @return true iff a given function descriptor should be compiled to a method with boxed return type regardless of whether return type - * of that descriptor is nullable or not. This happens when a function returning a value of a primitive type overrides another function - * with a non-primitive return type. In that case the generated method's return type should be boxed: otherwise it's not possible to use + * of that descriptor is nullable or not. This happens in two cases: + * - when a target function is a synthetic box method of erased inline class; + * - when a function returning a value of a primitive type overrides another function with a non-primitive return type. + * In that case the generated method's return type should be boxed: otherwise it's not possible to use * this class from Java since javac issues errors when loading the class (incompatible return types) */ private static boolean forceBoxedReturnType(@NotNull FunctionDescriptor descriptor) { + if (isBoxMethodForInlineClass(descriptor)) return true; + //noinspection ConstantConditions if (!KotlinBuiltIns.isPrimitiveType(descriptor.getReturnType())) return false; @@ -1247,6 +1256,14 @@ public class KotlinTypeMapper { return false; } + private static boolean isBoxMethodForInlineClass(@NotNull FunctionDescriptor descriptor) { + DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration(); + if (!InlineClassesUtilsKt.isInlineClass(containingDeclaration)) return false; + + return CallableMemberDescriptor.Kind.SYNTHESIZED == descriptor.getKind() && + InlineClassDescriptorResolver.BOX_METHOD_NAME.equals(descriptor.getName()); + } + private static void writeVoidReturn(@NotNull JvmSignatureWriter sw) { sw.writeReturnType(); sw.writeAsmType(Type.VOID_TYPE); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/InlineClassDescriptorResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/InlineClassDescriptorResolver.kt new file mode 100644 index 00000000000..ee497c0b092 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/InlineClassDescriptorResolver.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve + +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl +import org.jetbrains.kotlin.name.Name + +object InlineClassDescriptorResolver { + @JvmField + val BOX_METHOD_NAME = Name.identifier("box") + + @JvmField + val UNBOX_METHOD_NAME = Name.identifier("unbox") + + private val BOXING_VALUE_PARAMETER_NAME = Name.identifier("v") + + fun createBoxFunctionDescriptor(owner: ClassDescriptor): SimpleFunctionDescriptor? { + return createConversionFunctionDescriptor(true, owner) + } + + fun createUnboxFunctionDescriptor(owner: ClassDescriptor): SimpleFunctionDescriptor? = + createConversionFunctionDescriptor(false, owner) + + private fun createConversionFunctionDescriptor( + isBoxMethod: Boolean, + owner: ClassDescriptor + ): SimpleFunctionDescriptor? { + val inlinedValue = owner.underlyingRepresentation() ?: return null + + val functionDescriptor = SimpleFunctionDescriptorImpl.create( + owner, + Annotations.EMPTY, + if (isBoxMethod) BOX_METHOD_NAME else UNBOX_METHOD_NAME, + CallableMemberDescriptor.Kind.SYNTHESIZED, + SourceElement.NO_SOURCE + ) + + functionDescriptor.initialize( + null, + if (isBoxMethod) null else owner.thisAsReceiverParameter, + emptyList(), + if (isBoxMethod) listOf(createValueParameterForBoxing(functionDescriptor, inlinedValue)) else emptyList(), + if (isBoxMethod) owner.defaultType else inlinedValue.returnType, + Modality.FINAL, + Visibilities.PUBLIC + ) + + return functionDescriptor + } + + private fun createValueParameterForBoxing( + functionDescriptor: FunctionDescriptor, + inlinedValue: ValueParameterDescriptor + ): ValueParameterDescriptorImpl { + return ValueParameterDescriptorImpl( + functionDescriptor, + null, + 0, + Annotations.EMPTY, + BOXING_VALUE_PARAMETER_NAME, + inlinedValue.type, + false, false, false, null, SourceElement.NO_SOURCE + ) + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeListing/inlineClasses/shapeOfInlineClassWithPrimitive.txt b/compiler/testData/codegen/bytecodeListing/inlineClasses/shapeOfInlineClassWithPrimitive.txt index ad887512f60..3bd9ab7fb09 100644 --- a/compiler/testData/codegen/bytecodeListing/inlineClasses/shapeOfInlineClassWithPrimitive.txt +++ b/compiler/testData/codegen/bytecodeListing/inlineClasses/shapeOfInlineClassWithPrimitive.txt @@ -1,5 +1,6 @@ @kotlin.Metadata public final static class Foo$Erased { + public final static @org.jetbrains.annotations.NotNull method box(p0: long): Foo public final static method empty(p0: long): void public final static method extension(p0: long, @org.jetbrains.annotations.NotNull p1: java.lang.Object, @org.jetbrains.annotations.NotNull p2: java.lang.String): void public final static method param(p0: long, p1: double): void diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/TypeMappingMode.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/TypeMappingMode.kt index 0381bcb71e3..e229d42cdb5 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/TypeMappingMode.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/kotlin/TypeMappingMode.kt @@ -27,6 +27,13 @@ class TypeMappingMode private constructor( @JvmField val GENERIC_ARGUMENT = TypeMappingMode() + /** + * see KotlinTypeMapper.forceBoxedReturnType() + * This configuration should be called only for method return type + */ + @JvmField + val RETURN_TYPE_BOXED = TypeMappingMode(needInlineClassWrapping = true) + /** * kotlin.Int is mapped to I */