diff --git a/idea/src/org/jetbrains/jet/plugin/codeInsight/ShortenReferences.kt b/idea/src/org/jetbrains/jet/plugin/codeInsight/ShortenReferences.kt index d333dff1818..05c157f7c94 100644 --- a/idea/src/org/jetbrains/jet/plugin/codeInsight/ShortenReferences.kt +++ b/idea/src/org/jetbrains/jet/plugin/codeInsight/ShortenReferences.kt @@ -14,6 +14,8 @@ import org.jetbrains.jet.renderer.DescriptorRenderer; import java.util.Collections; import java.util.HashSet; import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.containers.HashMap +import java.util.ArrayList public object ShortenReferences { public fun process(element: JetElement) { @@ -23,59 +25,131 @@ public object ShortenReferences { public fun process(elements: Iterable) { val first = elements.firstOrNull() if (first == null) return + val file = first.getContainingFile() as JetFile - val visitor = Visitor(first.getContainingFile() as JetFile) + // first resolve all qualified references - optimization + val resolveAllVisitor = ResolveAllReferencesVisitor(file) + processElements(elements, resolveAllVisitor) + + val shortenTypesVisitor = ShortenTypesVisitor(file, resolveAllVisitor.result) + processElements(elements, shortenTypesVisitor) + shortenTypesVisitor.finish() + + processElements(elements, ShortenQualifiedExpressionsVisitor(file, resolveAllVisitor.result)) + } + + private fun processElements(elements: Iterable, visitor: JetVisitorVoid) { for (element in elements) { element.accept(visitor) } } - private class Visitor(val file: JetFile) : JetVisitorVoid() { - private val resolveSession : CancelableResolveSession - get() = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(file) + private class ResolveAllReferencesVisitor(file: JetFile) : JetVisitorVoid() { + private val resolveSession = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(file) + private val resolveMap = HashMap() + + public val result: Map = resolveMap override fun visitJetElement(element : JetElement) { - acceptChildren(element) + element.acceptChildren(this) } override fun visitUserType(userType: JetUserType) { - val resultElement = processType(userType) - acceptChildren(resultElement) + userType.acceptChildren(this) + + if (userType.getQualifier() != null) { + val referenceExpression = userType.getReferenceExpression() + if (referenceExpression != null) { + resolveMap[referenceExpression] = resolveSession.resolveToElement(referenceExpression) + } + } } - private fun processType(userType: JetUserType): PsiElement { - if (userType.getQualifier() == null) return userType + override fun visitDotQualifiedExpression(expression: JetDotQualifiedExpression) { + expression.acceptChildren(this) - val bindingContext = resolveSession.resolveToElement(userType) - val target = bindingContext.get(BindingContext.REFERENCE_TARGET, userType.getReferenceExpression()) - if (target == null) return userType + val referenceExpression = referenceExpression(expression.getSelectorExpression()) + if (referenceExpression != null) { + resolveMap[referenceExpression] = resolveSession.resolveToElement(referenceExpression) + } + } + } + + private class ShortenTypesVisitor(val file: JetFile, val resolveMap: Map) : JetVisitorVoid() { + private val resolveSession : CancelableResolveSession + get() = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(file) + + private val typesToShorten = ArrayList() + + public fun finish() { + for (userType in typesToShorten) { + shortenType(userType) + } + } + + private fun bindingContext(expression: JetReferenceExpression): BindingContext = resolveMap[expression]!! + + override fun visitJetElement(element : JetElement) { + element.acceptChildren(this) + } + + override fun visitUserType(userType: JetUserType) { + userType.getTypeArgumentList()?.accept(this) + + if (canShortenType(userType)) { + typesToShorten.add(userType) + } + else{ + userType.getQualifier()?.accept(this) + } + } + + private fun canShortenType(userType: JetUserType): Boolean { + if (userType.getQualifier() == null) return false + val referenceExpression = userType.getReferenceExpression() + if (referenceExpression == null) return false + + val target = bindingContext(referenceExpression).get(BindingContext.REFERENCE_TARGET, referenceExpression) + if (target == null) return false // references to nested classes should be shortened when visiting qualifier - if (target.getContainingDeclaration() is ClassDescriptor) return userType + if (target.getContainingDeclaration() is ClassDescriptor) return false val typeReference = PsiTreeUtil.getParentOfType(userType, javaClass())!! - val scope = bindingContext.get(BindingContext.TYPE_RESOLUTION_SCOPE, typeReference)!! + val scope = resolveSession.resolveToElement(typeReference).get(BindingContext.TYPE_RESOLUTION_SCOPE, typeReference)!! val name = target.getName() val targetByName = scope.getClassifier(name) ?: scope.getPackage(name) - if (target == targetByName) { - return shortenType(userType) + if (targetByName == null) { + addImportIfNeeded(target, file) + return true } - else if (targetByName == null) { - addImportIfNeeded(target) - return shortenType(userType) + else if (target.asString() == targetByName.asString()) { + return true } else { // leave FQ name - return userType + return false } } - private fun shortenType(userType: JetUserType): JetUserType { + private fun shortenType(userType: JetUserType) { val referenceExpression = userType.getReferenceExpression() - if (referenceExpression == null) return userType + if (referenceExpression == null) return val typeArgumentList = userType.getTypeArgumentList() val text = referenceExpression.getText() + (if (typeArgumentList != null) typeArgumentList.getText() else "") - val newUserType = JetPsiFactory.createType(userType.getProject(), text).getTypeElement() as JetUserType - return userType.replace(newUserType) as JetUserType + val newUserType = JetPsiFactory.createType(userType.getProject(), text).getTypeElement()!! + userType.replace(newUserType) + } + } + + private class ShortenQualifiedExpressionsVisitor(val file: JetFile, val resolveMap: Map) : JetVisitorVoid() { + private val resolveSession : CancelableResolveSession + get() = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(file) + + private fun bindingContext(expression: JetReferenceExpression): BindingContext + = resolveMap[expression] ?: resolveSession.resolveToElement(expression) // binding context can be absent in the map if some references have been shortened already + + override fun visitJetElement(element : JetElement) { + acceptChildren(element) } override fun visitDotQualifiedExpression(expression: JetDotQualifiedExpression) { @@ -90,22 +164,22 @@ public object ShortenReferences { if (calleeExpression is JetReferenceExpression) { val targetClass = instantiatedClass(calleeExpression) if (targetClass != null) { - return shortenIfPossible(qualifiedExpression, targetClass) + return shortenIfPossible(qualifiedExpression, targetClass, bindingContext(calleeExpression)) } } } else if (selectorExpression is JetReferenceExpression) { - val bindingContext = resolveSession.resolveToElement(selectorExpression) + val bindingContext = bindingContext(selectorExpression) val target = bindingContext.get(BindingContext.REFERENCE_TARGET, selectorExpression) if (target is ClassDescriptor || target is PackageViewDescriptor) { //TODO: should we ever add imports to real packages? - return shortenIfPossible(qualifiedExpression, target) + return shortenIfPossible(qualifiedExpression, target, bindingContext) } } return qualifiedExpression } private fun instantiatedClass(calleeExpression: JetReferenceExpression): ClassDescriptor? { - val bindingContext = resolveSession.resolveToElement(calleeExpression) + val bindingContext = bindingContext(calleeExpression) val target = bindingContext.get(BindingContext.REFERENCE_TARGET, calleeExpression) if (target != null) { if (target is ConstructorDescriptor) { @@ -132,47 +206,40 @@ public object ShortenReferences { return null } - private fun shortenIfPossible(qualifiedExpression: JetDotQualifiedExpression, targetClassOrPackage: DeclarationDescriptor): PsiElement { + private fun shortenIfPossible(qualifiedExpression: JetDotQualifiedExpression, targetClassOrPackage: DeclarationDescriptor, bindingContext: BindingContext): PsiElement { // references to nested classes should be shortened when visiting qualifier if (targetClassOrPackage.getContainingDeclaration() is ClassDescriptor) return qualifiedExpression - var bindingContext = resolveSession.resolveToElement(qualifiedExpression) - - val referenceExpression = referenceExpression(qualifiedExpression.getSelectorExpression()!!) + val referenceExpression = referenceExpression(qualifiedExpression.getSelectorExpression())!! val resolveBefore = resolveState(referenceExpression, bindingContext) val copy = qualifiedExpression.copy() val selectorExpression = qualifiedExpression.getSelectorExpression()!! val newExpression = qualifiedExpression.replace(selectorExpression) as JetExpression - val newReferenceExpression = referenceExpression(newExpression) + val newReferenceExpression = referenceExpression(newExpression)!! - bindingContext = resolveSession.resolveToElement(newReferenceExpression) - val resolveAfter = resolveState(newReferenceExpression, bindingContext) + val newBindingContext = resolveSession.resolveToElement(newReferenceExpression) + val resolveAfter = resolveState(newReferenceExpression, newBindingContext) if (resolveAfter != null) { if (resolveBefore == resolveAfter) return newExpression return newExpression.replace(copy) // revert shortening } - addImportIfNeeded(targetClassOrPackage) + addImportIfNeeded(targetClassOrPackage, file) return newExpression } private fun resolveState(referenceExpression: JetReferenceExpression, bindingContext: BindingContext): Any? { val target = bindingContext.get(BindingContext.REFERENCE_TARGET, referenceExpression) - if (target != null) return DescriptorRenderer.TEXT.render(target.getOriginal()) + if (target != null) return target.asString() val targets = bindingContext.get(BindingContext.AMBIGUOUS_REFERENCE_TARGET, referenceExpression) - if (targets != null) return HashSet(targets.map{DescriptorRenderer.TEXT.render(it!!)}) + if (targets != null) return HashSet(targets.map{it!!.asString()}) return null } - //TODO: do we need this "IfNeeded" check? - private fun addImportIfNeeded(descriptor : DeclarationDescriptor) { - ImportInsertHelper.addImportDirectiveIfNeeded(DescriptorUtils.getFqNameSafe(descriptor), file) - } - // we do not use standard PsiElement.acceptChildren because it won't work correctly if the element is replaced by the visitor private fun acceptChildren(element: PsiElement) { var child = element.getFirstChild() @@ -184,10 +251,18 @@ public object ShortenReferences { } } - private fun referenceExpression(selectorExpression: JetExpression) = if (selectorExpression is JetCallExpression) - selectorExpression.getCalleeExpression() as JetReferenceExpression - else - selectorExpression as JetReferenceExpression + private fun referenceExpression(selectorExpression: JetExpression?): JetReferenceExpression? + = if (selectorExpression is JetCallExpression) + selectorExpression.getCalleeExpression() as? JetReferenceExpression + else + selectorExpression as? JetReferenceExpression + + private fun DeclarationDescriptor.asString() = DescriptorRenderer.TEXT.render(this) + + //TODO: do we need this "IfNeeded" check? + private fun addImportIfNeeded(descriptor: DeclarationDescriptor, file: JetFile) { + ImportInsertHelper.addImportDirectiveIfNeeded(DescriptorUtils.getFqNameSafe(descriptor), file) + } } //TODO: how about such function in stdlib? diff --git a/idea/testData/completion/handlers/smart/ConstructorInsertsImport2.kt.after b/idea/testData/completion/handlers/smart/ConstructorInsertsImport2.kt.after index 6ef99fc5432..9ed305347cc 100644 --- a/idea/testData/completion/handlers/smart/ConstructorInsertsImport2.kt.after +++ b/idea/testData/completion/handlers/smart/ConstructorInsertsImport2.kt.after @@ -1,6 +1,6 @@ import java.util.HashMap -import java.util.AbstractMap import java.io.File +import java.util.AbstractMap class X { fun foo(p: java.util.HashMap>){} diff --git a/idea/testData/shortenRefs/constructor/GenericType.kt.after b/idea/testData/shortenRefs/constructor/GenericType.kt.after index 045ced61558..f9d331b584a 100644 --- a/idea/testData/shortenRefs/constructor/GenericType.kt.after +++ b/idea/testData/shortenRefs/constructor/GenericType.kt.after @@ -1,6 +1,6 @@ -import java.util.HashMap -import java.util.ArrayList import java.io.File +import java.util.ArrayList +import java.util.HashMap class A { val x = HashMap, String>() diff --git a/idea/testData/shortenRefs/type/GenericType.kt.after b/idea/testData/shortenRefs/type/GenericType.kt.after index c25ded9e2f1..573c851e34f 100644 --- a/idea/testData/shortenRefs/type/GenericType.kt.after +++ b/idea/testData/shortenRefs/type/GenericType.kt.after @@ -1,6 +1,6 @@ -import java.util.HashMap -import java.util.ArrayList import java.io.File +import java.util.ArrayList +import java.util.HashMap class A { val x: HashMap, String> diff --git a/idea/testData/shortenRefs/type/SameClassTwice.kt.after b/idea/testData/shortenRefs/type/SameClassTwice.kt.after index cb3c2d496d9..ec3d2fdd1ab 100644 --- a/idea/testData/shortenRefs/type/SameClassTwice.kt.after +++ b/idea/testData/shortenRefs/type/SameClassTwice.kt.after @@ -1,5 +1,5 @@ -import java.util.ArrayList import java.io.File +import java.util.ArrayList class A { val x: ArrayList> diff --git a/idea/testData/shortenRefs/type/TwoClassesSameNames.kt.after b/idea/testData/shortenRefs/type/TwoClassesSameNames.kt.after index 887bf4f5d9d..bb33bf5f6d7 100644 --- a/idea/testData/shortenRefs/type/TwoClassesSameNames.kt.after +++ b/idea/testData/shortenRefs/type/TwoClassesSameNames.kt.after @@ -1,6 +1,6 @@ -import java.util.HashMap import java.util.Date import java.sql +import java.util.HashMap class A { val x: HashMap