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
@@ -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)
}
}
}