Move type qualifiers extraction to SignatureEnhancement
These methods will depend on other components soon
This commit is contained in:
+160
-1
@@ -18,11 +18,19 @@ package org.jetbrains.kotlin.load.java.typeEnhancement
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.load.java.*
|
||||||
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
|
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
|
||||||
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
|
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
|
||||||
import org.jetbrains.kotlin.load.kotlin.SignatureBuildingComponents
|
import org.jetbrains.kotlin.load.kotlin.SignatureBuildingComponents
|
||||||
import org.jetbrains.kotlin.load.kotlin.computeJvmDescriptor
|
import org.jetbrains.kotlin.load.kotlin.computeJvmDescriptor
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.asFlexibleType
|
||||||
|
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||||
|
import org.jetbrains.kotlin.types.isFlexible
|
||||||
|
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class SignatureEnhancement {
|
class SignatureEnhancement {
|
||||||
fun <D : CallableMemberDescriptor> enhanceSignatures(platformSignatures: Collection<D>): Collection<D> {
|
fun <D : CallableMemberDescriptor> enhanceSignatures(platformSignatures: Collection<D>): Collection<D> {
|
||||||
@@ -76,7 +84,7 @@ class SignatureEnhancement {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SignatureParts(
|
private inner class SignatureParts(
|
||||||
val fromOverride: KotlinType,
|
val fromOverride: KotlinType,
|
||||||
val fromOverridden: Collection<KotlinType>,
|
val fromOverridden: Collection<KotlinType>,
|
||||||
val isCovariant: Boolean
|
val isCovariant: Boolean
|
||||||
@@ -110,4 +118,155 @@ class SignatureEnhancement {
|
|||||||
isCovariant
|
isCovariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KotlinType.extractQualifiers(): JavaTypeQualifiers {
|
||||||
|
val (lower, upper) =
|
||||||
|
if (this.isFlexible())
|
||||||
|
asFlexibleType().let { Pair(it.lowerBound, it.upperBound) }
|
||||||
|
else Pair(this, this)
|
||||||
|
|
||||||
|
val mapping = JavaToKotlinClassMap
|
||||||
|
return JavaTypeQualifiers(
|
||||||
|
when {
|
||||||
|
lower.isMarkedNullable -> NullabilityQualifier.NULLABLE
|
||||||
|
!upper.isMarkedNullable -> NullabilityQualifier.NOT_NULL
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
|
when {
|
||||||
|
mapping.isReadOnly(lower) -> MutabilityQualifier.READ_ONLY
|
||||||
|
mapping.isMutable(upper) -> MutabilityQualifier.MUTABLE
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
|
isNotNullTypeParameter = unwrap() is NotNullTypeParameter)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KotlinType.extractQualifiersFromAnnotations(): JavaTypeQualifiers {
|
||||||
|
fun <T: Any> List<FqName>.ifPresent(qualifier: T) = if (any { annotations.findAnnotation(it) != null}) qualifier else null
|
||||||
|
|
||||||
|
// These two overloads are just for sake of optimization as in most cases last parameter in second overload is null
|
||||||
|
fun <T: Any> uniqueNotNull(x: T?, y: T?) = if (x == null || y == null || x == y) x ?: y else null
|
||||||
|
fun <T: Any> uniqueNotNull(a: T?, b: T?, c: T?) =
|
||||||
|
if (c == null)
|
||||||
|
uniqueNotNull(a, b)
|
||||||
|
else
|
||||||
|
listOf(a, b, c).filterNotNull().toSet().singleOrNull()
|
||||||
|
|
||||||
|
// Javax/FundBugs NonNull annotation has parameter `when` that determines actual nullability
|
||||||
|
fun FqName.extractQualifierFromAnnotationWithWhen(): NullabilityQualifier? {
|
||||||
|
val annotationDescriptor = annotations.findAnnotation(this) ?: return null
|
||||||
|
return annotationDescriptor.allValueArguments.values.singleOrNull()?.value?.let {
|
||||||
|
enumEntryDescriptor ->
|
||||||
|
if (enumEntryDescriptor !is ClassDescriptor) return@let null
|
||||||
|
if (enumEntryDescriptor.name.asString() == "ALWAYS") NullabilityQualifier.NOT_NULL else NullabilityQualifier.NULLABLE
|
||||||
|
} ?: NullabilityQualifier.NOT_NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
val nullability = uniqueNotNull(
|
||||||
|
NULLABLE_ANNOTATIONS.ifPresent(NullabilityQualifier.NULLABLE),
|
||||||
|
NOT_NULL_ANNOTATIONS.ifPresent(NullabilityQualifier.NOT_NULL),
|
||||||
|
JAVAX_NONNULL_ANNOTATION.extractQualifierFromAnnotationWithWhen()
|
||||||
|
)
|
||||||
|
return JavaTypeQualifiers(
|
||||||
|
nullability,
|
||||||
|
uniqueNotNull(
|
||||||
|
READ_ONLY_ANNOTATIONS.ifPresent(
|
||||||
|
MutabilityQualifier.READ_ONLY
|
||||||
|
),
|
||||||
|
MUTABLE_ANNOTATIONS.ifPresent(
|
||||||
|
MutabilityQualifier.MUTABLE
|
||||||
|
)
|
||||||
|
),
|
||||||
|
isNotNullTypeParameter = nullability == NullabilityQualifier.NOT_NULL && isTypeParameter()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KotlinType.computeIndexedQualifiersForOverride(
|
||||||
|
fromSupertypes: Collection<KotlinType>, isCovariant: Boolean
|
||||||
|
): (Int) -> JavaTypeQualifiers {
|
||||||
|
fun KotlinType.toIndexed(): List<KotlinType> {
|
||||||
|
val list = ArrayList<KotlinType>(1)
|
||||||
|
|
||||||
|
fun add(type: KotlinType) {
|
||||||
|
list.add(type)
|
||||||
|
for (arg in type.arguments) {
|
||||||
|
if (arg.isStarProjection) {
|
||||||
|
list.add(arg.type)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
add(arg.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(this)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
val indexedFromSupertypes = fromSupertypes.map { it.toIndexed() }
|
||||||
|
val indexedThisType = this.toIndexed()
|
||||||
|
|
||||||
|
// The covariant case may be hard, e.g. in the superclass the return may be Super<T>, but in the subclass it may be Derived, which
|
||||||
|
// is declared to extend Super<T>, and propagating data here is highly non-trivial, so we only look at the head type constructor
|
||||||
|
// (outermost type), unless the type in the subclass is interchangeable with the all the types in superclasses:
|
||||||
|
// e.g. we have (Mutable)List<String!>! in the subclass and { List<String!>, (Mutable)List<String>! } from superclasses
|
||||||
|
// Note that `this` is flexible here, so it's equal to it's bounds
|
||||||
|
val onlyHeadTypeConstructor = isCovariant && fromSupertypes.any { !KotlinTypeChecker.DEFAULT.equalTypes(it, this) }
|
||||||
|
|
||||||
|
val treeSize = if (onlyHeadTypeConstructor) 1 else indexedThisType.size
|
||||||
|
val computedResult = Array(treeSize) {
|
||||||
|
index ->
|
||||||
|
val isHeadTypeConstructor = index == 0
|
||||||
|
assert(isHeadTypeConstructor || !onlyHeadTypeConstructor) { "Only head type constructors should be computed" }
|
||||||
|
|
||||||
|
val qualifiers = indexedThisType[index]
|
||||||
|
val verticalSlice = indexedFromSupertypes.mapNotNull { it.getOrNull(index) }
|
||||||
|
|
||||||
|
// Only the head type constructor is safely co-variant
|
||||||
|
qualifiers.computeQualifiersForOverride(verticalSlice, isCovariant && isHeadTypeConstructor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { index -> computedResult.getOrElse(index) { JavaTypeQualifiers.NONE } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KotlinType.computeQualifiersForOverride(fromSupertypes: Collection<KotlinType>, isCovariant: Boolean): JavaTypeQualifiers {
|
||||||
|
val nullabilityFromSupertypes = fromSupertypes.mapNotNull { it.extractQualifiers().nullability }.toSet()
|
||||||
|
val mutabilityFromSupertypes = fromSupertypes.mapNotNull { it.extractQualifiers().mutability }.toSet()
|
||||||
|
|
||||||
|
val own = extractQualifiersFromAnnotations()
|
||||||
|
|
||||||
|
val isAnyNonNullTypeParameter = own.isNotNullTypeParameter || fromSupertypes.any { it.extractQualifiers().isNotNullTypeParameter }
|
||||||
|
|
||||||
|
fun createJavaTypeQualifiers(nullability: NullabilityQualifier?, mutability: MutabilityQualifier?): JavaTypeQualifiers {
|
||||||
|
if (!isAnyNonNullTypeParameter || nullability != NullabilityQualifier.NOT_NULL) {
|
||||||
|
return JavaTypeQualifiers(nullability, mutability, false)
|
||||||
|
}
|
||||||
|
return JavaTypeQualifiers(
|
||||||
|
nullability, mutability,
|
||||||
|
isNotNullTypeParameter = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCovariant) {
|
||||||
|
fun <T : Any> Set<T>.selectCovariantly(low: T, high: T, own: T?): T? {
|
||||||
|
val supertypeQualifier = if (low in this) low else if (high in this) high else null
|
||||||
|
return if (supertypeQualifier == low && own == high) null else own ?: supertypeQualifier
|
||||||
|
}
|
||||||
|
return createJavaTypeQualifiers(
|
||||||
|
nullabilityFromSupertypes.selectCovariantly(NullabilityQualifier.NOT_NULL, NullabilityQualifier.NULLABLE, own.nullability),
|
||||||
|
mutabilityFromSupertypes.selectCovariantly(MutabilityQualifier.MUTABLE, MutabilityQualifier.READ_ONLY, own.mutability)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fun <T : Any> Set<T>.selectInvariantly(own: T?): T? {
|
||||||
|
val effectiveSet = own?.let { (this + own).toSet() } ?: this
|
||||||
|
// if this set contains exactly one element, it is the qualifier everybody agrees upon,
|
||||||
|
// otherwise (no qualifiers, or multiple qualifiers), there's no single such qualifier
|
||||||
|
// and all qualifiers are discarded
|
||||||
|
return effectiveSet.singleOrNull()
|
||||||
|
}
|
||||||
|
return createJavaTypeQualifiers(
|
||||||
|
nullabilityFromSupertypes.selectInvariantly(own.nullability),
|
||||||
|
mutabilityFromSupertypes.selectInvariantly(own.mutability)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-145
@@ -16,19 +16,6 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.load.java.typeEnhancement
|
package org.jetbrains.kotlin.load.java.typeEnhancement
|
||||||
|
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
|
||||||
import org.jetbrains.kotlin.load.java.*
|
|
||||||
import org.jetbrains.kotlin.load.java.typeEnhancement.MutabilityQualifier.MUTABLE
|
|
||||||
import org.jetbrains.kotlin.load.java.typeEnhancement.MutabilityQualifier.READ_ONLY
|
|
||||||
import org.jetbrains.kotlin.load.java.typeEnhancement.NullabilityQualifier.NOT_NULL
|
|
||||||
import org.jetbrains.kotlin.load.java.typeEnhancement.NullabilityQualifier.NULLABLE
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
|
|
||||||
import org.jetbrains.kotlin.types.*
|
|
||||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
|
||||||
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
enum class NullabilityQualifier {
|
enum class NullabilityQualifier {
|
||||||
NULLABLE,
|
NULLABLE,
|
||||||
NOT_NULL
|
NOT_NULL
|
||||||
@@ -48,135 +35,3 @@ class JavaTypeQualifiers internal constructor(
|
|||||||
val NONE = JavaTypeQualifiers(null, null, false)
|
val NONE = JavaTypeQualifiers(null, null, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KotlinType.extractQualifiers(): JavaTypeQualifiers {
|
|
||||||
val (lower, upper) =
|
|
||||||
if (this.isFlexible())
|
|
||||||
asFlexibleType().let { Pair(it.lowerBound, it.upperBound) }
|
|
||||||
else Pair(this, this)
|
|
||||||
|
|
||||||
val mapping = JavaToKotlinClassMap
|
|
||||||
return JavaTypeQualifiers(
|
|
||||||
if (lower.isMarkedNullable) NULLABLE else if (!upper.isMarkedNullable) NOT_NULL else null,
|
|
||||||
if (mapping.isReadOnly(lower)) READ_ONLY else if (mapping.isMutable(upper)) MUTABLE else null,
|
|
||||||
isNotNullTypeParameter = unwrap() is NotNullTypeParameter)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KotlinType.extractQualifiersFromAnnotations(): JavaTypeQualifiers {
|
|
||||||
fun <T: Any> List<FqName>.ifPresent(qualifier: T) = if (any { annotations.findAnnotation(it) != null}) qualifier else null
|
|
||||||
|
|
||||||
// These two overloads are just for sake of optimization as in most cases last parameter in second overload is null
|
|
||||||
fun <T: Any> uniqueNotNull(x: T?, y: T?) = if (x == null || y == null || x == y) x ?: y else null
|
|
||||||
fun <T: Any> uniqueNotNull(a: T?, b: T?, c: T?) =
|
|
||||||
if (c == null)
|
|
||||||
uniqueNotNull(a, b)
|
|
||||||
else
|
|
||||||
listOf(a, b, c).filterNotNull().toSet().singleOrNull()
|
|
||||||
|
|
||||||
// Javax/FundBugs NonNull annotation has parameter `when` that determines actual nullability
|
|
||||||
fun FqName.extractQualifierFromAnnotationWithWhen(): NullabilityQualifier? {
|
|
||||||
val annotationDescriptor = annotations.findAnnotation(this) ?: return null
|
|
||||||
return annotationDescriptor.allValueArguments.values.singleOrNull()?.value?.let {
|
|
||||||
enumEntryDescriptor ->
|
|
||||||
if (enumEntryDescriptor !is ClassDescriptor) return@let null
|
|
||||||
if (enumEntryDescriptor.name.asString() == "ALWAYS") NOT_NULL else NULLABLE
|
|
||||||
} ?: NOT_NULL
|
|
||||||
}
|
|
||||||
|
|
||||||
val nullability = uniqueNotNull(
|
|
||||||
NULLABLE_ANNOTATIONS.ifPresent(NULLABLE),
|
|
||||||
NOT_NULL_ANNOTATIONS.ifPresent(NOT_NULL),
|
|
||||||
JAVAX_NONNULL_ANNOTATION.extractQualifierFromAnnotationWithWhen()
|
|
||||||
)
|
|
||||||
return JavaTypeQualifiers(
|
|
||||||
nullability,
|
|
||||||
uniqueNotNull(READ_ONLY_ANNOTATIONS.ifPresent(READ_ONLY), MUTABLE_ANNOTATIONS.ifPresent(MUTABLE)),
|
|
||||||
isNotNullTypeParameter = nullability == NOT_NULL && isTypeParameter()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun KotlinType.computeIndexedQualifiersForOverride(fromSupertypes: Collection<KotlinType>, isCovariant: Boolean): (Int) -> JavaTypeQualifiers {
|
|
||||||
fun KotlinType.toIndexed(): List<KotlinType> {
|
|
||||||
val list = ArrayList<KotlinType>(1)
|
|
||||||
|
|
||||||
fun add(type: KotlinType) {
|
|
||||||
list.add(type)
|
|
||||||
for (arg in type.arguments) {
|
|
||||||
if (arg.isStarProjection) {
|
|
||||||
list.add(arg.type)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
add(arg.type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add(this)
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
val indexedFromSupertypes = fromSupertypes.map { it.toIndexed() }
|
|
||||||
val indexedThisType = this.toIndexed()
|
|
||||||
|
|
||||||
// The covariant case may be hard, e.g. in the superclass the return may be Super<T>, but in the subclass it may be Derived, which
|
|
||||||
// is declared to extend Super<T>, and propagating data here is highly non-trivial, so we only look at the head type constructor
|
|
||||||
// (outermost type), unless the type in the subclass is interchangeable with the all the types in superclasses:
|
|
||||||
// e.g. we have (Mutable)List<String!>! in the subclass and { List<String!>, (Mutable)List<String>! } from superclasses
|
|
||||||
// Note that `this` is flexible here, so it's equal to it's bounds
|
|
||||||
val onlyHeadTypeConstructor = isCovariant && fromSupertypes.any { !KotlinTypeChecker.DEFAULT.equalTypes(it, this) }
|
|
||||||
|
|
||||||
val treeSize = if (onlyHeadTypeConstructor) 1 else indexedThisType.size
|
|
||||||
val computedResult = Array(treeSize) {
|
|
||||||
index ->
|
|
||||||
val isHeadTypeConstructor = index == 0
|
|
||||||
assert(isHeadTypeConstructor || !onlyHeadTypeConstructor) { "Only head type constructors should be computed" }
|
|
||||||
|
|
||||||
val qualifiers = indexedThisType[index]
|
|
||||||
val verticalSlice = indexedFromSupertypes.mapNotNull { it.getOrNull(index) }
|
|
||||||
|
|
||||||
// Only the head type constructor is safely co-variant
|
|
||||||
qualifiers.computeQualifiersForOverride(verticalSlice, isCovariant && isHeadTypeConstructor)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { index -> computedResult.getOrElse(index) { JavaTypeQualifiers.NONE } }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KotlinType.computeQualifiersForOverride(fromSupertypes: Collection<KotlinType>, isCovariant: Boolean): JavaTypeQualifiers {
|
|
||||||
val nullabilityFromSupertypes = fromSupertypes.mapNotNull { it.extractQualifiers().nullability }.toSet()
|
|
||||||
val mutabilityFromSupertypes = fromSupertypes.mapNotNull { it.extractQualifiers().mutability }.toSet()
|
|
||||||
|
|
||||||
val own = extractQualifiersFromAnnotations()
|
|
||||||
|
|
||||||
val isAnyNonNullTypeParameter = own.isNotNullTypeParameter || fromSupertypes.any { it.extractQualifiers().isNotNullTypeParameter }
|
|
||||||
|
|
||||||
fun createJavaTypeQualifiers(nullability: NullabilityQualifier?, mutability: MutabilityQualifier?): JavaTypeQualifiers {
|
|
||||||
if (!isAnyNonNullTypeParameter || nullability != NOT_NULL) return JavaTypeQualifiers(nullability, mutability, false)
|
|
||||||
return JavaTypeQualifiers(
|
|
||||||
nullability, mutability,
|
|
||||||
isNotNullTypeParameter = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCovariant) {
|
|
||||||
fun <T : Any> Set<T>.selectCovariantly(low: T, high: T, own: T?): T? {
|
|
||||||
val supertypeQualifier = if (low in this) low else if (high in this) high else null
|
|
||||||
return if (supertypeQualifier == low && own == high) null else own ?: supertypeQualifier
|
|
||||||
}
|
|
||||||
return createJavaTypeQualifiers(
|
|
||||||
nullabilityFromSupertypes.selectCovariantly(NOT_NULL, NULLABLE, own.nullability),
|
|
||||||
mutabilityFromSupertypes.selectCovariantly(MUTABLE, READ_ONLY, own.mutability)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fun <T : Any> Set<T>.selectInvariantly(own: T?): T? {
|
|
||||||
val effectiveSet = own?.let { (this + own).toSet() } ?: this
|
|
||||||
// if this set contains exactly one element, it is the qualifier everybody agrees upon,
|
|
||||||
// otherwise (no qualifiers, or multiple qualifiers), there's no single such qualifier
|
|
||||||
// and all qualifiers are discarded
|
|
||||||
return effectiveSet.singleOrNull()
|
|
||||||
}
|
|
||||||
return createJavaTypeQualifiers(
|
|
||||||
nullabilityFromSupertypes.selectInvariantly(own.nullability),
|
|
||||||
mutabilityFromSupertypes.selectInvariantly(own.mutability)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user