diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index ea5e105f02d..e14add5ed0e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -296,7 +296,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration, } } - NamedArgumentCompletion.complete(position, collector, bindingContext) + NamedArgumentCompletion.complete(position, collector, expectedInfos) } private fun addNonImported(completionKind: CompletionKind) { diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt index 68ac2b8e3e2..cbffdbc423c 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt @@ -56,7 +56,7 @@ import org.jetbrains.kotlin.types.typeUtil.makeNotNullable import org.jetbrains.kotlin.types.typeUtil.makeNullable import org.jetbrains.kotlin.utils.addToStdlib.check import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList -import java.util.LinkedHashSet +import java.util.* enum class Tail { COMMA, @@ -78,6 +78,10 @@ interface ByTypeFilter { object All : ByTypeFilter { override fun matchingSubstitutor(descriptorType: FuzzyType) = TypeSubstitutor.EMPTY } + + object None : ByTypeFilter { + override fun matchingSubstitutor(descriptorType: FuzzyType) = null + } } class ByExpectedTypeFilter(val fuzzyType: FuzzyType) : ByTypeFilter { @@ -88,7 +92,8 @@ class ByExpectedTypeFilter(val fuzzyType: FuzzyType) : ByTypeFilter { override fun hashCode() = fuzzyType.hashCode() } -data class ExpectedInfo( +data /* for copy() */ +class ExpectedInfo( val filter: ByTypeFilter, val expectedName: String?, val tail: Tail?, @@ -109,8 +114,12 @@ data class ExpectedInfo( fun matchingSubstitutor(descriptorType: JetType): TypeSubstitutor? = matchingSubstitutor(FuzzyType(descriptorType, emptyList())) companion object { - fun createForArgument(type: JetType, name: String?, tail: Tail?, function: FunctionDescriptor, position: ArgumentPosition, itemOptions: ItemOptions = ItemOptions.DEFAULT): ExpectedInfo { - return ExpectedInfo(FuzzyType(type, function.typeParameters), name, tail, itemOptions, ArgumentAdditionalData(function, position)) + fun createForArgument(type: JetType, expectedName: String?, tail: Tail?, argumentData: ArgumentPositionData, itemOptions: ItemOptions = ItemOptions.DEFAULT): ExpectedInfo { + return ExpectedInfo(FuzzyType(type, argumentData.function.typeParameters), expectedName, tail, itemOptions, argumentData) + } + + fun createForNamedArgumentExpected(argumentData: ArgumentPositionData): ExpectedInfo { + return ExpectedInfo(ByTypeFilter.None, null, null/*TODO?*/, ItemOptions.DEFAULT, argumentData) } fun createForReturnValue(type: JetType?, callable: CallableDescriptor): ExpectedInfo { @@ -123,26 +132,18 @@ data class ExpectedInfo( val ExpectedInfo.fuzzyType: FuzzyType? get() = (this.filter as? ByExpectedTypeFilter)?.fuzzyType -data class ArgumentPosition(val argumentIndex: Int, val argumentName: Name?, val isFunctionLiteralArgument: Boolean) { - constructor(argumentIndex: Int, isFunctionLiteralArgument: Boolean = false) : this(argumentIndex, null, isFunctionLiteralArgument) - constructor(argumentIndex: Int, argumentName: Name?) : this(argumentIndex, argumentName, false) +sealed class ArgumentPositionData(val function: FunctionDescriptor) : ExpectedInfo.AdditionalData { + class Positional( + function: FunctionDescriptor, + val argumentIndex: Int, + val isFunctionLiteralArgument: Boolean, + val namedArgumentCandidates: Collection? + ) : ArgumentPositionData(function) + + class Named(function: FunctionDescriptor, val argumentName: Name) : ArgumentPositionData(function) } -class ArgumentAdditionalData(val function: FunctionDescriptor, val position: ArgumentPosition) : ExpectedInfo.AdditionalData { - override fun equals(other: Any?) - = other is ArgumentAdditionalData && function == other.function && position == other.position - - override fun hashCode() - = function.hashCode() -} - -class ReturnValueAdditionalData(val callable: CallableDescriptor) : ExpectedInfo.AdditionalData { - override fun equals(other: Any?) - = other is ReturnValueAdditionalData && callable == other.callable - - override fun hashCode() - = callable.hashCode() -} +class ReturnValueAdditionalData(val callable: CallableDescriptor) : ExpectedInfo.AdditionalData class ExpectedInfos( val bindingContext: BindingContext, @@ -193,7 +194,7 @@ class ExpectedInfos( val results = calculateForArgument(call, TypeUtils.NO_EXPECTED_TYPE, argument) fun makesSenseToUseOuterCallExpectedType(info: ExpectedInfo): Boolean { - val data = info.additionalData as ArgumentAdditionalData + val data = info.additionalData as ArgumentPositionData return info.fuzzyType != null && info.fuzzyType!!.freeParameters.isNotEmpty() && data.function.fuzzyReturnType()?.freeParameters?.isNotEmpty() ?: false @@ -211,7 +212,6 @@ class ExpectedInfos( .map { it.type } .toSet() .flatMap { calculateForArgument(call, it, argument) } - .toSet() } return results @@ -224,7 +224,6 @@ class ExpectedInfos( } val argumentName = argument.getArgumentName()?.asName val isFunctionLiteralArgument = argument is FunctionLiteralArgument - val argumentPosition = ArgumentPosition(argumentIndex, argumentName, isFunctionLiteralArgument) // leave only arguments before the current one val truncatedCall = object : DelegatingCall(call) { @@ -245,7 +244,7 @@ class ExpectedInfos( val callResolver = resolutionFacade.frontendService() val results: OverloadResolutionResults = callResolver.resolveFunctionCall(callResolutionContext) - val expectedInfos = LinkedHashSet() + val expectedInfos = ArrayList() for (candidate: ResolvedCall in results.getAllCandidates()!!) { val status = candidate.getStatus() if (status == ResolutionStatus.RECEIVER_TYPE_ERROR || status == ResolutionStatus.RECEIVER_PRESENCE_ERROR) continue @@ -260,15 +259,35 @@ class ExpectedInfos( if (!Visibilities.isVisible(thisReceiver, descriptor, resolutionScope.ownerDescriptor)) continue var argumentToParameter = call.mapArgumentsToParameters(descriptor) - var parameter = argumentToParameter[argument] ?: continue + var parameter = argumentToParameter[argument] //TODO: we can loose partially inferred substitution here but what to do? - if (parameter.type.containsError()) { + if (parameter != null && parameter.type.containsError()) { descriptor = descriptor.original - parameter = descriptor.valueParameters[parameter.index] + parameter = descriptor.valueParameters[parameter.index]!! argumentToParameter = call.mapArgumentsToParameters(descriptor) } + val argumentPositionData = if (argumentName != null) { + ArgumentPositionData.Named(descriptor, argumentName) + } + else { + val namedArgumentCandidates = if (!isFunctionLiteralArgument && descriptor.hasStableParameterNames()) { + val usedParameters = argumentToParameter.filter { it.key != argument }.map { it.value }.toSet() + descriptor.valueParameters.filter { it !in usedParameters } + } + else { + null + } + ArgumentPositionData.Positional(descriptor, argumentIndex, isFunctionLiteralArgument, namedArgumentCandidates) + } + + if (parameter == null) { + if (argumentPositionData !is ArgumentPositionData.Positional || argumentPositionData.namedArgumentCandidates == null) continue + expectedInfos.add(ExpectedInfo.createForNamedArgumentExpected(argumentPositionData)) + continue + } + val expectedName = if (descriptor.hasSynthesizedParameterNames()) null else parameter.getName().asString() val parameters = descriptor.valueParameters @@ -295,7 +314,7 @@ class ExpectedInfos( val alreadyHasStar = argument.getSpreadElement() != null - val varargElementType = parameter.getVarargElementType() + val varargElementType = parameter!!.getVarargElementType() if (varargElementType != null) { if (isFunctionLiteralArgument) continue @@ -305,28 +324,28 @@ class ExpectedInfos( tail if (!alreadyHasStar) { - expectedInfos.add(ExpectedInfo.createForArgument(varargElementType, expectedName?.unpluralize(), varargTail, descriptor, argumentPosition)) + expectedInfos.add(ExpectedInfo.createForArgument(varargElementType, expectedName?.unpluralize(), varargTail, argumentPositionData)) } val starOptions = if (!alreadyHasStar) ItemOptions.STAR_PREFIX else ItemOptions.DEFAULT - expectedInfos.add(ExpectedInfo.createForArgument(parameter.getType(), expectedName, varargTail, descriptor, argumentPosition, starOptions)) + expectedInfos.add(ExpectedInfo.createForArgument(parameter!!.getType(), expectedName, varargTail, argumentPositionData, starOptions)) } else { if (alreadyHasStar) continue val parameterType = if (useHeuristicSignatures) resolutionFacade.ideService(). - correctedParameterType(descriptor, parameter) ?: parameter.getType() + correctedParameterType(descriptor, parameter!!) ?: parameter!!.getType() else - parameter.getType() + parameter!!.getType() if (isFunctionLiteralArgument) { if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameterType)) { - expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, null, descriptor, argumentPosition)) + expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, null, argumentPositionData)) } } else { - expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, tail, descriptor, argumentPosition)) + expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, tail, argumentPositionData)) } } } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt index b292e979329..87421100999 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt @@ -26,24 +26,17 @@ import com.intellij.psi.filters.ClassFilter import com.intellij.psi.filters.OrFilter import com.intellij.psi.filters.position.ParentElementFilter import org.jetbrains.kotlin.core.FirstChildInParentFilter -import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.JetIcons import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler -import org.jetbrains.kotlin.idea.core.mapArgumentsToParameters -import org.jetbrains.kotlin.idea.references.JetReference -import org.jetbrains.kotlin.idea.references.mainReference -import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.JetCallElement import org.jetbrains.kotlin.psi.JetValueArgument import org.jetbrains.kotlin.psi.JetValueArgumentName -import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.render -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.calls.callUtil.getCall +import org.jetbrains.kotlin.types.JetType import java.util.* object NamedArgumentCompletion { @@ -73,41 +66,31 @@ object NamedArgumentCompletion { return false } - public fun complete(position: PsiElement, collector: LookupElementsCollector, bindingContext: BindingContext) { + public fun complete(position: PsiElement, collector: LookupElementsCollector, expectedInfos: Collection) { if (!positionFilter.isAcceptable(position, position)) return - val valueArgument = position.getStrictParentOfType()!! - - val callElement = valueArgument.getStrictParentOfType() ?: return - val callSimpleName = callElement.getCallNameExpression() ?: return - - val functionDescriptors = callSimpleName.mainReference.resolveToDescriptors(bindingContext) - .filterIsInstance() - - for (funDescriptor in functionDescriptors) { - if (!funDescriptor.hasStableParameterNames()) continue - val call = callElement.getCall(bindingContext) ?: continue - - var argumentToParameter = HashMap(call.mapArgumentsToParameters(funDescriptor)) - argumentToParameter.remove(valueArgument) - val usedParameters = argumentToParameter.values().toSet() - - for (parameter in funDescriptor.getValueParameters()) { - if (parameter !in usedParameters) { - val name = parameter.getName().asString() - val lookupElement = LookupElementBuilder.create(name) - .withPresentableText("$name =") - .withTailText(" ${DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(parameter.getType())}") - .withIcon(JetIcons.PARAMETER) - .withInsertHandler(NamedArgumentInsertHandler(parameter.getName())) - .assignPriority(ItemPriority.NAMED_PARAMETER) - collector.addElement(lookupElement) - } + val nameToParameterType = HashMap>() + for (expectedInfo in expectedInfos) { + val argumentData = expectedInfo.additionalData as? ArgumentPositionData.Positional ?: continue + argumentData.namedArgumentCandidates?.forEach { parameter -> + nameToParameterType.getOrPut(parameter.name) { HashSet() }.add(parameter.type) } } + + for ((name, types) in nameToParameterType) { + val typeText = types.singleOrNull()?.let { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it) } ?: "..." + val nameString = name.asString() + val lookupElement = LookupElementBuilder.create(nameString) + .withPresentableText("$nameString =") + .withTailText(" $typeText") + .withIcon(JetIcons.PARAMETER) + .withInsertHandler(NamedArgumentInsertHandler(name)) + .assignPriority(ItemPriority.NAMED_PARAMETER) + collector.addElement(lookupElement) + } } - private class NamedArgumentInsertHandler(val parameterName: Name) : InsertHandler { + private class NamedArgumentInsertHandler(private val parameterName: Name) : InsertHandler { override fun handleInsert(context: InsertionContext, item: LookupElement) { val editor = context.getEditor() val text = parameterName.render() diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/MultipleArgumentsItemProvider.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/MultipleArgumentsItemProvider.kt index 67ee38d6463..5022de1de30 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/MultipleArgumentsItemProvider.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/MultipleArgumentsItemProvider.kt @@ -23,7 +23,10 @@ import com.intellij.ui.LayeredIcon import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.idea.JetDescriptorIconProvider -import org.jetbrains.kotlin.idea.completion.* +import org.jetbrains.kotlin.idea.completion.ArgumentPositionData +import org.jetbrains.kotlin.idea.completion.ExpectedInfo +import org.jetbrains.kotlin.idea.completion.SmartCastCalculator +import org.jetbrains.kotlin.idea.completion.Tail import org.jetbrains.kotlin.idea.util.getVariableFromImplicitReceivers import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.psi.JetExpression @@ -44,7 +47,7 @@ class MultipleArgumentsItemProvider(val bindingContext: BindingContext, val added = HashSet() for (expectedInfo in expectedInfos) { - if (expectedInfo.additionalData is ArgumentAdditionalData && expectedInfo.additionalData.position == ArgumentPosition(0)) { + if (expectedInfo.additionalData is ArgumentPositionData.Positional && expectedInfo.additionalData.argumentIndex == 0) { val parameters = expectedInfo.additionalData.function.valueParameters if (parameters.size() > 1) { val variables = ArrayList() diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt index 45df742588f..0b7e68fc228 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt @@ -51,7 +51,7 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para override fun doComplete() { if (NamedArgumentCompletion.isOnlyNamedArgumentExpected(position)) { - NamedArgumentCompletion.complete(position, collector, bindingContext) + NamedArgumentCompletion.complete(position, collector, expectedInfos) return } diff --git a/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads.kt b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads.kt index fdfc904c969..1ca470ef68a 100644 --- a/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads.kt +++ b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads.kt @@ -10,11 +10,9 @@ fun other() { foo(n) } -// EXIST: { lookupString:"nFirst", itemText:"nFirst =" } // EXIST: nLocal - -// todo - should exist -// ABSENT: nClassParam -// ABSENT: nSecond -// ABSENT: nThird -// ABSENT: nClassField \ No newline at end of file +// EXIST: { lookupString:"nFirst", itemText:"nFirst =", tailText: " String" } +// EXIST: { itemText: "nClassParam =", tailText: " String" } +// EXIST: { itemText: "nClassField =", tailText: " String" } +// EXIST: { itemText: "nSecond =", tailText: " String?" } +// EXIST: { itemText: "nThird =", tailText: " Int" } diff --git a/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads2.kt b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads2.kt new file mode 100644 index 00000000000..ac185171844 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads2.kt @@ -0,0 +1,13 @@ +fun foo(p: Int, xxP1: String, xxP2: Int){} +fun foo(p: Int, xxP1: String, xxP2: Char){} +fun foo(p: Int, xxx: Any?){} +fun foo(p: String, xxy: String){} + +fun f() { + foo(1, xx) +} + +// EXIST: { itemText:"xxP1 =", tailText: " String" } +// EXIST: { itemText:"xxP2 =", tailText: " ..." } +// EXIST: { itemText:"xxx =", tailText: " Any?" } +// NOTHING_ELSE diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index 1cb8493cbe1..08dc413f2da 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -1485,6 +1485,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes doTest(fileName); } + @TestMetadata("NamedArgumentsFromOverloads2.kt") + public void testNamedArgumentsFromOverloads2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads2.kt"); + doTest(fileName); + } + @TestMetadata("NoAlreadyUsedParameters.kt") public void testNoAlreadyUsedParameters() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/NoAlreadyUsedParameters.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index 690c92a8909..1e35f2a0593 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -1485,6 +1485,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT doTest(fileName); } + @TestMetadata("NamedArgumentsFromOverloads2.kt") + public void testNamedArgumentsFromOverloads2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads2.kt"); + doTest(fileName); + } + @TestMetadata("NoAlreadyUsedParameters.kt") public void testNoAlreadyUsedParameters() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/NoAlreadyUsedParameters.kt");