diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/CollectionStubMethodGenerator.kt b/compiler/backend/src/org/jetbrains/jet/codegen/CollectionStubMethodGenerator.kt new file mode 100644 index 00000000000..08b213f3877 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/jet/codegen/CollectionStubMethodGenerator.kt @@ -0,0 +1,232 @@ +/* + * Copyright 2010-2014 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.jet.codegen + +import org.jetbrains.jet.codegen.state.GenerationState +import org.jetbrains.jet.lang.descriptors.* +import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor.Kind.* +import org.jetbrains.jet.lang.descriptors.impl.* +import org.jetbrains.org.objectweb.asm.Opcodes.* +import org.jetbrains.jet.lang.resolve.java.diagnostics.JvmDeclarationOrigin +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns +import org.jetbrains.jet.lang.types.* +import org.jetbrains.jet.lang.resolve.scopes.JetScope +import org.jetbrains.jet.lang.descriptors.annotations.Annotations +import org.jetbrains.jet.lang.resolve.OverrideResolver +import org.jetbrains.jet.lang.resolve.OverridingUtil +import java.util.LinkedHashSet +import org.jetbrains.jet.lang.resolve.name.Name +import java.util.ArrayList +import org.jetbrains.jet.lang.types.checker.JetTypeChecker +import java.util.HashSet + +/** + * Generates exception-throwing stubs for methods from mutable collection classes not implemented in Kotlin classes which inherit only from + * Kotlin's read-only collections. This is required on JVM because Kotlin's read-only collections are mapped to mutable JDK collections + */ +class CollectionStubMethodGenerator( + state: GenerationState, + private val descriptor: ClassDescriptor, + private val functionCodegen: FunctionCodegen, + private val v: ClassBuilder +) { + private val typeMapper = state.getTypeMapper() + + fun generate() { + val superCollectionClasses = findRelevantSuperCollectionClasses() + if (superCollectionClasses.isEmpty()) return + + val methodStubsToGenerate = LinkedHashSet() + val syntheticStubsToGenerate = LinkedHashSet() + + for ((readOnlyClass, mutableClass) in superCollectionClasses) { + // To determine which method stubs we need to generate, we create a synthetic class (named 'child' here) which inherits from + // our class ('descriptor') and the corresponding MutableCollection class (for example; the process is the same for every + // built-in read-only/mutable class pair). We then construct and bind fake overrides in this synthetic class with the usual + // override resolution process. Resulting fake overrides with originals in MutableCollection are considered as candidates for + // method stubs or bridges to the actual implementation that happened to be present in the class + val (child, typeParameters) = createSyntheticSubclass() + // If the original class has any type parameters, we copied them and now we need to substitute types of the newly created type + // parameters as arguments for the type parameters of the original class + val parentType = newType(descriptor, typeParameters.map { TypeProjectionImpl(it.getDefaultType()) }) + + // Now we need to determine the arguments which should be substituted for the MutableCollection super class. To do that, + // we look for type arguments which were substituted in the inheritance of the original class from Collection and use them + // to construct the needed MutableCollection type. Since getAllSupertypes() may return several types which correspond to the + // Collection class descriptor, we find the most specific one (which is guaranteed to exist by front-end) + val readOnlyCollectionType = TypeUtils.getAllSupertypes(parentType).findMostSpecificTypeForClass(readOnlyClass) + val mutableCollectionType = newType(mutableClass, readOnlyCollectionType.getArguments()) + + child.addSupertype(parentType) + child.addSupertype(mutableCollectionType) + child.createTypeConstructor() + + // Bind fake overrides and for each fake override originated from the MutableCollection, save its signature to generate a stub + // or save its descriptor to generate all the needed bridges + for (method in findFakeOverridesForMethodsFromMutableCollection(child, mutableClass)) { + if (method.getModality() == Modality.ABSTRACT) { + // If the fake override is abstract and it's _declared_ as abstract in the class, skip it because the method is already + // present in the bytecode (abstract) and we don't want a duplicate signature error + if (method.findOverriddenFromDirectSuperClass(descriptor)?.getKind() == DECLARATION) continue + + // Otherwise we can safely generate the stub with the substituted signature + val signature = method.signature() + methodStubsToGenerate.add(signature) + + // If the substituted signature differs from the original one in MutableCollection, we should also generate a stub with + // the original (erased) signature. It doesn't really matter if this is a bridge method delegating to the first stub or + // a method with its own exception-throwing code, for simplicity we do the latter here. + // What _does_ matter though, is that these two methods can't be both non-synthetic at once: javac issues compilation + // errors when compiling Java against such classes because one of them doesn't seem to override the generic method + // declared in the Java Collection interface (can't override generic with erased). So we maintain an additional set of + // methods which need to be generated with the ACC_SYNTHETIC flag + val originalSignature = method.findOverriddenFromDirectSuperClass(mutableClass)!!.getOriginal().signature() + if (originalSignature != signature) { + syntheticStubsToGenerate.add(originalSignature) + } + } + else { + // If the fake override is non-abstract, its implementation is already present in the class or inherited from one of its + // super classes, but is not related to the MutableCollection hierarchy. So maybe it uses more specific return types + // and we may need to generate some bridges + functionCodegen.generateBridges(method) + } + } + } + + for (signature in methodStubsToGenerate) { + generateMethodStub(signature, synthetic = false) + } + + for (signature in syntheticStubsToGenerate) { + generateMethodStub(signature, synthetic = true) + } + } + + private data class CollectionClassPair( + val readOnlyClass: ClassDescriptor, + val mutableClass: ClassDescriptor + ) + + private fun findRelevantSuperCollectionClasses(): Collection { + fun pair(readOnlyClass: ClassDescriptor, mutableClass: ClassDescriptor) = CollectionClassPair(readOnlyClass, mutableClass) + + val collectionClasses = with(KotlinBuiltIns.getInstance()) { + listOf( + pair(getCollection(), getMutableCollection()), + pair(getSet(), getMutableSet()), + pair(getList(), getMutableList()), + pair(getMap(), getMutableMap()), + pair(getMapEntry(), getMutableMapEntry()), + pair(getIterable(), getMutableIterable()), + pair(getIterator(), getMutableIterator()), + pair(getListIterator(), getMutableListIterator()) + ) + } + + val allSuperClasses = TypeUtils.getAllSupertypes(descriptor.getDefaultType()).classes().toHashSet() + + val ourSuperCollectionClasses = collectionClasses.filter { pair -> + pair.readOnlyClass in allSuperClasses && pair.mutableClass !in allSuperClasses + } + if (ourSuperCollectionClasses.isEmpty()) return listOf() + + // Filter out built-in classes which are overridden by other built-in classes in the list, to avoid duplicating methods. + val redundantClasses = ourSuperCollectionClasses.flatMapTo(HashSet()) { pair -> + pair.readOnlyClass.getTypeConstructor().getSupertypes().classes() + } + return ourSuperCollectionClasses.filter { klass -> klass.readOnlyClass !in redundantClasses } + } + + private fun Collection.classes(): Collection = + this.map { it.getConstructor().getDeclarationDescriptor() as ClassDescriptor } + + private fun findFakeOverridesForMethodsFromMutableCollection( + klass: ClassDescriptor, + mutableCollectionClass: ClassDescriptor + ): List { + val result = ArrayList() + + OverrideResolver.generateOverridesInAClass(klass, listOf(), object : OverridingUtil.DescriptorSink { + override fun addToScope(fakeOverride: CallableMemberDescriptor) { + if (fakeOverride !is FunctionDescriptor) return + if (fakeOverride.findOverriddenFromDirectSuperClass(mutableCollectionClass) != null) { + result.add(fakeOverride) + } + } + + override fun conflict(fromSuper: CallableMemberDescriptor, fromCurrent: CallableMemberDescriptor) { + // Ignore conflicts here + // TODO: report a warning that javac will prohibit use/inheritance from such class + } + }) + + return result + } + + private fun Collection.findMostSpecificTypeForClass(klass: ClassDescriptor): JetType { + val types = this.filter { it.getConstructor().getDeclarationDescriptor() == klass } + if (types.isEmpty()) error("No supertype of $klass in $this") + if (types.size == 1) return types.first() + // Find the first type in the list such that it's a subtype of every other type in that list + return types.first { type -> + types.all { other -> JetTypeChecker.DEFAULT.isSubtypeOf(type, other) } + } + } + + private fun createSyntheticSubclass(): Pair> { + val child = MutableClassDescriptor(descriptor.getContainingDeclaration(), JetScope.EMPTY, ClassKind.CLASS, false, + Name.special(""), descriptor.getSource()) + child.setModality(Modality.FINAL) + child.setVisibility(Visibilities.PUBLIC) + val typeParameters = descriptor.getTypeConstructor().getParameters() + val newTypeParameters = ArrayList(typeParameters.size()) + DescriptorSubstitutor.substituteTypeParameters(typeParameters, TypeSubstitutor.EMPTY, child, newTypeParameters) + child.setTypeParameterDescriptors(typeParameters) + return Pair(child, newTypeParameters) + } + + private fun FunctionDescriptor.findOverriddenFromDirectSuperClass(classDescriptor: ClassDescriptor): FunctionDescriptor? { + return this.getOverriddenDescriptors().firstOrNull { it.getContainingDeclaration() == classDescriptor } + } + + private fun newType(classDescriptor: ClassDescriptor, typeArguments: List): JetType { + return JetTypeImpl(Annotations.EMPTY, classDescriptor.getTypeConstructor(), false, typeArguments, + classDescriptor.getMemberScope(typeArguments)) + } + + private fun FunctionDescriptor.signature(): String { + val method = typeMapper.mapSignature(this).getAsmMethod() + return method.getName() + method.getDescriptor() + } + + private fun generateMethodStub(signature: String, synthetic: Boolean) { + // TODO: investigate if it makes sense to generate abstract stubs in traits + var access = ACC_PUBLIC + if (descriptor.getKind() == ClassKind.TRAIT) access = access or ACC_ABSTRACT + if (synthetic) access = access or ACC_SYNTHETIC + + val paren = signature.indexOf('(') + val mv = v.newMethod(JvmDeclarationOrigin.NO_ORIGIN, access, signature.substring(0, paren), signature.substring(paren), null, null) + if (descriptor.getKind() != ClassKind.TRAIT) { + mv.visitCode() + AsmUtil.genThrow(InstructionAdapter(mv), "java/lang/UnsupportedOperationException", "Mutating immutable collection") + FunctionCodegen.endVisit(mv, "built-in stub for $signature", null) + } + } +} diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java index bde814f81df..a266b846fd6 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ImplementationBodyCodegen.java @@ -427,7 +427,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { generateFunctionsForDataClasses(); - generateBuiltinMethodStubs(); + new CollectionStubMethodGenerator(state, descriptor, functionCodegen, v).generate(); generateToArray(); @@ -510,82 +510,6 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { } } - @NotNull - private Set collectSignaturesOfExistingNonAbstractMethods() { - Set existingMethodSignatures = new HashSet(); - - for (DeclarationDescriptor member : descriptor.getDefaultType().getMemberScope().getAllDescriptors()) { - if (!(member instanceof FunctionDescriptor)) continue; - FunctionDescriptor function = (FunctionDescriptor) member; - if (function.getModality() == Modality.ABSTRACT) continue; - for (FunctionDescriptor overridden : DescriptorUtils.getAllOverriddenDescriptors(function)) { - Method overriddenMethod = typeMapper.mapSignature(overridden.getOriginal()).getAsmMethod(); - existingMethodSignatures.add(overriddenMethod.getName() + overriddenMethod.getDescriptor()); - } - - Method method = typeMapper.mapSignature(function.getOriginal()).getAsmMethod(); - existingMethodSignatures.add(method.getName() + method.getDescriptor()); - } - - return existingMethodSignatures; - } - - private void generateMethodStubs(@NotNull Set signatures) { - if (signatures.isEmpty()) return; - - Set existingMethodSignatures = collectSignaturesOfExistingNonAbstractMethods(); - - for (String signature : signatures) { - if (!existingMethodSignatures.add(signature)) return; - int access = descriptor.getKind() == ClassKind.TRAIT ? - ACC_PUBLIC | ACC_ABSTRACT : - ACC_PUBLIC; - int paren = signature.indexOf('('); - MethodVisitor mv = v.newMethod(NO_ORIGIN, access, signature.substring(0, paren), signature.substring(paren), null, null); - if (descriptor.getKind() != ClassKind.TRAIT) { - mv.visitCode(); - genThrow(new InstructionAdapter(mv), "java/lang/UnsupportedOperationException", "Mutating immutable collection"); - FunctionCodegen.endVisit(mv, "built-in stub for " + signature, null); - } - } - } - - private void generateBuiltinMethodStubs() { - KotlinBuiltIns builtIns = KotlinBuiltIns.getInstance(); - Set methodStubs = new LinkedHashSet(); - if (isSubclass(descriptor, builtIns.getCollection())) { - methodStubs.add("add(Ljava/lang/Object;)Z"); - methodStubs.add("remove(Ljava/lang/Object;)Z"); - methodStubs.add("addAll(Ljava/util/Collection;)Z"); - methodStubs.add("removeAll(Ljava/util/Collection;)Z"); - methodStubs.add("retainAll(Ljava/util/Collection;)Z"); - methodStubs.add("clear()V"); - } - - if (isSubclass(descriptor, builtIns.getList())) { - methodStubs.add("set(ILjava/lang/Object;)Ljava/lang/Object;"); - methodStubs.add("add(ILjava/lang/Object;)V"); - methodStubs.add("remove(I)Ljava/lang/Object;"); - } - - if (isSubclass(descriptor, builtIns.getMap())) { - methodStubs.add("put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - methodStubs.add("remove(Ljava/lang/Object;)Ljava/lang/Object;"); - methodStubs.add("putAll(Ljava/util/Map;)V"); - methodStubs.add("clear()V"); - } - - if (isSubclass(descriptor, builtIns.getMapEntry())) { - methodStubs.add("setValue(Ljava/lang/Object;)Ljava/lang/Object;"); - } - - if (isSubclass(descriptor, builtIns.getIterator())) { - methodStubs.add("remove()V"); - } - - generateMethodStubs(methodStubs); - } - private void generateFunctionsForDataClasses() { if (!KotlinBuiltIns.getInstance().isData(descriptor)) return; diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverrideResolver.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverrideResolver.java index b3007ff8ba6..ddc812c7107 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverrideResolver.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverrideResolver.java @@ -121,12 +121,40 @@ public class OverrideResolver { } private void generateOverridesInAClass(@NotNull final MutableClassDescriptor classDescriptor) { + generateOverridesInAClass(classDescriptor, classDescriptor.getDeclaredCallableMembers(), new OverridingUtil.DescriptorSink() { + @Override + public void addToScope(@NotNull CallableMemberDescriptor fakeOverride) { + if (fakeOverride instanceof PropertyDescriptor) { + classDescriptor.getBuilder().addPropertyDescriptor((PropertyDescriptor) fakeOverride); + } + else if (fakeOverride instanceof SimpleFunctionDescriptor) { + classDescriptor.getBuilder().addFunctionDescriptor((SimpleFunctionDescriptor) fakeOverride); + } + else { + throw new IllegalStateException(fakeOverride.getClass().getName()); + } + } + + @Override + public void conflict(@NotNull CallableMemberDescriptor fromSuper, @NotNull CallableMemberDescriptor fromCurrent) { + JetDeclaration declaration = (JetDeclaration) DescriptorToSourceUtils.descriptorToDeclaration(fromCurrent); + //noinspection ConstantConditions + trace.report(CONFLICTING_OVERLOADS.on(declaration, fromCurrent, fromCurrent.getContainingDeclaration().getName().asString())); + } + }); + + resolveUnknownVisibilities(classDescriptor.getAllCallableMembers(), trace); + } + + public static void generateOverridesInAClass( + @NotNull ClassDescriptor classDescriptor, + @NotNull Collection membersFromCurrent, + @NotNull OverridingUtil.DescriptorSink sink + ) { List membersFromSupertypes = getCallableMembersFromSupertypes(classDescriptor); - + MultiMap membersFromCurrentByName = groupDescriptorsByName(membersFromCurrent); MultiMap membersFromSupertypesByName = groupDescriptorsByName(membersFromSupertypes); - MultiMap membersFromCurrentByName = groupDescriptorsByName(classDescriptor.getDeclaredCallableMembers()); - Set memberNames = new LinkedHashSet(); memberNames.addAll(membersFromSupertypesByName.keySet()); memberNames.addAll(membersFromCurrentByName.keySet()); @@ -135,34 +163,8 @@ public class OverrideResolver { Collection fromSupertypes = membersFromSupertypesByName.get(memberName); Collection fromCurrent = membersFromCurrentByName.get(memberName); - OverridingUtil.generateOverridesInFunctionGroup( - memberName, - fromSupertypes, - fromCurrent, - classDescriptor, - new OverridingUtil.DescriptorSink() { - @Override - public void addToScope(@NotNull CallableMemberDescriptor fakeOverride) { - if (fakeOverride instanceof PropertyDescriptor) { - classDescriptor.getBuilder().addPropertyDescriptor((PropertyDescriptor) fakeOverride); - } - else if (fakeOverride instanceof SimpleFunctionDescriptor) { - classDescriptor.getBuilder().addFunctionDescriptor((SimpleFunctionDescriptor) fakeOverride); - } - else { - throw new IllegalStateException(fakeOverride.getClass().getName()); - } - } - - @Override - public void conflict(@NotNull CallableMemberDescriptor fromSuper, @NotNull CallableMemberDescriptor fromCurrent) { - JetDeclaration declaration = (JetDeclaration) DescriptorToSourceUtils.descriptorToDeclaration(fromCurrent); - //noinspection ConstantConditions - trace.report(CONFLICTING_OVERLOADS.on(declaration, fromCurrent, fromCurrent.getContainingDeclaration().getName().asString())); - } - }); + OverridingUtil.generateOverridesInFunctionGroup(memberName, fromSupertypes, fromCurrent, classDescriptor, sink); } - resolveUnknownVisibilities(classDescriptor.getAllCallableMembers(), trace); } public static void resolveUnknownVisibilities( diff --git a/compiler/testData/codegen/box/builtinStubMethods/ListIterator.kt b/compiler/testData/codegen/box/builtinStubMethods/ListIterator.kt new file mode 100644 index 00000000000..c20ffc26ff7 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/ListIterator.kt @@ -0,0 +1,25 @@ +class MyListIterator : ListIterator { + override fun next(): T = null!! + override fun hasNext(): Boolean = null!! + override fun hasPrevious(): Boolean = null!! + override fun previous(): T = null!! + override fun nextIndex(): Int = null!! + override fun previousIndex(): Int = null!! +} + +fun expectUoe(block: () -> Any) { + try { + block() + throw AssertionError() + } catch (e: UnsupportedOperationException) { + } +} + +fun box(): String { + val list = MyListIterator() as MutableListIterator + + expectUoe { list.set("") } + expectUoe { list.add("") } + + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/abstractMember.kt b/compiler/testData/codegen/box/builtinStubMethods/abstractMember.kt new file mode 100644 index 00000000000..40a42011eb3 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/abstractMember.kt @@ -0,0 +1,20 @@ +abstract class A : Iterator { + abstract fun remove(): Unit +} + +class B(var result: String) : A() { + override fun next() = "" + override fun hasNext() = false + override fun remove() { + result = "OK" + } +} + +fun box(): String { + val a = B("Fail") as MutableIterator + a.next() + a.hasNext() + a.remove() + + return (a as B).result +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/delegationToArrayList.kt b/compiler/testData/codegen/box/builtinStubMethods/delegationToArrayList.kt new file mode 100644 index 00000000000..b235c682914 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/delegationToArrayList.kt @@ -0,0 +1,47 @@ +import java.util.ArrayList + +class A : List by ArrayList() + +class B : List by A() + +fun expectUoe(block: () -> Any) { + try { + block() + throw AssertionError() + } catch (e: UnsupportedOperationException) { + } +} + +fun box(): String { + val a = A() as MutableList + expectUoe { a.add("") } + expectUoe { a.remove("") } + expectUoe { a.addAll(a) } + expectUoe { a.addAll(0, a) } + expectUoe { a.removeAll(a) } + expectUoe { a.retainAll(a) } + expectUoe { a.clear() } + expectUoe { a.add(0, "") } + expectUoe { a.set(0, "") } + expectUoe { a.remove(0) } + a.listIterator() + a.listIterator(0) + a.subList(0, 0) + + val b = B() as MutableList + expectUoe { b.add("") } + expectUoe { b.remove("") } + expectUoe { b.addAll(b) } + expectUoe { b.addAll(0, b) } + expectUoe { b.removeAll(b) } + expectUoe { b.retainAll(b) } + expectUoe { b.clear() } + expectUoe { b.add(0, "") } + expectUoe { b.set(0, "") } + expectUoe { b.remove(0) } + b.listIterator() + b.listIterator(0) + b.subList(0, 0) + + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractList.kt b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractList.kt new file mode 100644 index 00000000000..e339325fe33 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractList.kt @@ -0,0 +1,20 @@ +import java.util.AbstractList + +class A : AbstractList() { + override fun get(index: Int): String = "" + override fun size(): Int = 0 +} + +fun box(): String { + val a = A() + val b = A() + + a.addAll(b) + a.addAll(0, b) + a.removeAll(b) + a.retainAll(b) + a.clear() + a.remove("") + + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractMap.kt b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractMap.kt new file mode 100644 index 00000000000..59f2d50e8ad --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractMap.kt @@ -0,0 +1,22 @@ +import java.util.AbstractMap +import java.util.Collections + +class A : AbstractMap() { + override fun entrySet(): Set> = Collections.emptySet() +} + +fun box(): String { + val a = A() + val b = A() + + a.remove(0) + + a.putAll(b) + a.clear() + + a.keySet() + a.values() + a.entrySet() + + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractSet.kt b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractSet.kt new file mode 100644 index 00000000000..78cd670b04a --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractSet.kt @@ -0,0 +1,20 @@ +import java.util.HashSet + +class A : HashSet() + +fun box(): String { + val a = A() + val b = A() + + a.iterator() + + a.add(0L) + a.remove(0L) + + a.addAll(b) + a.removeAll(b) + a.retainAll(b) + a.clear() + + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/noMethodsForClassExtendingMutableCollection.kt b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/arrayList.kt similarity index 100% rename from compiler/testData/codegen/box/builtinStubMethods/noMethodsForClassExtendingMutableCollection.kt rename to compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/arrayList.kt diff --git a/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/hashMap.kt b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/hashMap.kt new file mode 100644 index 00000000000..246391e18d9 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/hashMap.kt @@ -0,0 +1,20 @@ +import java.util.HashMap + +class A : HashMap() + +fun box(): String { + val a = A() + val b = A() + + a.put("", 0.0) + a.remove("") + + a.putAll(b) + a.clear() + + a.keySet() + a.values() + a.entrySet() + + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/hashSet.kt b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/hashSet.kt new file mode 100644 index 00000000000..78cd670b04a --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/hashSet.kt @@ -0,0 +1,20 @@ +import java.util.HashSet + +class A : HashSet() + +fun box(): String { + val a = A() + val b = A() + + a.iterator() + + a.add(0L) + a.remove(0L) + + a.addAll(b) + a.removeAll(b) + a.retainAll(b) + a.clear() + + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/implementationInTrait.kt b/compiler/testData/codegen/box/builtinStubMethods/implementationInTrait.kt new file mode 100644 index 00000000000..d08887a8854 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/implementationInTrait.kt @@ -0,0 +1,29 @@ +trait Addable { + fun add(s: String): Boolean = true +} + +class C : Addable, List { + override fun size(): Int = null!! + override fun isEmpty(): Boolean = null!! + override fun contains(o: Any?): Boolean = null!! + override fun iterator(): Iterator = null!! + override fun containsAll(c: Collection): Boolean = null!! + override fun get(index: Int): String = null!! + override fun indexOf(o: Any?): Int = null!! + override fun lastIndexOf(o: Any?): Int = null!! + override fun listIterator(): ListIterator = null!! + override fun listIterator(index: Int): ListIterator = null!! + override fun subList(fromIndex: Int, toIndex: Int): List = null!! +} + +fun box(): String { + try { + val a = C() + if (!a.add("")) return "Fail 1" + if (!(a as Addable).add("")) return "Fail 2" + if (!(a as MutableList).add("")) return "Fail 3" + return "OK" + } catch (e: UnsupportedOperationException) { + return "Fail: no stub method should be generated" + } +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/inheritedImplementations.kt b/compiler/testData/codegen/box/builtinStubMethods/inheritedImplementations.kt new file mode 100644 index 00000000000..ff5f0de6418 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/inheritedImplementations.kt @@ -0,0 +1,21 @@ +open class SetStringImpl { + fun add(s: String): Boolean = false + fun remove(o: Any?): Boolean = false + fun clear(): Unit {} +} + +class S : Set, SetStringImpl() { + override fun size(): Int = 0 + override fun isEmpty(): Boolean = true + override fun contains(o: Any?): Boolean = false + override fun iterator(): Iterator = null!! + override fun containsAll(c: Collection) = false +} + +fun box(): String { + val s = S() as MutableSet + s.add("") + s.remove("") + s.clear() + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/manyTypeParametersWithUpperBounds.kt b/compiler/testData/codegen/box/builtinStubMethods/manyTypeParametersWithUpperBounds.kt new file mode 100644 index 00000000000..31bc1608dbe --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/manyTypeParametersWithUpperBounds.kt @@ -0,0 +1,32 @@ +import java.util.Collections + +class A : Set { + override fun size(): Int = 0 + override fun isEmpty(): Boolean = true + override fun contains(o: Any?): Boolean = false + override fun iterator(): Iterator = Collections.emptySet().iterator() + override fun containsAll(c: Collection): Boolean = c.isEmpty() +} + +fun expectUoe(block: () -> Any) { + try { + block() + throw AssertionError() + } catch (e: UnsupportedOperationException) { + } +} + +fun box(): String { + val a = A() as MutableSet + + a.iterator() + + expectUoe { a.add(42) } + expectUoe { a.remove(42) } + expectUoe { a.addAll(a) } + expectUoe { a.removeAll(a) } + expectUoe { a.retainAll(a) } + expectUoe { a.clear() } + + return "OK" +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/nonTrivialSubstitution.kt b/compiler/testData/codegen/box/builtinStubMethods/nonTrivialSubstitution.kt new file mode 100644 index 00000000000..382a5d9bf5c --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/nonTrivialSubstitution.kt @@ -0,0 +1,19 @@ +import java.util.ArrayList + +class MyCollection : Collection>> { + override fun iterator() = null!! + override fun size(): Int = null!! + override fun isEmpty(): Boolean = null!! + override fun contains(o: Any?): Boolean = null!! + override fun containsAll(c: Collection): Boolean = null!! +} + +fun box(): String { + val c = MyCollection() as MutableCollection>> + try { + c.add(ArrayList()) + return "Fail" + } catch (e: UnsupportedOperationException) { + return "OK" + } +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/nonTrivialUpperBound.kt b/compiler/testData/codegen/box/builtinStubMethods/nonTrivialUpperBound.kt new file mode 100644 index 00000000000..470eaaaf9e6 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/nonTrivialUpperBound.kt @@ -0,0 +1,13 @@ +class MyIterator : Iterator { + override fun next() = null!! + override fun hasNext() = null!! +} + +fun box(): String { + try { + (MyIterator() as MutableIterator).remove() + return "Fail" + } catch (e: UnsupportedOperationException) { + return "OK" + } +} diff --git a/compiler/testData/codegen/box/builtinStubMethods/twoListsInSupertypes.kt b/compiler/testData/codegen/box/builtinStubMethods/twoListsInSupertypes.kt new file mode 100644 index 00000000000..d989ac5ba15 --- /dev/null +++ b/compiler/testData/codegen/box/builtinStubMethods/twoListsInSupertypes.kt @@ -0,0 +1,26 @@ +trait ListAny : List +trait ListString : List + +trait AddStringImpl { + fun add(s: String) {} +} + +class A : ListAny, ListString, AddStringImpl { + override fun size(): Int = 0 + override fun isEmpty(): Boolean = true + override fun contains(o: Any?): Boolean = false + override fun iterator(): Iterator = null!! + override fun containsAll(c: Collection): Boolean = false + override fun get(index: Int): String = null!! + override fun indexOf(o: Any?): Int = -1 + override fun lastIndexOf(o: Any?): Int = -1 + override fun listIterator(): ListIterator = null!! + override fun listIterator(index: Int): ListIterator = null!! + override fun subList(fromIndex: Int, toIndex: Int): List = null!! +} + +fun box(): String { + val a = A() as MutableList + a.add("Fail") + return "OK" +} diff --git a/compiler/testData/codegen/boxWithJava/builtinStubMethods/extendJavaCollections/Test.java b/compiler/testData/codegen/boxWithJava/builtinStubMethods/extendJavaCollections/Test.java new file mode 100644 index 00000000000..486b274dcab --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/builtinStubMethods/extendJavaCollections/Test.java @@ -0,0 +1,20 @@ +import java.lang.*; +import java.util.*; + +public class Test { + public static class IterableImpl implements Iterable { + public Iterator iterator() { return new IteratorImpl(); } + } + + public static class IteratorImpl implements Iterator { + public boolean hasNext() { return false; } + public String next() { return null; } + public void remove() { } + } + + public static class MapEntryImpl implements Map.Entry { + public String getKey() { return null; } + public String getValue() { return null; } + public String setValue(String s) { return null; } + } +} diff --git a/compiler/testData/codegen/boxWithJava/builtinStubMethods/extendJavaCollections/extendJavaCollections.kt b/compiler/testData/codegen/boxWithJava/builtinStubMethods/extendJavaCollections/extendJavaCollections.kt new file mode 100644 index 00000000000..b63591c3be1 --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/builtinStubMethods/extendJavaCollections/extendJavaCollections.kt @@ -0,0 +1,19 @@ +class MyIterable : Test.IterableImpl() +class MyIterator : Test.IteratorImpl() +class MyMapEntry : Test.MapEntryImpl() + +fun box(): String { + MyIterable().iterator() + + val a = MyIterator() + a.hasNext() + a.next() + a.remove() + + val b = MyMapEntry() + b.getKey() + b.getValue() + b.setValue(null) + + return "OK" +} diff --git a/compiler/testData/codegen/boxWithJava/builtinStubMethods/substitutedList/Test.java b/compiler/testData/codegen/boxWithJava/builtinStubMethods/substitutedList/Test.java new file mode 100644 index 00000000000..434b2d7a2a5 --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/builtinStubMethods/substitutedList/Test.java @@ -0,0 +1,12 @@ +class Test { + interface A { + boolean add(String s); + } + + static class D extends C {} + + void test() { + A a = new D(); + a.add("lol"); + } +} diff --git a/compiler/testData/codegen/boxWithJava/builtinStubMethods/substitutedList/substitutedList.kt b/compiler/testData/codegen/boxWithJava/builtinStubMethods/substitutedList/substitutedList.kt new file mode 100644 index 00000000000..e9b64a54ca1 --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/builtinStubMethods/substitutedList/substitutedList.kt @@ -0,0 +1,22 @@ +abstract class C : Test.A, List { + override fun size(): Int = null!! + override fun isEmpty(): Boolean = null!! + override fun contains(o: Any?): Boolean = null!! + override fun iterator(): Iterator = null!! + override fun containsAll(c: Collection): Boolean = null!! + override fun get(index: Int): String = null!! + override fun indexOf(o: Any?): Int = null!! + override fun lastIndexOf(o: Any?): Int = null!! + override fun listIterator(): ListIterator = null!! + override fun listIterator(index: Int): ListIterator = null!! + override fun subList(fromIndex: Int, toIndex: Int): List = null!! +} + +fun box(): String { + try { + Test().test() + return "Fail" + } catch (e: UnsupportedOperationException) { + return "OK" + } +} diff --git a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java index c461e7885f4..f5ab39109a4 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxCodegenTestGenerated.java @@ -670,8 +670,15 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { @TestMetadata("compiler/testData/codegen/box/builtinStubMethods") @TestDataPath("$PROJECT_ROOT") + @InnerTestClasses({BuiltinStubMethods.ExtendJavaCollections.class}) @RunWith(JUnit3RunnerWithInners.class) public static class BuiltinStubMethods extends AbstractBlackBoxCodegenTest { + @TestMetadata("abstractMember.kt") + public void testAbstractMember() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/abstractMember.kt"); + doTest(fileName); + } + public void testAllFilesPresentInBuiltinStubMethods() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/builtinStubMethods"), Pattern.compile("^(.+)\\.kt$"), true); } @@ -682,12 +689,30 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("delegationToArrayList.kt") + public void testDelegationToArrayList() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/delegationToArrayList.kt"); + doTest(fileName); + } + @TestMetadata("dontGenerateBodyInTrait.kt") public void testDontGenerateBodyInTrait() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/dontGenerateBodyInTrait.kt"); doTest(fileName); } + @TestMetadata("implementationInTrait.kt") + public void testImplementationInTrait() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/implementationInTrait.kt"); + doTest(fileName); + } + + @TestMetadata("inheritedImplementations.kt") + public void testInheritedImplementations() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/inheritedImplementations.kt"); + doTest(fileName); + } + @TestMetadata("Iterator.kt") public void testIterator() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/Iterator.kt"); @@ -706,6 +731,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("ListIterator.kt") + public void testListIterator() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/ListIterator.kt"); + doTest(fileName); + } + @TestMetadata("ListWithAllImplementations.kt") public void testListWithAllImplementations() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/ListWithAllImplementations.kt"); @@ -718,6 +749,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("manyTypeParametersWithUpperBounds.kt") + public void testManyTypeParametersWithUpperBounds() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/manyTypeParametersWithUpperBounds.kt"); + doTest(fileName); + } + @TestMetadata("Map.kt") public void testMap() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/Map.kt"); @@ -742,9 +779,15 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } - @TestMetadata("noMethodsForClassExtendingMutableCollection.kt") - public void testNoMethodsForClassExtendingMutableCollection() throws Exception { - String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/noMethodsForClassExtendingMutableCollection.kt"); + @TestMetadata("nonTrivialSubstitution.kt") + public void testNonTrivialSubstitution() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/nonTrivialSubstitution.kt"); + doTest(fileName); + } + + @TestMetadata("nonTrivialUpperBound.kt") + public void testNonTrivialUpperBound() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/nonTrivialUpperBound.kt"); doTest(fileName); } @@ -753,6 +796,57 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/SubstitutedList.kt"); doTest(fileName); } + + @TestMetadata("twoListsInSupertypes.kt") + public void testTwoListsInSupertypes() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/twoListsInSupertypes.kt"); + doTest(fileName); + } + + @TestMetadata("compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ExtendJavaCollections extends AbstractBlackBoxCodegenTest { + @TestMetadata("abstractList.kt") + public void testAbstractList() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractList.kt"); + doTest(fileName); + } + + @TestMetadata("abstractMap.kt") + public void testAbstractMap() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractMap.kt"); + doTest(fileName); + } + + @TestMetadata("abstractSet.kt") + public void testAbstractSet() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/abstractSet.kt"); + doTest(fileName); + } + + public void testAllFilesPresentInExtendJavaCollections() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("arrayList.kt") + public void testArrayList() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/arrayList.kt"); + doTest(fileName); + } + + @TestMetadata("hashMap.kt") + public void testHashMap() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/hashMap.kt"); + doTest(fileName); + } + + @TestMetadata("hashSet.kt") + public void testHashSet() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/box/builtinStubMethods/extendJavaCollections/hashSet.kt"); + doTest(fileName); + } + } } @TestMetadata("compiler/testData/codegen/box/casts") diff --git a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java index 77ebf816fec..d6a36d2fcd8 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java @@ -30,7 +30,7 @@ import java.util.regex.Pattern; @SuppressWarnings("all") @TestMetadata("compiler/testData/codegen/boxWithJava") @TestDataPath("$PROJECT_ROOT") -@InnerTestClasses({BlackBoxWithJavaCodegenTestGenerated.PlatformStatic.class, BlackBoxWithJavaCodegenTestGenerated.Properties.class}) +@InnerTestClasses({BlackBoxWithJavaCodegenTestGenerated.BuiltinStubMethods.class, BlackBoxWithJavaCodegenTestGenerated.PlatformStatic.class, BlackBoxWithJavaCodegenTestGenerated.Properties.class}) @RunWith(JUnit3RunnerWithInners.class) public class BlackBoxWithJavaCodegenTestGenerated extends AbstractBlackBoxCodegenTest { public void testAllFilesPresentInBoxWithJava() throws Exception { @@ -49,6 +49,29 @@ public class BlackBoxWithJavaCodegenTestGenerated extends AbstractBlackBoxCodege doTestWithJava(fileName); } + @TestMetadata("compiler/testData/codegen/boxWithJava/builtinStubMethods") + @TestDataPath("$PROJECT_ROOT") + @InnerTestClasses({}) + @RunWith(JUnit3RunnerWithInners.class) + public static class BuiltinStubMethods extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInBuiltinStubMethods() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithJava/builtinStubMethods"), Pattern.compile("^([^\\.]+)$"), true); + } + + @TestMetadata("extendJavaCollections") + public void testExtendJavaCollections() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/builtinStubMethods/extendJavaCollections/"); + doTestWithJava(fileName); + } + + @TestMetadata("substitutedList") + public void testSubstitutedList() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/builtinStubMethods/substitutedList/"); + doTestWithJava(fileName); + } + + } + @TestMetadata("compiler/testData/codegen/boxWithJava/platformStatic") @TestDataPath("$PROJECT_ROOT") @InnerTestClasses({}) diff --git a/core/descriptors/src/org/jetbrains/jet/lang/types/DescriptorSubstitutor.java b/core/descriptors/src/org/jetbrains/jet/lang/types/DescriptorSubstitutor.java index 45fccc7d0f3..c6163371c21 100644 --- a/core/descriptors/src/org/jetbrains/jet/lang/types/DescriptorSubstitutor.java +++ b/core/descriptors/src/org/jetbrains/jet/lang/types/DescriptorSubstitutor.java @@ -16,6 +16,7 @@ package org.jetbrains.jet.lang.types; +import org.jetbrains.annotations.Mutable; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.SourceElement; @@ -35,7 +36,7 @@ public class DescriptorSubstitutor { @NotNull List typeParameters, @NotNull final TypeSubstitutor originalSubstitutor, @NotNull DeclarationDescriptor newContainingDeclaration, - @NotNull List result + @NotNull @Mutable List result ) { final Map mutableSubstitution = new HashMap(); TypeSubstitutor substitutor = TypeSubstitutor.create(new TypeSubstitution() {