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 a47cfff7fd7..7e155688247 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java @@ -21,7 +21,6 @@ import com.intellij.psi.PsiElement; import kotlin.Pair; import kotlin.Unit; import kotlin.collections.CollectionsKt; -import kotlin.jvm.functions.Function2; import kotlin.jvm.functions.Function3; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -81,7 +80,6 @@ import org.jetbrains.org.objectweb.asm.commons.Method; import java.util.Collection; import java.util.List; -import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isUnit; import static org.jetbrains.kotlin.codegen.AsmUtil.*; import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.*; import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.*; @@ -348,7 +346,7 @@ public class KotlinTypeMapper { return Type.VOID_TYPE; } - if (isUnit(returnType) && !TypeUtils.isNullableType(returnType) && !(descriptor instanceof PropertyGetterDescriptor)) { + if (TypeSignatureMappingKt.hasVoidReturnType(descriptor)) { if (sw != null) { sw.writeAsmType(Type.VOID_TYPE); } diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/BuiltInClassesAreSerializableOnJvm.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/BuiltInClassesAreSerializableOnJvm.kt index 800487bbca2..a10c4e37337 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/BuiltInClassesAreSerializableOnJvm.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/BuiltInClassesAreSerializableOnJvm.kt @@ -24,10 +24,6 @@ import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.load.java.components.JavaResolverCache import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor -import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassMemberScope -import org.jetbrains.kotlin.load.java.lazy.replaceComponents -import org.jetbrains.kotlin.load.java.sources.JavaSourceElement -import org.jetbrains.kotlin.load.java.structure.JavaClass import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.JavaToKotlinClassMap @@ -42,10 +38,9 @@ import org.jetbrains.kotlin.serialization.deserialization.AdditionalClassPartsPr import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor import org.jetbrains.kotlin.types.DelegatingType import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.SmartSet import org.jetbrains.kotlin.utils.addToStdlib.check -import org.jetbrains.kotlin.utils.sure import java.io.Serializable import java.util.* @@ -112,11 +107,9 @@ open class BuiltInClassesAreSerializableOnJvm( ): Collection { val javaAnalogueDescriptor = classDescriptor.getJavaAnalogue() ?: return emptyList() - val platformClassDescriptors = j2kClassMap.mapPlatformClass(javaAnalogueDescriptor.fqNameSafe, DefaultBuiltIns.Instance) - val kotlinMutableClassIfContainer = platformClassDescriptors.lastOrNull() ?: return emptyList() - val platformVersions = SmartSet.create(platformClassDescriptors.map { it.fqNameSafe }) - - if (javaAnalogueDescriptor.fqNameSafe in IGNORE_BY_DEFAULT_CLASS_FQ_NAMES) return emptyList() + val kotlinClassDescriptors = j2kClassMap.mapPlatformClass(javaAnalogueDescriptor.fqNameSafe, DefaultBuiltIns.Instance) + val kotlinMutableClassIfContainer = kotlinClassDescriptors.lastOrNull() ?: return emptyList() + val kotlinVersions = SmartSet.create(kotlinClassDescriptors.map { it.fqNameSafe }) val isMutable = j2kClassMap.isMutable(classDescriptor) @@ -131,18 +124,48 @@ open class BuiltInClassesAreSerializableOnJvm( .filter { analogueMember -> if (analogueMember.kind != CallableMemberDescriptor.Kind.DECLARATION) return@filter false if (!analogueMember.visibility.isPublicAPI) return@filter false + if (KotlinBuiltIns.isDeprecated(analogueMember)) return@filter false - val methodFqName = analogueMember.fqNameSafe + if (analogueMember.overriddenDescriptors.any { + it.containingDeclaration.fqNameSafe in kotlinVersions + }) return@filter false - if (methodFqName in BLACK_LIST_METHODS_FQ_NAMES) return@filter false - if ((methodFqName in MUTABLE_METHODS_FQ_NAMES) xor isMutable) return@filter false - - analogueMember.overriddenDescriptors.none { - it.containingDeclaration.fqNameSafe in platformVersions - } + !analogueMember.isInBlackOrMutabilityViolation(isMutable) } } + private fun SimpleFunctionDescriptor.isInBlackOrMutabilityViolation(isMutable: Boolean): Boolean { + val jvmDescriptor = computeJvmDescriptor() + val owner = containingDeclaration as ClassDescriptor + if (DFS.ifAny( + listOf(owner), + { + // Search through mapped supertypes to determine that Set.toArray is in blacklist, while we have only + // Collection.toArray there explicitly + // Note, that we can't find j.u.Collection.toArray within overriddenDescriptors of j.u.Set.toArray + it.typeConstructor.supertypes.mapNotNull { + (it.constructor.declarationDescriptor?.original as? ClassDescriptor)?.getJavaAnalogue() + } + } + ) { + javaClassDescriptor -> + signature(javaClassDescriptor, jvmDescriptor) in BLACK_LIST_METHOD_SIGNATURES + }) return true + + if ((signature(owner, jvmDescriptor) in MUTABLE_METHOD_SIGNATURES) xor isMutable) return true + + return DFS.ifAny( + listOf(this), + { it.original.overriddenDescriptors } + ) { + overridden -> + overridden.kind == CallableMemberDescriptor.Kind.DECLARATION && + j2kClassMap.isMutable(overridden.containingDeclaration as ClassDescriptor) + } + } + + private fun signature(javaClassDescriptor: ClassDescriptor, jvmDescriptor: String) = javaClassDescriptor.internalName + "." + jvmDescriptor + private fun ClassDescriptor.getJavaAnalogue(): LazyJavaClassDescriptor? { // Prevents recursive dependency: memberScope(Any) -> memberScope(Object) -> memberScope(Any) // No additional members should be added to Any @@ -205,35 +228,75 @@ open class BuiltInClassesAreSerializableOnJvm( return Serializable::class.java.isAssignableFrom(classViaReflection) } - private val IGNORE_BY_DEFAULT_CLASS_FQ_NAMES = - setOf(FqName("java.lang.String")) + - JvmPrimitiveType.values().map { it.wrapperFqName } - - private val BLACK_LIST_METHODS_FQ_NAMES = + private val BLACK_LIST_METHOD_SIGNATURES: Set = buildPrimitiveValueMethodsSet() + - FqName("java.util.Collection.toArray") + - FqName("java.util.List.toArray") + - FqName("java.util.Set.toArray") + - FqName("java.lang.annotation.Annotation.annotationType") - private val MUTABLE_METHODS_FQ_NAMES = - inClass("java.util.Collection", - "removeIf") + + "java/lang/annotation/Annotation.annotationType()${javaLang("Class").t}" + - inClass("java.util.List", - "sort", "replaceAll") + + inJavaUtil( + "Collection", "toArray()[Ljava/lang/Object;", "toArray([Ljava/lang/Object;)[Ljava/lang/Object;" + ) + - inClass("java.util.Map", - "compute", "computeIfAbsent", "computeIfPresent", "remove", "merge", "putIfAbsent", "replace", "replaceAll") + inJavaLang("String", + "codePointAt(I)I", "codePointBefore(I)I", "codePointCount(II)I", "compareToIgnoreCase($stringType)I", + "concat($stringType)$stringType", "contains(Ljava/lang/CharSequence;)Z", + "contentEquals(Ljava/lang/CharSequence;)Z", "contentEquals(Ljava/lang/StringBuffer;)Z", + "endsWith($stringType)Z", "equalsIgnoreCase($stringType)Z", "getBytes()[B", "getBytes(II[BI)V", + "getBytes($stringType)[B", "getBytes(Ljava/nio/charset/Charset;)[B", "getChars(II[CI)V", + "indexOf(I)I", "indexOf(II)I", "indexOf($stringType)I", "indexOf(${stringType}I)I", + "intern()$stringType", "isEmpty()Z", "lastIndexOf(I)I", "lastIndexOf(II)I", + "lastIndexOf($stringType)I", "lastIndexOf(${stringType}I)I", "matches($stringType)Z", + "offsetByCodePoints(II)I", "regionMatches(I${stringType}II)Z", "regionMatches(ZI${stringType}II)Z", + "replaceAll($stringType$stringType)$stringType", "replace(CC)$stringType", + "replaceFirst($stringType$stringType)$stringType", + "replace(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)$stringType", + "split(${stringType}I)[$stringType", "split($stringType)[$stringType", + "startsWith(${stringType}I)Z", "startsWith($stringType)Z", "substring(II)$stringType", + "substring(I)$stringType", "toCharArray()[C", "toLowerCase()$stringType", + "toLowerCase(Ljava/util/Locale;)$stringType", "toUpperCase()$stringType", + "toUpperCase(Ljava/util/Locale;)$stringType", "trim()$stringType") + - private fun buildPrimitiveValueMethodsSet() = - JvmPrimitiveType.values().mapTo(LinkedHashSet()) { - it.wrapperFqName.child(Name.identifier(it.javaKeywordName + "Value")) + inJavaLang("Double", "isInfinite()Z", "isNaN()Z") + + inJavaLang("Float", "isInfinite()Z", "isNaN()Z") + + + inJavaUtil("Collection", "toArray([$objectType)[$objectType", "toArray()[$objectType") + + + private val MUTABLE_METHOD_SIGNATURES: Set = + inJavaUtil("Collection", "removeIf(Ljava/util/function/Predicate;)Z") + + + inJavaUtil("List", + "sort(Ljava/util/Comparator;)V", "replaceAll(Ljava/util/function/UnaryOperator;)V") + + + inJavaUtil("Map", + "computeIfAbsent(${objectType}Ljava/util/function/Function;)$objectType", + "computeIfPresent(${objectType}Ljava/util/function/BiFunction;)$objectType", + "compute(${objectType}Ljava/util/function/BiFunction;)$objectType", + "merge($objectType${objectType}Ljava/util/function/BiFunction;)$objectType", + "putIfAbsent($objectType$objectType)$objectType", + "remove($objectType$objectType)Z", "replaceAll(Ljava/util/function/BiFunction;)V", + "replace($objectType$objectType)$objectType", + "replace($objectType$objectType$objectType)Z") + + private fun buildPrimitiveValueMethodsSet(): Set = + JvmPrimitiveType.values().flatMapTo(LinkedHashSet()) { + inJavaLang(it.wrapperFqName.shortName().asString(), "${it.javaKeywordName}Value()${it.desc}") } - - private fun inClass(classFqName: String, vararg names: String) = - names.mapTo(LinkedHashSet()) { FqName(classFqName).child(Name.identifier(it)) } } } -private val ClassDescriptor.isAny: Boolean get() = fqNameUnsafe == KotlinBuiltIns.FQ_NAMES.any \ No newline at end of file +private val ClassDescriptor.isAny: Boolean get() = fqNameUnsafe == KotlinBuiltIns.FQ_NAMES.any + +private val String.t: String + get() = "L$this;" + +private val stringType = javaLang("String").t +private val objectType = javaLang("Object").t + +private fun javaLang(name: String) = "java/lang/$name" +private fun javaUtil(name: String) = "java/util/$name" + +private fun inJavaLang(name: String, vararg signatures: String) = inClass(javaLang(name), *signatures) +private fun inJavaUtil(name: String, vararg signatures: String) = inClass(javaUtil(name), *signatures) + +private fun inClass(internalName: String, vararg signatures: String) = signatures.mapTo(LinkedHashSet()) { internalName + "." + it } diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/methodSignatureMapping.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/methodSignatureMapping.kt new file mode 100644 index 00000000000..06d48c4d4d3 --- /dev/null +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/methodSignatureMapping.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2010-2016 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. + */ + +package org.jetbrains.kotlin.load.kotlin + +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ConstructorDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType +import org.jetbrains.kotlin.types.KotlinType + +fun FunctionDescriptor.computeJvmDescriptor() + = StringBuilder().apply { + append(if (this@computeJvmDescriptor is ConstructorDescriptor) "" else name.asString()) + append("(") + + valueParameters.forEach { + appendErasedType(it.type) + } + + append(")") + + if (hasVoidReturnType(this@computeJvmDescriptor)) { + append("V") + } + else { + appendErasedType(returnType!!) + } + }.toString() + + +val ClassDescriptor.internalName: String + get() { + return computeInternalName(this) + } + +private fun StringBuilder.appendErasedType(type: KotlinType) { + append( + JvmTypeFactoryImpl.toString( + mapType(type, JvmTypeFactoryImpl, TypeMappingMode.DEFAULT, TypeMappingConfigurationImpl, descriptorTypeWriter = null))) +} + +private sealed class JvmType { + // null means 'void' + class Primitive(val jvmPrimitiveType: JvmPrimitiveType?) : JvmType() + class Object(val internalName: String) : JvmType() + class Array(val elementType: JvmType) : JvmType() +} + +private object JvmTypeFactoryImpl : JvmTypeFactory { + override fun boxType(possiblyPrimitiveType: JvmType) = + when { + possiblyPrimitiveType is JvmType.Primitive && possiblyPrimitiveType.jvmPrimitiveType != null -> + createObjectType( + JvmClassName.byFqNameWithoutInnerClasses( + possiblyPrimitiveType.jvmPrimitiveType.wrapperFqName).internalName) + else -> possiblyPrimitiveType + } + + override fun createFromString(representation: String): JvmType { + assert(representation.length > 0) { "empty string as JvmType" } + val firstChar = representation[0] + + JvmPrimitiveType.values().firstOrNull { it.desc[0] == firstChar }?.let { + return JvmType.Primitive(it) + } + + return when (firstChar) { + 'V' -> JvmType.Primitive(null) + '[' -> JvmType.Array(createFromString(representation.substring(1))) + else -> { + assert(firstChar == 'L' && representation.endsWith(';')) { + "Type that is not primitive nor array should be Object, but '$representation' was found" + } + + JvmType.Object(representation.substring(1, representation.length - 1)) + } + } + } + + override fun createObjectType(internalName: String) = JvmType.Object(internalName) + + override fun toString(type: JvmType): String = + when (type) { + is JvmType.Array -> "[" + toString(type.elementType) + is JvmType.Primitive -> type.jvmPrimitiveType?.desc ?: "V" + is JvmType.Object -> "L" + type.internalName + ";" + } + + override val javaLangClassType: JvmType + get() = createObjectType("java/lang/Class") + +} + +private object TypeMappingConfigurationImpl : TypeMappingConfiguration { + override fun commonSupertype(types: Collection): KotlinType { + throw AssertionError("There should be no intersection type in existing descriptors, but found: " + types.joinToString()) + } + + override fun getPredefinedTypeForClass(classDescriptor: ClassDescriptor) = null + + override fun processErrorType(kotlinType: KotlinType, descriptor: ClassDescriptor) { + // DO nothing + } +} diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt index 47f47a64c1a..c3ab7087df4 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/typeSignatureMapping.kt @@ -17,10 +17,7 @@ package org.jetbrains.kotlin.load.kotlin import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor -import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.load.java.typeEnhancement.hasEnhancedNullability import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames @@ -145,6 +142,13 @@ fun mapType( } } + +fun hasVoidReturnType(descriptor: CallableDescriptor): Boolean { + if (descriptor is ConstructorDescriptor) return true + return KotlinBuiltIns.isUnit(descriptor.returnType!!) && !TypeUtils.isNullableType(descriptor.returnType!!) + && descriptor !is PropertyGetterDescriptor +} + private fun mapBuiltInType( type: KotlinType, typeFactory: JvmTypeFactory @@ -173,7 +177,7 @@ private fun mapBuiltInType( return null } -private fun computeInternalName(klass: ClassDescriptor): String { +internal fun computeInternalName(klass: ClassDescriptor): String { val container = klass.containingDeclaration val name = SpecialNames.safeIdentifier(klass.name).identifier diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt index 3d58df8be83..97160fd1dab 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/DescriptorUtils.kt @@ -156,26 +156,13 @@ fun CallableDescriptor.getOwnerForEffectiveDispatchReceiverParameter(): Declarat * @return `true` iff the parameter has a default value, i.e. declares it or inherits by overriding a parameter which has a default value. */ fun ValueParameterDescriptor.hasDefaultValue(): Boolean { - val handler = object : DFS.AbstractNodeHandler() { - var result = false - - override fun beforeChildren(current: ValueParameterDescriptor): Boolean { - result = result || current.declaresDefaultValue() - return !result - } - - override fun result() = result - } - - DFS.dfs( + return DFS.ifAny( listOf(this), DFS.Neighbors { current -> current.overriddenDescriptors.map(ValueParameterDescriptor::getOriginal) }, - handler + ValueParameterDescriptor::declaresDefaultValue ) - - return handler.result() } fun Annotated.isRepeatableAnnotation(): Boolean = diff --git a/core/util.runtime/src/org/jetbrains/kotlin/utils/DFS.java b/core/util.runtime/src/org/jetbrains/kotlin/utils/DFS.java index 46833dcdc57..287628976ed 100644 --- a/core/util.runtime/src/org/jetbrains/kotlin/utils/DFS.java +++ b/core/util.runtime/src/org/jetbrains/kotlin/utils/DFS.java @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.utils; +import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -36,6 +37,30 @@ public class DFS { return dfs(nodes, neighbors, new VisitedWithSet(), handler); } + public static Boolean ifAny( + @NotNull Collection nodes, + @NotNull Neighbors neighbors, + @NotNull final Function1 predicate + ) { + final boolean[] result = new boolean[1]; + + return dfs(nodes, neighbors, new AbstractNodeHandler() { + @Override + public boolean beforeChildren(N current) { + if (predicate.invoke(current)) { + result[0] = true; + } + + return !result[0]; + } + + @Override + public Boolean result() { + return result[0]; + } + }); + } + public static R dfsFromNode(@NotNull N node, @NotNull Neighbors neighbors, @NotNull Visited visited, @NotNull NodeHandler handler) { doDfs(node, neighbors, visited, handler); return handler.result();