More correct named arguments completion

This commit is contained in:
Valentin Kipyatkov
2015-09-02 17:37:46 +03:00
parent 7cf4d265bb
commit db4c163539
9 changed files with 112 additions and 84 deletions
@@ -296,7 +296,7 @@ class BasicCompletionSession(configuration: CompletionSessionConfiguration,
}
}
NamedArgumentCompletion.complete(position, collector, bindingContext)
NamedArgumentCompletion.complete(position, collector, expectedInfos)
}
private fun addNonImported(completionKind: CompletionKind) {
@@ -56,7 +56,7 @@ import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.types.typeUtil.makeNullable
import org.jetbrains.kotlin.utils.addToStdlib.check
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
import java.util.LinkedHashSet
import java.util.*
enum class Tail {
COMMA,
@@ -78,6 +78,10 @@ interface ByTypeFilter {
object All : ByTypeFilter {
override fun matchingSubstitutor(descriptorType: FuzzyType) = TypeSubstitutor.EMPTY
}
object None : ByTypeFilter {
override fun matchingSubstitutor(descriptorType: FuzzyType) = null
}
}
class ByExpectedTypeFilter(val fuzzyType: FuzzyType) : ByTypeFilter {
@@ -88,7 +92,8 @@ class ByExpectedTypeFilter(val fuzzyType: FuzzyType) : ByTypeFilter {
override fun hashCode() = fuzzyType.hashCode()
}
data class ExpectedInfo(
data /* for copy() */
class ExpectedInfo(
val filter: ByTypeFilter,
val expectedName: String?,
val tail: Tail?,
@@ -109,8 +114,12 @@ data class ExpectedInfo(
fun matchingSubstitutor(descriptorType: JetType): TypeSubstitutor? = matchingSubstitutor(FuzzyType(descriptorType, emptyList()))
companion object {
fun createForArgument(type: JetType, name: String?, tail: Tail?, function: FunctionDescriptor, position: ArgumentPosition, itemOptions: ItemOptions = ItemOptions.DEFAULT): ExpectedInfo {
return ExpectedInfo(FuzzyType(type, function.typeParameters), name, tail, itemOptions, ArgumentAdditionalData(function, position))
fun createForArgument(type: JetType, expectedName: String?, tail: Tail?, argumentData: ArgumentPositionData, itemOptions: ItemOptions = ItemOptions.DEFAULT): ExpectedInfo {
return ExpectedInfo(FuzzyType(type, argumentData.function.typeParameters), expectedName, tail, itemOptions, argumentData)
}
fun createForNamedArgumentExpected(argumentData: ArgumentPositionData): ExpectedInfo {
return ExpectedInfo(ByTypeFilter.None, null, null/*TODO?*/, ItemOptions.DEFAULT, argumentData)
}
fun createForReturnValue(type: JetType?, callable: CallableDescriptor): ExpectedInfo {
@@ -123,26 +132,18 @@ data class ExpectedInfo(
val ExpectedInfo.fuzzyType: FuzzyType?
get() = (this.filter as? ByExpectedTypeFilter)?.fuzzyType
data class ArgumentPosition(val argumentIndex: Int, val argumentName: Name?, val isFunctionLiteralArgument: Boolean) {
constructor(argumentIndex: Int, isFunctionLiteralArgument: Boolean = false) : this(argumentIndex, null, isFunctionLiteralArgument)
constructor(argumentIndex: Int, argumentName: Name?) : this(argumentIndex, argumentName, false)
sealed class ArgumentPositionData(val function: FunctionDescriptor) : ExpectedInfo.AdditionalData {
class Positional(
function: FunctionDescriptor,
val argumentIndex: Int,
val isFunctionLiteralArgument: Boolean,
val namedArgumentCandidates: Collection<ParameterDescriptor>?
) : ArgumentPositionData(function)
class Named(function: FunctionDescriptor, val argumentName: Name) : ArgumentPositionData(function)
}
class ArgumentAdditionalData(val function: FunctionDescriptor, val position: ArgumentPosition) : ExpectedInfo.AdditionalData {
override fun equals(other: Any?)
= other is ArgumentAdditionalData && function == other.function && position == other.position
override fun hashCode()
= function.hashCode()
}
class ReturnValueAdditionalData(val callable: CallableDescriptor) : ExpectedInfo.AdditionalData {
override fun equals(other: Any?)
= other is ReturnValueAdditionalData && callable == other.callable
override fun hashCode()
= callable.hashCode()
}
class ReturnValueAdditionalData(val callable: CallableDescriptor) : ExpectedInfo.AdditionalData
class ExpectedInfos(
val bindingContext: BindingContext,
@@ -193,7 +194,7 @@ class ExpectedInfos(
val results = calculateForArgument(call, TypeUtils.NO_EXPECTED_TYPE, argument)
fun makesSenseToUseOuterCallExpectedType(info: ExpectedInfo): Boolean {
val data = info.additionalData as ArgumentAdditionalData
val data = info.additionalData as ArgumentPositionData
return info.fuzzyType != null
&& info.fuzzyType!!.freeParameters.isNotEmpty()
&& data.function.fuzzyReturnType()?.freeParameters?.isNotEmpty() ?: false
@@ -211,7 +212,6 @@ class ExpectedInfos(
.map { it.type }
.toSet()
.flatMap { calculateForArgument(call, it, argument) }
.toSet()
}
return results
@@ -224,7 +224,6 @@ class ExpectedInfos(
}
val argumentName = argument.getArgumentName()?.asName
val isFunctionLiteralArgument = argument is FunctionLiteralArgument
val argumentPosition = ArgumentPosition(argumentIndex, argumentName, isFunctionLiteralArgument)
// leave only arguments before the current one
val truncatedCall = object : DelegatingCall(call) {
@@ -245,7 +244,7 @@ class ExpectedInfos(
val callResolver = resolutionFacade.frontendService<CallResolver>()
val results: OverloadResolutionResults<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
val expectedInfos = LinkedHashSet<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
@@ -260,15 +259,35 @@ class ExpectedInfos(
if (!Visibilities.isVisible(thisReceiver, descriptor, resolutionScope.ownerDescriptor)) continue
var argumentToParameter = call.mapArgumentsToParameters(descriptor)
var parameter = argumentToParameter[argument] ?: continue
var parameter = argumentToParameter[argument]
//TODO: we can loose partially inferred substitution here but what to do?
if (parameter.type.containsError()) {
if (parameter != null && parameter.type.containsError()) {
descriptor = descriptor.original
parameter = descriptor.valueParameters[parameter.index]
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
@@ -295,7 +314,7 @@ class ExpectedInfos(
val alreadyHasStar = argument.getSpreadElement() != null
val varargElementType = parameter.getVarargElementType()
val varargElementType = parameter!!.getVarargElementType()
if (varargElementType != null) {
if (isFunctionLiteralArgument) continue
@@ -305,28 +324,28 @@ class ExpectedInfos(
tail
if (!alreadyHasStar) {
expectedInfos.add(ExpectedInfo.createForArgument(varargElementType, expectedName?.unpluralize(), varargTail, descriptor, argumentPosition))
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, descriptor, argumentPosition, starOptions))
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()
correctedParameterType(descriptor, parameter!!) ?: parameter!!.getType()
else
parameter.getType()
parameter!!.getType()
if (isFunctionLiteralArgument) {
if (KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(parameterType)) {
expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, null, descriptor, argumentPosition))
expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, null, argumentPositionData))
}
}
else {
expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, tail, descriptor, argumentPosition))
expectedInfos.add(ExpectedInfo.createForArgument(parameterType, expectedName, tail, argumentPositionData))
}
}
}
@@ -26,24 +26,17 @@ import com.intellij.psi.filters.ClassFilter
import com.intellij.psi.filters.OrFilter
import com.intellij.psi.filters.position.ParentElementFilter
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.mapArgumentsToParameters
import org.jetbrains.kotlin.idea.references.JetReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.JetCallElement
import org.jetbrains.kotlin.psi.JetValueArgument
import org.jetbrains.kotlin.psi.JetValueArgumentName
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.types.JetType
import java.util.*
object NamedArgumentCompletion {
@@ -73,41 +66,31 @@ object NamedArgumentCompletion {
return false
}
public fun complete(position: PsiElement, collector: LookupElementsCollector, bindingContext: BindingContext) {
public fun complete(position: PsiElement, collector: LookupElementsCollector, expectedInfos: Collection<ExpectedInfo>) {
if (!positionFilter.isAcceptable(position, position)) return
val valueArgument = position.getStrictParentOfType<JetValueArgument>()!!
val callElement = valueArgument.getStrictParentOfType<JetCallElement>() ?: return
val callSimpleName = callElement.getCallNameExpression() ?: return
val functionDescriptors = callSimpleName.mainReference.resolveToDescriptors(bindingContext)
.filterIsInstance<FunctionDescriptor>()
for (funDescriptor in functionDescriptors) {
if (!funDescriptor.hasStableParameterNames()) continue
val call = callElement.getCall(bindingContext) ?: continue
var argumentToParameter = HashMap(call.mapArgumentsToParameters(funDescriptor))
argumentToParameter.remove(valueArgument)
val usedParameters = argumentToParameter.values().toSet()
for (parameter in funDescriptor.getValueParameters()) {
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(NamedArgumentInsertHandler(parameter.getName()))
.assignPriority(ItemPriority.NAMED_PARAMETER)
collector.addElement(lookupElement)
}
val nameToParameterType = HashMap<Name, MutableSet<JetType>>()
for (expectedInfo in expectedInfos) {
val argumentData = expectedInfo.additionalData as? ArgumentPositionData.Positional ?: continue
argumentData.namedArgumentCandidates?.forEach { parameter ->
nameToParameterType.getOrPut(parameter.name) { HashSet() }.add(parameter.type)
}
}
for ((name, types) in nameToParameterType) {
val typeText = types.singleOrNull()?.let { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it) } ?: "..."
val nameString = name.asString()
val lookupElement = LookupElementBuilder.create(nameString)
.withPresentableText("$nameString =")
.withTailText(" $typeText")
.withIcon(JetIcons.PARAMETER)
.withInsertHandler(NamedArgumentInsertHandler(name))
.assignPriority(ItemPriority.NAMED_PARAMETER)
collector.addElement(lookupElement)
}
}
private class NamedArgumentInsertHandler(val parameterName: Name) : InsertHandler<LookupElement> {
private class NamedArgumentInsertHandler(private val parameterName: Name) : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val editor = context.getEditor()
val text = parameterName.render()
@@ -23,7 +23,10 @@ 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.completion.ArgumentPositionData
import org.jetbrains.kotlin.idea.completion.ExpectedInfo
import org.jetbrains.kotlin.idea.completion.SmartCastCalculator
import org.jetbrains.kotlin.idea.completion.Tail
import org.jetbrains.kotlin.idea.util.getVariableFromImplicitReceivers
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.psi.JetExpression
@@ -44,7 +47,7 @@ class MultipleArgumentsItemProvider(val bindingContext: BindingContext,
val added = HashSet<String>()
for (expectedInfo in expectedInfos) {
if (expectedInfo.additionalData is ArgumentAdditionalData && expectedInfo.additionalData.position == ArgumentPosition(0)) {
if (expectedInfo.additionalData is ArgumentPositionData.Positional && expectedInfo.additionalData.argumentIndex == 0) {
val parameters = expectedInfo.additionalData.function.valueParameters
if (parameters.size() > 1) {
val variables = ArrayList<VariableDescriptor>()
@@ -51,7 +51,7 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para
override fun doComplete() {
if (NamedArgumentCompletion.isOnlyNamedArgumentExpected(position)) {
NamedArgumentCompletion.complete(position, collector, bindingContext)
NamedArgumentCompletion.complete(position, collector, expectedInfos)
return
}
@@ -10,11 +10,9 @@ fun other() {
foo(n<caret>)
}
// EXIST: { lookupString:"nFirst", itemText:"nFirst =" }
// EXIST: nLocal
// todo - should exist
// ABSENT: nClassParam
// ABSENT: nSecond
// ABSENT: nThird
// ABSENT: nClassField
// EXIST: { lookupString:"nFirst", itemText:"nFirst =", tailText: " String" }
// EXIST: { itemText: "nClassParam =", tailText: " String" }
// EXIST: { itemText: "nClassField =", tailText: " String" }
// EXIST: { itemText: "nSecond =", tailText: " String?" }
// EXIST: { itemText: "nThird =", tailText: " Int" }
@@ -0,0 +1,13 @@
fun foo(p: Int, xxP1: String, xxP2: Int){}
fun foo(p: Int, xxP1: String, xxP2: Char){}
fun foo(p: Int, xxx: Any?){}
fun foo(p: String, xxy: String){}
fun f() {
foo(1, xx<caret>)
}
// EXIST: { itemText:"xxP1 =", tailText: " String" }
// EXIST: { itemText:"xxP2 =", tailText: " ..." }
// EXIST: { itemText:"xxx =", tailText: " Any?" }
// NOTHING_ELSE
@@ -1485,6 +1485,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest(fileName);
}
@TestMetadata("NamedArgumentsFromOverloads2.kt")
public void testNamedArgumentsFromOverloads2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads2.kt");
doTest(fileName);
}
@TestMetadata("NoAlreadyUsedParameters.kt")
public void testNoAlreadyUsedParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/NoAlreadyUsedParameters.kt");
@@ -1485,6 +1485,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName);
}
@TestMetadata("NamedArgumentsFromOverloads2.kt")
public void testNamedArgumentsFromOverloads2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads2.kt");
doTest(fileName);
}
@TestMetadata("NoAlreadyUsedParameters.kt")
public void testNoAlreadyUsedParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/NoAlreadyUsedParameters.kt");