diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/typeEnhancement.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/typeEnhancement.kt new file mode 100644 index 00000000000..e85c342c1c7 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/typeEnhancement.kt @@ -0,0 +1,78 @@ +/* + * 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.components + +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.load.java.components.MutabilityQualifier.MUTABLE +import org.jetbrains.kotlin.load.java.components.MutabilityQualifier.READ_ONLY +import org.jetbrains.kotlin.load.java.components.NullabilityQualifier.NOT_NULL +import org.jetbrains.kotlin.load.java.components.NullabilityQualifier.NULLABLE +import org.jetbrains.kotlin.platform.JavaToKotlinClassMap +import org.jetbrains.kotlin.types.* + + +fun JetType.enhance(qualifiers: JavaTypeQualifiers): JetType { + val mutabilityEnhanced = + if (this.isFlexible()) + this.flexibility().enhanceMutability(qualifiers.mutability) + else this + return mutabilityEnhanced.enhanceNullability(qualifiers.nullability) +} + +private fun Flexibility.enhanceMutability(qualifier: MutabilityQualifier?): JetType { + val mapping = JavaToKotlinClassMap.INSTANCE + + val (newLower, newUpper) = run { + when (qualifier) { + READ_ONLY -> { + val lowerClass = TypeUtils.getClassDescriptor(lowerBound) + if (lowerClass != null && mapping.isMutable(lowerClass)) { + return@run Pair(lowerBound.replaceClass(mapping.convertMutableToReadOnly(lowerClass)), upperBound) + } + } + MUTABLE -> { + val upperClass = TypeUtils.getClassDescriptor(upperBound) + if (upperClass != null && mapping.isReadOnly(upperClass) ) { + return@run Pair(lowerBound, upperBound.replaceClass(mapping.convertReadOnlyToMutable(upperClass))) + } + } + } + return@run Pair(lowerBound, upperBound) + } + + return DelegatingFlexibleType.create(newLower, newUpper, extraCapabilities) +} + +private fun JetType.enhanceNullability(qualifier: NullabilityQualifier?): JetType { + return when (qualifier) { + NULLABLE -> TypeUtils.makeNullable(this) + NOT_NULL -> TypeUtils.makeNotNullable(this) + else -> this + } +} + +private fun JetType.replaceClass(newClass: ClassDescriptor): JetType { + assert(newClass.getTypeConstructor().getParameters().size() == getArguments().size(), + {"Can't replace type constructor ${getConstructor()} by ${newClass}: type parameter count does not match"}) + return JetTypeImpl( + getAnnotations(), + newClass.getTypeConstructor(), + isMarkedNullable(), + getArguments(), + newClass.getMemberScope(getArguments()) + ) +} \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/typeQualifiers.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/typeQualifiers.kt new file mode 100644 index 00000000000..a95f22544c9 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/components/typeQualifiers.kt @@ -0,0 +1,113 @@ +/* + * 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.components + +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.load.java.JvmAnnotationNames +import org.jetbrains.kotlin.load.java.components.MutabilityQualifier.MUTABLE +import org.jetbrains.kotlin.load.java.components.MutabilityQualifier.READ_ONLY +import org.jetbrains.kotlin.load.java.components.NullabilityQualifier.NOT_NULL +import org.jetbrains.kotlin.load.java.components.NullabilityQualifier.NULLABLE +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.platform.JavaToKotlinClassMap +import org.jetbrains.kotlin.types.JetType +import org.jetbrains.kotlin.types.flexibility +import org.jetbrains.kotlin.types.isFlexible + +enum class NullabilityQualifier { + NULLABLE, + NOT_NULL +} + +val NULLABLE_ANNOTATIONS = listOf( + JvmAnnotationNames.JETBRAINS_NULLABLE_ANNOTATION +) + +val NOT_NULL_ANNOTATIONS = listOf( + JvmAnnotationNames.JETBRAINS_NOT_NULL_ANNOTATION +) + +enum class MutabilityQualifier { + READ_ONLY, + MUTABLE +} + +val READ_ONLY_ANNOTATIONS = listOf( + JvmAnnotationNames.JETBRAINS_READONLY_ANNOTATION +) + +val MUTABLE_ANNOTATIONS = listOf( + JvmAnnotationNames.JETBRAINS_MUTABLE_ANNOTATION +) + +class JavaTypeQualifiers( + val nullability: NullabilityQualifier?, + val mutability: MutabilityQualifier? +) + +private fun JetType.extractQualifiers(): JavaTypeQualifiers { + val (lower, upper) = + if (this.isFlexible()) + flexibility().let { Pair(it.lowerBound, it.upperBound) } + else Pair(this, this) + + val mapping = JavaToKotlinClassMap.INSTANCE + 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 + ) +} + +private fun Annotations.extractQualifiers(): JavaTypeQualifiers { + fun List.ifPresent(qualifier: T) = if (any { findAnnotation(it) != null}) qualifier else null + fun singleNotNull(x: T?, y: T?) = if (x == null || y == null) x ?: y else null + + return JavaTypeQualifiers( + singleNotNull(NULLABLE_ANNOTATIONS.ifPresent(NULLABLE), NOT_NULL_ANNOTATIONS.ifPresent(NOT_NULL)), + singleNotNull(READ_ONLY_ANNOTATIONS.ifPresent(READ_ONLY), MUTABLE_ANNOTATIONS.ifPresent(MUTABLE)) + ) +} + +fun JetType.computeQualifiersForOverride(fromSupertypes: Collection, isCovariant: Boolean): JavaTypeQualifiers { + val nullabilityFromSupertypes = fromSupertypes.map { it.extractQualifiers().nullability }.filterNotNull().toSet() + val mutabilityFromSupertypes = fromSupertypes.map { it.extractQualifiers().mutability }.filterNotNull().toSet() + val own = getAnnotations().extractQualifiers() + + 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 JavaTypeQualifiers( + 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 JavaTypeQualifiers( + nullabilityFromSupertypes.selectInvariantly(own.nullability), + mutabilityFromSupertypes.selectInvariantly(own.mutability) + ) + } +} diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/platform/JavaToKotlinClassMap.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/platform/JavaToKotlinClassMap.java index 72462feb817..fee5cb32198 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/platform/JavaToKotlinClassMap.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/platform/JavaToKotlinClassMap.java @@ -29,6 +29,8 @@ import org.jetbrains.kotlin.name.FqNameUnsafe; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType; +import org.jetbrains.kotlin.types.JetType; +import org.jetbrains.kotlin.types.TypeUtils; import java.lang.annotation.Annotation; import java.util.*; @@ -186,10 +188,20 @@ public class JavaToKotlinClassMap implements PlatformToKotlinClassMap { return mutableToReadOnly.containsKey(mutable); } + public boolean isMutable(@NotNull JetType type) { + ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type); + return classDescriptor != null && isMutable(classDescriptor); + } + public boolean isReadOnly(@NotNull ClassDescriptor readOnly) { return readOnlyToMutable.containsKey(readOnly); } + public boolean isReadOnly(@NotNull JetType type) { + ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(type); + return classDescriptor != null && isReadOnly(classDescriptor); + } + @NotNull public ClassDescriptor convertMutableToReadOnly(@NotNull ClassDescriptor mutable) { ClassDescriptor readOnly = mutableToReadOnly.get(mutable);