Smart completion to work when some of previous arguments do not match
#KT-8400 Fixed
This commit is contained in:
@@ -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<ParameterDescriptor>?
|
||||
val namedArgumentCandidates: Collection<ParameterDescriptor>
|
||||
) : 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<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
|
||||
|
||||
val expectedInfos = ArrayList<ExpectedInfo>()
|
||||
for (candidate: ResolvedCall<FunctionDescriptor> 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<HeuristicSignatures>().
|
||||
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<ExpectedInfo>.addExpectedInfoForCandidate(
|
||||
candidate: ResolvedCall<FunctionDescriptor>,
|
||||
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<HeuristicSignatures>().
|
||||
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 <D : CallableDescriptor> ResolvedCall<D>.allArgumentsMapped()
|
||||
= call.valueArguments.all { argument -> getArgumentMapping(argument) is ArgumentMatch }
|
||||
|
||||
private fun <D : CallableDescriptor> ResolvedCall<D>.allArgumentsMatched()
|
||||
= call.valueArguments.none { argument -> getArgumentMapping(argument).isError() && !argument.hasError() /* ignore arguments that has error type */ }
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ object NamedArgumentCompletion {
|
||||
val nameToParameterType = HashMap<Name, MutableSet<JetType>>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user