Add PurelyImplements annotation
It's parameter is FQ-name of class (currently only from builtins) that added as supertype to annotated Java class. Parameters of annotated class used as non-flexible arguments of added supertype, that helps to propagate more precise types when using in Kotlin. Some standard JDK collections loaded as they annotated with PurelyImplements. See tests for clarification. Before: ArrayList<Int>.add(x: Int!) // possible to add null After: ArrayList<Int>.add(x: Int) // impossible to add null #KT-7628 Fixed #KT-7835 Fixed
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.load.java
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
|
||||
public object FakePureImplementationsProvider {
|
||||
public fun getPurelyImplementedInterface(classFqName: FqName): FqName? = when(classFqName) {
|
||||
in MUTABLE_LISTS_IMPLEMENTATIONS -> MUTABLE_LIST_FQ_NAME
|
||||
in MUTABLE_MAPS_IMPLEMENTATIONS -> MUTABLE_MAP_FQ_NAME
|
||||
in MUTABLE_SETS_IMPLEMENTATIONS -> MUTABLE_SET_FQ_NAME
|
||||
else -> null
|
||||
}
|
||||
|
||||
private val kotlinBuiltins = KotlinBuiltIns.getInstance()
|
||||
|
||||
private val MUTABLE_LIST_FQ_NAME = DescriptorUtils.getFqNameSafe(kotlinBuiltins.getMutableList())
|
||||
private val MUTABLE_SET_FQ_NAME = DescriptorUtils.getFqNameSafe(kotlinBuiltins.getMutableSet())
|
||||
private val MUTABLE_MAP_FQ_NAME = DescriptorUtils.getFqNameSafe(kotlinBuiltins.getMutableMap())
|
||||
|
||||
private val MUTABLE_LISTS_IMPLEMENTATIONS = setOfFqNames("java.util.ArrayList", "java.util.LinkedList")
|
||||
private val MUTABLE_MAPS_IMPLEMENTATIONS = setOfFqNames(
|
||||
"java.util.HashMap", "java.util.TreeMap", "java.util.LinkedHashMap",
|
||||
"java.util.concurrent.ConcurrentHashMap", "java.util.concurrent.ConcurrentSkipListMap"
|
||||
)
|
||||
private val MUTABLE_SETS_IMPLEMENTATIONS = setOfFqNames(
|
||||
"java.util.HashSet", "java.util.TreeSet", "java.util.LinkedHashSet"
|
||||
)
|
||||
}
|
||||
|
||||
private fun setOfFqNames(vararg names: String) = names.map { FqName(it) }.toSet()
|
||||
@@ -44,6 +44,8 @@ public final class JvmAnnotationNames {
|
||||
public static final FqName JETBRAINS_MUTABLE_ANNOTATION = new FqName("org.jetbrains.annotations.Mutable");
|
||||
public static final FqName JETBRAINS_READONLY_ANNOTATION = new FqName("org.jetbrains.annotations.ReadOnly");
|
||||
|
||||
public static final FqName PURELY_IMPLEMENTS_ANNOTATION = new FqName("kotlin.jvm.PurelyImplements");
|
||||
|
||||
public static class KotlinClass {
|
||||
public static final JvmClassName CLASS_NAME = JvmClassName.byInternalName("kotlin/jvm/internal/KotlinClass");
|
||||
public static final ClassId KIND_CLASS_ID =
|
||||
|
||||
+49
-3
@@ -20,6 +20,8 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorBase
|
||||
import org.jetbrains.kotlin.load.java.FakePureImplementationsProvider
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||
import org.jetbrains.kotlin.load.java.components.TypeUsage
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
|
||||
@@ -30,11 +32,12 @@ import org.jetbrains.kotlin.load.java.structure.JavaClass
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaClassifierType
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaType
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.isValidJavaFqName
|
||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||
import org.jetbrains.kotlin.resolve.scopes.InnerClassesScopeWrapper
|
||||
import org.jetbrains.kotlin.resolve.scopes.JetScope
|
||||
import org.jetbrains.kotlin.types.AbstractClassTypeConstructor
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
import org.jetbrains.kotlin.types.TypeConstructor
|
||||
import org.jetbrains.kotlin.types.*
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.utils.toReadOnlyList
|
||||
import java.util.ArrayList
|
||||
|
||||
@@ -121,17 +124,26 @@ class LazyJavaClassDescriptor(
|
||||
val result = ArrayList<JetType>(javaTypes.size())
|
||||
val incomplete = ArrayList<JavaType>(0)
|
||||
|
||||
val purelyImplementedSupertype: JetType? = getPurelyImplementedSupertype()
|
||||
|
||||
for (javaType in javaTypes) {
|
||||
val jetType = c.typeResolver.transformJavaType(javaType, TypeUsage.SUPERTYPE.toAttributes())
|
||||
if (jetType.isError()) {
|
||||
incomplete.add(javaType)
|
||||
continue
|
||||
}
|
||||
|
||||
if (jetType.getConstructor() == purelyImplementedSupertype?.getConstructor()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!KotlinBuiltIns.isAnyOrNullableAny(jetType)) {
|
||||
result.add(jetType)
|
||||
}
|
||||
}
|
||||
|
||||
result.addIfNotNull(purelyImplementedSupertype)
|
||||
|
||||
if (incomplete.isNotEmpty()) {
|
||||
c.errorReporter.reportIncompleteHierarchy(getDeclarationDescriptor(), incomplete.map { javaType ->
|
||||
(javaType as JavaClassifierType).getPresentableText()
|
||||
@@ -141,6 +153,40 @@ class LazyJavaClassDescriptor(
|
||||
if (result.isNotEmpty()) result.toReadOnlyList() else listOf(c.module.builtIns.getAnyType())
|
||||
}
|
||||
|
||||
private fun getPurelyImplementedSupertype(): JetType? {
|
||||
val purelyImplementedFqName = getPurelyImplementsFqNameFromAnnotation()
|
||||
?: FakePureImplementationsProvider.getPurelyImplementedInterface(fqName)
|
||||
?: return null
|
||||
|
||||
if (purelyImplementedFqName.parent() != KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME) return null
|
||||
|
||||
val classDescriptor = KotlinBuiltIns.getInstance().getBuiltInClassByNameNullable(purelyImplementedFqName.shortName())
|
||||
?: return null
|
||||
|
||||
if (classDescriptor.getTypeConstructor().getParameters().size() != getParameters().size()) return null
|
||||
|
||||
val parametersAsTypeProjections = getParameters().map {
|
||||
parameter -> TypeProjectionImpl(Variance.INVARIANT, parameter.getDefaultType())
|
||||
}
|
||||
|
||||
return JetTypeImpl(
|
||||
Annotations.EMPTY, classDescriptor.getTypeConstructor(),
|
||||
/* nullable =*/ false, parametersAsTypeProjections,
|
||||
classDescriptor.getMemberScope(parametersAsTypeProjections)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getPurelyImplementsFqNameFromAnnotation(): FqName? {
|
||||
val annotation = this@LazyJavaClassDescriptor.
|
||||
getAnnotations().
|
||||
findAnnotation(JvmAnnotationNames.PURELY_IMPLEMENTS_ANNOTATION) ?: return null
|
||||
|
||||
val fqNameString = (annotation.getAllValueArguments().values().singleOrNull() as? StringValue)?.getValue() ?: return null
|
||||
if (!isValidJavaFqName(fqNameString)) return null
|
||||
|
||||
return FqName(fqNameString)
|
||||
}
|
||||
|
||||
override fun getSupertypes(): Collection<JetType> = supertypes()
|
||||
|
||||
override fun getAnnotations() = Annotations.EMPTY
|
||||
|
||||
Reference in New Issue
Block a user