diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/KotlinAddImportAction.kt b/idea/src/org/jetbrains/kotlin/idea/actions/KotlinAddImportAction.kt index 6a1f728a7a7..17fbcb21e16 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/KotlinAddImportAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/KotlinAddImportAction.kt @@ -59,8 +59,8 @@ internal fun createSingleImportAction( element: KtElement, fqNames: Collection ): KotlinAddImportAction { - val file = element.getContainingKtFile() - val prioritizer = Prioritizer(element.getContainingKtFile()) + val file = element.containingKtFile + val prioritizer = Prioritizer(element.containingKtFile) val variants = fqNames .map { fqName -> val sameFqNameDescriptors = file.resolveImportReference(fqName) @@ -73,6 +73,29 @@ internal fun createSingleImportAction( return KotlinAddImportAction(project, editor, element, variants) } +internal fun createSingleImportActionForConstructor( + project: Project, + editor: Editor, + element: KtElement, + fqNames: Collection +): KotlinAddImportAction { + val file = element.containingKtFile + val prioritizer = Prioritizer(element.containingKtFile) + val variants = fqNames + .map { fqName -> + val sameFqNameDescriptors = file.resolveImportReference(fqName.parent()) + .filterIsInstance() + .flatMap { it.constructors } + + val priority = sameFqNameDescriptors.map { prioritizer.priority(it) }.min() ?: return@map null + Prioritizer.VariantWithPriority(SingleImportVariant(fqName, sameFqNameDescriptors), priority) + } + .filterNotNull() + .sortedBy { it.priority } + .map { it.variant } + return KotlinAddImportAction(project, editor, element, variants) +} + internal fun createGroupedImportsAction( project: Project, editor: Editor, diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt index 99fb83fe805..eb5ba415fdf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt @@ -40,6 +40,7 @@ import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.actions.KotlinAddImportAction import org.jetbrains.kotlin.idea.actions.createGroupedImportsAction import org.jetbrains.kotlin.idea.actions.createSingleImportAction +import org.jetbrains.kotlin.idea.actions.createSingleImportActionForConstructor import org.jetbrains.kotlin.idea.analysis.analyzeInContext import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade @@ -238,17 +239,9 @@ internal abstract class OrdinaryImportFixBase(expression: T, f if (expression is KtSimpleNameExpression) { if (!expression.isImportDirectiveExpression() && !isSelectorInQualified(expression)) { - val filterByCallType = { descriptor: DeclarationDescriptor -> callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor) } + val filterByCallType = callTypeAndReceiver.toFilter() - when (TargetPlatformDetector.getPlatform(expression.getContainingKtFile())) { - JsPlatform -> indicesHelper - // Enum entries should be contributes with members import fix - .getKotlinClasses({ it == name }, - psiFilter = { ktDeclaration -> ktDeclaration !is KtEnumEntry }, - kindFilter = { kind -> kind != ClassKind.ENUM_ENTRY }) - .filterTo(result, filterByCallType) - JvmPlatform -> indicesHelper.getJvmClassesByName(name).filterTo(result, filterByCallType) - } + indicesHelper.getClassesByName(expression, name).filterTo(result, filterByCallType) indicesHelper.getTopLevelTypeAliases { it == name }.filterTo(result, filterByCallType) @@ -272,6 +265,31 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix } } +internal class ImportConstructorReferenceFix(expression: KtSimpleNameExpression) : ImportFixBase(expression, MyFactory) { + override fun getCallTypeAndReceiver() = element?.let { + CallTypeAndReceiver.detect(it) as? CallTypeAndReceiver.CALLABLE_REFERENCE + } + + override fun fillCandidates(name: String, callTypeAndReceiver: CallTypeAndReceiver<*, *>, bindingContext: BindingContext, indicesHelper: KotlinIndicesHelper): List { + val expression = element ?: return emptyList() + + val filterByCallType = callTypeAndReceiver.toFilter() + // TODO Type-aliases + return indicesHelper.getClassesByName(expression, name).asSequence().map { it.constructors }.flatten().filter(filterByCallType).toList() + } + + override fun createAction(project: Project, editor: Editor, element: KtExpression): KotlinAddImportAction { + return createSingleImportActionForConstructor(project, editor, element, suggestions) + } + + override val importNames = element?.mainReference?.resolvesByNames ?: emptyList() + + companion object MyFactory : Factory() { + override fun createAction(diagnostic: Diagnostic) = + (diagnostic.psiElement as? KtSimpleNameExpression)?.let(::ImportConstructorReferenceFix) + } +} + internal class InvokeImportFix(expression: KtExpression) : OrdinaryImportFixBase(expression, MyFactory) { override val importNames = OperatorNameConventions.INVOKE.singletonList() @@ -279,7 +297,7 @@ internal class InvokeImportFix(expression: KtExpression) : OrdinaryImportFixBase companion object MyFactory : Factory() { override fun createAction(diagnostic: Diagnostic) = - (diagnostic.psiElement as? KtExpression)?.let { InvokeImportFix(it) } + (diagnostic.psiElement as? KtExpression)?.let(::InvokeImportFix) } } @@ -406,7 +424,7 @@ internal class ImportMemberFix(expression: KtSimpleNameExpression) : ImportFixBa val result = ArrayList() - val filterByCallType = { descriptor: DeclarationDescriptor -> callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor) } + val filterByCallType = callTypeAndReceiver.toFilter() indicesHelper.getKotlinEnumsByName(name).filterTo(result, filterByCallType) @@ -552,4 +570,17 @@ object ImportForMissingOperatorFactory : KotlinSingleIntentionActionFactory() { return null } -} \ No newline at end of file +} + + +private fun KotlinIndicesHelper.getClassesByName(expressionForPlatform: KtExpression, name: String) = + when (TargetPlatformDetector.getPlatform(expressionForPlatform.containingKtFile)) { + JsPlatform -> getKotlinClasses({ it == name }, + // Enum entries should be contributes with members import fix + psiFilter = { ktDeclaration -> ktDeclaration !is KtEnumEntry }, + kindFilter = { kind -> kind != ClassKind.ENUM_ENTRY }) + JvmPlatform -> getJvmClassesByName(name) + else -> emptyList() + } + +private fun CallTypeAndReceiver<*, *>.toFilter() = { descriptor: DeclarationDescriptor -> this.callType.descriptorKindFilter.accepts(descriptor) } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index eef407ecc5d..6fccc62ec84 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -140,6 +140,7 @@ class QuickFixRegistrar : QuickFixContributor { UNRESOLVED_REFERENCE.registerFactory(ImportMemberFix) UNRESOLVED_REFERENCE.registerFactory(ImportFix) + UNRESOLVED_REFERENCE.registerFactory(ImportConstructorReferenceFix) TOO_MANY_ARGUMENTS.registerFactory(ImportForMismatchingArgumentsFix) NO_VALUE_FOR_PARAMETER.registerFactory(ImportForMismatchingArgumentsFix) diff --git a/idea/testData/quickfix/autoImports/constructorReference.after.kt b/idea/testData/quickfix/autoImports/constructorReference.after.kt new file mode 100644 index 00000000000..f7ed694c722 --- /dev/null +++ b/idea/testData/quickfix/autoImports/constructorReference.after.kt @@ -0,0 +1,10 @@ +// "Import" "true" +// ERROR: Unresolved reference: Some +package p1 + +import p2.Some +import p2.receiveSomeCtor + +fun a() { + receiveSomeCtor(::Some) +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/constructorReference.before.Dependency.kt b/idea/testData/quickfix/autoImports/constructorReference.before.Dependency.kt new file mode 100644 index 00000000000..665522cd444 --- /dev/null +++ b/idea/testData/quickfix/autoImports/constructorReference.before.Dependency.kt @@ -0,0 +1,10 @@ +package p2 + +class Some { + +} + + +fun receiveSomeCtor(ctor: () -> Some) { + +} diff --git a/idea/testData/quickfix/autoImports/constructorReference.before.Main.kt b/idea/testData/quickfix/autoImports/constructorReference.before.Main.kt new file mode 100644 index 00000000000..2179de50453 --- /dev/null +++ b/idea/testData/quickfix/autoImports/constructorReference.before.Main.kt @@ -0,0 +1,9 @@ +// "Import" "true" +// ERROR: Unresolved reference: Some +package p1 + +import p2.receiveSomeCtor + +fun a() { + receiveSomeCtor(::Some) +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.after.kt b/idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.after.kt new file mode 100644 index 00000000000..90b76272ff9 --- /dev/null +++ b/idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.after.kt @@ -0,0 +1,14 @@ +// "Import" "false" +// ERROR: Unresolved reference: Some +// ACTION: Convert reference to lambda +// ACTION: Convert to expression body +// ACTION: Create function 'Some' +// ACTION: Rename reference + +package p1 + +import p2.receiveSomeCtor + +fun some() { + receiveSomeCtor(::Some) +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.before.Dependency.kt b/idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.before.Dependency.kt new file mode 100644 index 00000000000..29406180a86 --- /dev/null +++ b/idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.before.Dependency.kt @@ -0,0 +1,7 @@ +package p2 + +interface Some + +fun receiveSomeCtor(ctor: () -> Some) { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.before.Main.kt b/idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.before.Main.kt new file mode 100644 index 00000000000..90b76272ff9 --- /dev/null +++ b/idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.before.Main.kt @@ -0,0 +1,14 @@ +// "Import" "false" +// ERROR: Unresolved reference: Some +// ACTION: Convert reference to lambda +// ACTION: Convert to expression body +// ACTION: Create function 'Some' +// ACTION: Rename reference + +package p1 + +import p2.receiveSomeCtor + +fun some() { + receiveSomeCtor(::Some) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index 2c27836326d..9995b7d1db8 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -96,6 +96,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes doTestWithExtraFile(fileName); } + @TestMetadata("constructorReference.before.Main.kt") + public void testConstructorReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/constructorReference.before.Main.kt"); + doTestWithExtraFile(fileName); + } + @TestMetadata("delegateExtensionBoth.test") public void testDelegateExtensionBoth() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/delegateExtensionBoth.test"); @@ -474,6 +480,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes doTestWithExtraFile(fileName); } + @TestMetadata("noImportInterfaceRefAsConstructor.before.Main.kt") + public void testNoImportInterfaceRefAsConstructor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/noImportInterfaceRefAsConstructor.before.Main.kt"); + doTestWithExtraFile(fileName); + } + @TestMetadata("noImportsForClassInExcludedPackage.before.Main.kt") public void testNoImportsForClassInExcludedPackage() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/noImportsForClassInExcludedPackage.before.Main.kt");