diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JavaNullabilityChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JavaNullabilityChecker.kt index 3dd145f7d4d..65f3e09caff 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JavaNullabilityChecker.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JavaNullabilityChecker.kt @@ -37,8 +37,11 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue import org.jetbrains.kotlin.types.* 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.model.KotlinTypeMarker +import org.jetbrains.kotlin.types.typeUtil.contains +import org.jetbrains.kotlin.types.typeUtil.makeNotNullable class JavaNullabilityChecker : AdditionalTypeChecker { @@ -187,8 +190,7 @@ class JavaNullabilityChecker : AdditionalTypeChecker { receiverParameter.type, { dataFlowValue }, c.dataFlowInfo - ) { expectedType, - actualType -> + ) { expectedType, actualType -> val receiverExpression = (receiverArgument as? ExpressionReceiver)?.expression if (receiverExpression != null) { c.trace.report(ErrorsJvm.RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS.on(receiverExpression, actualType)) @@ -202,43 +204,81 @@ class JavaNullabilityChecker : AdditionalTypeChecker { private fun doCheckType( expressionType: KotlinType, expectedType: KotlinType, - dataFlowValue: () -> DataFlowValue, + expressionTypeDataFlowValue: () -> DataFlowValue, dataFlowInfo: DataFlowInfo, reportWarning: (expectedType: KotlinType, actualType: KotlinType) -> Unit ) { - if (TypeUtils.noExpectedType(expectedType)) { - return - } + if (TypeUtils.noExpectedType(expectedType)) return - val expectedMustNotBeNull = expectedType.mustNotBeNull() ?: return - val actualMayBeNull = expressionType.mayBeNull() ?: return - if (expectedMustNotBeNull.isFromKotlin && actualMayBeNull.isFromKotlin) { - // a type mismatch error will be reported elsewhere - return - } + val doesExpectedTypeContainsEnhancement = expectedType.contains { it is TypeWithEnhancement } + val doesExpressionTypeContainsEnhancement = expressionType.contains { it is TypeWithEnhancement } - if (dataFlowInfo.getStableNullability(dataFlowValue()) != Nullability.NOT_NULL) { - reportWarning(expectedMustNotBeNull.enhancedType, actualMayBeNull.enhancedType) + if (!doesExpectedTypeContainsEnhancement && !doesExpressionTypeContainsEnhancement) return + + 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 doIfNotNull( type: KotlinType, dataFlowValue: () -> DataFlowValue, c: ResolutionContext<*>, body: () -> T - ) = if (type.mustNotBeNull()?.isFromJava == true && - c.dataFlowInfo.getStableNullability(dataFlowValue()).canBeNull() - ) + ) = if (type.mustNotBeNull()?.isFromJava == true && c.dataFlowInfo.getStableNullability(dataFlowValue()).canBeNull()) { body() - else + } else { null + } - private fun KotlinType.mayBeNull(): EnhancedNullabilityInfo? = when { - !isError && !isFlexible() && TypeUtils.acceptsNullable(this) -> enhancementFromKotlin() - isFlexible() && TypeUtils.acceptsNullable(asFlexibleType().lowerBound) -> enhancementFromKotlin() - this is TypeWithEnhancement && enhancement.mayBeNull() != null -> enhancementFromJava() - else -> null + @OptIn(ExperimentalStdlibApi::class) + private fun enhanceTypeArguments(arguments: List) = + buildList { + for (argument in arguments) { + // 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 + ) } } diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/StarProjectionImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/types/StarProjectionImpl.kt index 0353d4019c2..79cc27f7f13 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/StarProjectionImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/StarProjectionImpl.kt @@ -39,6 +39,10 @@ class StarProjectionImpl( @TypeRefinement 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 { @@ -69,4 +73,8 @@ class StarProjectionForAbsentTypeParameter( @TypeRefinement override fun refine(kotlinTypeRefiner: KotlinTypeRefiner): TypeProjection = this + + override fun replaceType(type: KotlinType): TypeProjection { + throw UnsupportedOperationException("Replacing type for star projection is unsupported") + } } diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjection.java b/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjection.java index b31841fd09e..13480f55d69 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjection.java +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjection.java @@ -33,4 +33,7 @@ public interface TypeProjection extends TypeArgumentMarker { @NotNull @TypeRefinement TypeProjection refine(@NotNull KotlinTypeRefiner kotlinTypeRefiner); + + @NotNull + TypeProjection replaceType(@NotNull KotlinType type); } diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjectionImpl.java b/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjectionImpl.java index 9ae2b9c550e..e32aedf5adf 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjectionImpl.java +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjectionImpl.java @@ -33,6 +33,12 @@ public class TypeProjectionImpl extends TypeProjectionBase { this(Variance.INVARIANT, type); } + @Override + @NotNull + public TypeProjectionBase replaceType(@NotNull KotlinType type) { + return new TypeProjectionImpl(this.projection, type); + } + @Override @NotNull public Variance getProjectionKind() { diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeSubstitution.kt b/core/descriptors/src/org/jetbrains/kotlin/types/TypeSubstitution.kt index b5e75beb208..645874abcd1 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeSubstitution.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeSubstitution.kt @@ -21,8 +21,9 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotations abstract class TypeSubstitution { companion object { - @JvmField val EMPTY: TypeSubstitution = object : TypeSubstitution() { - override fun get(key: KotlinType) = null + @JvmField + val EMPTY: TypeSubstitution = object : TypeSubstitution() { + override fun get(key: KotlinType): Nothing? = null override fun isEmpty() = true override fun toString() = "Empty TypeSubstitution" } @@ -52,8 +53,8 @@ abstract class TypeConstructorSubstitution : TypeSubstitution() { @JvmStatic @JvmOverloads fun createByConstructorsMap( - map: Map, - approximateCapturedTypes: Boolean = false + map: Map, + approximateCapturedTypes: Boolean = false ): TypeConstructorSubstitution = object : TypeConstructorSubstitution() { override fun get(key: TypeConstructor) = map[key] @@ -61,18 +62,21 @@ abstract class TypeConstructorSubstitution : TypeSubstitution() { override fun approximateCapturedTypes() = approximateCapturedTypes } - @JvmStatic fun createByParametersMap(map: Map): TypeConstructorSubstitution = + @JvmStatic + fun createByParametersMap(map: Map): TypeConstructorSubstitution = object : TypeConstructorSubstitution() { override fun get(key: TypeConstructor) = map[key.declarationDescriptor] 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): TypeSubstitution { + @JvmStatic + fun create(typeConstructor: TypeConstructor, arguments: List): TypeSubstitution { val parameters = typeConstructor.parameters - if (parameters.lastOrNull()?.isCapturedFromOuterDeclaration ?: false) { + if (parameters.lastOrNull()?.isCapturedFromOuterDeclaration == true) { return createByConstructorsMap(typeConstructor.parameters.map { it.typeConstructor }.zip(arguments).toMap()) } @@ -93,7 +97,8 @@ class IndexedParametersSubstitution( } constructor( - parameters: List, argumentsList: List + parameters: List, + argumentsList: List ) : this(parameters.toTypedArray(), argumentsList.toTypedArray()) override fun isEmpty(): Boolean = arguments.isEmpty() @@ -114,23 +119,25 @@ class IndexedParametersSubstitution( @JvmOverloads fun KotlinType.replace( - newArguments: List = arguments, - newAnnotations: Annotations = annotations + newArguments: List = arguments, + newAnnotations: Annotations = annotations, + newArgumentsForUpperBound: List = newArguments ): KotlinType { if ((newArguments.isEmpty() || newArguments === arguments) && newAnnotations === annotations) return this - val unwrapped = unwrap() - return when(unwrapped) { - is FlexibleType -> KotlinTypeFactory.flexibleType(unwrapped.lowerBound.replace(newArguments, newAnnotations), - unwrapped.upperBound.replace(newArguments, newAnnotations)) + return when (val unwrapped = unwrap()) { + is FlexibleType -> KotlinTypeFactory.flexibleType( + unwrapped.lowerBound.replace(newArguments, newAnnotations), + unwrapped.upperBound.replace(newArgumentsForUpperBound, newAnnotations) + ) is SimpleType -> unwrapped.replace(newArguments, newAnnotations) } } @JvmOverloads fun SimpleType.replace( - newArguments: List = arguments, - newAnnotations: Annotations = annotations + newArguments: List = arguments, + newAnnotations: Annotations = annotations ): SimpleType { if (newArguments.isEmpty() && newAnnotations === annotations) return this @@ -139,16 +146,17 @@ fun SimpleType.replace( } return KotlinTypeFactory.simpleType( - newAnnotations, - constructor, - newArguments, - isMarkedNullable + newAnnotations, + constructor, + newArguments, + isMarkedNullable ) } -open class DelegatedTypeSubstitution(val substitution: TypeSubstitution): TypeSubstitution() { +open class DelegatedTypeSubstitution(val substitution: TypeSubstitution) : TypeSubstitution() { 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()