diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 2979176d3f9..0a5ab2b5e86 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -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 { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/EnhancedUpperBoundChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/EnhancedUpperBoundChecker.kt new file mode 100644 index 00000000000..73599004670 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/EnhancedUpperBoundChecker.kt @@ -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 + } +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java index 354c80dddd4..52a4ce117ab 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java @@ -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. " + diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index 479358a99aa..05edf57b0c1 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -137,6 +137,9 @@ public interface ErrorsJvm { DiagnosticFactory2 NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS = DiagnosticFactory2.create(WARNING); + DiagnosticFactory2 UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS + = DiagnosticFactory2.create(WARNING); + DiagnosticFactory1 NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER = DiagnosticFactory1.create(WARNING); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index cc8db2176c6..07a7924d721 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -97,6 +97,7 @@ object JvmPlatformConfigurator : PlatformConfiguratorBase( ) { override fun configureModuleComponents(container: StorageComponentContainer) { container.useImpl() + container.useImpl() container.useImpl() container.useImpl() container.useImpl() diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt index 39e42e8a342..de02be8ba01 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt @@ -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) } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java index 3e5126207e1..d141dc9164b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DescriptorResolver.java @@ -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 parameters = type.getConstructor().getParameters(); - List arguments = type.getArguments(); - assert parameters.size() == arguments.size(); - - List ktTypeArguments = typeElement.getTypeArgumentsAsTypes(); - - // A type reference from Kotlin code can yield a flexible type only if it's `ft`, 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, but was: " - + PsiUtilsKt.getElementTextWithContext(typeReference); - // it's really ft - 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, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeResolver.kt index ee724725e62..d85b0f53cdc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeResolver.kt @@ -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) } } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/UpperBoundChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/UpperBoundChecker.kt new file mode 100644 index 00000000000..fd0ebaa36ed --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/UpperBoundChecker.kt @@ -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`, 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, but was: " + + typeReference.getElementTextWithContext()) + } + // it's really ft + 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 + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt index 8e47249548b..c7868682f92 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt @@ -53,7 +53,8 @@ class CandidateResolver( private val reflectionTypes: ReflectionTypes, private val additionalTypeCheckers: Iterable, private val smartCastManager: SmartCastManager, - private val dataFlowValueFactory: DataFlowValueFactory + private val dataFlowValueFactory: DataFlowValueFactory, + private val upperBoundChecker: UpperBoundChecker ) { fun performResolutionForCandidateCall( context: CallCandidateResolutionContext, @@ -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) } } } diff --git a/compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.fir.kt new file mode 100644 index 00000000000..e89ef63ceb7 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.fir.kt @@ -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 map); +} + +// FILE: main.kt +fun test(map : MapLike) {} diff --git a/compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.kt b/compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.kt new file mode 100644 index 00000000000..ba54ca91941 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.kt @@ -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 map); +} + +// FILE: main.kt +fun test0(map : MapLike<Int?, Int>) {} +fun test11(map : MapLike<K, K>) {} +fun test12(map : MapLike<K?, K>) {} +fun test13(map : MapLike) {} +fun test14(map : MapLike<K?, K>) {} diff --git a/compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.txt b/compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.txt new file mode 100644 index 00000000000..4307fbac502 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/java/checkEnhancedUpperBounds.txt @@ -0,0 +1,15 @@ +package + +public fun test0(/*0*/ map: MapLike): kotlin.Unit +public fun test11(/*0*/ map: MapLike): kotlin.Unit +public fun test12(/*0*/ map: MapLike): kotlin.Unit +public fun test13(/*0*/ map: MapLike): kotlin.Unit +public fun test14(/*0*/ map: MapLike): kotlin.Unit +public fun test2(/*0*/ map: MapLike): kotlin.Unit + +public interface MapLike { + 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!): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index b41b5ab81ca..12b689e3f1c 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -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 {