From f2a55d590c1717e6dee650ddcd18490e40ad785c Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Tue, 13 Jun 2017 16:00:48 +0300 Subject: [PATCH] Move type qualifiers extraction to SignatureEnhancement These methods will depend on other components soon --- .../typeEnhancement/signatureEnhancement.kt | 161 +++++++++++++++++- .../java/typeEnhancement/typeQualifiers.kt | 145 ---------------- 2 files changed, 160 insertions(+), 146 deletions(-) diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt index ba27cf0e7f0..32b4e32f7d8 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt @@ -18,11 +18,19 @@ package org.jetbrains.kotlin.load.java.typeEnhancement import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor 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.JavaMethodDescriptor import org.jetbrains.kotlin.load.kotlin.SignatureBuildingComponents 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.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 { fun enhanceSignatures(platformSignatures: Collection): Collection { @@ -76,7 +84,7 @@ class SignatureEnhancement { return this } - private class SignatureParts( + private inner class SignatureParts( val fromOverride: KotlinType, val fromOverridden: Collection, val isCovariant: Boolean @@ -110,4 +118,155 @@ class SignatureEnhancement { 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 List.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 uniqueNotNull(x: T?, y: T?) = if (x == null || y == null || x == y) x ?: y else null + fun 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, isCovariant: Boolean + ): (Int) -> JavaTypeQualifiers { + fun KotlinType.toIndexed(): List { + val list = ArrayList(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, but in the subclass it may be Derived, which + // is declared to extend Super, 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! in the subclass and { List, (Mutable)List! } 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, 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 Set.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 Set.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) + ) + } + } } diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/typeQualifiers.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/typeQualifiers.kt index a9cf6ca190f..ca48496da56 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/typeQualifiers.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/typeQualifiers.kt @@ -16,19 +16,6 @@ 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 { NULLABLE, NOT_NULL @@ -48,135 +35,3 @@ class JavaTypeQualifiers internal constructor( 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 List.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 uniqueNotNull(x: T?, y: T?) = if (x == null || y == null || x == y) x ?: y else null - fun 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, isCovariant: Boolean): (Int) -> JavaTypeQualifiers { - fun KotlinType.toIndexed(): List { - val list = ArrayList(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, but in the subclass it may be Derived, which - // is declared to extend Super, 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! in the subclass and { List, (Mutable)List! } 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, 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 Set.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 Set.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) - ) - } -}