Use JVM signatures instead of FQ-names for additional built-ins
This commit is contained in:
+104
-41
@@ -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<SimpleFunctionDescriptor> {
|
||||
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<CallableMemberDescriptor>(
|
||||
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<String> =
|
||||
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<String> =
|
||||
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<String> =
|
||||
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
|
||||
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 }
|
||||
|
||||
+120
@@ -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) "<init>" 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<JvmType> {
|
||||
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<JvmType> {
|
||||
override fun commonSupertype(types: Collection<KotlinType>): 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
|
||||
}
|
||||
}
|
||||
+9
-5
@@ -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 <T : Any> 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 <T : Any> mapBuiltInType(
|
||||
type: KotlinType,
|
||||
typeFactory: JvmTypeFactory<T>
|
||||
@@ -173,7 +177,7 @@ private fun <T : Any> 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
|
||||
|
||||
Reference in New Issue
Block a user