Report warnings about type mismatches based on freshly supported nullability annotations deeply

This commit is contained in:
Victor Petukhov
2020-12-11 17:35:26 +03:00
parent d6017420de
commit 9a52863fbd
5 changed files with 111 additions and 46 deletions
@@ -37,8 +37,11 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.ClassicTypeCheckerContext import org.jetbrains.kotlin.types.checker.ClassicTypeCheckerContext
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.expressions.SenselessComparisonChecker import org.jetbrains.kotlin.types.expressions.SenselessComparisonChecker
import org.jetbrains.kotlin.types.model.KotlinTypeMarker import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.typeUtil.contains
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
class JavaNullabilityChecker : AdditionalTypeChecker { class JavaNullabilityChecker : AdditionalTypeChecker {
@@ -187,8 +190,7 @@ class JavaNullabilityChecker : AdditionalTypeChecker {
receiverParameter.type, receiverParameter.type,
{ dataFlowValue }, { dataFlowValue },
c.dataFlowInfo c.dataFlowInfo
) { expectedType, ) { expectedType, actualType ->
actualType ->
val receiverExpression = (receiverArgument as? ExpressionReceiver)?.expression val receiverExpression = (receiverArgument as? ExpressionReceiver)?.expression
if (receiverExpression != null) { if (receiverExpression != null) {
c.trace.report(ErrorsJvm.RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS.on(receiverExpression, actualType)) c.trace.report(ErrorsJvm.RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS.on(receiverExpression, actualType))
@@ -202,43 +204,81 @@ class JavaNullabilityChecker : AdditionalTypeChecker {
private fun doCheckType( private fun doCheckType(
expressionType: KotlinType, expressionType: KotlinType,
expectedType: KotlinType, expectedType: KotlinType,
dataFlowValue: () -> DataFlowValue, expressionTypeDataFlowValue: () -> DataFlowValue,
dataFlowInfo: DataFlowInfo, dataFlowInfo: DataFlowInfo,
reportWarning: (expectedType: KotlinType, actualType: KotlinType) -> Unit reportWarning: (expectedType: KotlinType, actualType: KotlinType) -> Unit
) { ) {
if (TypeUtils.noExpectedType(expectedType)) { if (TypeUtils.noExpectedType(expectedType)) return
return
}
val expectedMustNotBeNull = expectedType.mustNotBeNull() ?: return val doesExpectedTypeContainsEnhancement = expectedType.contains { it is TypeWithEnhancement }
val actualMayBeNull = expressionType.mayBeNull() ?: return val doesExpressionTypeContainsEnhancement = expressionType.contains { it is TypeWithEnhancement }
if (expectedMustNotBeNull.isFromKotlin && actualMayBeNull.isFromKotlin) {
// a type mismatch error will be reported elsewhere
return
}
if (dataFlowInfo.getStableNullability(dataFlowValue()) != Nullability.NOT_NULL) { if (!doesExpectedTypeContainsEnhancement && !doesExpressionTypeContainsEnhancement) return
reportWarning(expectedMustNotBeNull.enhancedType, actualMayBeNull.enhancedType)
val enhancedExpectedType = if (doesExpectedTypeContainsEnhancement) buildTypeWithEnhancement(expectedType) else expectedType
val enhancedExpressionType = enhanceExpressionTypeByDataFlowNullability(
if (doesExpressionTypeContainsEnhancement) buildTypeWithEnhancement(expressionType) else expressionType,
expressionTypeDataFlowValue,
dataFlowInfo
)
val isEnhancedExpectedTypeSubtypeOfExpressionType =
KotlinTypeChecker.DEFAULT.isSubtypeOf(enhancedExpressionType, enhancedExpectedType)
if (isEnhancedExpectedTypeSubtypeOfExpressionType) return
val isExpectedTypeSubtypeOfExpressionType = KotlinTypeChecker.DEFAULT.isSubtypeOf(expressionType, expectedType)
if (!isEnhancedExpectedTypeSubtypeOfExpressionType && isExpectedTypeSubtypeOfExpressionType) {
reportWarning(enhancedExpectedType, enhancedExpressionType)
} }
} }
private fun enhanceExpressionTypeByDataFlowNullability(
expressionType: KotlinType,
expressionTypeDataFlowValue: () -> DataFlowValue,
dataFlowInfo: DataFlowInfo,
): KotlinType {
val isNotNullByDataFlowInfo = dataFlowInfo.getStableNullability(expressionTypeDataFlowValue()) == Nullability.NOT_NULL
return if (expressionType.isNullable() && isNotNullByDataFlowInfo) expressionType.makeNotNullable() else expressionType
}
private fun <T : Any> doIfNotNull( private fun <T : Any> doIfNotNull(
type: KotlinType, type: KotlinType,
dataFlowValue: () -> DataFlowValue, dataFlowValue: () -> DataFlowValue,
c: ResolutionContext<*>, c: ResolutionContext<*>,
body: () -> T body: () -> T
) = if (type.mustNotBeNull()?.isFromJava == true && ) = if (type.mustNotBeNull()?.isFromJava == true && c.dataFlowInfo.getStableNullability(dataFlowValue()).canBeNull()) {
c.dataFlowInfo.getStableNullability(dataFlowValue()).canBeNull()
)
body() body()
else } else {
null null
}
private fun KotlinType.mayBeNull(): EnhancedNullabilityInfo? = when { @OptIn(ExperimentalStdlibApi::class)
!isError && !isFlexible() && TypeUtils.acceptsNullable(this) -> enhancementFromKotlin() private fun enhanceTypeArguments(arguments: List<TypeProjection>) =
isFlexible() && TypeUtils.acceptsNullable(asFlexibleType().lowerBound) -> enhancementFromKotlin() buildList {
this is TypeWithEnhancement && enhancement.mayBeNull() != null -> enhancementFromJava() for (argument in arguments) {
else -> null // TODO: think about star projections with enhancement (e.g. came from Java: Foo<@NotNull ?>)
if (argument.isStarProjection) continue
val argumentType = argument.type
val enhancedArgumentType = if (argumentType is TypeWithEnhancement) argumentType.enhancement else argumentType
val enhancedDeeplyArgumentType = buildTypeWithEnhancement(enhancedArgumentType)
add(argument.replaceType(enhancedDeeplyArgumentType))
}
}
fun buildTypeWithEnhancement(type: KotlinType): KotlinType {
val newArguments = enhanceTypeArguments(type.arguments)
val newArgumentsForUpperBound =
if (type is FlexibleType) {
enhanceTypeArguments(type.upperBound.arguments)
} else newArguments
val enhancedType = if (type is TypeWithEnhancement) type.enhancement else type
return enhancedType.replace(
newArguments = newArguments,
newArgumentsForUpperBound = newArgumentsForUpperBound
)
} }
} }
@@ -39,6 +39,10 @@ class StarProjectionImpl(
@TypeRefinement @TypeRefinement
override fun refine(kotlinTypeRefiner: KotlinTypeRefiner): TypeProjection = this override fun refine(kotlinTypeRefiner: KotlinTypeRefiner): TypeProjection = this
override fun replaceType(type: KotlinType): TypeProjection {
throw UnsupportedOperationException("Replacing type for star projection is unsupported")
}
} }
fun TypeParameterDescriptor.starProjectionType(): KotlinType { fun TypeParameterDescriptor.starProjectionType(): KotlinType {
@@ -69,4 +73,8 @@ class StarProjectionForAbsentTypeParameter(
@TypeRefinement @TypeRefinement
override fun refine(kotlinTypeRefiner: KotlinTypeRefiner): TypeProjection = this override fun refine(kotlinTypeRefiner: KotlinTypeRefiner): TypeProjection = this
override fun replaceType(type: KotlinType): TypeProjection {
throw UnsupportedOperationException("Replacing type for star projection is unsupported")
}
} }
@@ -33,4 +33,7 @@ public interface TypeProjection extends TypeArgumentMarker {
@NotNull @NotNull
@TypeRefinement @TypeRefinement
TypeProjection refine(@NotNull KotlinTypeRefiner kotlinTypeRefiner); TypeProjection refine(@NotNull KotlinTypeRefiner kotlinTypeRefiner);
@NotNull
TypeProjection replaceType(@NotNull KotlinType type);
} }
@@ -33,6 +33,12 @@ public class TypeProjectionImpl extends TypeProjectionBase {
this(Variance.INVARIANT, type); this(Variance.INVARIANT, type);
} }
@Override
@NotNull
public TypeProjectionBase replaceType(@NotNull KotlinType type) {
return new TypeProjectionImpl(this.projection, type);
}
@Override @Override
@NotNull @NotNull
public Variance getProjectionKind() { public Variance getProjectionKind() {
@@ -21,8 +21,9 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotations
abstract class TypeSubstitution { abstract class TypeSubstitution {
companion object { companion object {
@JvmField val EMPTY: TypeSubstitution = object : TypeSubstitution() { @JvmField
override fun get(key: KotlinType) = null val EMPTY: TypeSubstitution = object : TypeSubstitution() {
override fun get(key: KotlinType): Nothing? = null
override fun isEmpty() = true override fun isEmpty() = true
override fun toString() = "Empty TypeSubstitution" override fun toString() = "Empty TypeSubstitution"
} }
@@ -52,8 +53,8 @@ abstract class TypeConstructorSubstitution : TypeSubstitution() {
@JvmStatic @JvmStatic
@JvmOverloads @JvmOverloads
fun createByConstructorsMap( fun createByConstructorsMap(
map: Map<TypeConstructor, TypeProjection>, map: Map<TypeConstructor, TypeProjection>,
approximateCapturedTypes: Boolean = false approximateCapturedTypes: Boolean = false
): TypeConstructorSubstitution = ): TypeConstructorSubstitution =
object : TypeConstructorSubstitution() { object : TypeConstructorSubstitution() {
override fun get(key: TypeConstructor) = map[key] override fun get(key: TypeConstructor) = map[key]
@@ -61,18 +62,21 @@ abstract class TypeConstructorSubstitution : TypeSubstitution() {
override fun approximateCapturedTypes() = approximateCapturedTypes override fun approximateCapturedTypes() = approximateCapturedTypes
} }
@JvmStatic fun createByParametersMap(map: Map<TypeParameterDescriptor, TypeProjection>): TypeConstructorSubstitution = @JvmStatic
fun createByParametersMap(map: Map<TypeParameterDescriptor, TypeProjection>): TypeConstructorSubstitution =
object : TypeConstructorSubstitution() { object : TypeConstructorSubstitution() {
override fun get(key: TypeConstructor) = map[key.declarationDescriptor] override fun get(key: TypeConstructor) = map[key.declarationDescriptor]
override fun isEmpty() = map.isEmpty() override fun isEmpty() = map.isEmpty()
} }
@JvmStatic fun create(kotlinType: KotlinType) = create(kotlinType.constructor, kotlinType.arguments) @JvmStatic
fun create(kotlinType: KotlinType) = create(kotlinType.constructor, kotlinType.arguments)
@JvmStatic fun create(typeConstructor: TypeConstructor, arguments: List<TypeProjection>): TypeSubstitution { @JvmStatic
fun create(typeConstructor: TypeConstructor, arguments: List<TypeProjection>): TypeSubstitution {
val parameters = typeConstructor.parameters val parameters = typeConstructor.parameters
if (parameters.lastOrNull()?.isCapturedFromOuterDeclaration ?: false) { if (parameters.lastOrNull()?.isCapturedFromOuterDeclaration == true) {
return createByConstructorsMap(typeConstructor.parameters.map { it.typeConstructor }.zip(arguments).toMap()) return createByConstructorsMap(typeConstructor.parameters.map { it.typeConstructor }.zip(arguments).toMap())
} }
@@ -93,7 +97,8 @@ class IndexedParametersSubstitution(
} }
constructor( constructor(
parameters: List<TypeParameterDescriptor>, argumentsList: List<TypeProjection> parameters: List<TypeParameterDescriptor>,
argumentsList: List<TypeProjection>
) : this(parameters.toTypedArray(), argumentsList.toTypedArray()) ) : this(parameters.toTypedArray(), argumentsList.toTypedArray())
override fun isEmpty(): Boolean = arguments.isEmpty() override fun isEmpty(): Boolean = arguments.isEmpty()
@@ -114,23 +119,25 @@ class IndexedParametersSubstitution(
@JvmOverloads @JvmOverloads
fun KotlinType.replace( fun KotlinType.replace(
newArguments: List<TypeProjection> = arguments, newArguments: List<TypeProjection> = arguments,
newAnnotations: Annotations = annotations newAnnotations: Annotations = annotations,
newArgumentsForUpperBound: List<TypeProjection> = newArguments
): KotlinType { ): KotlinType {
if ((newArguments.isEmpty() || newArguments === arguments) && newAnnotations === annotations) return this if ((newArguments.isEmpty() || newArguments === arguments) && newAnnotations === annotations) return this
val unwrapped = unwrap() return when (val unwrapped = unwrap()) {
return when(unwrapped) { is FlexibleType -> KotlinTypeFactory.flexibleType(
is FlexibleType -> KotlinTypeFactory.flexibleType(unwrapped.lowerBound.replace(newArguments, newAnnotations), unwrapped.lowerBound.replace(newArguments, newAnnotations),
unwrapped.upperBound.replace(newArguments, newAnnotations)) unwrapped.upperBound.replace(newArgumentsForUpperBound, newAnnotations)
)
is SimpleType -> unwrapped.replace(newArguments, newAnnotations) is SimpleType -> unwrapped.replace(newArguments, newAnnotations)
} }
} }
@JvmOverloads @JvmOverloads
fun SimpleType.replace( fun SimpleType.replace(
newArguments: List<TypeProjection> = arguments, newArguments: List<TypeProjection> = arguments,
newAnnotations: Annotations = annotations newAnnotations: Annotations = annotations
): SimpleType { ): SimpleType {
if (newArguments.isEmpty() && newAnnotations === annotations) return this if (newArguments.isEmpty() && newAnnotations === annotations) return this
@@ -139,16 +146,17 @@ fun SimpleType.replace(
} }
return KotlinTypeFactory.simpleType( return KotlinTypeFactory.simpleType(
newAnnotations, newAnnotations,
constructor, constructor,
newArguments, newArguments,
isMarkedNullable isMarkedNullable
) )
} }
open class DelegatedTypeSubstitution(val substitution: TypeSubstitution): TypeSubstitution() { open class DelegatedTypeSubstitution(val substitution: TypeSubstitution) : TypeSubstitution() {
override fun get(key: KotlinType) = substitution[key] override fun get(key: KotlinType) = substitution[key]
override fun prepareTopLevelType(topLevelType: KotlinType, position: Variance) = substitution.prepareTopLevelType(topLevelType, position) override fun prepareTopLevelType(topLevelType: KotlinType, position: Variance) =
substitution.prepareTopLevelType(topLevelType, position)
override fun isEmpty() = substitution.isEmpty() override fun isEmpty() = substitution.isEmpty()