From 8b49a1d66005d8fa98738347b65bf9b258d21796 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Wed, 1 Jul 2015 15:31:18 +0300 Subject: [PATCH] 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.add(x: Int!) // possible to add null After: ArrayList.add(x: Int) // impossible to add null #KT-7628 Fixed #KT-7835 Fixed --- .../purelyImplementedCollection/arrayList.kt | 22 ++++++ .../purelyImplementedCollection/arrayList.txt | 4 + .../arrayListNullable.kt | 22 ++++++ .../arrayListNullable.txt | 4 + .../customClassMutableCollection.kt | 31 ++++++++ .../customClassMutableCollection.txt | 24 ++++++ .../customClassMutableList.kt | 39 ++++++++++ .../customClassMutableList.txt | 38 +++++++++ .../purelyImplementedCollection/maps.kt | 78 +++++++++++++++++++ .../purelyImplementedCollection/maps.txt | 7 ++ .../mapsWithNullableKey.kt | 54 +++++++++++++ .../mapsWithNullableKey.txt | 6 ++ .../mapsWithNullableValues.kt | 53 +++++++++++++ .../mapsWithNullableValues.txt | 6 ++ .../purelyImplementedCollection/sets.kt | 46 +++++++++++ .../purelyImplementedCollection/sets.txt | 6 ++ .../wrongTypeParametersCount.kt | 39 ++++++++++ .../wrongTypeParametersCount.txt | 38 +++++++++ ...JetDiagnosticsTestWithStdLibGenerated.java | 63 +++++++++++++++ .../java/FakePureImplementationsProvider.kt | 47 +++++++++++ .../kotlin/load/java/JvmAnnotationNames.java | 2 + .../descriptors/LazyJavaClassDescriptor.kt | 52 ++++++++++++- .../kotlin/builtins/KotlinBuiltIns.java | 10 ++- .../src/kotlin/jvm/PurelyImplements.kt | 37 +++++++++ 24 files changed, 724 insertions(+), 4 deletions(-) create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayList.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayList.txt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayListNullable.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayListNullable.txt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableCollection.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableCollection.txt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableList.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableList.txt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/maps.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/maps.txt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableKey.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableKey.txt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableValues.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableValues.txt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/sets.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/sets.txt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/wrongTypeParametersCount.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/wrongTypeParametersCount.txt create mode 100644 core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/FakePureImplementationsProvider.kt create mode 100644 core/runtime.jvm/src/kotlin/jvm/PurelyImplements.kt diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayList.kt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayList.kt new file mode 100644 index 00000000000..b28f1a8c27e --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayList.kt @@ -0,0 +1,22 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +import java.util.* + +fun bar(): String? = null + +fun foo() { + var x = ArrayList() + x.add(null) + x.add(bar()) + x.add("") + + x[0] = null + x[0] = bar() + x[0] = "" + + val b1: MutableList = x + val b2: MutableList = x + val b3: List = x + + val b4: Collection = x + val b6: MutableCollection = x +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayList.txt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayList.txt new file mode 100644 index 00000000000..dd1f7a31060 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayList.txt @@ -0,0 +1,4 @@ +package + +internal fun bar(): kotlin.String? +internal fun foo(): kotlin.Unit diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayListNullable.kt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayListNullable.kt new file mode 100644 index 00000000000..fad8fa935c2 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayListNullable.kt @@ -0,0 +1,22 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +import java.util.* + +fun bar(): String? = null + +fun foo() { + var x = ArrayList() + x.add(null) + x.add(bar()) + x.add("") + + x[0] = null + x[0] = bar() + x[0] = "" + + val b1: MutableList = x + val b2: MutableList = x + val b3: List = x + + val b4: Collection = x + val b6: MutableCollection = x +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayListNullable.txt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayListNullable.txt new file mode 100644 index 00000000000..dd1f7a31060 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayListNullable.txt @@ -0,0 +1,4 @@ +package + +internal fun bar(): kotlin.String? +internal fun foo(): kotlin.Unit diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableCollection.kt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableCollection.kt new file mode 100644 index 00000000000..b67883babd4 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableCollection.kt @@ -0,0 +1,31 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE + +import java.util.* + +// FILE: A.java +@kotlin.jvm.PurelyImplements("kotlin.MutableCollection") +class A extends AbstractCollection { + @Override + public Iterator iterator() { + return null; + } + + @Override + public int size() { + return 0; + } +} + +// FILE: b.kt + +fun bar(): String? = null + +fun foo() { + var x = A() + x.add(null) + x.add(bar()) + x.add("") + + val b1: Collection = x + val b2: MutableCollection = x +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableCollection.txt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableCollection.txt new file mode 100644 index 00000000000..c8966bb576a --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableCollection.txt @@ -0,0 +1,24 @@ +package + +internal fun bar(): kotlin.String? +internal fun foo(): kotlin.Unit + +kotlin.jvm.PurelyImplements(value = "kotlin.MutableCollection") public/*package*/ open class A : java.util.AbstractCollection, kotlin.MutableCollection { + public/*package*/ constructor A() + public open override /*2*/ /*fake_override*/ fun add(/*0*/ e: T): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun addAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun clear(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun contains(/*0*/ o: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun containsAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun isEmpty(): kotlin.Boolean + java.lang.Override() public open override /*2*/ fun iterator(): kotlin.MutableIterator + public open override /*2*/ /*fake_override*/ fun remove(/*0*/ o: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun removeAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun retainAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + java.lang.Override() public open override /*2*/ fun size(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toArray(): kotlin.Array<(out) kotlin.Any!>! + public open override /*1*/ /*fake_override*/ fun toArray(/*0*/ p0: kotlin.Array<(out) T!>!): kotlin.Array<(out) T!>! + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableList.kt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableList.kt new file mode 100644 index 00000000000..31e3c57ad0f --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableList.kt @@ -0,0 +1,39 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE + +import java.util.* + +// FILE: A.java +@kotlin.jvm.PurelyImplements("kotlin.MutableList") +class A extends AbstractList { + @Override + public T get(int index) { + return null; + } + + @Override + public int size() { + return 0; + } +} + +// FILE: b.kt + +fun bar(): String? = null + +fun foo() { + var x = A() + x.add(null) + x.add(bar()) + x.add("") + + x[0] = null + x[0] = bar() + x[0] = "" + + val b1: MutableList = x + val b2: MutableList = x + val b3: List = x + + val b4: Collection = x + val b6: MutableCollection = x +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableList.txt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableList.txt new file mode 100644 index 00000000000..41c160f2daf --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableList.txt @@ -0,0 +1,38 @@ +package + +internal fun bar(): kotlin.String? +internal fun foo(): kotlin.Unit + +kotlin.jvm.PurelyImplements(value = "kotlin.MutableList") public/*package*/ open class A : java.util.AbstractList, kotlin.MutableList { + public/*package*/ constructor A() + protected/*protected and package*/ final override /*1*/ /*fake_override*/ var modCount: kotlin.Int + public open override /*2*/ /*fake_override*/ fun add(/*0*/ e: T): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun add(/*0*/ index: kotlin.Int, /*1*/ element: T): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun addAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun addAll(/*0*/ index: kotlin.Int, /*1*/ c: kotlin.Collection): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun clear(): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun contains(/*0*/ o: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun containsAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + java.lang.Override() public open override /*2*/ fun get(/*0*/ index: kotlin.Int): T + public open override /*2*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun indexOf(/*0*/ o: kotlin.Any?): kotlin.Int + public open override /*2*/ /*fake_override*/ fun isEmpty(): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun iterator(): kotlin.MutableIterator + public open override /*2*/ /*fake_override*/ fun lastIndexOf(/*0*/ o: kotlin.Any?): kotlin.Int + public open override /*2*/ /*fake_override*/ fun listIterator(): kotlin.MutableListIterator + public open override /*2*/ /*fake_override*/ fun listIterator(/*0*/ index: kotlin.Int): kotlin.MutableListIterator + invisible_fake open override /*1*/ /*fake_override*/ fun outOfBoundsMsg(/*0*/ p0: kotlin.Int): kotlin.String! + invisible_fake open override /*1*/ /*fake_override*/ fun rangeCheckForAdd(/*0*/ p0: kotlin.Int): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun remove(/*0*/ o: kotlin.Any?): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun remove(/*0*/ index: kotlin.Int): T + public open override /*2*/ /*fake_override*/ fun removeAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + protected/*protected and package*/ open override /*1*/ /*fake_override*/ fun removeRange(/*0*/ p0: kotlin.Int, /*1*/ p1: kotlin.Int): kotlin.Unit + public open override /*2*/ /*fake_override*/ fun retainAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + public open override /*2*/ /*fake_override*/ fun set(/*0*/ index: kotlin.Int, /*1*/ element: T): T + java.lang.Override() public open override /*2*/ fun size(): kotlin.Int + public open override /*2*/ /*fake_override*/ fun subList(/*0*/ fromIndex: kotlin.Int, /*1*/ toIndex: kotlin.Int): kotlin.MutableList + public open override /*1*/ /*fake_override*/ fun toArray(): kotlin.Array<(out) kotlin.Any!>! + public open override /*1*/ /*fake_override*/ fun toArray(/*0*/ p0: kotlin.Array<(out) T!>!): kotlin.Array<(out) T!>! + public open override /*2*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/maps.kt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/maps.kt new file mode 100644 index 00000000000..8cb8a523cf8 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/maps.kt @@ -0,0 +1,78 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +import java.util.* +import java.util.concurrent.* + +fun bar(): String? = null +val nullableInt: Int? = null + +fun hashMapTest() { + var x: HashMap = HashMap() + x.put(null, null) + x.put("", null) + x.put(bar(), 1) + x.put("", 1) + + x[null] = 1 + x[bar()] = 1 + x[""] = nullableInt + x[""] = 1 + + val b1: MutableMap = x + val b2: MutableMap = x + val b3: Map = x + val b4: Map = x + val b5: Map = x + + val b6: Int = x[""] + val b7: Int = x.get("") + + val b8: Int? = x.get("") +} + +fun treeMapTest() { + var x: TreeMap = TreeMap() + x.put(null, null) + x.put("", null) + x.put(bar(), 1) + x.put("", 1) + + x[null] = 1 + x[bar()] = 1 + x[""] = nullableInt + x[""] = 1 + + val b1: MutableMap = x + val b2: MutableMap = x + val b3: Map = x + val b4: Map = x + val b5: Map = x + + val b6: Int = x[""] + val b7: Int = x.get("") + + val b8: Int? = x.get("") +} + +fun concurrentHashMapTest() { + var x: ConcurrentHashMap = ConcurrentHashMap() + x.put(null, null) + x.put("", null) + x.put(bar(), 1) + x.put("", 1) + + x[null] = 1 + x[bar()] = 1 + x[""] = nullableInt + x[""] = 1 + + val b1: MutableMap = x + val b2: MutableMap = x + val b3: Map = x + val b4: Map = x + val b5: Map = x + + val b6: Int = x[""] + val b7: Int = x.get("") + + val b8: Int? = x.get("") +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/maps.txt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/maps.txt new file mode 100644 index 00000000000..3b7d946afb4 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/maps.txt @@ -0,0 +1,7 @@ +package + +internal val nullableInt: kotlin.Int? = null +internal fun bar(): kotlin.String? +internal fun concurrentHashMapTest(): kotlin.Unit +internal fun hashMapTest(): kotlin.Unit +internal fun treeMapTest(): kotlin.Unit diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableKey.kt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableKey.kt new file mode 100644 index 00000000000..cbf06958749 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableKey.kt @@ -0,0 +1,54 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +import java.util.* + +fun bar(): String? = null +val nullableInt: Int? = null + +fun hashMapTest() { + var x: HashMap = HashMap() + x.put(null, null) + x.put("", null) + x.put(bar(), 1) + x.put("", 1) + + x[null] = 1 + x[bar()] = 1 + x[""] = nullableInt + x[""] = 1 + + val b1: MutableMap = x + val b2: MutableMap = x + val b3: Map = x + val b4: Map = x + val b5: Map = x + + val b6: Int = x[""] + val b7: Int = x[null] + val b8: Int = x.get("") + + val b9: Int? = x.get("") +} + +fun treeMapTest() { + var x: TreeMap = TreeMap() + x.put(null, null) + x.put("", null) + x.put(bar(), 1) + x.put("", 1) + + x[null] = 1 + x[bar()] = 1 + x[""] = nullableInt + x[""] = 1 + + val b1: MutableMap = x + val b2: MutableMap = x + val b3: Map = x + val b4: Map = x + val b5: Map = x + + val b6: Int = x[""] + val b7: Int = x.get("") + + val b8: Int? = x.get("") +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableKey.txt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableKey.txt new file mode 100644 index 00000000000..51b387b7d42 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableKey.txt @@ -0,0 +1,6 @@ +package + +internal val nullableInt: kotlin.Int? = null +internal fun bar(): kotlin.String? +internal fun hashMapTest(): kotlin.Unit +internal fun treeMapTest(): kotlin.Unit diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableValues.kt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableValues.kt new file mode 100644 index 00000000000..2727dcaee70 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableValues.kt @@ -0,0 +1,53 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +import java.util.* + +fun bar(): String? = null +val nullableInt: Int? = null + +fun hashMapTest() { + var x: HashMap = HashMap() + x.put(null, null) + x.put("", null) + x.put(bar(), 1) + x.put("", 1) + + x[null] = 1 + x[bar()] = 1 + x[""] = nullableInt + x[""] = 1 + + val b1: MutableMap = x + val b2: MutableMap = x + val b3: Map = x + val b4: Map = x + val b5: Map = x + + val b6: Int = x[""] + val b7: Int = x.get("") + + val b8: Int? = x.get("") +} + +fun treeMapTest() { + var x: TreeMap = TreeMap() + x.put(null, null) + x.put("", null) + x.put(bar(), 1) + x.put("", 1) + + x[null] = 1 + x[bar()] = 1 + x[""] = nullableInt + x[""] = 1 + + val b1: MutableMap = x + val b2: MutableMap = x + val b3: Map = x + val b4: Map = x + val b5: Map = x + + val b6: Int = x[""] + val b7: Int = x.get("") + + val b8: Int? = x.get("") +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableValues.txt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableValues.txt new file mode 100644 index 00000000000..51b387b7d42 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableValues.txt @@ -0,0 +1,6 @@ +package + +internal val nullableInt: kotlin.Int? = null +internal fun bar(): kotlin.String? +internal fun hashMapTest(): kotlin.Unit +internal fun treeMapTest(): kotlin.Unit diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/sets.kt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/sets.kt new file mode 100644 index 00000000000..7e37f0d0b55 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/sets.kt @@ -0,0 +1,46 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE +import java.util.* + +fun bar(): String? = null + +fun fooHashSet() { + var x = HashSet() + x.add(null) + x.add(bar()) + x.add("") + + val b1: MutableSet = x + val b2: MutableSet = x + val b3: Set = x + + val b4: Collection = x + val b6: MutableCollection = x +} + +fun fooTreeSet() { + var x = TreeSet() + x.add(null) + x.add(bar()) + x.add("") + + val b1: MutableSet = x + val b2: MutableSet = x + val b3: Set = x + + val b4: Collection = x + val b6: MutableCollection = x +} + +fun fooLinkedHashSet() { + var x = LinkedHashSet() + x.add(null) + x.add(bar()) + x.add("") + + val b1: MutableSet = x + val b2: MutableSet = x + val b3: Set = x + + val b4: Collection = x + val b6: MutableCollection = x +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/sets.txt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/sets.txt new file mode 100644 index 00000000000..60371b78964 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/sets.txt @@ -0,0 +1,6 @@ +package + +internal fun bar(): kotlin.String? +internal fun fooHashSet(): kotlin.Unit +internal fun fooLinkedHashSet(): kotlin.Unit +internal fun fooTreeSet(): kotlin.Unit diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/wrongTypeParametersCount.kt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/wrongTypeParametersCount.kt new file mode 100644 index 00000000000..63d95658bdb --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/wrongTypeParametersCount.kt @@ -0,0 +1,39 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE + +import java.util.* + +// FILE: A.java +@kotlin.jvm.PurelyImplements("kotlin.MutableList") +class A extends AbstractList { + @Override + public T get(int index) { + return null; + } + + @Override + public int size() { + return 0; + } +} + +// FILE: b.kt + +fun bar(): String? = null + +fun foo() { + var x = A() + x.add(null) + x.add(bar()) + x.add("") + + x[0] = null + x[0] = bar() + x[0] = "" + + val b1: MutableList = x + val b2: MutableList = x + val b3: List = x + + val b4: Collection = x + val b6: MutableCollection = x +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/wrongTypeParametersCount.txt b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/wrongTypeParametersCount.txt new file mode 100644 index 00000000000..436b84b19f5 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/wrongTypeParametersCount.txt @@ -0,0 +1,38 @@ +package + +internal fun bar(): kotlin.String? +internal fun foo(): kotlin.Unit + +kotlin.jvm.PurelyImplements(value = "kotlin.MutableList") public/*package*/ open class A : java.util.AbstractList { + public/*package*/ constructor A() + protected/*protected and package*/ final override /*1*/ /*fake_override*/ var modCount: kotlin.Int + public open override /*1*/ /*fake_override*/ fun add(/*0*/ e: T!): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun add(/*0*/ index: kotlin.Int, /*1*/ element: T!): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun addAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun addAll(/*0*/ index: kotlin.Int, /*1*/ c: kotlin.Collection): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun clear(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun contains(/*0*/ o: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun containsAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + java.lang.Override() public open override /*1*/ fun get(/*0*/ index: kotlin.Int): T! + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun indexOf(/*0*/ o: kotlin.Any?): kotlin.Int + public open override /*1*/ /*fake_override*/ fun isEmpty(): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun iterator(): kotlin.MutableIterator + public open override /*1*/ /*fake_override*/ fun lastIndexOf(/*0*/ o: kotlin.Any?): kotlin.Int + public open override /*1*/ /*fake_override*/ fun listIterator(): kotlin.MutableListIterator + public open override /*1*/ /*fake_override*/ fun listIterator(/*0*/ index: kotlin.Int): kotlin.MutableListIterator + invisible_fake open override /*1*/ /*fake_override*/ fun outOfBoundsMsg(/*0*/ p0: kotlin.Int): kotlin.String! + invisible_fake open override /*1*/ /*fake_override*/ fun rangeCheckForAdd(/*0*/ p0: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun remove(/*0*/ o: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun remove(/*0*/ index: kotlin.Int): T! + public open override /*1*/ /*fake_override*/ fun removeAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + protected/*protected and package*/ open override /*1*/ /*fake_override*/ fun removeRange(/*0*/ p0: kotlin.Int, /*1*/ p1: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun retainAll(/*0*/ c: kotlin.Collection): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun set(/*0*/ index: kotlin.Int, /*1*/ element: T!): T! + java.lang.Override() public open override /*1*/ fun size(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun subList(/*0*/ fromIndex: kotlin.Int, /*1*/ toIndex: kotlin.Int): kotlin.MutableList + public open override /*1*/ /*fake_override*/ fun toArray(): kotlin.Array<(out) kotlin.Any!>! + public open override /*1*/ /*fake_override*/ fun toArray(/*0*/ p0: kotlin.Array<(out) T!>!): kotlin.Array<(out) T!>! + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestWithStdLibGenerated.java index ed32fc97ab0..dd046cf6fa6 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestWithStdLibGenerated.java @@ -611,6 +611,69 @@ public class JetDiagnosticsTestWithStdLibGenerated extends AbstractJetDiagnostic } } + @TestMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class PurelyImplementedCollection extends AbstractJetDiagnosticsTestWithStdLib { + public void testAllFilesPresentInPurelyImplementedCollection() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("arrayList.kt") + public void testArrayList() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayList.kt"); + doTest(fileName); + } + + @TestMetadata("arrayListNullable.kt") + public void testArrayListNullable() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/arrayListNullable.kt"); + doTest(fileName); + } + + @TestMetadata("customClassMutableCollection.kt") + public void testCustomClassMutableCollection() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableCollection.kt"); + doTest(fileName); + } + + @TestMetadata("customClassMutableList.kt") + public void testCustomClassMutableList() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/customClassMutableList.kt"); + doTest(fileName); + } + + @TestMetadata("maps.kt") + public void testMaps() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/maps.kt"); + doTest(fileName); + } + + @TestMetadata("mapsWithNullableKey.kt") + public void testMapsWithNullableKey() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableKey.kt"); + doTest(fileName); + } + + @TestMetadata("mapsWithNullableValues.kt") + public void testMapsWithNullableValues() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/mapsWithNullableValues.kt"); + doTest(fileName); + } + + @TestMetadata("sets.kt") + public void testSets() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/sets.kt"); + doTest(fileName); + } + + @TestMetadata("wrongTypeParametersCount.kt") + public void testWrongTypeParametersCount() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/purelyImplementedCollection/wrongTypeParametersCount.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/testsWithStdLib/regression") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/FakePureImplementationsProvider.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/FakePureImplementationsProvider.kt new file mode 100644 index 00000000000..b416ae514f7 --- /dev/null +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/FakePureImplementationsProvider.kt @@ -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() diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java index 7ff48a45ae4..a6c887c1762 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java @@ -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 = diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt index 3005f1a847b..b80311bb371 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassDescriptor.kt @@ -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(javaTypes.size()) val incomplete = ArrayList(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 = supertypes() override fun getAnnotations() = Annotations.EMPTY diff --git a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java index f7c80e56a14..5c8a0a59259 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java +++ b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java @@ -225,8 +225,16 @@ public class KotlinBuiltIns { @NotNull public ClassDescriptor getBuiltInClassByName(@NotNull Name simpleName) { + ClassDescriptor classDescriptor = getBuiltInClassByNameNullable(simpleName); + assert classDescriptor != null : "Must be a class descriptor " + simpleName + ", but was null"; + return classDescriptor; + } + + @Nullable + public ClassDescriptor getBuiltInClassByNameNullable(@NotNull Name simpleName) { ClassifierDescriptor classifier = getBuiltInsPackageFragment().getMemberScope().getClassifier(simpleName); - assert classifier instanceof ClassDescriptor : "Must be a class descriptor " + simpleName + ", but was " + classifier; + assert classifier == null || + classifier instanceof ClassDescriptor : "Must be a class descriptor " + simpleName + ", but was " + classifier; return (ClassDescriptor) classifier; } diff --git a/core/runtime.jvm/src/kotlin/jvm/PurelyImplements.kt b/core/runtime.jvm/src/kotlin/jvm/PurelyImplements.kt new file mode 100644 index 00000000000..3f0d061d69f --- /dev/null +++ b/core/runtime.jvm/src/kotlin/jvm/PurelyImplements.kt @@ -0,0 +1,37 @@ +/* + * 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 kotlin.jvm + +/** + * Instructs the Kotlin compiler to treat annotated Java class as pure implementation of given Kotlin interface. + * "Pure" means here that each type parameter of class becomes non-platform type argument of that interface. + * + * Example: + * + * class MyList extends AbstractList { ... } + * + * Methods defined in MyList use T as platform, i.e. it's possible to perform unsafe operation in Kotlin: + * MyList().add(null) // compiles + * + * @PurelyImplements("kotlin.MutableList") + * class MyPureList extends AbstractList { ... } + * + * Methods defined in MyPureList overriding methods in MutableList use T as non-platform types: + * MyList().add(null) // Error + * MyList().add(null) // Ok + */ +public annotation class PurelyImplements(public val value: String)