Record special descriptor in REFERENCE_TARGET for type alias object

If a type alias is used to reference an object (companion object) as a
qualifier, record FakeCallableDescriptorForTypeAliasObject in
REFERENCE_TARGET. This tells IDE that type alias was used in the file,
thus, if it's imported, such import isn't redundant.
REFERENCE_TARGET is used mostly by IDE and by ClassifierUsageChecker,
which we also have to update to handle qualifiers with
FakeCallableDescriptorForTypeAliasObject in REFERENCE_TARGET.

Rewrite some parts of ClassifierUsageChecker for cleaner interaction.

 #KT-21863 Fixed Target versions 1.2.40
This commit is contained in:
Dmitry Petrov
2018-03-13 15:17:30 +03:00
parent 3b3cc5233f
commit f956e8d85d
13 changed files with 156 additions and 42 deletions
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForTypeAliasObject
import org.jetbrains.kotlin.resolve.descriptorUtil.classValueDescriptor
import org.jetbrains.kotlin.resolve.descriptorUtil.classValueTypeDescriptor
import org.jetbrains.kotlin.resolve.descriptorUtil.hasCompanionObject
@@ -97,10 +98,10 @@ private fun resolveQualifierReferenceTarget(
// Given a class qualifier in expression position,
// it should provide a proper REFERENCE_TARGET (with type),
// and, in case of implicit companion object reference, SHORT_REFERENCE_TO_COMPANION_OBJECT.
val classValueDescriptor = classifier.classValueDescriptor
if (selectorIsCallable && classValueDescriptor != null) {
val receiverClassifierDescriptor = classifier.getCallableReceiverDescriptorRetainingTypeAliasReference()
if (selectorIsCallable && receiverClassifierDescriptor != null) {
val classValueTypeDescriptor = classifier.classValueTypeDescriptor!!
context.trace.record(BindingContext.REFERENCE_TARGET, qualifier.referenceExpression, classValueDescriptor)
context.trace.record(BindingContext.REFERENCE_TARGET, qualifier.referenceExpression, receiverClassifierDescriptor)
context.trace.recordType(qualifier.expression, classValueTypeDescriptor.defaultType)
if (classifier.hasCompanionObject) {
context.trace.record(BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, qualifier.referenceExpression, classifier)
@@ -111,3 +112,16 @@ private fun resolveQualifierReferenceTarget(
return qualifier.descriptor
}
private fun ClassifierDescriptor.getCallableReceiverDescriptorRetainingTypeAliasReference(): DeclarationDescriptor? =
when (this) {
is ClassDescriptor -> classValueDescriptor
is TypeAliasDescriptor ->
if (classDescriptor?.classValueDescriptor != null)
FakeCallableDescriptorForTypeAliasObject(this)
else
this
else -> null
}
@@ -26,7 +26,8 @@ import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DeprecationResolver
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForTypeAliasObject
import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
interface ClassifierUsageChecker {
fun check(targetDescriptor: ClassifierDescriptor, element: PsiElement, context: ClassifierUsageCheckerContext)
@@ -55,16 +56,9 @@ fun checkClassifierUsages(
return
}
val target = getReferencedClassifier(expression) ?: return
runCheckersWithTarget(target, expression)
getReferenceToCompanionViaClassifier(expression, target)?.let { referenceClassifier ->
val outerClass = target.containingDeclaration as ClassDescriptor
runCheckersWithTarget(outerClass, expression)
if (referenceClassifier is TypeAliasDescriptor) {
runCheckersWithTarget(referenceClassifier, expression)
}
val targets = getReferencedClassifiers(expression)
for (target in targets) {
runCheckersWithTarget(target, expression)
}
}
@@ -74,28 +68,49 @@ fun checkClassifierUsages(
}
}
private fun getReferencedClassifier(expression: KtReferenceExpression): ClassifierDescriptor? {
private fun getReferencedClassifiers(expression: KtReferenceExpression): List<ClassifierDescriptor> {
val target = context.trace.get(BindingContext.REFERENCE_TARGET, expression)
if (target is ClassifierDescriptor) return target
if (target is ClassConstructorDescriptor) return target.constructedClass
// "Comparable" in "import java.lang.Comparable" references both a class and a SAM constructor and prevents
// REFERENCE_TARGET from being recorded in favor of AMBIGUOUS_REFERENCE_TARGET. But we must still run checkers
// to report if there's something wrong with the class. We characterize this case below by the following properties:
// 1) Exactly one of the references is a classifier
// 2) All references refer to the same source element, i.e. their source is the same
val targets = context.trace.get(BindingContext.AMBIGUOUS_REFERENCE_TARGET, expression) ?: return null
if (targets.groupBy { (it as? DeclarationDescriptorWithSource)?.source }.size != 1) return null
return targets.filterIsInstance<ClassifierDescriptor>().singleOrNull()
return when (target) {
is ClassifierDescriptor ->
listOfNotNull(
target,
getClassifierUsedToReferenceCompanionObject(target, expression)
)
is ClassConstructorDescriptor -> listOf(target.constructedClass)
is FakeCallableDescriptorForTypeAliasObject -> {
val referencedObject = target.getReferencedObject()
val referencedTypeAlias = target.typeAliasDescriptor
if (referencedObject != referencedTypeAlias.classDescriptor)
listOf(referencedObject, referencedTypeAlias)
else
listOf(referencedTypeAlias)
}
else -> {
// "Comparable" in "import java.lang.Comparable" references both a class and a SAM constructor and prevents
// REFERENCE_TARGET from being recorded in favor of AMBIGUOUS_REFERENCE_TARGET. But we must still run checkers
// to report if there's something wrong with the class. We characterize this case below by the following properties:
// 1) Exactly one of the references is a classifier
// 2) All references refer to the same source element, i.e. their source is the same
val targets = context.trace.get(BindingContext.AMBIGUOUS_REFERENCE_TARGET, expression) ?: return emptyList()
if (targets.groupBy { (it as? DeclarationDescriptorWithSource)?.source }.size != 1) return emptyList()
val targetClassifiers = targets.filterIsInstance<ClassifierDescriptor>()
if (targetClassifiers.size == 1) targetClassifiers else emptyList()
}
}
}
private fun getReferenceToCompanionViaClassifier(
expression: KtReferenceExpression,
target: ClassifierDescriptor
): ClassifierDescriptor? {
if (!DescriptorUtils.isCompanionObject(target)) return null
return context.trace.get(BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, expression)
}
private fun getClassifierUsedToReferenceCompanionObject(
referencedObject: ClassifierDescriptor,
expression: KtReferenceExpression
): ClassifierDescriptor? =
if (referencedObject.isCompanionObject())
context.trace.get(BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, expression)
else
null
}
for (declaration in declarations) {