From 67cfd9d51691de8302653211d2f6cc03fc89c8a6 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Thu, 7 May 2015 19:48:52 +0300 Subject: [PATCH] KT-4909 Smart completion does not work for named arguments KT-7668 Named argument completion does not work after vararg #KT-4909 Fixed #KT-7668 Fixed --- .../kotlin/codegen/intrinsics/Concat.kt | 2 +- .../src/org/jetbrains/kotlin/psi/Call.java | 2 +- .../kotlin/psi/JetFunctionLiteralArgument.kt | 8 +- .../{ValueArgument.java => ValueArgument.kt} | 32 ++-- .../kotlin/resolve/calls/CallCompleter.kt | 4 +- .../kotlin/resolve/calls/CallTransformer.java | 2 +- .../ValueArgumentsToParametersMapper.java | 4 +- .../kotlin/resolve/calls/util/CallMaker.java | 4 +- .../resolve/calls/util/DelegatingCall.java | 2 +- .../ControlStructureTypingUtils.java | 2 +- .../idea/completion/CompletionSession.kt | 51 ++++-- .../kotlin/idea/completion/ExpectedInfos.kt | 164 +++++++++--------- .../idea/completion/HeuristicSignatures.kt | 31 ++-- .../completion/NamedParametersCompletion.kt | 20 ++- .../smart/MultipleArgumentsItemProvider.kt | 32 ++-- .../idea/completion/smart/NameSimilarity.kt | 2 +- .../idea/completion/smart/SmartCompletion.kt | 2 +- .../smart/TypesWithContainsDetector.kt | 2 +- .../kotlin/idea/completion/smart/Utils.kt | 2 +- .../common/namedParameters/AfterVararg.kt | 7 + .../testData/handlers/smart/AfterVararg.kt | 7 + .../handlers/smart/AfterVararg.kt.after | 7 + .../testData/handlers/smart/NamedArgument1.kt | 7 + .../handlers/smart/NamedArgument1.kt.after | 7 + .../testData/handlers/smart/NamedArgument2.kt | 7 + .../handlers/smart/NamedArgument2.kt.after | 7 + .../testData/handlers/smart/NamedArgument3.kt | 7 + .../handlers/smart/NamedArgument3.kt.after | 7 + .../handlers/smart/NamedArgumentVararg1.kt | 7 + .../smart/NamedArgumentVararg1.kt.after | 7 + .../handlers/smart/NamedArgumentVararg2.kt | 7 + .../smart/NamedArgumentVararg2.kt.after | 7 + .../handlers/smart/NamedArgumentVararg3.kt | 7 + .../smart/NamedArgumentVararg3.kt.after | 7 + .../testData/handlers/smart/SecondVararg.kt | 7 + .../handlers/smart/SecondVararg.kt.after | 7 + .../testData/smart/NamedArgument.kt | 8 + .../testData/smart/SecondVararg.kt | 9 + .../test/JSBasicCompletionTestGenerated.java | 6 + .../test/JvmBasicCompletionTestGenerated.java | 6 + .../test/JvmSmartCompletionTestGenerated.java | 12 ++ .../SmartCompletionHandlerTestGenerated.java | 48 +++++ .../org/jetbrains/kotlin/idea/core/Utils.kt | 65 +++++++ .../idea/core/quickfix/QuickFixUtil.java | 38 ---- .../idea/quickfix/AddNameToArgumentFix.java | 10 +- 45 files changed, 474 insertions(+), 215 deletions(-) rename compiler/frontend/src/org/jetbrains/kotlin/psi/{ValueArgument.java => ValueArgument.kt} (58%) create mode 100644 idea/idea-completion/testData/basic/common/namedParameters/AfterVararg.kt create mode 100644 idea/idea-completion/testData/handlers/smart/AfterVararg.kt create mode 100644 idea/idea-completion/testData/handlers/smart/AfterVararg.kt.after create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgument1.kt create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgument1.kt.after create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgument2.kt create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgument2.kt.after create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgument3.kt create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgument3.kt.after create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgumentVararg1.kt create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgumentVararg1.kt.after create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgumentVararg2.kt create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgumentVararg2.kt.after create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgumentVararg3.kt create mode 100644 idea/idea-completion/testData/handlers/smart/NamedArgumentVararg3.kt.after create mode 100644 idea/idea-completion/testData/handlers/smart/SecondVararg.kt create mode 100644 idea/idea-completion/testData/handlers/smart/SecondVararg.kt.after create mode 100644 idea/idea-completion/testData/smart/NamedArgument.kt create mode 100644 idea/idea-completion/testData/smart/SecondVararg.kt create mode 100644 idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Concat.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Concat.kt index 2a075700229..2ad3cd8ecad 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Concat.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Concat.kt @@ -69,7 +69,7 @@ public class Concat : IntrinsicMethod() { codegen: ExpressionCodegen ): StackValue { return StackValue.operation(returnType) { - val arguments = resolvedCall.getCall().getValueArguments().map { it.getArgumentExpression() } + val arguments = resolvedCall.getCall().getValueArguments().map { it.getArgumentExpression()!! } val actualType = generateImpl( codegen, it, returnType, resolvedCall.getCall().getCallElement(), diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/Call.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/Call.java index 82fbba7dec2..4a824adc219 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/Call.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/Call.java @@ -48,7 +48,7 @@ public interface Call { @ReadOnly @NotNull - List getFunctionLiteralArguments(); + List getFunctionLiteralArguments(); @ReadOnly @NotNull diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetFunctionLiteralArgument.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetFunctionLiteralArgument.kt index d3633e3b7e2..0248deb1d9b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetFunctionLiteralArgument.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetFunctionLiteralArgument.kt @@ -19,14 +19,14 @@ package org.jetbrains.kotlin.psi import com.intellij.lang.ASTNode import com.intellij.psi.PsiWhiteSpace -public class JetFunctionLiteralArgument(node: ASTNode) : JetValueArgument(node) { +public class JetFunctionLiteralArgument(node: ASTNode) : JetValueArgument(node), FunctionLiteralArgument { private fun assertFL() = throw AssertionError("Function literal argument doesn't contain function literal expression: " + - "${super.getArgumentExpression()?.getText()} (it should be guaranteed by parser)") + "${super.getArgumentExpression()?.getText()} (it should be guaranteed by parser)") - override fun getArgumentExpression() = super.getArgumentExpression() ?: assertFL() + override fun getArgumentExpression() = super.getArgumentExpression() ?: assertFL() - public fun getFunctionLiteral(): JetFunctionLiteralExpression = unpackFunctionLiteral(getArgumentExpression()) + override fun getFunctionLiteral(): JetFunctionLiteralExpression = unpackFunctionLiteral(getArgumentExpression()) private fun unpackFunctionLiteral(expression: JetExpression?): JetFunctionLiteralExpression = when (expression) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/ValueArgument.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/ValueArgument.kt similarity index 58% rename from compiler/frontend/src/org/jetbrains/kotlin/psi/ValueArgument.java rename to compiler/frontend/src/org/jetbrains/kotlin/psi/ValueArgument.kt index 2a5008fdf4b..6c9baee81a2 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/ValueArgument.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/ValueArgument.kt @@ -14,29 +14,29 @@ * limitations under the License. */ -package org.jetbrains.kotlin.psi; +package org.jetbrains.kotlin.psi -import com.intellij.psi.impl.source.tree.LeafPsiElement; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import com.intellij.psi.impl.source.tree.LeafPsiElement -public interface ValueArgument { - @Nullable - @IfNotParsed - JetExpression getArgumentExpression(); +public trait ValueArgument { + IfNotParsed + public fun getArgumentExpression(): JetExpression? - @Nullable - JetValueArgumentName getArgumentName(); + public fun getArgumentName(): JetValueArgumentName? - boolean isNamed(); + public fun isNamed(): Boolean - @NotNull - JetElement asElement(); + public fun asElement(): JetElement /* The '*' in something like foo(*arr) i.e. pass an array as a number of vararg arguments */ - @Nullable - LeafPsiElement getSpreadElement(); + public fun getSpreadElement(): LeafPsiElement? /* The argument is placed externally to call element, e.g. in 'when' condition with subject: 'when (a) { in c -> }' */ - boolean isExternal(); + public fun isExternal(): Boolean +} + +public trait FunctionLiteralArgument : ValueArgument { + public fun getFunctionLiteral(): JetFunctionLiteralExpression + + override fun getArgumentExpression(): JetExpression } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt index 906e8172e3a..3fa1966c744 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt @@ -229,11 +229,11 @@ public class CallCompleter( ) { if (valueArgument.isExternal()) return - val expression = valueArgument.getArgumentExpression() + val expression = valueArgument.getArgumentExpression() ?: return val deparenthesized = ArgumentTypeResolver.getLastElementDeparenthesized(expression, context) if (deparenthesized == null) return - val recordedType = expression?.let { context.trace.getType(it) } + val recordedType = expression.let { context.trace.getType(it) } var updatedType: JetType? = recordedType val results = completeCallForArgument(deparenthesized, context) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallTransformer.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallTransformer.java index c8c83a5f27a..c30606d5a0c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallTransformer.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallTransformer.java @@ -164,7 +164,7 @@ public class CallTransformer { @NotNull @Override - public List getFunctionLiteralArguments() { + public List getFunctionLiteralArguments() { return Collections.emptyList(); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java index 790ac7a86de..645c6025e0f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java @@ -213,9 +213,9 @@ import static org.jetbrains.kotlin.resolve.calls.ValueArgumentsToParametersMappe D candidate = candidateCall.getCandidateDescriptor(); List valueParameters = candidate.getValueParameters(); - List functionLiteralArguments = call.getFunctionLiteralArguments(); + List functionLiteralArguments = call.getFunctionLiteralArguments(); if (!functionLiteralArguments.isEmpty()) { - JetFunctionLiteralArgument functionLiteralArgument = functionLiteralArguments.get(0); + FunctionLiteralArgument functionLiteralArgument = functionLiteralArguments.get(0); JetExpression possiblyLabeledFunctionLiteral = functionLiteralArgument.getArgumentExpression(); if (valueParameters.isEmpty()) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/CallMaker.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/CallMaker.java index 2714dcece44..6f1d45d31ed 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/CallMaker.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/CallMaker.java @@ -162,7 +162,7 @@ public class CallMaker { @NotNull @Override - public List getFunctionLiteralArguments() { + public List getFunctionLiteralArguments() { return Collections.emptyList(); } @NotNull @@ -301,7 +301,7 @@ public class CallMaker { @Override @NotNull - public List getFunctionLiteralArguments() { + public List getFunctionLiteralArguments() { return callElement.getFunctionLiteralArguments(); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/DelegatingCall.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/DelegatingCall.java index dcd78d696ad..7f8cf7d609f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/DelegatingCall.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/DelegatingCall.java @@ -72,7 +72,7 @@ public class DelegatingCall implements Call { @Override @NotNull - public List getFunctionLiteralArguments() { + public List getFunctionLiteralArguments() { return delegate.getFunctionLiteralArguments(); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingUtils.java index e77814534ff..b48bc668227 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingUtils.java @@ -214,7 +214,7 @@ public class ControlStructureTypingUtils { @NotNull @Override - public List getFunctionLiteralArguments() { + public List getFunctionLiteralArguments() { return Collections.emptyList(); } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt index f013a2ad90e..032638ca5ac 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt @@ -23,6 +23,7 @@ import com.intellij.patterns.ElementPattern import com.intellij.patterns.StandardPatterns import com.intellij.psi.PsiCompiledElement import com.intellij.psi.PsiElement +import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.search.DelegatingGlobalSearchScope import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil @@ -50,6 +51,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo import org.jetbrains.kotlin.resolve.calls.callUtil.getCall import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastUtils +import org.jetbrains.kotlin.resolve.calls.util.DelegatingCall import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter @@ -400,21 +402,7 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para if (expression != null) { val mapper = ToFromOriginalFileMapper(parameters.getOriginalFile() as JetFile, position.getContainingFile() as JetFile, parameters.getOffset()) - // special completion for outside parenthesis lambda argument - if (reference != null) { - val receiverData = ReferenceVariantsHelper.getExplicitReceiverData(reference.expression) - if (receiverData != null && receiverData.second == CallType.INFIX) { - val call = receiverData.first.getCall(bindingContext) - if (call != null && call.getFunctionLiteralArguments().isEmpty()) { - val argumentIndex = call.getValueArguments().size() - val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, moduleDescriptor, true) - .calculateForArgument(call, argumentIndex, true) - if (expectedInfos != null) { - collector.addElements(LambdaItems.collect(expectedInfos)) - } - } - } - } + addFunctionLiteralArgumentCompletions() val completion = SmartCompletion(expression, resolutionFacade, moduleDescriptor, bindingContext, { isVisibleDescriptor(it) }, inDescriptor, prefixMatcher, originalSearchScope, @@ -448,6 +436,39 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para } } + // special completion for outside parenthesis lambda argument + private fun addFunctionLiteralArgumentCompletions() { + if (reference != null) { + val receiverData = ReferenceVariantsHelper.getExplicitReceiverData(reference.expression) + if (receiverData != null && receiverData.second == CallType.INFIX) { + val call = receiverData.first.getCall(bindingContext) + if (call != null && call.getFunctionLiteralArguments().isEmpty()) { + val dummyArgument = object : FunctionLiteralArgument { + override fun getFunctionLiteral() = throw UnsupportedOperationException() + override fun getArgumentExpression() = throw UnsupportedOperationException() + override fun getArgumentName(): JetValueArgumentName? = null + override fun isNamed() = false + override fun asElement() = throw UnsupportedOperationException() + override fun getSpreadElement(): LeafPsiElement? = null + override fun isExternal() = false + } + val dummyArguments = call!!.getValueArguments() + listOf(dummyArgument) + val dummyCall = object : DelegatingCall(call) { + override fun getValueArguments() = dummyArguments + override fun getFunctionLiteralArguments() = listOf(dummyArgument) + override fun getValueArgumentList() = throw UnsupportedOperationException() + } + + val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, moduleDescriptor, true) + .calculateForArgument(dummyCall, dummyArgument) + if (expectedInfos != null) { + collector.addElements(LambdaItems.collect(expectedInfos)) + } + } + } + } + } + private fun processNonImported(processor: (DeclarationDescriptor) -> Unit) { getTopLevelExtensions().forEach(processor) 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 41577b956b2..a2fdc5deda7 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 @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.di.InjectorForMacros import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.completion.smart.toList +import org.jetbrains.kotlin.idea.core.mapArgumentsToParameters import org.jetbrains.kotlin.idea.util.makeNotNullable import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.psi.* @@ -29,6 +30,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DelegatingBindingTrace import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo import org.jetbrains.kotlin.resolve.bindingContextUtil.getTargetFunctionDescriptor +import org.jetbrains.kotlin.resolve.calls.callUtil.getCall import org.jetbrains.kotlin.resolve.calls.callUtil.noErrorsInValueArguments import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker import org.jetbrains.kotlin.resolve.calls.checkers.CompositeChecker @@ -62,18 +64,24 @@ data class ItemOptions(val starPrefix: Boolean) { } } -open data class ExpectedInfo(val type: JetType, val name: String?, val tail: Tail?, val itemOptions: ItemOptions = ItemOptions.DEFAULT) +open data class ExpectedInfo(val type: JetType, val expectedName: String?, val tail: Tail?, val itemOptions: ItemOptions = ItemOptions.DEFAULT) -class PositionalArgumentExpectedInfo(type: JetType, name: String?, tail: Tail?, val function: FunctionDescriptor, val parameterIndex: Int, itemOptions: ItemOptions = ItemOptions.DEFAULT) +data class ArgumentPosition(val argumentIndex: Int, val argumentName: String?, val isFunctionLiteralArgument: Boolean) { + constructor(argumentIndex: Int, isFunctionLiteralArgument: Boolean = false) : this(argumentIndex, null, isFunctionLiteralArgument) + constructor(argumentIndex: Int, argumentName: String?) : this(argumentIndex, argumentName, false) +} + +class ArgumentExpectedInfo(type: JetType, name: String?, tail: Tail?, val function: FunctionDescriptor, val position: ArgumentPosition, itemOptions: ItemOptions = ItemOptions.DEFAULT) : ExpectedInfo(type, name, tail, itemOptions) { override fun equals(other: Any?) - = other is PositionalArgumentExpectedInfo && super.equals(other) && function == other.function && parameterIndex == other.parameterIndex + = other is ArgumentExpectedInfo && super.equals(other) && function == other.function && position == other.position override fun hashCode() = function.hashCode() } + class ExpectedInfos( val bindingContext: BindingContext, val resolutionFacade: ResolutionFacade, @@ -97,67 +105,42 @@ class ExpectedInfos( private fun calculateForArgument(expressionWithType: JetExpression): Collection? { val argument = expressionWithType.getParent() as? JetValueArgument ?: return null - if (argument.isNamed()) return null //TODO - support named arguments (also do not forget to check for presence of named arguments before) val argumentList = argument.getParent() as? JetValueArgumentList ?: return null - val argumentIndex = argumentList.getArguments().indexOf(argument) val callElement = argumentList.getParent() as? JetCallElement ?: return null - return calculateForArgument(callElement, argumentIndex, false) + return calculateForArgument(callElement, argument) } private fun calculateForFunctionLiteralArgument(expressionWithType: JetExpression): Collection? { val functionLiteralArgument = expressionWithType.getParent() as? JetFunctionLiteralArgument - val callExpression = functionLiteralArgument?.getParent() as? JetCallExpression - if (callExpression != null) { - if (callExpression.getFunctionLiteralArguments().firstOrNull()?.getArgumentExpression() == expressionWithType) { - return calculateForArgument(callExpression, callExpression.getValueArguments().size() - 1, true) - } - } - return null + val callExpression = functionLiteralArgument?.getParent() as? JetCallExpression ?: return null + val literalArgument = callExpression.getFunctionLiteralArguments().firstOrNull() ?: return null + if (literalArgument.getArgumentExpression() != expressionWithType) return null + return calculateForArgument(callExpression, literalArgument) } - private fun calculateForArgument(callElement: JetCallElement, argumentIndex: Int, isFunctionLiteralArgument: Boolean): Collection? { - val parent = callElement.getParent() - val receiver: ReceiverValue - val callOperationNode: ASTNode? - if (parent is JetQualifiedExpression && callElement == parent.getSelectorExpression()) { - val receiverExpression = parent.getReceiverExpression() - val expressionType = bindingContext.getType(receiverExpression) - val qualifier = bindingContext[BindingContext.QUALIFIER, receiverExpression] - if (expressionType != null) { - receiver = ExpressionReceiver(receiverExpression, expressionType) - callOperationNode = parent.getOperationTokenNode() - } - else if (qualifier != null) { - receiver = qualifier - callOperationNode = null - } - else { - return null - } - } - else { - receiver = ReceiverValue.NO_RECEIVER - callOperationNode = null - } - - val call = CallMaker.makeCall(receiver, callOperationNode, callElement) - return calculateForArgument(call, argumentIndex, isFunctionLiteralArgument) + private fun calculateForArgument(callElement: JetCallElement, argument: ValueArgument): Collection? { + val call = callElement.getCall(bindingContext) ?: return null + return calculateForArgument(call, argument) } - public fun calculateForArgument(call: Call, argumentIndex: Int, isFunctionLiteralArgument: Boolean): Collection? { + public fun calculateForArgument(call: Call, argument: ValueArgument): Collection? { + val argumentIndex = call.getValueArguments().indexOf(argument) + assert(argumentIndex >= 0) + val argumentName = argument.getArgumentName()?.getReferenceExpression()?.getReferencedName() + val isFunctionLiteralArgument = argument is FunctionLiteralArgument + val argumentPosition = ArgumentPosition(argumentIndex, argumentName, isFunctionLiteralArgument) + val callElement = call.getCallElement() val calleeExpression = call.getCalleeExpression() - val truncatedCall = if (!isFunctionLiteralArgument) { // leave only arguments before the current one - object : DelegatingCall(call) { - override fun getValueArguments() = super.getValueArguments().subList(0, argumentIndex) - override fun getValueArgumentList() = null - } - } - else { - call - } + // leave only arguments before the current one + val truncatedCall = object : DelegatingCall(call) { + val arguments = call.getValueArguments().subList(0, argumentIndex) + override fun getValueArguments() = arguments + override fun getFunctionLiteralArguments() = emptyList() + override fun getValueArgumentList() = null + } val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, calleeExpression] ?: return null //TODO: discuss it val expectedType = (callElement as? JetExpression)?.let { bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, it] } ?: TypeUtils.NO_EXPECTED_TYPE @@ -185,36 +168,30 @@ class ExpectedInfos( val parameters = descriptor.getValueParameters() if (parameters.isEmpty()) continue - val parameterIndex = if (isFunctionLiteralArgument) { - parameters.lastIndex - } - else { - val varArgIndex = parameters.indexOfFirst { it.getVarargElementType() != null } - if (varArgIndex < 0) { - if (parameters.size() <= argumentIndex) continue - argumentIndex - } - else { - if (argumentIndex < varArgIndex) argumentIndex else varArgIndex - } - } + val argumentToParameter = call.mapArgumentsToParameters(descriptor) + val parameter = argumentToParameter[argument] ?: continue val thisReceiver = ExpressionTypingUtils.normalizeReceiverValueForVisibility(candidate.getDispatchReceiver(), bindingContext) if (!Visibilities.isVisible(thisReceiver, descriptor, resolutionScope.getContainingDeclaration())) continue - val parameter = parameters[parameterIndex] - val varargElementType = parameter.getVarargElementType() - val expectedName = if (descriptor.hasSynthesizedParameterNames()) null else parameter.getName().asString() + val varargElementType = parameter.getVarargElementType() if (varargElementType != null) { if (isFunctionLiteralArgument) continue - expectedInfos.add(PositionalArgumentExpectedInfo(varargElementType, expectedName?.fromPlural(), null, descriptor, parameterIndex)) + if (argumentName == null) { + expectedInfos.add(ArgumentExpectedInfo(varargElementType, expectedName?.fromPlural(), null, descriptor, argumentPosition)) - if (argumentIndex == parameterIndex) { - val tail = if (parameterIndex == parameters.lastIndex) Tail.RPARENTH else null - expectedInfos.add(PositionalArgumentExpectedInfo(parameter.getType(), expectedName, tail, descriptor, parameterIndex, ItemOptions.STAR_PREFIX)) + if (argumentIndex == parameters.indexOf(parameter)) { + val tail = if (parameter == parameters.last()) Tail.RPARENTH else null + expectedInfos.add(ArgumentExpectedInfo(parameter.getType(), expectedName, tail, descriptor, argumentPosition, ItemOptions.STAR_PREFIX)) + } + } + else { + val tail = namedArgumentTail(argumentToParameter, argumentName, descriptor) + expectedInfos.add(ArgumentExpectedInfo(varargElementType, expectedName?.fromPlural(), tail, descriptor, argumentPosition)) + expectedInfos.add(ArgumentExpectedInfo(parameter.getType(), expectedName, tail, descriptor, argumentPosition, ItemOptions.STAR_PREFIX)) } } else { @@ -228,28 +205,47 @@ class ExpectedInfos( return true } - val tail = if (isFunctionLiteralArgument) - null - else if (parameterIndex == parameters.lastIndex) - Tail.RPARENTH //TODO: support square brackets - else if (parameters.drop(parameterIndex + 1).none(::needCommaForParameter)) - null - else - Tail.COMMA - val parameterType = if (useHeuristicSignatures) - HeuristicSignatures.correctedParameterType(descriptor, argumentIndex, moduleDescriptor, callElement.getProject()) ?: parameter.getType() + HeuristicSignatures.correctedParameterType(descriptor, parameter, moduleDescriptor, callElement.getProject()) ?: parameter.getType() else parameter.getType() - if (isFunctionLiteralArgument && !KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameterType)) continue + if (isFunctionLiteralArgument) { + if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameterType)) { + expectedInfos.add(ArgumentExpectedInfo(parameterType, expectedName, null, descriptor, argumentPosition)) + } + } + else { + val tail = if (argumentName == null) { + if (parameter == parameters.last()) + Tail.RPARENTH //TODO: support square brackets + else if (parameters.dropWhile { it != parameter }.drop(1).none(::needCommaForParameter)) + null + else + Tail.COMMA + } + else { + namedArgumentTail(argumentToParameter, argumentName, descriptor) + } - expectedInfos.add(PositionalArgumentExpectedInfo(parameterType, expectedName, tail, descriptor, parameterIndex)) + expectedInfos.add(ArgumentExpectedInfo(parameterType, expectedName, tail, descriptor, argumentPosition)) + } } } return expectedInfos } + private fun namedArgumentTail(argumentToParameter: Map, argumentName: String, descriptor: FunctionDescriptor): Tail? { + val usedParameterNames = (argumentToParameter.values().map { it.getName().asString() } + listOf(argumentName)).toSet() + val notUsedParameters = descriptor.getValueParameters().filter { it.getName().asString() !in usedParameterNames } + return if (notUsedParameters.isEmpty()) + Tail.RPARENTH + else if (notUsedParameters.all { it.hasDefaultValue() }) + null + else + Tail.COMMA + } + private fun calculateForEqAndAssignment(expressionWithType: JetExpression): Collection? { val binaryExpression = expressionWithType.getParent() as? JetBinaryExpression if (binaryExpression != null) { @@ -271,7 +267,7 @@ class ExpectedInfos( return when (expressionWithType) { ifExpression.getCondition() -> listOf(ExpectedInfo(KotlinBuiltIns.getInstance().getBooleanType(), null, Tail.RPARENTH)) - ifExpression.getThen() -> calculate(ifExpression)?.map { ExpectedInfo(it.type, it.name, Tail.ELSE) } + ifExpression.getThen() -> calculate(ifExpression)?.map { ExpectedInfo(it.type, it.expectedName, Tail.ELSE) } ifExpression.getElse() -> { val ifExpectedInfo = calculate(ifExpression) @@ -312,7 +308,7 @@ class ExpectedInfos( private fun calculateForBlockExpression(expressionWithType: JetExpression): Collection? { val block = expressionWithType.getParent() as? JetBlockExpression ?: return null if (expressionWithType != block.getStatements().last()) return null - return calculate(block)?.map { ExpectedInfo(it.type, it.name, null) } + return calculate(block)?.map { ExpectedInfo(it.type, it.expectedName, null) } } private fun calculateForWhenEntryValue(expressionWithType: JetExpression): Collection? { diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/HeuristicSignatures.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/HeuristicSignatures.kt index 3e7a265d848..426d1a027f4 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/HeuristicSignatures.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/HeuristicSignatures.kt @@ -16,24 +16,25 @@ package org.jetbrains.kotlin.idea.completion +import com.intellij.openapi.project.Project import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import java.util.HashMap +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.di.InjectorForMacros import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.types.SubstitutionUtils -import org.jetbrains.kotlin.types.Variance -import org.jetbrains.kotlin.di.InjectorForMacros -import com.intellij.openapi.project.Project -import org.jetbrains.kotlin.descriptors.ModuleDescriptor -import org.jetbrains.kotlin.types.JetType -import org.jetbrains.kotlin.resolve.JetModuleUtil import org.jetbrains.kotlin.psi.JetPsiFactory import org.jetbrains.kotlin.resolve.BindingTraceContext -import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor -import org.jetbrains.kotlin.resolve.scopes.JetScope +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.JetModuleUtil import org.jetbrains.kotlin.resolve.scopes.ChainedScope +import org.jetbrains.kotlin.resolve.scopes.JetScope +import org.jetbrains.kotlin.types.JetType +import org.jetbrains.kotlin.types.SubstitutionUtils import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.Variance +import java.util.HashMap public object HeuristicSignatures { private val signatures = HashMap, List>() @@ -59,7 +60,13 @@ public object HeuristicSignatures { signatures[FqName(classFqName) to Name.identifier(name)] = parameterTypes.toList() } - public fun correctedParameterType(function: FunctionDescriptor, parameterIndex: Int, moduleDescriptor: ModuleDescriptor, project: Project): JetType? { + public fun correctedParameterType(function: FunctionDescriptor, parameter: ValueParameterDescriptor, moduleDescriptor: ModuleDescriptor, project: Project): JetType? { + val parameterIndex = function.getValueParameters().indexOf(parameter) + assert(parameterIndex >= 0) + return correctedParameterType(function, parameterIndex, moduleDescriptor, project) + } + + private fun correctedParameterType(function: FunctionDescriptor, parameterIndex: Int, moduleDescriptor: ModuleDescriptor, project: Project): JetType? { val ownerType = function.getDispatchReceiverParameter()?.getType() ?: return null val superFunctions = function.getOverriddenDescriptors() diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedParametersCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedParametersCompletion.kt index 5f11922497f..5492a6b38e7 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedParametersCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedParametersCompletion.kt @@ -29,7 +29,7 @@ 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.quickfix.QuickFixUtil +import org.jetbrains.kotlin.idea.core.mapArgumentsToParameters import org.jetbrains.kotlin.idea.references.JetReference import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.lexer.JetTokens @@ -41,6 +41,8 @@ import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getCall +import java.util.* object NamedParametersCompletion { private val positionFilter = AndFilter( @@ -83,18 +85,20 @@ object NamedParametersCompletion { for (funDescriptor in functionDescriptors) { if (!funDescriptor.hasStableParameterNames()) continue + val call = callElement.getCall(bindingContext) ?: continue - val usedArguments = QuickFixUtil.getUsedParameters(callElement, valueArgument, funDescriptor) + var argumentToParameter = HashMap(call.mapArgumentsToParameters(funDescriptor)) + argumentToParameter.remove(valueArgument) + val usedParameters = argumentToParameter.values().toSet() for (parameter in funDescriptor.getValueParameters()) { - val name = parameter.getName() - val nameString = name.asString() - if (nameString !in usedArguments) { - val lookupElement = LookupElementBuilder.create(nameString) - .withPresentableText("$nameString =") + 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(NamedParameterInsertHandler(name)) + .withInsertHandler(NamedParameterInsertHandler(parameter.getName())) .assignPriority(ItemPriority.NAMED_PARAMETER) collector.addElement(lookupElement) } 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 ea4c775b594..4a540209642 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 @@ -16,26 +16,22 @@ package org.jetbrains.kotlin.idea.completion.smart -import org.jetbrains.kotlin.idea.completion.ExpectedInfo -import org.jetbrains.kotlin.psi.JetExpression -import com.intellij.codeInsight.lookup.LookupElement -import com.intellij.ui.LayeredIcon -import com.intellij.codeInsight.lookup.LookupElementBuilder -import org.jetbrains.kotlin.idea.completion.Tail -import org.jetbrains.kotlin.idea.completion.ItemPriority -import org.jetbrains.kotlin.resolve.BindingContext -import java.util.HashSet -import org.jetbrains.kotlin.resolve.scopes.JetScope -import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor -import org.jetbrains.kotlin.idea.JetDescriptorIconProvider -import org.jetbrains.kotlin.descriptors.VariableDescriptor -import org.jetbrains.kotlin.idea.completion.PositionalArgumentExpectedInfo -import java.util.ArrayList -import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers -import org.jetbrains.kotlin.idea.completion.assignPriority import com.intellij.codeInsight.lookup.Lookup +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +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.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.psi.JetExpression +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.scopes.JetScope import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.checker.JetTypeChecker +import java.util.ArrayList +import java.util.HashSet class MultipleArgumentsItemProvider(val bindingContext: BindingContext, val smartCastTypes: (VariableDescriptor) -> Collection) { @@ -47,7 +43,7 @@ class MultipleArgumentsItemProvider(val bindingContext: BindingContext, val added = HashSet() for (expectedInfo in expectedInfos) { - if (expectedInfo is PositionalArgumentExpectedInfo && expectedInfo.parameterIndex == 0) { + if (expectedInfo is ArgumentExpectedInfo && expectedInfo.position == ArgumentPosition(0)) { val parameters = expectedInfo.function.getValueParameters() if (parameters.size() > 1) { val variables = ArrayList() diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/NameSimilarity.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/NameSimilarity.kt index dc5f5782110..3d98c25791b 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/NameSimilarity.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/NameSimilarity.kt @@ -32,7 +32,7 @@ object NameSimilarityWeigher : LookupElementWeigher("kotlin.nameSimilarity") { fun calcNameSimilarity(name: String, expectedInfos: Collection): Int { return expectedInfos - .map { it.name } + .map { it.expectedName } .filterNotNull() .map { calcNameSimilarity(name, it) } .max() ?: 0 diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt index 995908a580d..111614cf0cc 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt @@ -130,7 +130,7 @@ class SmartCompletion( // if we complete argument of == or !=, make types in expected info's nullable to allow nullable items too val expectedInfos = if ((expressionWithType.getParent() as? JetBinaryExpression)?.getOperationToken() in COMPARISON_TOKENS) - filteredExpectedInfos.map { ExpectedInfo(it.type.makeNullable(), it.name, it.tail) } + filteredExpectedInfos.map { ExpectedInfo(it.type.makeNullable(), it.expectedName, it.tail) } else filteredExpectedInfos diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/TypesWithContainsDetector.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/TypesWithContainsDetector.kt index 17110eae4e0..985c2a7ba34 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/TypesWithContainsDetector.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/TypesWithContainsDetector.kt @@ -58,7 +58,7 @@ class TypesWithContainsDetector( private fun isGoodContainsFunction(function: FunctionDescriptor, freeTypeParams: Collection): Boolean { if (!TypeUtils.equalTypes(function.getReturnType(), booleanType)) return false val parameter = function.getValueParameters().singleOrNull() ?: return false - val parameterType = HeuristicSignatures.correctedParameterType(function, 0, moduleDescriptor, project) ?: parameter.getType() + val parameterType = HeuristicSignatures.correctedParameterType(function, parameter, moduleDescriptor, project) ?: parameter.getType() val fuzzyParameterType = FuzzyType(parameterType, function.getTypeParameters() + freeTypeParams) return fuzzyParameterType.checkIsSuperTypeOf(argumentType) != null } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt index 47dd711fd41..099c9db2ab4 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt @@ -107,7 +107,7 @@ fun LookupElement.withOptions(options: ItemOptions): LookupElement { val token = context.getFile().findElementAt(offset)!! val argument = token.getStrictParentOfType() if (argument != null) { - context.getDocument().insertString(argument.getTextRange().getStartOffset(), "*") + context.getDocument().insertString(argument.getArgumentExpression()!!.getTextRange().getStartOffset(), "*") } } } diff --git a/idea/idea-completion/testData/basic/common/namedParameters/AfterVararg.kt b/idea/idea-completion/testData/basic/common/namedParameters/AfterVararg.kt new file mode 100644 index 00000000000..9256c01bae4 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/namedParameters/AfterVararg.kt @@ -0,0 +1,7 @@ +fun foo(vararg strings: String, option: String = ""){ } + +fun bar(s: String){ + foo("", "", ) +} + +// EXIST: { lookupString:"option", itemText:"option =" } diff --git a/idea/idea-completion/testData/handlers/smart/AfterVararg.kt b/idea/idea-completion/testData/handlers/smart/AfterVararg.kt new file mode 100644 index 00000000000..342e400147a --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/AfterVararg.kt @@ -0,0 +1,7 @@ +fun foo(vararg v: Int, s: String) { } + +fun bar(s: String) { + foo(*intArrayOf(), ) +} + +// ELEMENT: s diff --git a/idea/idea-completion/testData/handlers/smart/AfterVararg.kt.after b/idea/idea-completion/testData/handlers/smart/AfterVararg.kt.after new file mode 100644 index 00000000000..2cba1771e52 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/AfterVararg.kt.after @@ -0,0 +1,7 @@ +fun foo(vararg v: Int, s: String) { } + +fun bar(s: String) { + foo(*intArrayOf(), s) +} + +// ELEMENT: s diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgument1.kt b/idea/idea-completion/testData/handlers/smart/NamedArgument1.kt new file mode 100644 index 00000000000..bd44a2e13d3 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgument1.kt @@ -0,0 +1,7 @@ +fun foo(param1: String, param2: Int) { } + +fun bar(pInt: Int) { + foo(param2 = ) +} + +// ELEMENT: pInt diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgument1.kt.after b/idea/idea-completion/testData/handlers/smart/NamedArgument1.kt.after new file mode 100644 index 00000000000..e40d0cf214b --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgument1.kt.after @@ -0,0 +1,7 @@ +fun foo(param1: String, param2: Int) { } + +fun bar(pInt: Int) { + foo(param2 = pInt, ) +} + +// ELEMENT: pInt diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgument2.kt b/idea/idea-completion/testData/handlers/smart/NamedArgument2.kt new file mode 100644 index 00000000000..c2485cf3a70 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgument2.kt @@ -0,0 +1,7 @@ +fun foo(param1: String = "", param2: Int, param3: Int = 0) { } + +fun bar(pInt: Int) { + foo(param2 = ) +} + +// ELEMENT: pInt diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgument2.kt.after b/idea/idea-completion/testData/handlers/smart/NamedArgument2.kt.after new file mode 100644 index 00000000000..58c033113b3 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgument2.kt.after @@ -0,0 +1,7 @@ +fun foo(param1: String = "", param2: Int, param3: Int = 0) { } + +fun bar(pInt: Int) { + foo(param2 = pInt) +} + +// ELEMENT: pInt diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgument3.kt b/idea/idea-completion/testData/handlers/smart/NamedArgument3.kt new file mode 100644 index 00000000000..815e6c4232c --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgument3.kt @@ -0,0 +1,7 @@ +fun foo(param1: String, param2: Int, param3: Int) { } + +fun bar(pInt: Int) { + foo("", param2 = 1, param3 = ) +} + +// ELEMENT: pInt diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgument3.kt.after b/idea/idea-completion/testData/handlers/smart/NamedArgument3.kt.after new file mode 100644 index 00000000000..8caa3202447 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgument3.kt.after @@ -0,0 +1,7 @@ +fun foo(param1: String, param2: Int, param3: Int) { } + +fun bar(pInt: Int) { + foo("", param2 = 1, param3 = pInt) +} + +// ELEMENT: pInt diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg1.kt b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg1.kt new file mode 100644 index 00000000000..36050447622 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg1.kt @@ -0,0 +1,7 @@ +fun foo(param1: String, vararg param2: Int) { } + +fun bar(arr: IntArray) { + foo("", param2 = ) +} + +// ELEMENT: arr diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg1.kt.after b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg1.kt.after new file mode 100644 index 00000000000..98618f145f7 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg1.kt.after @@ -0,0 +1,7 @@ +fun foo(param1: String, vararg param2: Int) { } + +fun bar(arr: IntArray) { + foo("", param2 = *arr) +} + +// ELEMENT: arr diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg2.kt b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg2.kt new file mode 100644 index 00000000000..de70b80e316 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg2.kt @@ -0,0 +1,7 @@ +fun foo(param1: String, vararg param2: Int) { } + +fun bar(p: Int) { + foo("", param2 = ) +} + +// ELEMENT: p diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg2.kt.after b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg2.kt.after new file mode 100644 index 00000000000..4697c3de87e --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg2.kt.after @@ -0,0 +1,7 @@ +fun foo(param1: String, vararg param2: Int) { } + +fun bar(p: Int) { + foo("", param2 = p) +} + +// ELEMENT: p diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg3.kt b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg3.kt new file mode 100644 index 00000000000..0f24642cc6d --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg3.kt @@ -0,0 +1,7 @@ +fun foo(param1: String, vararg param2: Int) { } + +fun bar(arr: IntArray) { + foo(param2 = ) +} + +// ELEMENT: arr diff --git a/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg3.kt.after b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg3.kt.after new file mode 100644 index 00000000000..e24dd90e6e3 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/NamedArgumentVararg3.kt.after @@ -0,0 +1,7 @@ +fun foo(param1: String, vararg param2: Int) { } + +fun bar(arr: IntArray) { + foo(param2 = *arr, ) +} + +// ELEMENT: arr diff --git a/idea/idea-completion/testData/handlers/smart/SecondVararg.kt b/idea/idea-completion/testData/handlers/smart/SecondVararg.kt new file mode 100644 index 00000000000..58d50c16b23 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/SecondVararg.kt @@ -0,0 +1,7 @@ +fun foo(vararg v1: Int, vararg v2: Char, s: String) { } + +fun bar(c: Char) { + foo(*intArrayOf(), ) +} + +// ELEMENT: c diff --git a/idea/idea-completion/testData/handlers/smart/SecondVararg.kt.after b/idea/idea-completion/testData/handlers/smart/SecondVararg.kt.after new file mode 100644 index 00000000000..e9e9feb8f77 --- /dev/null +++ b/idea/idea-completion/testData/handlers/smart/SecondVararg.kt.after @@ -0,0 +1,7 @@ +fun foo(vararg v1: Int, vararg v2: Char, s: String) { } + +fun bar(c: Char) { + foo(*intArrayOf(), c) +} + +// ELEMENT: c diff --git a/idea/idea-completion/testData/smart/NamedArgument.kt b/idea/idea-completion/testData/smart/NamedArgument.kt new file mode 100644 index 00000000000..20059661303 --- /dev/null +++ b/idea/idea-completion/testData/smart/NamedArgument.kt @@ -0,0 +1,8 @@ +fun foo(param1: String, param2: Int) { } + +fun bar(pInt: Int, pString: String) { + foo(param2 = ) +} + +// EXIST: pInt +// ABSENT: pString diff --git a/idea/idea-completion/testData/smart/SecondVararg.kt b/idea/idea-completion/testData/smart/SecondVararg.kt new file mode 100644 index 00000000000..064c7e5466d --- /dev/null +++ b/idea/idea-completion/testData/smart/SecondVararg.kt @@ -0,0 +1,9 @@ +fun foo(vararg v1: Int, vararg v2: Char, s: String) { } + +fun bar(c: Char, pInt: Int) { + foo(*intArrayOf(), ) +} + +// EXIST: c +// ABSENT: pInt +// EXIST: { lookupString: "charArrayOf", itemText: "*charArrayOf" } 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 6291683fe5c..c378e68e72f 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 @@ -1301,6 +1301,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes doTest(fileName); } + @TestMetadata("AfterVararg.kt") + public void testAfterVararg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedParameters/AfterVararg.kt"); + doTest(fileName); + } + public void testAllFilesPresentInNamedParameters() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/namedParameters"), Pattern.compile("^(.+)\\.kt$"), true); } 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 395f0382717..f1b0b50381c 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 @@ -1301,6 +1301,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT doTest(fileName); } + @TestMetadata("AfterVararg.kt") + public void testAfterVararg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedParameters/AfterVararg.kt"); + doTest(fileName); + } + public void testAllFilesPresentInNamedParameters() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/namedParameters"), Pattern.compile("^(.+)\\.kt$"), true); } diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java index 9ca5a350189..7cb22c37739 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmSmartCompletionTestGenerated.java @@ -251,6 +251,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest(fileName); } + @TestMetadata("NamedArgument.kt") + public void testNamedArgument() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/NamedArgument.kt"); + doTest(fileName); + } + @TestMetadata("NoExtensionMethodFromClassObject.kt") public void testNoExtensionMethodFromClassObject() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/NoExtensionMethodFromClassObject.kt"); @@ -353,6 +359,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest(fileName); } + @TestMetadata("SecondVararg.kt") + public void testSecondVararg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/SecondVararg.kt"); + doTest(fileName); + } + @TestMetadata("SkipDeclarationsOfType.kt") public void testSkipDeclarationsOfType() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/SkipDeclarationsOfType.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/SmartCompletionHandlerTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/SmartCompletionHandlerTestGenerated.java index 6f89a86ffa4..de5b8f9fcd9 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/SmartCompletionHandlerTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/handlers/SmartCompletionHandlerTestGenerated.java @@ -55,6 +55,12 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion doTest(fileName); } + @TestMetadata("AfterVararg.kt") + public void testAfterVararg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/AfterVararg.kt"); + doTest(fileName); + } + public void testAllFilesPresentInSmart() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/handlers/smart"), Pattern.compile("^(.+)\\.kt$"), true); } @@ -509,6 +515,42 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion doTest(fileName); } + @TestMetadata("NamedArgument1.kt") + public void testNamedArgument1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NamedArgument1.kt"); + doTest(fileName); + } + + @TestMetadata("NamedArgument2.kt") + public void testNamedArgument2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NamedArgument2.kt"); + doTest(fileName); + } + + @TestMetadata("NamedArgument3.kt") + public void testNamedArgument3() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NamedArgument3.kt"); + doTest(fileName); + } + + @TestMetadata("NamedArgumentVararg1.kt") + public void testNamedArgumentVararg1() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NamedArgumentVararg1.kt"); + doTest(fileName); + } + + @TestMetadata("NamedArgumentVararg2.kt") + public void testNamedArgumentVararg2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NamedArgumentVararg2.kt"); + doTest(fileName); + } + + @TestMetadata("NamedArgumentVararg3.kt") + public void testNamedArgumentVararg3() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NamedArgumentVararg3.kt"); + doTest(fileName); + } + @TestMetadata("NestedDataClass.kt") public void testNestedDataClass() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NestedDataClass.kt"); @@ -587,6 +629,12 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion doTest(fileName); } + @TestMetadata("SecondVararg.kt") + public void testSecondVararg() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/SecondVararg.kt"); + doTest(fileName); + } + @TestMetadata("TabReplaceComma1.kt") public void testTabReplaceComma1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/TabReplaceComma1.kt"); diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt new file mode 100644 index 00000000000..131eacb3d6c --- /dev/null +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.core + +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.psi.Call +import org.jetbrains.kotlin.psi.FunctionLiteralArgument +import org.jetbrains.kotlin.psi.ValueArgument +import java.util.HashMap + +public fun Call.mapArgumentsToParameters(targetDescriptor: CallableDescriptor): Map { + val parameters = targetDescriptor.getValueParameters() + if (parameters.isEmpty()) return emptyMap() + + val map = HashMap() + val parametersByName = parameters.toMap { it.getName().asString() } + + var positionalArgumentIndex: Int? = 0 + + for (argument in getValueArguments()) { + if (argument is FunctionLiteralArgument) { + map[argument] = parameters.last() + } + else { + val argumentName = argument.getArgumentName()?.getReferenceExpression()?.getReferencedName() + + if (argumentName != null) { + if (targetDescriptor.hasStableParameterNames()) { + val parameter = parametersByName[argumentName] + if (parameter != null) { + map[argument] = parameter + } + } + positionalArgumentIndex = null + } + else { + if (positionalArgumentIndex != null && positionalArgumentIndex < parameters.size()) { + val parameter = parameters[positionalArgumentIndex] + map[argument] = parameter + + if (parameter.getVarargElementType() == null || argument.getSpreadElement() != null) { + positionalArgumentIndex++ + } + } + } + } + } + + return map +} diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/quickfix/QuickFixUtil.java b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/quickfix/QuickFixUtil.java index 06e4c0d4dbb..cbfc9c2452b 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/quickfix/QuickFixUtil.java +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/quickfix/QuickFixUtil.java @@ -16,7 +16,6 @@ package org.jetbrains.kotlin.idea.core.quickfix; -import com.google.common.collect.Sets; import com.intellij.extapi.psi.ASTDelegatePsiElement; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -24,10 +23,8 @@ import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.ReadOnly; import org.jetbrains.kotlin.descriptors.CallableDescriptor; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; -import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor; import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; import org.jetbrains.kotlin.idea.references.BuiltInsReferenceResolver; @@ -44,8 +41,6 @@ import org.jetbrains.kotlin.types.DeferredType; import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.checker.JetTypeChecker; -import java.util.Set; - public class QuickFixUtil { private QuickFixUtil() { } @@ -167,39 +162,6 @@ public class QuickFixUtil { } } - @ReadOnly - @NotNull - public static Set getUsedParameters( - @NotNull JetCallElement callElement, - @Nullable JetValueArgument ignoreArgument, - @NotNull CallableDescriptor callableDescriptor - ) { - Set usedParameters = Sets.newHashSet(); - boolean isPositionalArgument = true; - int idx = 0; - for (ValueArgument argument : callElement.getValueArguments()) { - if (argument.isNamed()) { - JetValueArgumentName name = argument.getArgumentName(); - assert name != null : "Named argument's name cannot be null"; - if (argument != ignoreArgument) { - usedParameters.add(name.getText()); - } - isPositionalArgument = false; - } - else if (isPositionalArgument) { - if (callableDescriptor.getValueParameters().size() > idx) { - ValueParameterDescriptor parameter = callableDescriptor.getValueParameters().get(idx); - if (argument != ignoreArgument) { - usedParameters.add(parameter.getName().asString()); - } - idx++; - } - } - } - - return usedParameters; - } - public static String renderTypeWithFqNameOnClash(JetType type, String nameToCheckAgainst) { FqName typeFqName = DescriptorUtils.getFqNameSafe(DescriptorUtils.getClassDescriptorForType(type)); FqName fqNameToCheckAgainst = new FqName(nameToCheckAgainst); diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddNameToArgumentFix.java b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddNameToArgumentFix.java index e412e9622c6..a4aa744c1fa 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddNameToArgumentFix.java +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddNameToArgumentFix.java @@ -28,6 +28,7 @@ import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.util.PsiTreeUtil; +import kotlin.KotlinPackage; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.CallableDescriptor; @@ -36,6 +37,7 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.idea.JetBundle; import org.jetbrains.kotlin.idea.JetIcons; import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; +import org.jetbrains.kotlin.idea.core.CorePackage; import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil; import org.jetbrains.kotlin.psi.JetCallElement; import org.jetbrains.kotlin.psi.JetExpression; @@ -76,13 +78,11 @@ public class AddNameToArgumentFix extends JetIntentionAction { CallableDescriptor callableDescriptor = resolvedCall.getResultingDescriptor(); JetExpression argExpression = argument.getArgumentExpression(); JetType type = argExpression != null ? context.getType(argExpression) : null; - Set usedParameters = QuickFixUtil.getUsedParameters(callElement, null, callableDescriptor); + Set usedParameters = KotlinPackage.toSet( CorePackage.mapArgumentsToParameters(resolvedCall.getCall(), callableDescriptor).values()); List names = Lists.newArrayList(); for (ValueParameterDescriptor parameter: callableDescriptor.getValueParameters()) { - String name = parameter.getName().asString(); - if (usedParameters.contains(name)) continue; - if (type == null || JetTypeChecker.DEFAULT.isSubtypeOf(type, parameter.getType())) { - names.add(name); + if (!usedParameters.contains(parameter) && (type == null || JetTypeChecker.DEFAULT.isSubtypeOf(type, parameter.getType()))) { + names.add(parameter.getName().asString()); } } return names;