From ee98a76600a60d1e753c61b60f4ba27a2b24f179 Mon Sep 17 00:00:00 2001 From: Roman Golyshev Date: Tue, 19 Jan 2021 16:29:05 +0300 Subject: [PATCH] FIR IDE: Implement simple importing of the functions This is not a complete algorithm, but it already works in many cases Disable some tests that not yet work --- .../FirShortenRefsTestGenerated.java | 32 +++++++++- .../fir/components/KtFirReferenceShortener.kt | 59 +++++++++++++++---- .../calls/functionInSameFileAmbiguous.kt | 10 ++++ .../functionInSameFileAmbiguous.kt.after | 10 ++++ ...tedTopLevelFunctionAmbiguous.dependency.kt | 5 ++ .../notImportedTopLevelFunctionAmbiguous.kt | 6 ++ ...ImportedTopLevelFunctionAmbiguous.kt.after | 8 +++ ...unctionConflictsWithImported.dependency.kt | 3 + ...edTopLevelFunctionConflictsWithImported.kt | 8 +++ ...evelFunctionConflictsWithImported.kt.after | 8 +++ ...edTopLevelFunctionMissingArg.dependency.kt | 3 + .../notImportedTopLevelFunctionMissingArg.kt | 6 ++ ...mportedTopLevelFunctionMissingArg.kt.after | 8 +++ ...portedTopLevelFunctionNoArgs.dependency.kt | 3 + .../notImportedTopLevelFunctionNoArgs.kt | 6 ++ ...notImportedTopLevelFunctionNoArgs.kt.after | 8 +++ ...opLevelTypeConstructorNoArgs.dependency.kt | 3 + ...otImportedTopLevelTypeConstructorNoArgs.kt | 6 ++ ...rtedTopLevelTypeConstructorNoArgs.kt.after | 8 +++ .../rootPackageShortenFakeRootPackage.kt | 2 +- ...rameterTypeConflictingTopLevelClassUsed.kt | 2 +- 21 files changed, 191 insertions(+), 13 deletions(-) create mode 100644 idea/testData/shortenRefsFir/calls/functionInSameFileAmbiguous.kt create mode 100644 idea/testData/shortenRefsFir/calls/functionInSameFileAmbiguous.kt.after create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.dependency.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.kt.after create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.dependency.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.kt.after create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.dependency.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.kt.after create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.dependency.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.kt.after create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.dependency.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.kt create mode 100644 idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.kt.after diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/shortenRefs/FirShortenRefsTestGenerated.java b/idea/idea-fir/tests/org/jetbrains/kotlin/shortenRefs/FirShortenRefsTestGenerated.java index e1e234f7941..e0c9819ff0a 100644 --- a/idea/idea-fir/tests/org/jetbrains/kotlin/shortenRefs/FirShortenRefsTestGenerated.java +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/shortenRefs/FirShortenRefsTestGenerated.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -76,6 +76,36 @@ public class FirShortenRefsTestGenerated extends AbstractFirShortenRefsTest { runTest("idea/testData/shortenRefsFir/calls/functionInSameFile2.kt"); } + @TestMetadata("functionInSameFileAmbiguous.kt") + public void testFunctionInSameFileAmbiguous() throws Exception { + runTest("idea/testData/shortenRefsFir/calls/functionInSameFileAmbiguous.kt"); + } + + @TestMetadata("notImportedTopLevelFunctionAmbiguous.kt") + public void testNotImportedTopLevelFunctionAmbiguous() throws Exception { + runTest("idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.kt"); + } + + @TestMetadata("notImportedTopLevelFunctionConflictsWithImported.kt") + public void testNotImportedTopLevelFunctionConflictsWithImported() throws Exception { + runTest("idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.kt"); + } + + @TestMetadata("notImportedTopLevelFunctionMissingArg.kt") + public void testNotImportedTopLevelFunctionMissingArg() throws Exception { + runTest("idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.kt"); + } + + @TestMetadata("notImportedTopLevelFunctionNoArgs.kt") + public void testNotImportedTopLevelFunctionNoArgs() throws Exception { + runTest("idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.kt"); + } + + @TestMetadata("notImportedTopLevelTypeConstructorNoArgs.kt") + public void testNotImportedTopLevelTypeConstructorNoArgs() throws Exception { + runTest("idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.kt"); + } + @TestMetadata("propertyChainCall.kt") public void testPropertyChainCall() throws Exception { runTest("idea/testData/shortenRefsFir/calls/propertyChainCall.kt"); diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirReferenceShortener.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirReferenceShortener.kt index 25e88f6fa24..23ab798fed2 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirReferenceShortener.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirReferenceShortener.kt @@ -17,18 +17,20 @@ import org.jetbrains.kotlin.fir.expressions.FirFunctionCall import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression import org.jetbrains.kotlin.fir.psi +import org.jetbrains.kotlin.fir.references.FirErrorNamedReference +import org.jetbrains.kotlin.fir.references.FirNamedReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.ScopeSession +import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeAmbiguityError import org.jetbrains.kotlin.fir.scopes.FirScope +import org.jetbrains.kotlin.fir.scopes.getFunctions import org.jetbrains.kotlin.fir.scopes.impl.FirAbstractStarImportingScope 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.CallableId 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.symbols.impl.* import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef import org.jetbrains.kotlin.fir.types.classId import org.jetbrains.kotlin.fir.types.lowerBoundIfFlexible @@ -91,8 +93,8 @@ internal class KtFirReferenceShortener( return null } - private fun findSingleFunctionInScopesByName(scopes: List, name: Name): FirNamedFunctionSymbol? { - return scopes.asSequence().mapNotNull { it.getSingleFunctionByName(name) }.singleOrNull() + private fun findFunctionsInScopes(scopes: List, name: Name): List { + return scopes.flatMap { it.getFunctions(name) } } private fun findSinglePropertyInScopesByName(scopes: List, name: Name): FirVariableSymbol<*>? { @@ -252,13 +254,16 @@ internal class KtFirReferenceShortener( val callExpression = functionCall.psi as? KtCallExpression ?: return val qualifiedCallExpression = callExpression.getDotQualifiedExpressionForSelector() ?: return - val resolvedNamedReference = functionCall.calleeReference as? FirResolvedNamedReference ?: return - val callableId = (resolvedNamedReference.resolvedSymbol as? FirCallableSymbol<*>)?.callableId ?: return + val calleeReference = functionCall.calleeReference + val callableId = findUnambiguousReferencedCallableId(calleeReference) ?: return val scopes = findScopesAtPosition(callExpression, namesToImport) ?: return - val singleAvailableCallable = findSingleFunctionInScopesByName(scopes, callableId.callableName) + val availableCallables = findFunctionsInScopes(scopes, callableId.callableName) - if (singleAvailableCallable?.callableId == callableId) { + if (availableCallables.isEmpty()) { + val additionalImport = callableId.asImportableFqName() ?: return + addElementToImportAndShorten(additionalImport, qualifiedCallExpression) + } else if (availableCallables.all { it.callableId == callableId }) { addElementToShorten(qualifiedCallExpression) } } @@ -274,6 +279,38 @@ internal class KtFirReferenceShortener( return receiverType.classKind != ClassKind.OBJECT } + private fun findUnambiguousReferencedCallableId(namedReference: FirNamedReference): CallableId? { + val unambiguousSymbol = when (namedReference) { + is FirResolvedNamedReference -> namedReference.resolvedSymbol + is FirErrorNamedReference -> { + val candidateSymbol = namedReference.candidateSymbol + if (candidateSymbol !is FirErrorFunctionSymbol) { + candidateSymbol + } else { + getSingleUnambiguousCandidate(namedReference) + } + } + else -> null + } + + return (unambiguousSymbol as? FirCallableSymbol<*>)?.callableId + } + + /** + * If [namedReference] is ambiguous and all candidates point to the callables with same callableId, + * returns the first candidate; otherwise returns null. + */ + private fun getSingleUnambiguousCandidate(namedReference: FirErrorNamedReference): FirCallableSymbol<*>? { + val coneAmbiguityError = namedReference.diagnostic as? ConeAmbiguityError ?: return null + + val candidates = coneAmbiguityError.candidates.map { it as FirCallableSymbol<*> } + require(candidates.isNotEmpty()) { "Cannot have zero candidates" } + + val distinctCandidates = candidates.distinctBy { it.callableId } + return distinctCandidates.singleOrNull() + ?: error("Expected all candidates to have same callableId, but got: ${distinctCandidates.map { it.callableId }}") + } + override fun visitResolvedQualifier(resolvedQualifier: FirResolvedQualifier) { super.visitResolvedQualifier(resolvedQualifier) @@ -358,6 +395,8 @@ private class ShortenCommandImpl( } } +private fun CallableId.asImportableFqName(): FqName? = if (classId == null) packageName.child(callableName) else null + private fun KtElement.getDotQualifiedExpressionForSelector(): KtDotQualifiedExpression? = getQualifiedExpressionForSelector() as? KtDotQualifiedExpression diff --git a/idea/testData/shortenRefsFir/calls/functionInSameFileAmbiguous.kt b/idea/testData/shortenRefsFir/calls/functionInSameFileAmbiguous.kt new file mode 100644 index 00000000000..61bccdd046e --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/functionInSameFileAmbiguous.kt @@ -0,0 +1,10 @@ +// FIR_COMPARISON +package test + +fun foo(i: Int) {} + +fun foo(s: String) {} + +fun usage() { + test.foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/functionInSameFileAmbiguous.kt.after b/idea/testData/shortenRefsFir/calls/functionInSameFileAmbiguous.kt.after new file mode 100644 index 00000000000..2c25ff3226b --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/functionInSameFileAmbiguous.kt.after @@ -0,0 +1,10 @@ +// FIR_COMPARISON +package test + +fun foo(i: Int) {} + +fun foo(s: String) {} + +fun usage() { + foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.dependency.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.dependency.kt new file mode 100644 index 00000000000..31d25cffa32 --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.dependency.kt @@ -0,0 +1,5 @@ +package dependency + +fun foo(s: String) {} + +fun foo(i: Int) {} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.kt new file mode 100644 index 00000000000..ab8b588a32d --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.kt @@ -0,0 +1,6 @@ +// FIR_COMPARISON +package test + +fun usage() { + dependency.foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.kt.after b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.kt.after new file mode 100644 index 00000000000..26a25b648cd --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionAmbiguous.kt.after @@ -0,0 +1,8 @@ +// FIR_COMPARISON +package test + +import dependency.foo + +fun usage() { + foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.dependency.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.dependency.kt new file mode 100644 index 00000000000..3888aa931c6 --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.dependency.kt @@ -0,0 +1,3 @@ +package dependency + +fun foo() {} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.kt new file mode 100644 index 00000000000..1f183d298a4 --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.kt @@ -0,0 +1,8 @@ +// FIR_COMPARISON +package test + +fun foo() {} + +fun usage() { + dependency.foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.kt.after b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.kt.after new file mode 100644 index 00000000000..314619bef0c --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionConflictsWithImported.kt.after @@ -0,0 +1,8 @@ +// FIR_COMPARISON +package test + +fun foo() {} + +fun usage() { + dependency.foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.dependency.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.dependency.kt new file mode 100644 index 00000000000..47bcf393d6c --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.dependency.kt @@ -0,0 +1,3 @@ +package dependency + +fun foo(a: Any) {} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.kt new file mode 100644 index 00000000000..ab8b588a32d --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.kt @@ -0,0 +1,6 @@ +// FIR_COMPARISON +package test + +fun usage() { + dependency.foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.kt.after b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.kt.after new file mode 100644 index 00000000000..26a25b648cd --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionMissingArg.kt.after @@ -0,0 +1,8 @@ +// FIR_COMPARISON +package test + +import dependency.foo + +fun usage() { + foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.dependency.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.dependency.kt new file mode 100644 index 00000000000..cb273c2fb91 --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.dependency.kt @@ -0,0 +1,3 @@ +package dependency + +fun foo() {} diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.kt new file mode 100644 index 00000000000..ab8b588a32d --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.kt @@ -0,0 +1,6 @@ +// FIR_COMPARISON +package test + +fun usage() { + dependency.foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.kt.after b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.kt.after new file mode 100644 index 00000000000..26a25b648cd --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelFunctionNoArgs.kt.after @@ -0,0 +1,8 @@ +// FIR_COMPARISON +package test + +import dependency.foo + +fun usage() { + foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.dependency.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.dependency.kt new file mode 100644 index 00000000000..9bf4a365e82 --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.dependency.kt @@ -0,0 +1,3 @@ +package dependency + +class Foo \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.kt b/idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.kt new file mode 100644 index 00000000000..26c83e93c8e --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.kt @@ -0,0 +1,6 @@ +// FIR_IGNORE +package test + +fun usage() { + dependency.Foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.kt.after b/idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.kt.after new file mode 100644 index 00000000000..62e6e9b58db --- /dev/null +++ b/idea/testData/shortenRefsFir/calls/notImportedTopLevelTypeConstructorNoArgs.kt.after @@ -0,0 +1,8 @@ +// FIR_COMPARISON +package test + +import dependency.Foo + +fun usage() { + Foo() +} \ No newline at end of file diff --git a/idea/testData/shortenRefsFir/calls/rootPackageShortenFakeRootPackage.kt b/idea/testData/shortenRefsFir/calls/rootPackageShortenFakeRootPackage.kt index 82556f68718..d890c3ea23a 100644 --- a/idea/testData/shortenRefsFir/calls/rootPackageShortenFakeRootPackage.kt +++ b/idea/testData/shortenRefsFir/calls/rootPackageShortenFakeRootPackage.kt @@ -1,4 +1,4 @@ -// FIR_COMPARISON +// FIR_IGNORE package test fun test() {} diff --git a/idea/testData/shortenRefsFir/types/ParameterTypeConflictingTopLevelClassUsed.kt b/idea/testData/shortenRefsFir/types/ParameterTypeConflictingTopLevelClassUsed.kt index db9b1eae096..c5412878b54 100644 --- a/idea/testData/shortenRefsFir/types/ParameterTypeConflictingTopLevelClassUsed.kt +++ b/idea/testData/shortenRefsFir/types/ParameterTypeConflictingTopLevelClassUsed.kt @@ -1,4 +1,4 @@ -// FIR_COMPARISON +// FIR_IGNORE package test class Foo