FIR IDE: Add simple shortening for qualified calls and properties

This commit is contained in:
Roman Golyshev
2020-12-25 12:19:35 +03:00
committed by Space
parent f03ca5ea57
commit 534f4a66ad
20 changed files with 288 additions and 12 deletions
@@ -29,6 +29,64 @@ public class FirShortenRefsTestGenerated extends AbstractFirShortenRefsTest {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/shortenRefsFir"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("idea/testData/shortenRefsFir/calls")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Calls extends AbstractFirShortenRefsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTestWithMuting, this, testDataFilePath);
}
public void testAllFilesPresentInCalls() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/shortenRefsFir/calls"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("classInSameFile.kt")
public void testClassInSameFile() throws Exception {
runTest("idea/testData/shortenRefsFir/calls/classInSameFile.kt");
}
@TestMetadata("functionInSameFile.kt")
public void testFunctionInSameFile() throws Exception {
runTest("idea/testData/shortenRefsFir/calls/functionInSameFile.kt");
}
@TestMetadata("functionInSameFile2.kt")
public void testFunctionInSameFile2() throws Exception {
runTest("idea/testData/shortenRefsFir/calls/functionInSameFile2.kt");
}
@TestMetadata("propertyChainCall.kt")
public void testPropertyChainCall() throws Exception {
runTest("idea/testData/shortenRefsFir/calls/propertyChainCall.kt");
}
@TestMetadata("propertyInSameFile.kt")
public void testPropertyInSameFile() throws Exception {
runTest("idea/testData/shortenRefsFir/calls/propertyInSameFile.kt");
}
@TestMetadata("propertyInSameFile2.kt")
public void testPropertyInSameFile2() throws Exception {
runTest("idea/testData/shortenRefsFir/calls/propertyInSameFile2.kt");
}
@TestMetadata("rootPackage.kt")
public void testRootPackage() throws Exception {
runTest("idea/testData/shortenRefsFir/calls/rootPackage.kt");
}
@TestMetadata("rootPackageShortenFakeRootPackage.kt")
public void testRootPackageShortenFakeRootPackage() throws Exception {
runTest("idea/testData/shortenRefsFir/calls/rootPackageShortenFakeRootPackage.kt");
}
@TestMetadata("selfReferencingFunction.kt")
public void testSelfReferencingFunction() throws Exception {
runTest("idea/testData/shortenRefsFir/calls/selfReferencingFunction.kt");
}
}
@TestMetadata("idea/testData/shortenRefsFir/types")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -12,7 +12,9 @@ import com.intellij.util.containers.addIfNotNull
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvedImport
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.scopes.FirScope
import org.jetbrains.kotlin.fir.scopes.impl.FirAbstractStarImportingScope
@@ -20,7 +22,10 @@ import org.jetbrains.kotlin.fir.scopes.impl.FirExplicitSimpleImportingScope
import org.jetbrains.kotlin.fir.scopes.impl.FirPackageMemberScope
import org.jetbrains.kotlin.fir.scopes.processClassifiersByName
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassifierSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.lowerBoundIfFlexible
@@ -51,15 +56,19 @@ internal class KtFirReferenceShortener(
resolveFileToBodyResolve(file)
val firFile = file.getOrBuildFirOfType<FirFile>(firResolveState)
val typesToImport = mutableListOf<FqName>()
val typesToShorten = mutableListOf<KtUserType>()
val namesToImport = mutableListOf<FqName>()
firFile.acceptChildren(TypesCollectingVisitor(typesToImport, typesToShorten))
val typesToShorten = mutableListOf<KtUserType>()
val callsToShorten = mutableListOf<KtDotQualifiedExpression>()
firFile.acceptChildren(TypesCollectingVisitor(namesToImport, typesToShorten))
firFile.acceptChildren(CallsCollectingVisitor(namesToImport, callsToShorten))
return ShortenCommandImpl(
file,
typesToImport.distinct(),
typesToShorten.distinct().map { it.createSmartPointer() }
namesToImport.distinct(),
typesToShorten.distinct().map { it.createSmartPointer() },
callsToShorten.distinct().map { it.createSmartPointer() }
)
}
@@ -79,6 +88,14 @@ internal class KtFirReferenceShortener(
return null
}
private fun findSingleFunctionInScopesByName(scopes: List<FirScope>, name: Name): FirNamedFunctionSymbol? {
return scopes.asSequence().mapNotNull { it.getSingleFunctionByName(name) }.singleOrNull()
}
private fun findSinglePropertyInScopesByName(scopes: List<FirScope>, name: Name): FirVariableSymbol<*>? {
return scopes.asSequence().mapNotNull { it.getSinglePropertyByName(name) }.singleOrNull()
}
private fun resolveFileToBodyResolve(file: KtFile) {
for (declaration in file.declarations) {
declaration.getOrBuildFir(firResolveState) // temporary hack, resolves declaration to BODY_RESOLVE stage
@@ -98,12 +115,20 @@ internal class KtFirReferenceShortener(
}
@OptIn(ExperimentalStdlibApi::class)
private fun findScopesAtPosition(targetTypeReference: KtElement, newImports: List<FqName>): List<FirScope>? {
val towerDataContext = firResolveState.getTowerDataContextForElement(targetTypeReference) ?: return null
private fun FirScope.getSingleFunctionByName(name: Name): FirNamedFunctionSymbol? =
buildList { processFunctionsByName(name, this::add) }.singleOrNull()
@OptIn(ExperimentalStdlibApi::class)
private fun FirScope.getSinglePropertyByName(name: Name): FirVariableSymbol<*>? =
buildList { processPropertiesByName(name, this::add) }.singleOrNull()
@OptIn(ExperimentalStdlibApi::class)
private fun findScopesAtPosition(position: KtElement, newImports: List<FqName>): List<FirScope>? {
val towerDataContext = firResolveState.getTowerDataContextForElement(position) ?: return null
val result = buildList<FirScope> {
addAll(towerDataContext.nonLocalTowerDataElements.mapNotNull { it.scope })
addIfNotNull(createFakeImportingScope(targetTypeReference.project, newImports))
addIfNotNull(createFakeImportingScope(position.project, newImports))
addAll(towerDataContext.localScopes)
}
@@ -128,7 +153,7 @@ internal class KtFirReferenceShortener(
}
private inner class TypesCollectingVisitor(
private val typesToImport: MutableList<FqName>,
private val namesToImport: MutableList<FqName>,
private val typesToShorten: MutableList<KtUserType>,
) : FirVisitorVoid() {
override fun visitElement(element: FirElement) {
@@ -157,7 +182,7 @@ internal class KtFirReferenceShortener(
val allClassIds = generateSequence(wholeClassifierId) { it.outerClassId }
val allTypeElements = generateSequence(wholeTypeElement) { it.qualifier }
val positionScopes = findScopesAtPosition(wholeTypeElement, typesToImport) ?: return
val positionScopes = findScopesAtPosition(wholeTypeElement, namesToImport) ?: return
for ((classId, typeElement) in allClassIds.zip(allTypeElements)) {
val firstFoundClass = findFirstClassifierInScopesByName(positionScopes, classId.shortClassName)?.classId
@@ -184,16 +209,59 @@ internal class KtFirReferenceShortener(
}
private fun addTypeToImportAndShorten(classFqName: FqName, mostTopLevelTypeElement: KtUserType) {
typesToImport.add(classFqName)
namesToImport.add(classFqName)
typesToShorten.add(mostTopLevelTypeElement)
}
}
private inner class CallsCollectingVisitor(
private val namesToImport: List<FqName>,
private val callsToShorten: MutableList<KtDotQualifiedExpression>
) : FirVisitorVoid() {
override fun visitElement(element: FirElement) {
element.acceptChildren(this)
}
override fun visitResolvedNamedReference(resolvedNamedReference: FirResolvedNamedReference) {
super.visitResolvedNamedReference(resolvedNamedReference)
val referenceExpression = resolvedNamedReference.psi as? KtNameReferenceExpression
val qualifiedProperty = referenceExpression?.parent as? KtDotQualifiedExpression ?: return
val propertyId = (resolvedNamedReference.resolvedSymbol as? FirCallableSymbol<*>)?.callableId ?: return
val scopes = findScopesAtPosition(qualifiedProperty, namesToImport) ?: return
val singleAvailableProperty = findSinglePropertyInScopesByName(scopes, propertyId.callableName)
if (singleAvailableProperty?.callableId == propertyId) {
callsToShorten.add(qualifiedProperty)
}
}
override fun visitFunctionCall(functionCall: FirFunctionCall) {
super.visitFunctionCall(functionCall)
val callExpression = functionCall.psi as? KtCallExpression ?: return
val qualifiedCallExpression = callExpression.parent as? KtDotQualifiedExpression ?: return
val resolvedNamedReference = functionCall.calleeReference as? FirResolvedNamedReference ?: return
val callableId = (resolvedNamedReference.resolvedSymbol as? FirCallableSymbol<*>)?.callableId ?: return
val scopes = findScopesAtPosition(callExpression, namesToImport) ?: return
val singleAvailableCallable = findSingleFunctionInScopesByName(scopes, callableId.callableName)
if (singleAvailableCallable?.callableId == callableId) {
callsToShorten.add(qualifiedCallExpression)
}
}
}
}
private class ShortenCommandImpl(
val targetFile: KtFile,
val importsToAdd: List<FqName>,
val typesToShorten: List<SmartPsiElementPointer<KtUserType>>
val typesToShorten: List<SmartPsiElementPointer<KtUserType>>,
val callsToShorten: List<SmartPsiElementPointer<KtDotQualifiedExpression>>,
) : ShortenCommand {
override fun invokeShortening() {
@@ -207,8 +275,18 @@ private class ShortenCommandImpl(
val type = typePointer.element ?: continue
type.deleteQualifier()
}
for (callPointer in callsToShorten) {
val call = callPointer.element ?: continue
call.deleteQualifier()
}
}
}
private tailrec fun KtTypeElement?.unwrapNullable(): KtTypeElement? =
if (this is KtNullableType) this.innerType.unwrapNullable() else this
private fun KtDotQualifiedExpression.deleteQualifier(): KtExpression? {
val selectorExpression = selectorExpression ?: return null
return this.replace(selectorExpression) as KtExpression
}
+6
View File
@@ -0,0 +1,6 @@
// FIR_COMPARISON
package test
class A
fun usage(a: <selection>test.A</selection>) {}
@@ -0,0 +1,6 @@
// FIR_COMPARISON
package test
class A
fun usage(a: A) {}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test
fun foo() {}
fun usage() {
<selection>test.foo()</selection>
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test
fun foo() {}
fun usage() {
foo()
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test.test2
fun foo() {}
fun usage() {
<selection>test.test2.foo()</selection>
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test.test2
fun foo() {}
fun usage() {
foo()
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test.test2
val foo = 1
fun usage() {
<selection>test.test2.foo.toString()</selection>
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test.test2
val foo = 1
fun usage() {
foo.toString()
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test
val foo = 1
fun usage() {
<selection>test.foo</selection>
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test
val foo = 1
fun usage() {
foo
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test.test2
val foo = 1
fun usage() {
<selection>test.test2.foo</selection>
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test.test2
val foo = 1
fun usage() {
foo
}
+8
View File
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test
fun test() {}
fun usage() {
<selection>_root_ide_package_.test.test()</selection>
}
@@ -0,0 +1,8 @@
// FIR_COMPARISON
package test
fun test() {}
fun usage() {
test()
}
@@ -0,0 +1,10 @@
// FIR_COMPARISON
package test
fun test() {}
fun usage() {
fun test() {}
<selection>_root_ide_package_.test.test()</selection>
}
@@ -0,0 +1,10 @@
// FIR_COMPARISON
package test
fun test() {}
fun usage() {
fun test() {}
test.test()
}
@@ -0,0 +1,6 @@
// FIR_COMPARISON
package test.test1
fun foo() {
<selection>test.test1.foo()</selection>
}
@@ -0,0 +1,6 @@
// FIR_COMPARISON
package test.test1
fun foo() {
foo()
}