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.BasicCallResolutionContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
|
import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
|
||||||
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
|
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.model.ResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
|
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
|
||||||
import org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus
|
import org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus
|
||||||
@@ -136,7 +137,7 @@ sealed class ArgumentPositionData(val function: FunctionDescriptor) : ExpectedIn
|
|||||||
function: FunctionDescriptor,
|
function: FunctionDescriptor,
|
||||||
val argumentIndex: Int,
|
val argumentIndex: Int,
|
||||||
val isFunctionLiteralArgument: Boolean,
|
val isFunctionLiteralArgument: Boolean,
|
||||||
val namedArgumentCandidates: Collection<ParameterDescriptor>?
|
val namedArgumentCandidates: Collection<ParameterDescriptor>
|
||||||
) : ArgumentPositionData(function)
|
) : ArgumentPositionData(function)
|
||||||
|
|
||||||
class Named(function: FunctionDescriptor, val argumentName: Name) : ArgumentPositionData(function)
|
class Named(function: FunctionDescriptor, val argumentName: Name) : ArgumentPositionData(function)
|
||||||
@@ -221,8 +222,6 @@ class ExpectedInfos(
|
|||||||
assert(argumentIndex >= 0) {
|
assert(argumentIndex >= 0) {
|
||||||
"Could not find argument '$argument(${argument.asElement().text})' among arguments of call: $call"
|
"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
|
// leave only arguments before the current one
|
||||||
val truncatedCall = object : DelegatingCall(call) {
|
val truncatedCall = object : DelegatingCall(call) {
|
||||||
@@ -233,6 +232,7 @@ class ExpectedInfos(
|
|||||||
override fun getValueArgumentList() = null
|
override fun getValueArgumentList() = null
|
||||||
}
|
}
|
||||||
val resolutionScope = bindingContext[BindingContext.LEXICAL_SCOPE, call.calleeExpression] ?: return emptyList() //TODO: discuss it
|
val resolutionScope = bindingContext[BindingContext.LEXICAL_SCOPE, call.calleeExpression] ?: return emptyList() //TODO: discuss it
|
||||||
|
val inDescriptor = resolutionScope.ownerDescriptor
|
||||||
|
|
||||||
val dataFlowInfo = bindingContext.getDataFlowInfo(call.calleeExpression)
|
val dataFlowInfo = bindingContext.getDataFlowInfo(call.calleeExpression)
|
||||||
val bindingTrace = DelegatingBindingTrace(bindingContext, "Temporary trace for completion")
|
val bindingTrace = DelegatingBindingTrace(bindingContext, "Temporary trace for completion")
|
||||||
@@ -244,113 +244,142 @@ class ExpectedInfos(
|
|||||||
val results: OverloadResolutionResults<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
|
val results: OverloadResolutionResults<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
|
||||||
|
|
||||||
val expectedInfos = ArrayList<ExpectedInfo>()
|
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
|
for (candidate in results.allCandidates!!) {
|
||||||
if (!candidate.allArgumentsMatched()) continue
|
expectedInfos.addExpectedInfoForCandidate(candidate, call, argument, argumentIndex, inDescriptor, checkPrevArgumentsMatched = true)
|
||||||
|
}
|
||||||
|
|
||||||
var descriptor = candidate.getResultingDescriptor()
|
if (expectedInfos.isEmpty()) { // if no candidates have previous arguments matched, try with no type checking for them
|
||||||
if (descriptor.valueParameters.isEmpty()) continue
|
for (candidate in results.allCandidates!!) {
|
||||||
|
expectedInfos.addExpectedInfoForCandidate(candidate, call, argument, argumentIndex, inDescriptor, checkPrevArgumentsMatched = false)
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return expectedInfos
|
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()
|
private fun <D : CallableDescriptor> ResolvedCall<D>.allArgumentsMatched()
|
||||||
= call.valueArguments.none { argument -> getArgumentMapping(argument).isError() && !argument.hasError() /* ignore arguments that has error type */ }
|
= 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>>()
|
val nameToParameterType = HashMap<Name, MutableSet<JetType>>()
|
||||||
for (expectedInfo in expectedInfos) {
|
for (expectedInfo in expectedInfos) {
|
||||||
val argumentData = expectedInfo.additionalData as? ArgumentPositionData.Positional ?: continue
|
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)
|
nameToParameterType.getOrPut(parameter.name) { HashSet() }.add(parameter.type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -419,6 +419,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("QualifiedOverloadedMethodCallArgument1.kt")
|
||||||
public void testQualifiedOverloadedMethodCallArgument1() throws Exception {
|
public void testQualifiedOverloadedMethodCallArgument1() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/QualifiedOverloadedMethodCallArgument1.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/QualifiedOverloadedMethodCallArgument1.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user