More correct named arguments completion
This commit is contained in:
+1
-1
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-37
@@ -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()
|
||||
|
||||
+5
-2
@@ -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>()
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+5
-7
@@ -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" }
|
||||
|
||||
+13
@@ -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
|
||||
+6
@@ -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");
|
||||
|
||||
+6
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user