Smart completion to work when some of previous arguments do not match

#KT-8400 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-09-03 15:55:19 +03:00
parent 7663793e3d
commit 997aef8fee
4 changed files with 139 additions and 104 deletions
@@ -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 */ }
@@ -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)
}
}
@@ -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");