Report warnings about type mismatches based on freshly supported nullability annotations deeply
This commit is contained in:
+63
-23
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user