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:
+6
@@ -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 {
|
||||
|
||||
+46
@@ -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
|
||||
}
|
||||
}
|
||||
+1
@@ -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);
|
||||
|
||||
|
||||
+1
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -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>) {}
|
||||
+17
@@ -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>) {}
|
||||
+15
@@ -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
|
||||
}
|
||||
Generated
+6
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user