Type enhancement and qualifier extraction

This commit is contained in:
Andrey Breslav
2015-04-15 18:25:13 +03:00
committed by Denis Zharkov
parent e095162c19
commit 991f0fcf2e
3 changed files with 203 additions and 0 deletions
@@ -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())
)
}
@@ -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 <T: Any> List<FqName>.ifPresent(qualifier: T) = if (any { findAnnotation(it) != null}) qualifier else null
fun <T: Any> 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<JetType>, 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 <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 JavaTypeQualifiers(
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 JavaTypeQualifiers(
nullabilityFromSupertypes.selectInvariantly(own.nullability),
mutabilityFromSupertypes.selectInvariantly(own.mutability)
)
}
}
@@ -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);