From d44f180aa9456824a7f96b1699843ce8edad23b3 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Thu, 9 Jun 2022 13:22:57 +0200 Subject: [PATCH] FE 1.0: deprecate resolve to property when enum entry is at same level See also KT-52802 #KT-49200 Fixed --- .../jetbrains/kotlin/diagnostics/Errors.java | 2 + .../rendering/DefaultErrorMessages.java | 1 + .../kotlin/diagnostics/rendering/Renderers.kt | 4 ++ .../DiagnosticReporterByTrackingStrategy.kt | 9 +++ .../resolve/calls/KotlinCallResolver.kt | 72 ++++++++++++------- .../calls/model/KotlinCallDiagnostics.kt | 8 +++ .../calls/tower/ScopeTowerProcessors.kt | 51 ++++++++++++- .../EnumEntryVsStaticAmbiguity.diag.txt | 3 + .../imports/EnumEntryVsStaticAmbiguity.fir.kt | 1 + .../imports/EnumEntryVsStaticAmbiguity.kt | 3 +- 10 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.diag.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 1c9a31a1caf..50499fd0c96 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -147,6 +147,8 @@ public interface Errors { DiagnosticFactory1 DEPRECATED_ACCESS_TO_ENUM_COMPANION_PROPERTY = DiagnosticFactory1.create(WARNING); + DiagnosticFactory2 DEPRECATED_RESOLVE_WITH_AMBIGUOUS_ENUM_ENTRY = DiagnosticFactory2.create(WARNING); + DiagnosticFactory1 PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL = DiagnosticFactory1.create(ERROR); // Exposed visibility group diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 56f1641b4ba..42472a0e461 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -73,6 +73,7 @@ public class DefaultErrorMessages { MAP.put(INVISIBLE_MEMBER, "Cannot access ''{0}'': it is {1} in {2}", NAME, VISIBILITY, NAME_OF_CONTAINING_DECLARATION_OR_FILE); MAP.put(DEPRECATED_ACCESS_BY_SHORT_NAME, "Access to this type by short name is deprecated, and soon is going to be removed. Please, add explicit qualifier or import", NAME); MAP.put(DEPRECATED_ACCESS_TO_ENUM_COMPANION_PROPERTY, "Ambiguous access to companion''s property ''{0}'' in enum is deprecated. Please, add explicit Companion qualifier to the class name", NAME); + MAP.put(DEPRECATED_RESOLVE_WITH_AMBIGUOUS_ENUM_ENTRY, "Ambiguous access to property ''{0}'' is deprecated because similar enum entry ''{1}'' is available. Please add explicit named import or use fully qualified name", FQ_NAME, FQ_NAME); MAP.put(PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL, "Protected constructor ''{0}'' from other classes can only be used in super-call", Renderers.SHORT_NAMES_IN_TYPES); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt index e1e9500f287..2c406ca553c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt @@ -44,6 +44,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.Constrain import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.* import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.getValidityConstraintForConstituentType import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.checker.KotlinTypeChecker import org.jetbrains.kotlin.utils.IDEAPlatforms @@ -71,6 +72,9 @@ object Renderers { @JvmField val NAME = Renderer { it.name.asString() } + @JvmField + val FQ_NAME = Renderer { it.fqNameSafe.asString() } + @JvmField val MODULE_WITH_PLATFORM = Renderer { module -> val platform = module.platform diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt index 33eee647e12..e5ee3c81d1c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.builtins.UnsignedTypes import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor import org.jetbrains.kotlin.builtins.isExtensionFunctionType import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.diagnostics.Errors.BadNamedArgumentsTarget.* import org.jetbrains.kotlin.diagnostics.reportDiagnosticOnce @@ -107,6 +108,14 @@ class DiagnosticReporterByTrackingStrategy( CandidateChosenUsingOverloadResolutionByLambdaAnnotation::class.java -> { trace.report(CANDIDATE_CHOSEN_USING_OVERLOAD_RESOLUTION_BY_LAMBDA_ANNOTATION.on(psiKotlinCall.psiCall.callElement)) } + EnumEntryAmbiguityWarning::class.java -> { + val propertyDescriptor = (diagnostic as EnumEntryAmbiguityWarning).property + val enumEntryDescriptor = diagnostic.enumEntry + val enumCompanionDescriptor = (enumEntryDescriptor.containingDeclaration as? ClassDescriptor)?.companionObjectDescriptor + if (enumCompanionDescriptor == null || propertyDescriptor.containingDeclaration != enumCompanionDescriptor) { + trace.report(DEPRECATED_RESOLVE_WITH_AMBIGUOUS_ENUM_ENTRY.on(psiKotlinCall.psiCall.callElement, propertyDescriptor, enumEntryDescriptor)) + } + } CompatibilityWarning::class.java -> { val callElement = psiKotlinCall.psiCall.callElement trace.report( diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/KotlinCallResolver.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/KotlinCallResolver.kt index c17f994d53d..cc81efecaa3 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/KotlinCallResolver.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/KotlinCallResolver.kt @@ -6,6 +6,8 @@ package org.jetbrains.kotlin.resolve.calls import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus import org.jetbrains.kotlin.resolve.calls.components.* import org.jetbrains.kotlin.resolve.calls.components.candidate.ResolutionCandidate @@ -15,6 +17,7 @@ import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage import org.jetbrains.kotlin.resolve.calls.model.* import org.jetbrains.kotlin.resolve.calls.tower.* +import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject import org.jetbrains.kotlin.resolve.descriptorUtil.OVERLOAD_RESOLUTION_BY_LAMBDA_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValueWithSmartCastInfo import org.jetbrains.kotlin.types.UnwrappedType @@ -218,31 +221,52 @@ class KotlinCallResolver( ) } - if ( - maximallySpecificCandidates.size > 1 && - callComponents.languageVersionSettings.supportsFeature(LanguageFeature.OverloadResolutionByLambdaReturnType) && - candidates.all { resolutionCallbacks.inferenceSession.shouldRunCompletion(it) } && - kotlinCall.callKind != KotlinCallKind.CALLABLE_REFERENCE - ) { - val candidatesWithAnnotation = candidates.filter { - it.resolvedCall.candidateDescriptor.annotations.hasAnnotation(OVERLOAD_RESOLUTION_BY_LAMBDA_ANNOTATION_FQ_NAME) - }.toSet() - val candidatesWithoutAnnotation = candidates - candidatesWithAnnotation - if (candidatesWithAnnotation.isNotEmpty()) { - @Suppress("UNCHECKED_CAST") - val newCandidates = kotlinCallCompleter.chooseCandidateRegardingOverloadResolutionByLambdaReturnType( - maximallySpecificCandidates as Set, - resolutionCallbacks - ) - maximallySpecificCandidates = overloadingConflictResolver.chooseMaximallySpecificCandidates( - newCandidates, - CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS, - discriminateGenerics = true - ) + if (maximallySpecificCandidates.size > 1) { + if (maximallySpecificCandidates.size == 2) { + val enumEntryCandidate = maximallySpecificCandidates.find { + val descriptor = it.resolvedCall.candidateDescriptor + descriptor is FakeCallableDescriptorForObject && descriptor.classDescriptor.kind == ClassKind.ENUM_ENTRY + } + if (enumEntryCandidate != null) { + val otherCandidate = maximallySpecificCandidates.find { + val candidateDescriptor = it.resolvedCall.candidateDescriptor + candidateDescriptor !is FakeCallableDescriptorForObject + } + if (otherCandidate != null) { + val propertyDescriptor = otherCandidate.resolvedCall.candidateDescriptor + if (propertyDescriptor is PropertyDescriptor) { + val enumEntryDescriptor = + (enumEntryCandidate.resolvedCall.candidateDescriptor as FakeCallableDescriptorForObject).classDescriptor + otherCandidate.addDiagnostic(EnumEntryAmbiguityWarning(propertyDescriptor, enumEntryDescriptor)) + return setOf(otherCandidate) + } + } + } + } + if (callComponents.languageVersionSettings.supportsFeature(LanguageFeature.OverloadResolutionByLambdaReturnType) && + candidates.all { resolutionCallbacks.inferenceSession.shouldRunCompletion(it) } && + kotlinCall.callKind != KotlinCallKind.CALLABLE_REFERENCE + ) { + val candidatesWithAnnotation = candidates.filter { + it.resolvedCall.candidateDescriptor.annotations.hasAnnotation(OVERLOAD_RESOLUTION_BY_LAMBDA_ANNOTATION_FQ_NAME) + }.toSet() + val candidatesWithoutAnnotation = candidates - candidatesWithAnnotation + if (candidatesWithAnnotation.isNotEmpty()) { + @Suppress("UNCHECKED_CAST") + val newCandidates = kotlinCallCompleter.chooseCandidateRegardingOverloadResolutionByLambdaReturnType( + maximallySpecificCandidates as Set, + resolutionCallbacks + ) + maximallySpecificCandidates = overloadingConflictResolver.chooseMaximallySpecificCandidates( + newCandidates, + CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS, + discriminateGenerics = true + ) - if (maximallySpecificCandidates.size > 1 && candidatesWithoutAnnotation.any { it in maximallySpecificCandidates }) { - maximallySpecificCandidates = maximallySpecificCandidates.toMutableSet().apply { removeAll(candidatesWithAnnotation) } - maximallySpecificCandidates.singleOrNull()?.addDiagnostic(CandidateChosenUsingOverloadResolutionByLambdaAnnotation()) + if (maximallySpecificCandidates.size > 1 && candidatesWithoutAnnotation.any { it in maximallySpecificCandidates }) { + maximallySpecificCandidates = maximallySpecificCandidates.toMutableSet().apply { removeAll(candidatesWithAnnotation) } + maximallySpecificCandidates.singleOrNull()?.addDiagnostic(CandidateChosenUsingOverloadResolutionByLambdaAnnotation()) + } } } } diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/model/KotlinCallDiagnostics.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/model/KotlinCallDiagnostics.kt index 38f93dfaa47..3b22beffbac 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/model/KotlinCallDiagnostics.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/model/KotlinCallDiagnostics.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.resolve.calls.model import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.resolve.calls.components.candidate.ResolutionCandidate @@ -292,6 +294,12 @@ class CandidateChosenUsingOverloadResolutionByLambdaAnnotation : KotlinCallDiagn } } +class EnumEntryAmbiguityWarning(val property: PropertyDescriptor, val enumEntry: ClassDescriptor) : KotlinCallDiagnostic(RESOLVED) { + override fun report(reporter: DiagnosticReporter) { + reporter.onCall(this) + } +} + class CompatibilityWarning(val candidate: CallableDescriptor) : KotlinCallDiagnostic(RESOLVED) { override fun report(reporter: DiagnosticReporter) { reporter.onCall(this) diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTowerProcessors.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTowerProcessors.kt index 0249b3978b4..a382c229354 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTowerProcessors.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/ScopeTowerProcessors.kt @@ -16,8 +16,11 @@ package org.jetbrains.kotlin.resolve.calls.tower +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.calls.components.candidate.ResolutionCandidate import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind +import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject import org.jetbrains.kotlin.resolve.scopes.receivers.DetailedReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.QualifierReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValueWithSmartCastInfo @@ -43,6 +46,52 @@ class PrioritizedCompositeScopeTowerProcessor( } +class VariableAndObjectScopeTowerProcessor( + private val variableProcessor: ScopeTowerProcessor, + private val objectProcessor: ScopeTowerProcessor +) : ScopeTowerProcessor { + override fun process(data: TowerData): List> { + val variablesResult = variableProcessor.process(data) + val objectResult = objectProcessor.process(data) + if (objectResult.isEmpty()) return variablesResult + if (objectResult.none { level -> + level.any { + it.isEnumEntryCandidate() + } + } + ) return variablesResult + objectResult + val result = mutableListOf>() + result.addAll(variablesResult.map { it.toMutableList() }) + for ((index, objectLevel) in objectResult.withIndex()) { + val enumEntryLevel = objectLevel.filter { it.isEnumEntryCandidate() } + if (enumEntryLevel.isEmpty()) continue + if (index < variablesResult.size) { + // It's guaranteed this element is a mutable list + (result[index] as MutableList).addAll(enumEntryLevel) + } else { + result.add(enumEntryLevel) + } + } + for (objectLevel in objectResult) { + val nonEnumEntryLevel = objectLevel.filter { !it.isEnumEntryCandidate() } + if (nonEnumEntryLevel.isEmpty()) continue + result.add(nonEnumEntryLevel) + } + return result + } + + private fun Candidate.isEnumEntryCandidate(): Boolean { + if (this !is ResolutionCandidate) return false + val callableDescriptor = resolvedCall.candidateDescriptor as? FakeCallableDescriptorForObject ?: return false + return callableDescriptor.classDescriptor.kind == ClassKind.ENUM_ENTRY + } + + override fun recordLookups(skippedData: Collection, name: Name) { + variableProcessor.recordLookups(skippedData, name) + objectProcessor.recordLookups(skippedData, name) + } +} + // use this if all processors has same priority class SamePriorityCompositeScopeTowerProcessor( private vararg val processors: SimpleScopeTowerProcessor @@ -244,7 +293,7 @@ fun createVariableProcessor( fun createVariableAndObjectProcessor( scopeTower: ImplicitScopeTower, name: Name, context: CandidateFactory, explicitReceiver: DetailedReceiver?, classValueReceiver: Boolean = true -) = PrioritizedCompositeScopeTowerProcessor( +) = VariableAndObjectScopeTowerProcessor( createVariableProcessor(scopeTower, name, context, explicitReceiver), createSimpleProcessor(scopeTower, context, explicitReceiver, classValueReceiver) { getObjects(name, it) } ) diff --git a/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.diag.txt b/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.diag.txt new file mode 100644 index 00000000000..d997ff7a7e1 --- /dev/null +++ b/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.diag.txt @@ -0,0 +1,3 @@ +/test.kt:26:9: warning: ambiguous access to property 'first.KtNodeTypes.SOME' is deprecated because similar enum entry 'second.SomeEnum.SOME' is available. Please add explicit named import or use fully qualified name + SOME -> true + ^ diff --git a/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.fir.kt b/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.fir.kt index 17ecfc2a4f2..5ab5447635a 100644 --- a/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.fir.kt +++ b/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.fir.kt @@ -1,4 +1,5 @@ // KT-49200 +// !RENDER_DIAGNOSTICS_FULL_TEXT // FILE: first/KtNodeTypes.java package first; diff --git a/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.kt b/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.kt index 41d6adccc55..0e9d1fde9b6 100644 --- a/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.kt +++ b/compiler/testData/diagnostics/tests/imports/EnumEntryVsStaticAmbiguity.kt @@ -1,4 +1,5 @@ // KT-49200 +// !RENDER_DIAGNOSTICS_FULL_TEXT // FILE: first/KtNodeTypes.java package first; @@ -22,7 +23,7 @@ import second.SomeEnum.* fun test(arg: String): Boolean { return when (arg) { - SOME -> true + SOME -> true else -> false } }