Add temporary hack for wildcards in Collections

By default we would render 'MutableCollection<String>.addAll(Collection<String>)' as
'(LCollection<String>;)' (without wildcard) because String is final and
effectively it's the same as '(LCollection<? extends String>;)'.

But that's wrong signature in a sense that java.util.Collection has different
signature: '(LCollection<? extends E>)'.

Actually the problem is much wider than collections,
it concerns any Java code that uses Kotlin classes with covariant
parameters without '? extends E' wildcards.

Temporary solution is just to hardcode/enumerate builtin methods
with special signature.
This commit is contained in:
Denis Zharkov
2015-11-24 13:42:46 +03:00
parent 0255be7deb
commit 20cbceb56d
9 changed files with 124 additions and 39 deletions
@@ -19,16 +19,20 @@ package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.backend.common.bridges.DescriptorBasedFunctionHandle
import org.jetbrains.kotlin.backend.common.bridges.findAllReachableDeclarations
import org.jetbrains.kotlin.backend.common.bridges.findConcreteSuperDeclaration
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.load.java.*
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature.getSpecialSignatureInfo
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.java.getOverriddenBuiltinWithDifferentJvmDescriptor
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.firstOverridden
import org.jetbrains.kotlin.utils.singletonOrEmptyList
import java.util.*
@@ -1200,7 +1200,18 @@ public class JetTypeMapper {
@NotNull CallableDescriptor callableDescriptor
) {
sw.writeParameterType(kind);
mapType(type, sw, TypeMappingMode.getOptimalModeForValueParameter(type));
TypeMappingMode typeMappingMode;
if (TypeMappingUtil.isMethodWithDeclarationSiteWildcards(callableDescriptor) && !type.getArguments().isEmpty()) {
typeMappingMode = TypeMappingMode.GENERIC_TYPE; // Render all wildcards
}
else {
typeMappingMode = TypeMappingMode.getOptimalModeForValueParameter(type);
}
mapType(type, sw, typeMappingMode);
sw.writeParameterTypeEnd();
}
@@ -18,8 +18,11 @@
package org.jetbrains.kotlin.codegen.state
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.firstOverridden
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.resolve.descriptorUtil.propertyIfAccessor
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Variance
@@ -67,3 +70,16 @@ public fun getEffectiveVariance(parameterVariance: Variance, projectionKind: Var
return Variance.OUT_VARIANCE
}
val CallableDescriptor.isMethodWithDeclarationSiteWildcards: Boolean
get() {
if (this !is CallableMemberDescriptor) return false
return firstOverridden {
METHODS_WITH_DECLARATION_SITE_WILDCARDS.containsRaw(it.propertyIfAccessor.fqNameOrNull())
} != null
}
private val METHODS_WITH_DECLARATION_SITE_WILDCARDS = setOf(
FqName("kotlin.MutableCollection.addAll"),
FqName("kotlin.MutableList.addAll"),
FqName("kotlin.MutableMap.putAll")
)
@@ -0,0 +1,13 @@
import java.util.*;
public class J {
private static class MyList<E> extends KList<E> {}
public static String foo() {
Collection<String> collection = new MyList<String>();
if (!collection.contains("ABCDE")) return "fail 1";
if (!collection.containsAll(Arrays.asList(1, 2, 3))) return "fail 2";
return "OK";
}
}
@@ -0,0 +1,40 @@
open class KList<E> : List<E> {
override val size: Int
get() = throw UnsupportedOperationException()
override fun isEmpty(): Boolean {
throw UnsupportedOperationException()
}
override fun contains(o: E) = true
override fun containsAll(c: Collection<E>) = true
override fun iterator(): Iterator<E> {
throw UnsupportedOperationException()
}
override fun get(index: Int): E {
throw UnsupportedOperationException()
}
override fun indexOf(element: E): Int {
throw UnsupportedOperationException()
}
override fun lastIndexOf(element: E): Int {
throw UnsupportedOperationException()
}
override fun listIterator(): ListIterator<E> {
throw UnsupportedOperationException()
}
override fun listIterator(index: Int): ListIterator<E> {
throw UnsupportedOperationException()
}
override fun subList(fromIndex: Int, toIndex: Int): List<E> {
throw UnsupportedOperationException()
}
}
fun box() = J.foo()
@@ -1,4 +1,4 @@
open class KList : MutableList<String> {
abstract class KList : MutableList<String> {
override val size: Int
get() = throw UnsupportedOperationException()
@@ -287,6 +287,12 @@ public class BlackBoxWithJavaCodegenTestGenerated extends AbstractBlackBoxCodege
doTestWithJava(fileName);
}
@TestMetadata("readOnlyList")
public void testReadOnlyList() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/collections/readOnlyList/");
doTestWithJava(fileName);
}
@TestMetadata("removeAtInt")
public void testRemoveAtInt() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/collections/removeAtInt/");
@@ -27,13 +27,8 @@ import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.addToStdlib.check
object BuiltinSpecialProperties {
private val PROPERTY_FQ_NAME_TO_JVM_GETTER_NAME_MAP = mapOf(
@@ -280,33 +275,6 @@ private fun CallableMemberDescriptor.isFromBuiltins(): Boolean {
this.module == this.builtIns.builtInsModule
}
private val CallableMemberDescriptor.propertyIfAccessor: CallableMemberDescriptor
get() = if (this is PropertyAccessorDescriptor) correspondingProperty else this
private fun CallableDescriptor.fqNameOrNull(): FqName? = fqNameUnsafe.check { it.isSafe }?.toSafe()
public fun CallableMemberDescriptor.firstOverridden(
predicate: (CallableMemberDescriptor) -> Boolean
): CallableMemberDescriptor? {
var result: CallableMemberDescriptor? = null
return DFS.dfs(listOf(this),
object : DFS.Neighbors<CallableMemberDescriptor> {
override fun getNeighbors(current: CallableMemberDescriptor?): Iterable<CallableMemberDescriptor> {
return current?.overriddenDescriptors ?: emptyList()
}
},
object : DFS.AbstractNodeHandler<CallableMemberDescriptor, CallableMemberDescriptor?>() {
override fun beforeChildren(current: CallableMemberDescriptor) = result == null
override fun afterChildren(current: CallableMemberDescriptor) {
if (result == null && predicate(current)) {
result = current
}
}
override fun result(): CallableMemberDescriptor? = result
}
)
}
public fun CallableMemberDescriptor.isFromJavaOrBuiltins() = isFromJava || isFromBuiltins()
private fun Map<FqName, Name>.getInversedShortNamesMap(): Map<Name, List<Name>> =
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.constants.EnumValue
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.addToStdlib.check
public fun ClassDescriptor.getClassObjectReferenceTarget(): ClassDescriptor = getCompanionObjectDescriptor() ?: this
@@ -197,3 +198,29 @@ public val DeclarationDescriptor.parentsWithSelf: Sequence<DeclarationDescriptor
public val DeclarationDescriptor.parents: Sequence<DeclarationDescriptor>
get() = parentsWithSelf.drop(1)
val CallableMemberDescriptor.propertyIfAccessor: CallableMemberDescriptor
get() = if (this is PropertyAccessorDescriptor) correspondingProperty else this
fun CallableDescriptor.fqNameOrNull(): FqName? = fqNameUnsafe.check { it.isSafe }?.toSafe()
public fun CallableMemberDescriptor.firstOverridden(
predicate: (CallableMemberDescriptor) -> Boolean
): CallableMemberDescriptor? {
var result: CallableMemberDescriptor? = null
return DFS.dfs(listOf(this),
object : DFS.Neighbors<CallableMemberDescriptor> {
override fun getNeighbors(current: CallableMemberDescriptor?): Iterable<CallableMemberDescriptor> {
return current?.overriddenDescriptors ?: emptyList()
}
},
object : DFS.AbstractNodeHandler<CallableMemberDescriptor, CallableMemberDescriptor?>() {
override fun beforeChildren(current: CallableMemberDescriptor) = result == null
override fun afterChildren(current: CallableMemberDescriptor) {
if (result == null && predicate(current)) {
result = current
}
}
override fun result(): CallableMemberDescriptor? = result
}
)
}