diff --git a/idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt b/idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt new file mode 100644 index 00000000000..4218ccbb6a9 --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt @@ -0,0 +1,12 @@ +// FIR_COMPARISON +package test + +class AClass + +val AClass.ext get() = this + +fun usage(a: AClass) { + a.ex +} + +// ELEMENT: ext diff --git a/idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt.after b/idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt.after new file mode 100644 index 00000000000..6119876c25d --- /dev/null +++ b/idea/idea-completion/testData/handlers/basic/ExtensionPropertyFromSameFile.kt.after @@ -0,0 +1,12 @@ +// FIR_COMPARISON +package test + +class AClass + +val AClass.ext get() = this + +fun usage(a: AClass) { + a.ext +} + +// ELEMENT: ext diff --git a/idea/idea-completion/testData/handlers/multifile/ExtensionPropertyInSamePackageNoImport-1.kt b/idea/idea-completion/testData/handlers/multifile/ExtensionPropertyInSamePackageNoImport-1.kt new file mode 100644 index 00000000000..71a2c44193c --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/ExtensionPropertyInSamePackageNoImport-1.kt @@ -0,0 +1,6 @@ +// FIR_COMPARISON +package first + +fun foo() { + "".extensi +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/ExtensionPropertyInSamePackageNoImport-2.kt b/idea/idea-completion/testData/handlers/multifile/ExtensionPropertyInSamePackageNoImport-2.kt new file mode 100644 index 00000000000..253319db4fb --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/ExtensionPropertyInSamePackageNoImport-2.kt @@ -0,0 +1,4 @@ +package first + +val String.extensionProp: Int + get() = 1 \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/ExtensionPropertyInSamePackageNoImport.kt.after b/idea/idea-completion/testData/handlers/multifile/ExtensionPropertyInSamePackageNoImport.kt.after new file mode 100644 index 00000000000..41d8d41ad0a --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/ExtensionPropertyInSamePackageNoImport.kt.after @@ -0,0 +1,6 @@ +// FIR_COMPARISON +package first + +fun foo() { + "".extensionProp +} \ No newline at end of file diff --git a/idea/idea-completion/testData/handlers/multifile/TopLevelPropertyInSamePackageNoImport-1.kt b/idea/idea-completion/testData/handlers/multifile/TopLevelPropertyInSamePackageNoImport-1.kt new file mode 100644 index 00000000000..01bc8f1e782 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/TopLevelPropertyInSamePackageNoImport-1.kt @@ -0,0 +1,6 @@ +// FIR_COMPARISON +package some + +fun other() { + somePr +} diff --git a/idea/idea-completion/testData/handlers/multifile/TopLevelPropertyInSamePackageNoImport-2.kt b/idea/idea-completion/testData/handlers/multifile/TopLevelPropertyInSamePackageNoImport-2.kt new file mode 100644 index 00000000000..974a358db66 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/TopLevelPropertyInSamePackageNoImport-2.kt @@ -0,0 +1,4 @@ +// FIR_COMPARISON +package some + +val someProp: Int = 1 diff --git a/idea/idea-completion/testData/handlers/multifile/TopLevelPropertyInSamePackageNoImport.kt.after b/idea/idea-completion/testData/handlers/multifile/TopLevelPropertyInSamePackageNoImport.kt.after new file mode 100644 index 00000000000..bfdfd8c4503 --- /dev/null +++ b/idea/idea-completion/testData/handlers/multifile/TopLevelPropertyInSamePackageNoImport.kt.after @@ -0,0 +1,6 @@ +// FIR_COMPARISON +package some + +fun other() { + someProp +} diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java index 9761f702912..f5db064d7e0 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/BasicCompletionHandlerTestGenerated.java @@ -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"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt index 7fe6c35e821..769865ffa02 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.kt +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/CompletionMultifileHandlerTest.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() } diff --git a/idea/idea-fir-performance-tests/tests/org/jetbrains/kotlin/idea/perf/HighLevelPerformanceBasicCompletionHandlerTestGenerated.java b/idea/idea-fir-performance-tests/tests/org/jetbrains/kotlin/idea/perf/HighLevelPerformanceBasicCompletionHandlerTestGenerated.java index 51ca32271e5..e8503d44374 100644 --- a/idea/idea-fir-performance-tests/tests/org/jetbrains/kotlin/idea/perf/HighLevelPerformanceBasicCompletionHandlerTestGenerated.java +++ b/idea/idea-fir-performance-tests/tests/org/jetbrains/kotlin/idea/perf/HighLevelPerformanceBasicCompletionHandlerTestGenerated.java @@ -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"); diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirLookupElementFactory.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirLookupElementFactory.kt index 1f212cc65e6..a19d2a679fc 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirLookupElementFactory.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirLookupElementFactory.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 = diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/completion/test/handlers/HighLevelBasicCompletionHandlerTestGenerated.java b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/completion/test/handlers/HighLevelBasicCompletionHandlerTestGenerated.java index 574903f31f4..c0ca9112ec7 100644 --- a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/completion/test/handlers/HighLevelBasicCompletionHandlerTestGenerated.java +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/completion/test/handlers/HighLevelBasicCompletionHandlerTestGenerated.java @@ -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"); diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceBasicCompletionHandlerTestGenerated.java b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceBasicCompletionHandlerTestGenerated.java index 7529cb1d4ef..2e27918d096 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceBasicCompletionHandlerTestGenerated.java +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceBasicCompletionHandlerTestGenerated.java @@ -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");