Improve warnings related to type parameter bounds nullability

Explicitly supply the type parameter that needs to be non-nullified

^KT-36770 Relates
This commit is contained in:
Denis.Zharkov
2022-01-10 13:18:29 +03:00
parent 1827165716
commit d87eddaf8b
11 changed files with 88 additions and 30 deletions
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.resolve.jvm.checkers
import org.jetbrains.kotlin.cfg.WhenChecker
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
@@ -54,9 +55,10 @@ class JavaNullabilityChecker(val upperBoundChecker: UpperBoundChecker) : Additio
c.dataFlowValueFactory.createDataFlowValue(expression, expressionType, c)
}
if (isWrongTypeParameterNullabilityForSubtyping(expressionType, c) { dataFlowValue }) {
c.trace.report(ErrorsJvm.NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER.on(expression, expressionType))
findTypeParameterWithWrongBoundsNullability(expressionType, c) { dataFlowValue }?.let { typeParameterDescriptor ->
c.trace.report(ErrorsJvm.NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER.on(expression, typeParameterDescriptor))
}
doCheckType(
expressionType,
c.expectedType,
@@ -154,15 +156,15 @@ class JavaNullabilityChecker(val upperBoundChecker: UpperBoundChecker) : Additio
}
}
private fun isWrongTypeParameterNullabilityForSubtyping(
private fun findTypeParameterWithWrongBoundsNullability(
expressionType: KotlinType,
c: ResolutionContext<*>,
dataFlowValueForWholeExpression: () -> DataFlowValue
): Boolean {
if (c.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitUsingNullableTypeParameterAgainstNotNullAnnotated)) return false
if (TypeUtils.noExpectedType(c.expectedType)) return false
): TypeParameterDescriptor? {
if (c.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitUsingNullableTypeParameterAgainstNotNullAnnotated)) return null
if (TypeUtils.noExpectedType(c.expectedType)) return null
var metWrongNullabilityInsideArguments = false
var foundSubtypeTypeParameter: TypeParameterDescriptor? = null
@OptIn(ClassicTypeCheckerStateInternals::class)
val typeState: TypeCheckerState = object : ClassicTypeCheckerState(isErrorTypeEqualsToAnything = true) {
@@ -172,7 +174,7 @@ class JavaNullabilityChecker(val upperBoundChecker: UpperBoundChecker) : Additio
if (isNullableTypeAgainstNotNullTypeParameter(subType as KotlinType, superType as KotlinType)) {
// data flow value is only checked for top-level types
if (expectsTypeArgument || c.dataFlowInfo.getStableNullability(dataFlowValueForWholeExpression()) != Nullability.NOT_NULL) {
metWrongNullabilityInsideArguments = true
foundSubtypeTypeParameter = subType.constructor.declarationDescriptor as? TypeParameterDescriptor
return false
}
}
@@ -186,7 +188,7 @@ class JavaNullabilityChecker(val upperBoundChecker: UpperBoundChecker) : Additio
AbstractTypeChecker.isSubtypeOf(typeState, expressionType, c.expectedType)
return metWrongNullabilityInsideArguments
return foundSubtypeTypeParameter
}
override fun checkReceiver(
@@ -268,10 +270,6 @@ class JavaNullabilityChecker(val upperBoundChecker: UpperBoundChecker) : Additio
}
companion object {
val typePreparatorUnwrappingEnhancement: KotlinTypePreparator = object : KotlinTypePreparator() {
override fun prepareType(type: KotlinTypeMarker): UnwrappedType =
super.prepareType(type).let { it.getEnhancementDeeply() ?: it }.unwrap()
}
val typeCheckerForEnhancedTypes = NewKotlinTypeCheckerImpl(
kotlinTypeRefiner = KotlinTypeRefiner.Default,
kotlinTypePreparator = object : KotlinTypePreparator() {
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.resolve.jvm.checkers
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.OverridingUtil
@@ -15,13 +16,15 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.KotlinTypePreparator
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.utils.addIfNotNull
object JavaOverrideWithWrongNullabilityOverrideChecker : DeclarationChecker {
private val overridingUtilWithEnhancementUnwrapped =
OverridingUtil
.createWithTypePreparatorAndCustomSubtype(JavaNullabilityChecker.typePreparatorUnwrappingEnhancement) { subtype, supertype ->
!JavaNullabilityChecker.isNullableTypeAgainstNotNullTypeParameter(subtype, supertype)
}
private val typePreparatorUnwrappingEnhancement: KotlinTypePreparator = object : KotlinTypePreparator() {
override fun prepareType(type: KotlinTypeMarker): UnwrappedType =
super.prepareType(type).let { it.getEnhancementDeeply() ?: it }.unwrap()
}
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (descriptor !is CallableMemberDescriptor) return
@@ -31,6 +34,18 @@ object JavaOverrideWithWrongNullabilityOverrideChecker : DeclarationChecker {
for (overriddenDescriptor in descriptor.overriddenDescriptors) {
if (overriddenDescriptor !is JavaMethodDescriptor) continue
val relatedTypeParameters = mutableSetOf<TypeParameterDescriptor>()
val overridingUtilWithEnhancementUnwrapped =
OverridingUtil
.createWithTypePreparatorAndCustomSubtype(typePreparatorUnwrappingEnhancement) { subtype, supertype ->
!JavaNullabilityChecker.isNullableTypeAgainstNotNullTypeParameter(subtype, supertype).also {
if (it) {
relatedTypeParameters.addIfNotNull(subtype.constructor.declarationDescriptor as? TypeParameterDescriptor)
}
}
}
// Skip, if even with enhancement unwrapped, it's still a valid override
if (overridingUtilWithEnhancementUnwrapped
.isOverridableBy(
@@ -50,7 +65,13 @@ object JavaOverrideWithWrongNullabilityOverrideChecker : DeclarationChecker {
topLevelType.getEnhancementDeeply() ?: topLevelType
})) ?: overriddenDescriptor
context.trace.report(ErrorsJvm.WRONG_NULLABILITY_FOR_JAVA_OVERRIDE.on(declaration, descriptor, unwrappedOverridden))
if (relatedTypeParameters.isNotEmpty()) {
context.trace.report(
ErrorsJvm.WRONG_TYPE_PARAMETER_NULLABILITY_FOR_JAVA_OVERRIDE.on(declaration, relatedTypeParameters.first())
)
} else {
context.trace.report(ErrorsJvm.WRONG_NULLABILITY_FOR_JAVA_OVERRIDE.on(declaration, descriptor, unwrappedOverridden))
}
break
}
@@ -102,10 +102,10 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
"should be subtype of ''{0}'', substituted type is ''{1}''",
RENDER_TYPE, RENDER_TYPE, NAME);
MAP.put(NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER,
"Type mismatch: value of a nullable type {0} is used where non-nullable type is expected. " +
"Type parameter ''{0}'' has nullable upper bounds while non-nullable version is expected. " +
"This warning will become an error soon. " +
"See https://youtrack.jetbrains.com/issue/KT-36770 for details",
RENDER_TYPE
NAME
);
MAP.put(RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS,
"Unsafe use of a nullable receiver of type {0}", RENDER_TYPE);
@@ -113,6 +113,10 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(WRONG_NULLABILITY_FOR_JAVA_OVERRIDE,
"Override ''{0}'' has incorrect nullability in its signature comparing with overridden ''{1}''", COMPACT, COMPACT);
MAP.put(WRONG_TYPE_PARAMETER_NULLABILITY_FOR_JAVA_OVERRIDE,
"Type parameter ''{0}'' has nullable upper bound, so override has incorrect signature comparing with a base member with NotNull annotation. " +
"Please add a non-nullable upper bound (e.g. Any) to the type parameter. See https://kotlinlang.org/docs/generics.html#upper-bounds for more details",
NAME);
MAP.put(ANNOTATION_TARGETS_NON_EXISTENT_ACCESSOR,
"An accessor will not be generated for ''{0}'', so the annotation will not be written to the class file", STRING);
@@ -165,7 +165,7 @@ public interface ErrorsJvm {
UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS
= DiagnosticFactory3.create(WARNING);
DiagnosticFactory1<KtElement, KotlinType> NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER
DiagnosticFactory1<KtElement, TypeParameterDescriptor> NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER
= DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<KtElement, KotlinType> RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
@@ -173,6 +173,10 @@ public interface ErrorsJvm {
DiagnosticFactory2<KtModifierListOwner, CallableMemberDescriptor, CallableMemberDescriptor> WRONG_NULLABILITY_FOR_JAVA_OVERRIDE =
DiagnosticFactory2.create(WARNING, OVERRIDE_MODIFIER);
DiagnosticFactory1<KtModifierListOwner, TypeParameterDescriptor> WRONG_TYPE_PARAMETER_NULLABILITY_FOR_JAVA_OVERRIDE =
DiagnosticFactory1.create(WARNING, OVERRIDE_MODIFIER);
DiagnosticFactory1<KtAnnotationEntry, String> ANNOTATION_TARGETS_NON_EXISTENT_ACCESSOR = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<PsiElement, String> SUSPENSION_POINT_INSIDE_MONITOR = DiagnosticFactory1.create(ERROR);