Report warnings or errors for violated type parameter's upper bounds from Java annotated with nullability annotations

^KT-43262 Fixed
This commit is contained in:
Victor Petukhov
2021-02-01 16:52:23 +03:00
parent cf4e61bebb
commit befe8599c4
14 changed files with 202 additions and 64 deletions
@@ -34327,6 +34327,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/java"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Test
@TestMetadata("checkEnhancedUpperBounds.kt")
public void testCheckEnhancedUpperBounds() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.kt");
}
@Test
@TestMetadata("concurrentHashMapContains.kt")
public void testConcurrentHashMapContains() throws Exception {
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.jvm.checkers
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.UpperBoundChecker
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
class EnhancedUpperBoundChecker(override val languageVersionSettings: LanguageVersionSettings) : UpperBoundChecker {
override fun checkBound(
bound: KotlinType,
substitutor: TypeSubstitutor,
trace: BindingTrace,
jetTypeArgument: KtTypeReference,
typeArgument: KotlinType
): Boolean {
val isCheckPassed = super.checkBound(bound, substitutor, trace, jetTypeArgument, typeArgument)
// The error is already reported, it's unnecessary to do more checks
if (!isCheckPassed) return false
val enhancedBound = bound.getEnhancement() ?: return false
val isTypeEnhancementImprovementsEnabled =
languageVersionSettings.supportsFeature(LanguageFeature.ImprovementsAroundTypeEnhancement)
val substitutedBound = substitutor.safeSubstitute(enhancedBound, Variance.INVARIANT)
if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(typeArgument, substitutedBound)) {
if (isTypeEnhancementImprovementsEnabled) {
trace.report(Errors.UPPER_BOUND_VIOLATED.on(jetTypeArgument, substitutedBound, typeArgument))
} else {
trace.report(ErrorsJvm.UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS.on(jetTypeArgument, substitutedBound, typeArgument))
}
return false
}
return true
}
}
@@ -85,6 +85,7 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC, "Using non-JVM static members protected in the superclass companion is unsupported yet");
MAP.put(NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS, "Type mismatch: inferred type is {1} but {0} was expected", RENDER_TYPE, RENDER_TYPE);
MAP.put(UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS, "Type argument is not within its bounds: should be subtype of ''{0}''", RENDER_TYPE, RENDER_TYPE);
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. " +
"This warning will become an error soon. " +
@@ -137,6 +137,9 @@ public interface ErrorsJvm {
DiagnosticFactory2<KtElement, KotlinType, KotlinType> NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
= DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<KtTypeReference, KotlinType, KotlinType> UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS
= DiagnosticFactory2.create(WARNING);
DiagnosticFactory1<KtElement, KotlinType> NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER
= DiagnosticFactory1.create(WARNING);
@@ -97,6 +97,7 @@ object JvmPlatformConfigurator : PlatformConfiguratorBase(
) {
override fun configureModuleComponents(container: StorageComponentContainer) {
container.useImpl<JvmStaticChecker>()
container.useImpl<EnhancedUpperBoundChecker>()
container.useImpl<JvmReflectionAPICallChecker>()
container.useImpl<JavaSyntheticScopes>()
container.useImpl<SamConversionResolverImpl>()
@@ -56,11 +56,12 @@ internal class DeclarationsCheckerBuilder(
private val identifierChecker: IdentifierChecker,
private val languageVersionSettings: LanguageVersionSettings,
private val typeSpecificityComparator: TypeSpecificityComparator,
private val diagnosticSuppressor: PlatformDiagnosticSuppressor
private val diagnosticSuppressor: PlatformDiagnosticSuppressor,
private val upperBoundChecker: UpperBoundChecker
) {
fun withTrace(trace: BindingTrace) = DeclarationsChecker(
descriptorResolver, originalModifiersChecker, annotationChecker, identifierChecker, trace, languageVersionSettings,
typeSpecificityComparator, diagnosticSuppressor
typeSpecificityComparator, diagnosticSuppressor, upperBoundChecker
)
}
@@ -72,7 +73,8 @@ class DeclarationsChecker(
private val trace: BindingTrace,
private val languageVersionSettings: LanguageVersionSettings,
typeSpecificityComparator: TypeSpecificityComparator,
private val diagnosticSuppressor: PlatformDiagnosticSuppressor
private val diagnosticSuppressor: PlatformDiagnosticSuppressor,
private val upperBoundChecker: UpperBoundChecker
) {
private val modifiersChecker = modifiersChecker.withTrace(trace)
@@ -360,7 +362,7 @@ class DeclarationsChecker(
for (delegationSpecifier in classOrObject.superTypeListEntries) {
val typeReference = delegationSpecifier.typeReference ?: continue
typeReference.type()?.let { DescriptorResolver.checkBounds(typeReference, it, trace) }
typeReference.type()?.let { upperBoundChecker.checkBounds(typeReference, it, trace) }
}
if (classOrObject !is KtClass) return
@@ -383,7 +385,7 @@ class DeclarationsChecker(
DescriptorResolver.checkUpperBoundTypes(trace, upperBoundCheckRequests, false)
for (request in upperBoundCheckRequests) {
DescriptorResolver.checkBounds(request.upperBound, request.upperBoundType, trace)
upperBoundChecker.checkBounds(request.upperBound, request.upperBoundType, trace)
}
}
@@ -1312,61 +1312,6 @@ public class DescriptorResolver {
return propertyDescriptor;
}
public static void checkBounds(@NotNull KtTypeReference typeReference, @NotNull KotlinType type, @NotNull BindingTrace trace) {
if (KotlinTypeKt.isError(type)) return;
KtTypeElement typeElement = typeReference.getTypeElement();
if (typeElement == null) return;
List<TypeParameterDescriptor> parameters = type.getConstructor().getParameters();
List<TypeProjection> arguments = type.getArguments();
assert parameters.size() == arguments.size();
List<KtTypeReference> ktTypeArguments = typeElement.getTypeArgumentsAsTypes();
// A type reference from Kotlin code can yield a flexible type only if it's `ft<T1, T2>`, whose bounds should not be checked
if (FlexibleTypesKt.isFlexible(type) && !DynamicTypesKt.isDynamic(type)) {
assert ktTypeArguments.size() == 2
: "Flexible type cannot be denoted in Kotlin otherwise than as ft<T1, T2>, but was: "
+ PsiUtilsKt.getElementTextWithContext(typeReference);
// it's really ft<Foo, Bar>
FlexibleType flexibleType = FlexibleTypesKt.asFlexibleType(type);
checkBounds(ktTypeArguments.get(0), flexibleType.getLowerBound(), trace);
checkBounds(ktTypeArguments.get(1), flexibleType.getUpperBound(), trace);
return;
}
// If the numbers of type arguments do not match, the error has been already reported in TypeResolver
if (ktTypeArguments.size() != arguments.size()) return;
TypeSubstitutor substitutor = TypeSubstitutor.create(type);
for (int i = 0; i < ktTypeArguments.size(); i++) {
KtTypeReference ktTypeArgument = ktTypeArguments.get(i);
if (ktTypeArgument == null) continue;
KotlinType typeArgument = arguments.get(i).getType();
checkBounds(ktTypeArgument, typeArgument, trace);
TypeParameterDescriptor typeParameterDescriptor = parameters.get(i);
checkBounds(ktTypeArgument, typeArgument, typeParameterDescriptor, substitutor, trace);
}
}
public static void checkBounds(
@NotNull KtTypeReference jetTypeArgument,
@NotNull KotlinType typeArgument,
@NotNull TypeParameterDescriptor typeParameterDescriptor,
@NotNull TypeSubstitutor substitutor,
@NotNull BindingTrace trace
) {
for (KotlinType bound : typeParameterDescriptor.getUpperBounds()) {
KotlinType substitutedBound = substitutor.safeSubstitute(bound, Variance.INVARIANT);
if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(typeArgument, substitutedBound)) {
trace.report(UPPER_BOUND_VIOLATED.on(jetTypeArgument, substitutedBound, typeArgument));
}
}
}
public static boolean checkHasOuterClassInstance(
@NotNull LexicalScope scope,
@NotNull BindingTrace trace,
@@ -70,7 +70,8 @@ class TypeResolver(
private val dynamicCallableDescriptors: DynamicCallableDescriptors,
private val identifierChecker: IdentifierChecker,
private val platformToKotlinClassMapper: PlatformToKotlinClassMapper,
private val languageVersionSettings: LanguageVersionSettings
private val languageVersionSettings: LanguageVersionSettings,
private val upperBoundChecker: UpperBoundChecker
) {
private val isNonParenthesizedAnnotationsOnFunctionalTypesEnabled =
languageVersionSettings.getFeatureSupport(LanguageFeature.NonParenthesizedAnnotationsOnFunctionalTypes) == LanguageFeature.State.ENABLED
@@ -524,7 +525,7 @@ class TypeResolver(
val typeReference = collectedArgumentAsTypeProjections.getOrNull(i)?.typeReference
if (typeReference != null) {
DescriptorResolver.checkBounds(typeReference, argument, parameter, substitutor, c.trace)
upperBoundChecker.checkBounds(typeReference, argument, parameter, substitutor, c.trace)
}
}
}
@@ -0,0 +1,83 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.container.DefaultImplementation
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
@DefaultImplementation(impl = UpperBoundChecker::class)
interface UpperBoundChecker {
val languageVersionSettings: LanguageVersionSettings
fun checkBounds(typeReference: KtTypeReference, type: KotlinType, trace: BindingTrace) {
if (type.isError) return
val typeElement = typeReference.typeElement ?: return
val parameters = type.constructor.parameters
val arguments = type.arguments
assert(parameters.size == arguments.size)
val ktTypeArguments = typeElement.typeArgumentsAsTypes
// A type reference from Kotlin code can yield a flexible type only if it's `ft<T1, T2>`, whose bounds should not be checked
if (type.isFlexible() && !type.isDynamic()) {
assert(ktTypeArguments.size == 2) {
("Flexible type cannot be denoted in Kotlin otherwise than as ft<T1, T2>, but was: "
+ typeReference.getElementTextWithContext())
}
// it's really ft<Foo, Bar>
val flexibleType = type.asFlexibleType()
checkBounds(ktTypeArguments[0], flexibleType.lowerBound, trace)
checkBounds(ktTypeArguments[1], flexibleType.upperBound, trace)
return
}
// If the numbers of type arguments do not match, the error has been already reported in TypeResolver
if (ktTypeArguments.size != arguments.size) return
val substitutor = TypeSubstitutor.create(type)
for (i in ktTypeArguments.indices) {
val ktTypeArgument = ktTypeArguments[i] ?: continue
checkBounds(ktTypeArgument, arguments[i].type, trace)
checkBounds(ktTypeArgument, arguments[i].type, parameters[i], substitutor, trace)
}
}
fun checkBounds(
jetTypeArgument: KtTypeReference,
typeArgument: KotlinType,
typeParameterDescriptor: TypeParameterDescriptor,
substitutor: TypeSubstitutor,
trace: BindingTrace
) {
for (bound in typeParameterDescriptor.upperBounds) {
checkBound(bound, substitutor, trace, jetTypeArgument, typeArgument)
}
}
fun checkBound(
bound: KotlinType,
substitutor: TypeSubstitutor,
trace: BindingTrace,
jetTypeArgument: KtTypeReference,
typeArgument: KotlinType
): Boolean {
val substitutedBound = substitutor.safeSubstitute(bound, Variance.INVARIANT)
if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(typeArgument, substitutedBound)) {
trace.report(Errors.UPPER_BOUND_VIOLATED.on(jetTypeArgument, substitutedBound, typeArgument))
return false
}
return true
}
}
@@ -53,7 +53,8 @@ class CandidateResolver(
private val reflectionTypes: ReflectionTypes,
private val additionalTypeCheckers: Iterable<AdditionalTypeChecker>,
private val smartCastManager: SmartCastManager,
private val dataFlowValueFactory: DataFlowValueFactory
private val dataFlowValueFactory: DataFlowValueFactory,
private val upperBoundChecker: UpperBoundChecker
) {
fun <D : CallableDescriptor> performResolutionForCandidateCall(
context: CallCandidateResolutionContext<D>,
@@ -596,7 +597,7 @@ class CandidateResolver(
val typeArgument = typeArguments[i]
val typeReference = ktTypeArguments[i].typeReference
if (typeReference != null) {
DescriptorResolver.checkBounds(typeReference, typeArgument, typeParameterDescriptor, substitutor, trace)
upperBoundChecker.checkBounds(typeReference, typeArgument, typeParameterDescriptor, substitutor, trace)
}
}
}
@@ -0,0 +1,11 @@
// FULL_JDK
// FILE: MapLike.java
import java.util.Map;
public interface MapLike<@org.jetbrains.annotations.NotNull K, V> {
void putAll(Map<K, V> map);
}
// FILE: main.kt
fun test(map : MapLike<Int?, Int>) {}
@@ -0,0 +1,17 @@
// !LANGUAGE: +ProhibitUsingNullableTypeParameterAgainstNotNullAnnotated
// !DIAGNOSTICS: -UNUSED_PARAMETER
// FULL_JDK
// FILE: MapLike.java
import java.util.Map;
public interface MapLike<@org.jetbrains.annotations.NotNull K, V> {
void putAll(Map<K, V> map);
}
// FILE: main.kt
fun test0(map : MapLike<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Int?<!>, Int>) {}
fun <K> test11(map : MapLike<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>K<!>, K>) {}
fun <K> test12(map : MapLike<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>K?<!>, K>) {}
fun <K : Any> test13(map : MapLike<K, K>) {}
fun <K : Any> test14(map : MapLike<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>K?<!>, K>) {}
@@ -0,0 +1,15 @@
package
public fun test0(/*0*/ map: MapLike<kotlin.Int?, kotlin.Int>): kotlin.Unit
public fun </*0*/ K> test11(/*0*/ map: MapLike<K, K>): kotlin.Unit
public fun </*0*/ K> test12(/*0*/ map: MapLike<K?, K>): kotlin.Unit
public fun </*0*/ K : kotlin.Any> test13(/*0*/ map: MapLike<K, K>): kotlin.Unit
public fun </*0*/ K : kotlin.Any> test14(/*0*/ map: MapLike<K?, K>): kotlin.Unit
public fun test2(/*0*/ map: MapLike<kotlin.Int, kotlin.Int>): kotlin.Unit
public interface MapLike</*0*/ @org.jetbrains.annotations.NotNull K : kotlin.Any!, /*1*/ V : kotlin.Any!> {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public abstract fun putAll(/*0*/ map: kotlin.collections.(Mutable)Map<K!, V!>!): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -34423,6 +34423,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/java"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
}
@Test
@TestMetadata("checkEnhancedUpperBounds.kt")
public void testCheckEnhancedUpperBounds() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.kt");
}
@Test
@TestMetadata("concurrentHashMapContains.kt")
public void testConcurrentHashMapContains() throws Exception {