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 add40185c77..c2305b706af 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 @@ -41,6 +41,7 @@ import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode import org.jetbrains.kotlin.resolve.calls.context.ContextDependency +import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults import org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus @@ -136,7 +137,7 @@ sealed class ArgumentPositionData(val function: FunctionDescriptor) : ExpectedIn function: FunctionDescriptor, val argumentIndex: Int, val isFunctionLiteralArgument: Boolean, - val namedArgumentCandidates: Collection? + val namedArgumentCandidates: Collection ) : ArgumentPositionData(function) class Named(function: FunctionDescriptor, val argumentName: Name) : ArgumentPositionData(function) @@ -221,8 +222,6 @@ class ExpectedInfos( assert(argumentIndex >= 0) { "Could not find argument '$argument(${argument.asElement().text})' among arguments of call: $call" } - val argumentName = argument.getArgumentName()?.asName - val isFunctionLiteralArgument = argument is FunctionLiteralArgument // leave only arguments before the current one val truncatedCall = object : DelegatingCall(call) { @@ -233,6 +232,7 @@ class ExpectedInfos( override fun getValueArgumentList() = null } val resolutionScope = bindingContext[BindingContext.LEXICAL_SCOPE, call.calleeExpression] ?: return emptyList() //TODO: discuss it + val inDescriptor = resolutionScope.ownerDescriptor val dataFlowInfo = bindingContext.getDataFlowInfo(call.calleeExpression) val bindingTrace = DelegatingBindingTrace(bindingContext, "Temporary trace for completion") @@ -244,113 +244,142 @@ class ExpectedInfos( val results: OverloadResolutionResults = callResolver.resolveFunctionCall(callResolutionContext) 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 - // check that all arguments before the current one matched - if (!candidate.allArgumentsMatched()) continue + for (candidate in results.allCandidates!!) { + expectedInfos.addExpectedInfoForCandidate(candidate, call, argument, argumentIndex, inDescriptor, checkPrevArgumentsMatched = true) + } - var descriptor = candidate.getResultingDescriptor() - if (descriptor.valueParameters.isEmpty()) continue - - val thisReceiver = ExpressionTypingUtils.normalizeReceiverValueForVisibility(candidate.getDispatchReceiver(), bindingContext) - if (!Visibilities.isVisible(thisReceiver, descriptor, resolutionScope.ownerDescriptor)) continue - - var argumentToParameter = call.mapArgumentsToParameters(descriptor) - var parameter = argumentToParameter[argument] - - //TODO: we can loose partially inferred substitution here but what to do? - if (parameter != null && parameter.type.containsError()) { - descriptor = descriptor.original - 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 - - fun needCommaForParameter(parameter: ValueParameterDescriptor): Boolean { - if (parameter.hasDefaultValue()) return false // parameter is optional - if (parameter.getVarargElementType() != null) return false // vararg arguments list can be empty - // last parameter of functional type can be placed outside parenthesis: - if (parameter == parameters.last() && KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameter.getType())) return false - return true - } - - val tail = if (argumentName == null) { - if (parameter == parameters.last()) - Tail.RPARENTH //TODO: support square brackets - else if (parameters.dropWhile { it != parameter }.drop(1).any(::needCommaForParameter)) - Tail.COMMA - else - null - } - else { - namedArgumentTail(argumentToParameter, argumentName, descriptor) - } - - val alreadyHasStar = argument.getSpreadElement() != null - - val varargElementType = parameter!!.getVarargElementType() - if (varargElementType != null) { - if (isFunctionLiteralArgument) continue - - val varargTail = if (argumentName == null && tail == Tail.RPARENTH) - null /* even if it's the last parameter, there can be more arguments for the same parameter */ - else - tail - - if (!alreadyHasStar) { - 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, argumentPositionData, starOptions)) - } - else { - if (alreadyHasStar) continue - - val parameterType = if (useHeuristicSignatures) - resolutionFacade.ideService(). - correctedParameterType(descriptor, parameter!!) ?: parameter!!.getType() - else - parameter!!.getType() - - if (isFunctionLiteralArgument) { - if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameterType)) { - expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, null, argumentPositionData)) - } - } - else { - expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, tail, argumentPositionData)) - } + if (expectedInfos.isEmpty()) { // if no candidates have previous arguments matched, try with no type checking for them + for (candidate in results.allCandidates!!) { + expectedInfos.addExpectedInfoForCandidate(candidate, call, argument, argumentIndex, inDescriptor, checkPrevArgumentsMatched = false) } } + return expectedInfos } + private fun MutableCollection.addExpectedInfoForCandidate( + candidate: ResolvedCall, + call: Call, + argument: ValueArgument, + argumentIndex: Int, + inDescriptor: DeclarationDescriptor, + checkPrevArgumentsMatched: Boolean + ) { + val status = candidate.getStatus() + if (status == ResolutionStatus.RECEIVER_TYPE_ERROR || status == ResolutionStatus.RECEIVER_PRESENCE_ERROR) return + + // check that all arguments before the current has mappings to parameters + if (!candidate.allArgumentsMapped()) return + + // check that all arguments before the current one matched + if (checkPrevArgumentsMatched && !candidate.allArgumentsMatched()) return + + var descriptor = candidate.getResultingDescriptor() + if (descriptor.valueParameters.isEmpty()) return + + val thisReceiver = ExpressionTypingUtils.normalizeReceiverValueForVisibility(candidate.getDispatchReceiver(), bindingContext) + if (!Visibilities.isVisible(thisReceiver, descriptor, inDescriptor)) return + + var argumentToParameter = call.mapArgumentsToParameters(descriptor) + var parameter = argumentToParameter[argument] + + //TODO: we can loose partially inferred substitution here but what to do? + if (parameter != null && parameter.type.containsError()) { + descriptor = descriptor.original + parameter = descriptor.valueParameters[parameter.index]!! + argumentToParameter = call.mapArgumentsToParameters(descriptor) + } + + val argumentName = argument.getArgumentName()?.asName + val isFunctionLiteralArgument = argument is FunctionLiteralArgument + + 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 { + emptyList() + } + ArgumentPositionData.Positional(descriptor, argumentIndex, isFunctionLiteralArgument, namedArgumentCandidates) + } + + if (parameter == null) { + if (argumentPositionData is ArgumentPositionData.Positional && argumentPositionData.namedArgumentCandidates.isNotEmpty()) { + add(ExpectedInfo.createForNamedArgumentExpected(argumentPositionData)) + } + return + } + + val expectedName = if (descriptor.hasSynthesizedParameterNames()) null else parameter.getName().asString() + + val parameters = descriptor.valueParameters + + fun needCommaForParameter(parameter: ValueParameterDescriptor): Boolean { + if (parameter.hasDefaultValue()) return false // parameter is optional + if (parameter.getVarargElementType() != null) return false // vararg arguments list can be empty + // last parameter of functional type can be placed outside parenthesis: + if (parameter == parameters.last() && KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameter.getType())) return false + return true + } + + val tail = if (argumentName == null) { + if (parameter == parameters.last()) + Tail.RPARENTH //TODO: support square brackets + else if (parameters.dropWhile { it != parameter }.drop(1).any(::needCommaForParameter)) + Tail.COMMA + else + null + } + else { + namedArgumentTail(argumentToParameter, argumentName, descriptor) + } + + val alreadyHasStar = argument.getSpreadElement() != null + + val varargElementType = parameter!!.getVarargElementType() + if (varargElementType != null) { + if (isFunctionLiteralArgument) return + + val varargTail = if (argumentName == null && tail == Tail.RPARENTH) + null /* even if it's the last parameter, there can be more arguments for the same parameter */ + else + tail + + if (!alreadyHasStar) { + add(ExpectedInfo.createForArgument(varargElementType, expectedName?.unpluralize(), varargTail, argumentPositionData)) + } + + val starOptions = if (!alreadyHasStar) ItemOptions.STAR_PREFIX else ItemOptions.DEFAULT + add(ExpectedInfo.createForArgument(parameter!!.getType(), expectedName, varargTail, argumentPositionData, starOptions)) + } + else { + if (alreadyHasStar) return + + val parameterType = if (useHeuristicSignatures) + resolutionFacade.ideService(). + correctedParameterType(descriptor, parameter!!) ?: parameter!!.getType() + else + parameter!!.getType() + + if (isFunctionLiteralArgument) { + if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameterType)) { + add(ExpectedInfo.createForArgument(parameterType, expectedName, null, argumentPositionData)) + } + } + else { + add(ExpectedInfo.createForArgument(parameterType, expectedName, tail, argumentPositionData)) + } + } + } + + private fun ResolvedCall.allArgumentsMapped() + = call.valueArguments.all { argument -> getArgumentMapping(argument) is ArgumentMatch } + private fun ResolvedCall.allArgumentsMatched() = call.valueArguments.none { argument -> getArgumentMapping(argument).isError() && !argument.hasError() /* ignore arguments that has error type */ } 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 1288a1e6294..755f0e94c17 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 @@ -48,7 +48,7 @@ object NamedArgumentCompletion { val nameToParameterType = HashMap>() for (expectedInfo in expectedInfos) { val argumentData = expectedInfo.additionalData as? ArgumentPositionData.Positional ?: continue - argumentData.namedArgumentCandidates?.forEach { parameter -> + for (parameter in argumentData.namedArgumentCandidates) { nameToParameterType.getOrPut(parameter.name) { HashSet() }.add(parameter.type) } } diff --git a/idea/idea-completion/testData/smart/PreviousArgMismatchedType.kt.todo b/idea/idea-completion/testData/smart/PreviousArgMismatchedType.kt similarity index 100% rename from idea/idea-completion/testData/smart/PreviousArgMismatchedType.kt.todo rename to idea/idea-completion/testData/smart/PreviousArgMismatchedType.kt 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 c43e8b04388..14e07110c2c 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 @@ -419,6 +419,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest(fileName); } + @TestMetadata("PreviousArgMismatchedType.kt") + public void testPreviousArgMismatchedType() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/PreviousArgMismatchedType.kt"); + doTest(fileName); + } + @TestMetadata("QualifiedOverloadedMethodCallArgument1.kt") public void testQualifiedOverloadedMethodCallArgument1() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/QualifiedOverloadedMethodCallArgument1.kt");