FIR IDE: Add more correct importing of the callables

Now we use scopes to decide if the callable really needs to be inserted
This commit is contained in:
Roman Golyshev
2021-03-03 18:40:36 +03:00
parent 76ff106458
commit 7b7ba717d0
14 changed files with 142 additions and 21 deletions
@@ -0,0 +1,12 @@
// FIR_COMPARISON
package test
class AClass
val AClass.ext get() = this
fun usage(a: AClass) {
a.ex<caret>
}
// ELEMENT: ext
@@ -0,0 +1,12 @@
// FIR_COMPARISON
package test
class AClass
val AClass.ext get() = this
fun usage(a: AClass) {
a.ext<caret>
}
// ELEMENT: ext
@@ -0,0 +1,6 @@
// FIR_COMPARISON
package first
fun foo() {
"".extensi<caret>
}
@@ -0,0 +1,4 @@
package first
val String.extensionProp: Int
get() = 1
@@ -0,0 +1,6 @@
// FIR_COMPARISON
package first
fun foo() {
"".extensionProp<caret>
}
@@ -0,0 +1,6 @@
// FIR_COMPARISON
package some
fun other() {
somePr<caret>
}
@@ -0,0 +1,4 @@
// FIR_COMPARISON
package some
val someProp: Int = 1
@@ -0,0 +1,6 @@
// FIR_COMPARISON
package some
fun other() {
someProp<caret>
}
@@ -79,6 +79,11 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
runTest("idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt");
}
@TestMetadata("ExtensionPropertyFromSameFile.kt")
public void testExtensionPropertyFromSameFile() throws Exception {
runTest("idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt");
}
@TestMetadata("ExtensionReceiverTypeArg.kt")
public void testExtensionReceiverTypeArg() throws Exception {
runTest("idea/idea-completion/testData/handlers/basic/ExtensionReceiverTypeArg.kt");
@@ -26,6 +26,10 @@ open class CompletionMultiFileHandlerTest : KotlinFixtureCompletionBaseTestCase(
doTest()
}
fun testExtensionPropertyInSamePackageNoImport() {
doTest()
}
fun testImportAlreadyImportedObject() {
doTest()
}
@@ -50,6 +54,10 @@ open class CompletionMultiFileHandlerTest : KotlinFixtureCompletionBaseTestCase(
doTest()
}
fun testTopLevelPropertyInSamePackageNoImport() {
doTest()
}
fun testTopLevelValImportInStringTemplate() {
doTest()
}
@@ -79,6 +79,11 @@ public class HighLevelPerformanceBasicCompletionHandlerTestGenerated extends Abs
runTest("idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt");
}
@TestMetadata("ExtensionPropertyFromSameFile.kt")
public void testExtensionPropertyFromSameFile() throws Exception {
runTest("idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt");
}
@TestMetadata("ExtensionReceiverTypeArg.kt")
public void testExtensionReceiverTypeArg() throws Exception {
runTest("idea/idea-completion/testData/handlers/basic/ExtensionReceiverTypeArg.kt");
@@ -121,24 +121,10 @@ private class VariableLookupElementFactory {
return if (symbol.canBeCalledByFqName) {
ShorteningVariableInsertionHandler(callableId)
} else {
SimpleVariableInsertionHandler(callableId)
SimpleVariableInsertionHandler(symbol.name, symbol.importableFqName)
}
}
private val KtVariableLikeSymbol.callableIdIfExists: FqName?
get() = when (this) {
is KtJavaFieldSymbol -> callableIdIfNonLocal
is KtKotlinPropertySymbol -> callableIdIfNonLocal
is KtSyntheticJavaPropertySymbol -> callableIdIfNonLocal
// Compiler will complain if there would be a new type in the hierarchy
is KtEnumEntrySymbol,
is KtLocalVariableSymbol,
is KtFunctionParameterSymbol,
is KtConstructorParameterSymbol,
is KtSetterParameterSymbol -> null
}
private val KtVariableLikeSymbol.canBeCalledByFqName: Boolean
get() = when (this) {
is KtKotlinPropertySymbol -> dispatchType == null && receiverType == null
@@ -318,9 +304,10 @@ private class SimpleFunctionInsertionHandler(
val element = context.file.findElementAt(startOffset) ?: return
addArguments(context, element)
context.commitDocument()
if (nameToImport != null && targetFile.importDirectives.none { it.importPath?.fqName == nameToImport }) {
addImportToFile(context.project, targetFile, nameToImport)
if (nameToImport != null) {
addCallableImportIfRequired(targetFile, nameToImport)
}
}
}
@@ -366,15 +353,15 @@ private class ShorteningVariableInsertionHandler(private val name: FqName) : Ins
}
}
private class SimpleVariableInsertionHandler(private val nameToImport: FqName) :
QuotedNamesAwareInsertionHandler(nameToImport.shortName()) {
private class SimpleVariableInsertionHandler(name: Name, private val nameToImport: FqName?) :
QuotedNamesAwareInsertionHandler(name) {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
super.handleInsert(context, item)
val targetFile = context.file as? KtFile ?: return
if (targetFile.importDirectives.none { it.importPath?.fqName == nameToImport }) {
addImportToFile(context.project, targetFile, nameToImport)
if (nameToImport != null) {
addCallableImportIfRequired(targetFile, nameToImport)
}
}
}
@@ -391,6 +378,56 @@ private open class QuotedNamesAwareInsertionHandler(private val name: Name) : In
}
}
private fun addCallableImportIfRequired(targetFile: KtFile, nameToImport: FqName) {
if (!alreadyHasImport(targetFile, nameToImport)) {
addImportToFile(targetFile.project, targetFile, nameToImport)
}
}
private fun alreadyHasImport(file: KtFile, nameToImport: FqName): Boolean {
if (file.importDirectives.any { it.importPath?.fqName == nameToImport }) return false
withAllowedResolve {
analyze(file) {
val scopes = file.getScopeContextForFile().scopes
if (!scopes.containsName(nameToImport.shortName())) return false
return scopes
.getCallableSymbols { it == nameToImport.shortName() }
.any {
it is KtVariableLikeSymbol && it.callableIdIfExists == nameToImport ||
it is KtFunctionSymbol && it.callableIdIfNonLocal == nameToImport
}
}
}
}
private val KtVariableLikeSymbol.callableIdIfExists: FqName?
get() = when (this) {
is KtJavaFieldSymbol -> callableIdIfNonLocal
is KtKotlinPropertySymbol -> callableIdIfNonLocal
is KtSyntheticJavaPropertySymbol -> callableIdIfNonLocal
// Compiler will complain if there would be a new type in the hierarchy
is KtEnumEntrySymbol,
is KtLocalVariableSymbol,
is KtFunctionParameterSymbol,
is KtConstructorParameterSymbol,
is KtSetterParameterSymbol -> null
}
private val KtVariableLikeSymbol.importableFqName: FqName?
get() = when (this) {
is KtKotlinPropertySymbol -> if (dispatchType == null) callableIdIfNonLocal else null
is KtEnumEntrySymbol,
is KtJavaFieldSymbol,
is KtSyntheticJavaPropertySymbol,
is KtLocalVariableSymbol,
is KtFunctionParameterSymbol,
is KtConstructorParameterSymbol,
is KtSetterParameterSymbol -> null
}
private object ShortNamesRenderer {
fun KtAnalysisSession.renderFunctionParameters(function: KtFunctionSymbol): String =
@@ -79,6 +79,11 @@ public class HighLevelBasicCompletionHandlerTestGenerated extends AbstractHighLe
runTest("idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt");
}
@TestMetadata("ExtensionPropertyFromSameFile.kt")
public void testExtensionPropertyFromSameFile() throws Exception {
runTest("idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt");
}
@TestMetadata("ExtensionReceiverTypeArg.kt")
public void testExtensionReceiverTypeArg() throws Exception {
runTest("idea/idea-completion/testData/handlers/basic/ExtensionReceiverTypeArg.kt");
@@ -79,6 +79,11 @@ public class PerformanceBasicCompletionHandlerTestGenerated extends AbstractPerf
runTest("idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt");
}
@TestMetadata("ExtensionPropertyFromSameFile.kt")
public void testExtensionPropertyFromSameFile() throws Exception {
runTest("idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt");
}
@TestMetadata("ExtensionReceiverTypeArg.kt")
public void testExtensionReceiverTypeArg() throws Exception {
runTest("idea/idea-completion/testData/handlers/basic/ExtensionReceiverTypeArg.kt");