KT-39228 Fix inliner when latest 1.4 compiler used with 1.3 stdlib
Since 1.4.0-dev-8774, we mangle functions returning inline class values, including functions with return type 'kotlin.Result'. This causes incompatibility when 1.4 compiler is used with 1.3 (or just some pre-1.4.0-dev-8774) standard library. Also, write "message from the future" on functions returning inline class values indicating that they can be used since compiler version 1.4 (otherwise 1.3 compiler using 1.4 stdlib would fail to find some @InlineOnly functions such as 'Result.success' and 'Result.failure').
This commit is contained in:
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicArrayConstructors
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
@@ -24,7 +25,9 @@ import org.jetbrains.kotlin.resolve.ImportedFromObjectCallableDescriptor
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.inline.isInlineOnly
|
||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
|
||||
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
@@ -629,8 +632,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
|
||||
?: throw IllegalStateException("Couldn't find declaration file for $containerId")
|
||||
}
|
||||
|
||||
val methodNode =
|
||||
getMethodNode(bytes, asmMethod.name, asmMethod.descriptor, AsmUtil.asmTypeByClassId(containerId)) ?: return null
|
||||
val methodNode = getMethodNodeInner(containerId, bytes, asmMethod, callableDescriptor) ?: return null
|
||||
|
||||
// KLUDGE: Inline suspend function built with compiler version less than 1.1.4/1.2-M1 did not contain proper
|
||||
// before/after suspension point marks, so we detect those functions here and insert the corresponding marks
|
||||
@@ -641,6 +643,28 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
|
||||
return methodNode
|
||||
}
|
||||
|
||||
private fun getMethodNodeInner(
|
||||
containerId: ClassId,
|
||||
bytes: ByteArray,
|
||||
asmMethod: Method,
|
||||
callableDescriptor: CallableMemberDescriptor
|
||||
): SMAPAndMethodNode? {
|
||||
val classType = AsmUtil.asmTypeByClassId(containerId)
|
||||
var methodNode = getMethodNode(bytes, asmMethod.name, asmMethod.descriptor, classType)
|
||||
if (methodNode == null && requiresFunctionNameManglingForReturnType(callableDescriptor)) {
|
||||
val nameWithoutManglingSuffix = asmMethod.name.stripManglingSuffixOrNull()
|
||||
if (nameWithoutManglingSuffix != null) {
|
||||
methodNode = getMethodNode(bytes, nameWithoutManglingSuffix, asmMethod.descriptor, classType)
|
||||
}
|
||||
}
|
||||
return methodNode
|
||||
}
|
||||
|
||||
private fun String.stripManglingSuffixOrNull(): String? {
|
||||
val dashIndex = indexOf('-')
|
||||
return if (dashIndex < 0) null else substring(0, dashIndex)
|
||||
}
|
||||
|
||||
private fun isBuiltInArrayIntrinsic(callableDescriptor: CallableMemberDescriptor): Boolean {
|
||||
if (callableDescriptor is FictitiousArrayConstructor) return true
|
||||
val name = callableDescriptor.name.asString()
|
||||
|
||||
+22
-6
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.classId
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPrivateApi
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.nonSourceAnnotations
|
||||
import org.jetbrains.kotlin.resolve.jvm.annotations.hasJvmDefaultAnnotation
|
||||
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
|
||||
import org.jetbrains.kotlin.serialization.DescriptorSerializer
|
||||
import org.jetbrains.kotlin.serialization.DescriptorSerializer.Companion.writeVersionRequirement
|
||||
import org.jetbrains.kotlin.serialization.SerializerExtension
|
||||
@@ -54,6 +55,7 @@ class JvmSerializerExtension @JvmOverloads constructor(
|
||||
private val languageVersionSettings = state.languageVersionSettings
|
||||
private val isParamAssertionsDisabled = state.isParamAssertionsDisabled
|
||||
private val unifiedNullChecks = state.unifiedNullChecks
|
||||
private val functionsWithInlineClassReturnTypesMangled = state.functionsWithInlineClassReturnTypesMangled
|
||||
override val metadataVersion = state.metadataVersion
|
||||
private val jvmDefaultMode = state.jvmDefaultMode
|
||||
|
||||
@@ -75,17 +77,17 @@ class JvmSerializerExtension @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
override fun serializeClass(
|
||||
descriptor: ClassDescriptor,
|
||||
proto: ProtoBuf.Class.Builder,
|
||||
versionRequirementTable: MutableVersionRequirementTable,
|
||||
childSerializer: DescriptorSerializer
|
||||
descriptor: ClassDescriptor,
|
||||
proto: ProtoBuf.Class.Builder,
|
||||
versionRequirementTable: MutableVersionRequirementTable,
|
||||
childSerializer: DescriptorSerializer
|
||||
) {
|
||||
if (moduleName != JvmProtoBufUtil.DEFAULT_MODULE_NAME) {
|
||||
proto.setExtension(JvmProtoBuf.classModuleName, stringTable.getStringIndex(moduleName))
|
||||
}
|
||||
//TODO: support local delegated properties in new defaults scheme
|
||||
val containerAsmType =
|
||||
if (DescriptorUtils.isInterface(descriptor)) typeMapper.mapDefaultImpls(descriptor) else typeMapper.mapClass(descriptor)
|
||||
if (isInterface(descriptor)) typeMapper.mapDefaultImpls(descriptor) else typeMapper.mapClass(descriptor)
|
||||
writeLocalProperties(proto, containerAsmType, JvmProtoBuf.classLocalVariable)
|
||||
writeVersionRequirementForJvmDefaultIfNeeded(descriptor, proto, versionRequirementTable)
|
||||
|
||||
@@ -198,6 +200,10 @@ class JvmSerializerExtension @JvmOverloads constructor(
|
||||
if (descriptor.needsInlineParameterNullCheckRequirement()) {
|
||||
versionRequirementTable?.writeInlineParameterNullCheckRequirement(proto::addVersionRequirement)
|
||||
}
|
||||
|
||||
if (requiresFunctionNameManglingForReturnType(descriptor)) {
|
||||
versionRequirementTable?.writeFunctionNameManglingForReturnTypeRequirement(proto::addVersionRequirement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableVersionRequirementTable.writeInlineParameterNullCheckRequirement(add: (Int) -> Unit) {
|
||||
@@ -208,6 +214,12 @@ class JvmSerializerExtension @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableVersionRequirementTable.writeFunctionNameManglingForReturnTypeRequirement(add: (Int) -> Unit) {
|
||||
if (functionsWithInlineClassReturnTypesMangled) {
|
||||
add(writeVersionRequirement(1, 4, 0, ProtoBuf.VersionRequirement.VersionKind.COMPILER_VERSION, this))
|
||||
}
|
||||
}
|
||||
|
||||
private fun FunctionDescriptor.needsInlineParameterNullCheckRequirement(): Boolean =
|
||||
isInline && !isSuspend && !isParamAssertionsDisabled &&
|
||||
!Visibilities.isPrivate(visibility) &&
|
||||
@@ -253,6 +265,10 @@ class JvmSerializerExtension @JvmOverloads constructor(
|
||||
if (getter?.needsInlineParameterNullCheckRequirement() == true || setter?.needsInlineParameterNullCheckRequirement() == true) {
|
||||
versionRequirementTable?.writeInlineParameterNullCheckRequirement(proto::addVersionRequirement)
|
||||
}
|
||||
|
||||
if (requiresFunctionNameManglingForReturnType(descriptor)) {
|
||||
versionRequirementTable?.writeFunctionNameManglingForReturnTypeRequirement(proto::addVersionRequirement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PropertyDescriptor.isJvmFieldPropertyInInterfaceCompanion(): Boolean {
|
||||
@@ -262,7 +278,7 @@ class JvmSerializerExtension @JvmOverloads constructor(
|
||||
if (!DescriptorUtils.isCompanionObject(container)) return false
|
||||
|
||||
val grandParent = (container as ClassDescriptor).containingDeclaration
|
||||
return DescriptorUtils.isInterface(grandParent) || DescriptorUtils.isAnnotationClass(grandParent)
|
||||
return isInterface(grandParent) || DescriptorUtils.isAnnotationClass(grandParent)
|
||||
}
|
||||
|
||||
override fun serializeErrorType(type: KotlinType, builder: ProtoBuf.Type.Builder) {
|
||||
|
||||
@@ -251,6 +251,7 @@ class GenerationState private constructor(
|
||||
val isInlineDisabled: Boolean = configuration.getBoolean(CommonConfigurationKeys.DISABLE_INLINE)
|
||||
val useTypeTableInSerializer: Boolean = configuration.getBoolean(JVMConfigurationKeys.USE_TYPE_TABLE)
|
||||
val unifiedNullChecks: Boolean = languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4
|
||||
val functionsWithInlineClassReturnTypesMangled: Boolean = languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4
|
||||
|
||||
val rootContext: CodegenContext<*> = RootContext(this)
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspend
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForParameterTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.requiresFunctionNameManglingForReturnType
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
@@ -31,11 +30,9 @@ fun getManglingSuffixBasedOnKotlinSignature(descriptor: CallableMemberDescriptor
|
||||
// If a class member function returns inline class value, mangle its name.
|
||||
// NB here function can be a suspend function JVM view with return type replaced with 'Any',
|
||||
// should unwrap it and take original return type instead.
|
||||
if (descriptor.containingDeclaration is ClassDescriptor) {
|
||||
val returnType = descriptor.unwrapInitialDescriptorForSuspendFunction().returnType!!
|
||||
if (requiresFunctionNameManglingForReturnType(returnType)) {
|
||||
return "-" + md5base64(":" + getSignatureElementForMangling(returnType))
|
||||
}
|
||||
val unwrappedDescriptor = descriptor.unwrapInitialDescriptorForSuspendFunction()
|
||||
if (requiresFunctionNameManglingForReturnType(unwrappedDescriptor)) {
|
||||
return "-" + md5base64(":" + getSignatureElementForMangling(unwrappedDescriptor.returnType!!))
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ class JvmNameAnnotationChecker : DeclarationChecker {
|
||||
diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry))
|
||||
} else if (descriptor.containingDeclaration.isInlineClassThatRequiresMangling() ||
|
||||
requiresFunctionNameManglingForParameterTypes(descriptor.valueParameters.map { it.type }) ||
|
||||
descriptor.containingDeclaration is ClassDescriptor && requiresFunctionNameManglingForReturnType(descriptor.returnType)
|
||||
requiresFunctionNameManglingForReturnType(descriptor)
|
||||
) {
|
||||
diagnosticHolder.report(ErrorsJvm.INAPPLICABLE_JVM_NAME.on(annotationEntry))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package test
|
||||
|
||||
inline class IC(val x: Int)
|
||||
|
||||
class C {
|
||||
fun returnsInlineClassType(): IC = IC(42)
|
||||
val propertyOfInlineClassType: IC get() = IC(42)
|
||||
}
|
||||
+28
-12
@@ -33,33 +33,49 @@ abstract class AbstractVersionRequirementTest : TestCaseWithTmpdir() {
|
||||
fqNamesWithRequirements: List<String>,
|
||||
fqNamesWithoutRequirement: List<String> = emptyList()
|
||||
) {
|
||||
compileFiles(listOf(File("compiler/testData/versionRequirement/${getTestName(true)}.kt")), tmpdir, customLanguageVersion, analysisFlags)
|
||||
compileFiles(
|
||||
listOf(File("compiler/testData/versionRequirement/${getTestName(true)}.kt")),
|
||||
tmpdir, customLanguageVersion, analysisFlags
|
||||
)
|
||||
val module = loadModule(tmpdir)
|
||||
|
||||
for (fqName in fqNamesWithRequirements) {
|
||||
val descriptor = module.findUnambiguousDescriptorByFqName(fqName)
|
||||
|
||||
val requirement = extractRequirement(descriptor) ?: throw AssertionError("No VersionRequirement for $descriptor")
|
||||
val requirements = extractRequirement(descriptor)
|
||||
if (requirements.isEmpty()) throw AssertionError("No VersionRequirement for $descriptor")
|
||||
|
||||
assertEquals("Incorrect version for $fqName", expectedVersionRequirement, requirement.version)
|
||||
assertEquals("Incorrect level for $fqName", expectedLevel, requirement.level)
|
||||
assertEquals("Incorrect message for $fqName", expectedMessage, requirement.message)
|
||||
assertEquals("Incorrect versionKind for $fqName", expectedVersionKind, requirement.kind)
|
||||
assertEquals("Incorrect errorCode for $fqName", expectedErrorCode, requirement.errorCode)
|
||||
requirements.firstOrNull {
|
||||
expectedVersionRequirement == it.version &&
|
||||
expectedLevel == it.level &&
|
||||
expectedMessage == it.message &&
|
||||
expectedVersionKind == it.kind &&
|
||||
expectedErrorCode == it.errorCode
|
||||
}
|
||||
?: throw AssertionError(
|
||||
"Version requirement not found:\n" +
|
||||
"expectedVersionRequirement=" + expectedVersionRequirement +
|
||||
"; expectedLevel=" + expectedLevel +
|
||||
"; expectedMessage=" + expectedMessage +
|
||||
"; expectedVersionKind=" + expectedVersionKind +
|
||||
"; expectedErrorCode=" + expectedErrorCode +
|
||||
"\nActual requirements:\n" +
|
||||
requirements.joinToString(separator = "\n") { it.toString() }
|
||||
)
|
||||
}
|
||||
|
||||
for (fqName in fqNamesWithoutRequirement) {
|
||||
val descriptor = module.findUnambiguousDescriptorByFqName(fqName)
|
||||
|
||||
val requirenment = extractRequirement(descriptor)
|
||||
assertNull("Expecting absence of any requirements for $fqName, but `$requirenment`", requirenment)
|
||||
val requirement = extractRequirement(descriptor)
|
||||
assertTrue("Expecting absence of any requirements for $fqName, but `$requirement`", requirement.isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractRequirement(descriptor: DeclarationDescriptor): VersionRequirement? {
|
||||
private fun extractRequirement(descriptor: DeclarationDescriptor): List<VersionRequirement> {
|
||||
return when (descriptor) {
|
||||
is DeserializedMemberDescriptor -> descriptor.versionRequirements.singleOrNull()
|
||||
is DeserializedClassDescriptor -> descriptor.versionRequirements.singleOrNull()
|
||||
is DeserializedMemberDescriptor -> descriptor.versionRequirements
|
||||
is DeserializedClassDescriptor -> descriptor.versionRequirements
|
||||
else -> throw AssertionError("Unknown descriptor: $descriptor")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,17 @@ class JvmVersionRequirementTest : AbstractVersionRequirementTest() {
|
||||
)
|
||||
}
|
||||
|
||||
fun testInlineClassReturnTypeMangled() {
|
||||
doTest(
|
||||
VersionRequirement.Version(1, 4, 0), DeprecationLevel.ERROR, null, COMPILER_VERSION, null,
|
||||
fqNamesWithRequirements = listOf(
|
||||
"test.C.returnsInlineClassType",
|
||||
"test.C.propertyOfInlineClassType"
|
||||
),
|
||||
customLanguageVersion = LanguageVersion.KOTLIN_1_4
|
||||
)
|
||||
}
|
||||
|
||||
fun testSuspendFun_1_2() {
|
||||
doTest(
|
||||
VersionRequirement.Version(1, 1), DeprecationLevel.ERROR, null, LANGUAGE_VERSION, null,
|
||||
|
||||
+5
-2
@@ -29,8 +29,11 @@ fun requiresFunctionNameManglingForParameterTypes(valueParameterTypes: List<Kotl
|
||||
}
|
||||
|
||||
// NB functions returning all inline classes (including our special 'kotlin.Result') should be mangled.
|
||||
fun requiresFunctionNameManglingForReturnType(returnType: KotlinType?) =
|
||||
returnType != null && returnType.isInlineClassType()
|
||||
fun requiresFunctionNameManglingForReturnType(descriptor: CallableMemberDescriptor): Boolean {
|
||||
if (descriptor.containingDeclaration !is ClassDescriptor) return false
|
||||
val returnType = descriptor.returnType ?: return false
|
||||
return returnType.isInlineClassType()
|
||||
}
|
||||
|
||||
fun DeclarationDescriptor.isInlineClassThatRequiresMangling(): Boolean =
|
||||
isInlineClass() && !isDontMangleClass(this as ClassDescriptor)
|
||||
|
||||
@@ -79,41 +79,49 @@ public final annotation class A : kotlin/Annotation {
|
||||
public final get
|
||||
|
||||
// requires language version 1.3.0 (level=ERROR)
|
||||
// requires compiler version 1.4.0 (level=ERROR)
|
||||
// getter: ub()B
|
||||
public final val ub: kotlin/UByte
|
||||
public final get
|
||||
|
||||
// requires language version 1.3.0 (level=ERROR)
|
||||
// requires compiler version 1.4.0 (level=ERROR)
|
||||
// getter: ub_max()B
|
||||
public final val ub_max: kotlin/UByte
|
||||
public final get
|
||||
|
||||
// requires language version 1.3.0 (level=ERROR)
|
||||
// requires compiler version 1.4.0 (level=ERROR)
|
||||
// getter: ui()I
|
||||
public final val ui: kotlin/UInt
|
||||
public final get
|
||||
|
||||
// requires language version 1.3.0 (level=ERROR)
|
||||
// requires compiler version 1.4.0 (level=ERROR)
|
||||
// getter: ui_max()I
|
||||
public final val ui_max: kotlin/UInt
|
||||
public final get
|
||||
|
||||
// requires language version 1.3.0 (level=ERROR)
|
||||
// requires compiler version 1.4.0 (level=ERROR)
|
||||
// getter: ul()J
|
||||
public final val ul: kotlin/ULong
|
||||
public final get
|
||||
|
||||
// requires language version 1.3.0 (level=ERROR)
|
||||
// requires compiler version 1.4.0 (level=ERROR)
|
||||
// getter: ul_max()J
|
||||
public final val ul_max: kotlin/ULong
|
||||
public final get
|
||||
|
||||
// requires language version 1.3.0 (level=ERROR)
|
||||
// requires compiler version 1.4.0 (level=ERROR)
|
||||
// getter: us()S
|
||||
public final val us: kotlin/UShort
|
||||
public final get
|
||||
|
||||
// requires language version 1.3.0 (level=ERROR)
|
||||
// requires compiler version 1.4.0 (level=ERROR)
|
||||
// getter: us_max()S
|
||||
public final val us_max: kotlin/UShort
|
||||
public final get
|
||||
|
||||
Reference in New Issue
Block a user