KT-14386 Smart completion: add "<parameter name> = true/false" choices

#KT-14386 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-10-18 18:23:52 +03:00
parent 54f28cab45
commit a2ebf07a93
14 changed files with 134 additions and 1 deletions
@@ -24,6 +24,7 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.SmartList import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.completion.* import org.jetbrains.kotlin.idea.completion.*
import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler
@@ -33,12 +34,15 @@ import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.idea.util.isAlmostEverything import org.jetbrains.kotlin.idea.util.isAlmostEverything
import org.jetbrains.kotlin.idea.util.toFuzzyType import org.jetbrains.kotlin.idea.util.toFuzzyType
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.renderer.render
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.isBooleanOrNullableBoolean
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.check import org.jetbrains.kotlin.utils.addToStdlib.check
@@ -107,7 +111,7 @@ class SmartCompletion(
return postProcessedItems to postProcessedSearcher return postProcessedItems to postProcessedSearcher
} }
val descriptorsToSkip: Set<DeclarationDescriptor> by lazy<Set<DeclarationDescriptor>>(LazyThreadSafetyMode.NONE) { val descriptorsToSkip: Set<DeclarationDescriptor> by lazy<Set<DeclarationDescriptor>> {
val parent = expressionWithType.parent val parent = expressionWithType.parent
when (parent) { when (parent) {
is KtBinaryExpression -> { is KtBinaryExpression -> {
@@ -216,6 +220,8 @@ class SmartCompletion(
ClassLiteralItems.addToCollection(items, expectedInfos, lookupElementFactory.basicFactory, isJvmModule) ClassLiteralItems.addToCollection(items, expectedInfos, lookupElementFactory.basicFactory, isJvmModule)
items.addNamedArgumentsWithLiteralValueItems(expectedInfos)
if (!forBasicCompletion) { if (!forBasicCompletion) {
LambdaItems.addToCollection(items, expectedInfos) LambdaItems.addToCollection(items, expectedInfos)
@@ -287,6 +293,47 @@ class SmartCompletion(
} }
} }
private fun MutableCollection<LookupElement>.addNamedArgumentsWithLiteralValueItems(expectedInfos: Collection<ExpectedInfo>) {
data class NameAndValue(val name: Name, val value: String, val priority: SmartCompletionItemPriority)
val nameAndValues = HashMap<NameAndValue, MutableList<ExpectedInfo>>()
fun addNameAndValue(name: Name, value: String, priority: SmartCompletionItemPriority, expectedInfo: ExpectedInfo) {
nameAndValues.getOrPut(NameAndValue(name, value, priority)) { ArrayList() }.add(expectedInfo)
}
for (expectedInfo in expectedInfos) {
val argumentData = expectedInfo.additionalData as? ArgumentPositionData.Positional ?: continue
if (argumentData.namedArgumentCandidates.isEmpty()) continue
val parameters = argumentData.function.valueParameters
if (argumentData.argumentIndex >= parameters.size) continue
val parameterName = parameters[argumentData.argumentIndex].name
if (expectedInfo.fuzzyType?.type?.isBooleanOrNullableBoolean() == true) {
addNameAndValue(parameterName, "true", SmartCompletionItemPriority.NAMED_ARGUMENT_TRUE, expectedInfo)
addNameAndValue(parameterName, "false", SmartCompletionItemPriority.NAMED_ARGUMENT_FALSE, expectedInfo)
}
if (expectedInfo.fuzzyType?.type?.isMarkedNullable == true) {
addNameAndValue(parameterName, "null", SmartCompletionItemPriority.NAMED_ARGUMENT_NULL, expectedInfo)
}
}
for ((nameAndValue, infos) in nameAndValues) {
var lookupElement = createNamedArgumentWithValueLookupElement(nameAndValue.name, nameAndValue.value, nameAndValue.priority)
lookupElement = lookupElement.addTail(mergeTails(infos.map { it.tail }))
add(lookupElement)
}
}
private fun createNamedArgumentWithValueLookupElement(name: Name, value: String, priority: SmartCompletionItemPriority): LookupElement {
val lookupElement = LookupElementBuilder.create("${name.asString()} = $value")
.withIcon(KotlinIcons.PARAMETER)
.withInsertHandler({ context, item -> context.document.replaceString(context.startOffset, context.tailOffset, "${name.render()} = $value") })
lookupElement.putUserData(SmartCompletionInBasicWeigher.NAMED_ARGUMENT_KEY, Unit)
lookupElement.assignSmartCompletionPriority(priority)
return lookupElement
}
private fun calcExpectedInfos(expression: KtExpression): Collection<ExpectedInfo> { private fun calcExpectedInfos(expression: KtExpression): Collection<ExpectedInfo> {
// if our expression is initializer of implicitly typed variable - take type of variable from original file (+ the same for function) // if our expression is initializer of implicitly typed variable - take type of variable from original file (+ the same for function)
val declaration = implicitlyTypedDeclarationFromInitializer(expression) val declaration = implicitlyTypedDeclarationFromInitializer(expression)
@@ -266,6 +266,8 @@ enum class SmartCompletionItemPriority {
IT, IT,
TRUE, TRUE,
FALSE, FALSE,
NAMED_ARGUMENT_TRUE,
NAMED_ARGUMENT_FALSE,
CLASS_LITERAL, CLASS_LITERAL,
THIS, THIS,
DELEGATES_STATIC_MEMBER, DELEGATES_STATIC_MEMBER,
@@ -278,6 +280,7 @@ enum class SmartCompletionItemPriority {
LAMBDA, LAMBDA,
CALLABLE_REFERENCE, CALLABLE_REFERENCE,
NULL, NULL,
NAMED_ARGUMENT_NULL,
INHERITOR_INSTANTIATION INHERITOR_INSTANTIATION
} }
@@ -0,0 +1,8 @@
fun foo(p: Int, flag: Boolean){}
fun bar() {
foo(1, <caret>)
}
// EXIST: { lookupString: "flag = true", itemText: "flag = true", attributes: "" }
// EXIST: { lookupString: "flag = false", itemText: "flag = false", attributes: "" }
@@ -10,4 +10,5 @@ fun f() {
// EXIST: { itemText:"xxP1 =", tailText: " String" } // EXIST: { itemText:"xxP1 =", tailText: " String" }
// EXIST: { itemText:"xxP2 =", tailText: " ..." } // EXIST: { itemText:"xxP2 =", tailText: " ..." }
// EXIST: { itemText:"xxx =", tailText: " Any?" } // EXIST: { itemText:"xxx =", tailText: " Any?" }
// EXIST: { itemText:"xxx = null", tailText: null }
// NOTHING_ELSE // NOTHING_ELSE
@@ -0,0 +1,8 @@
fun foo(p: Int, flag: Boolean, x: Int){}
fun foo(p: Int, flag: Boolean?, y: Char){}
fun bar() {
foo(1, <caret>)
}
// ELEMENT_TEXT: "flag = true"
@@ -0,0 +1,8 @@
fun foo(p: Int, flag: Boolean, x: Int){}
fun foo(p: Int, flag: Boolean?, y: Char){}
fun bar() {
foo(1, flag = true, <caret>)
}
// ELEMENT_TEXT: "flag = true"
@@ -0,0 +1,12 @@
fun foo(p: Int, flag: Boolean){}
fun bar() {
foo(1, <caret>)
}
// WITH_ORDER
// EXIST: { itemText: "true", attributes: "bold" }
// EXIST: { itemText: "false", attributes: "bold" }
// EXIST: { lookupString: "flag = true", itemText: "flag = true", attributes: "" }
// EXIST: { lookupString: "flag = false", itemText: "flag = false", attributes: "" }
// NOTHING_ELSE
@@ -0,0 +1,13 @@
fun foo(s: String?, flag: Boolean, x: Int){}
fun foo(xx: Boolean){}
fun bar() {
foo(<caret>)
}
// EXIST: { itemText: "null", attributes: "bold" }
// EXIST: { itemText: "true", attributes: "bold" }
// EXIST: { itemText: "false", attributes: "bold" }
// EXIST: { lookupString: "s = null", itemText: "s = null", attributes: "" }
// EXIST: { lookupString: "xx = true", itemText: "xx = true", attributes: "" }
// EXIST: { lookupString: "xx = false", itemText: "xx = false", attributes: "" }
@@ -11,4 +11,5 @@ fun f() {
// EXIST: { lookupString: "Int", itemText: "Int::class", attributes: "" } // EXIST: { lookupString: "Int", itemText: "Int::class", attributes: "" }
// EXIST: { lookupString: "object" } // EXIST: { lookupString: "object" }
// EXIST: null // EXIST: null
// EXIST: { lookupString: "kClass = null", itemText: "kClass = null" }
// NOTHING_ELSE // NOTHING_ELSE
@@ -14,6 +14,8 @@ fun foo(pFoo: Boolean, s: String) {
// ORDER: "pFoo, s" // ORDER: "pFoo, s"
// ORDER: true // ORDER: true
// ORDER: false // ORDER: false
// ORDER: "pFoo = true"
// ORDER: "pFoo = false"
// ORDER: local // ORDER: local
// ORDER: nonNullable // ORDER: nonNullable
// ORDER: nullableX // ORDER: nullableX
@@ -1809,6 +1809,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/namedArguments"), Pattern.compile("^(.+)\\.kt$"), true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/namedArguments"), Pattern.compile("^(.+)\\.kt$"), true);
} }
@TestMetadata("BooleanArgumentExpected.kt")
public void testBooleanArgumentExpected() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/BooleanArgumentExpected.kt");
doTest(fileName);
}
@TestMetadata("CompactTypeNames.kt") @TestMetadata("CompactTypeNames.kt")
public void testCompactTypeNames() throws Exception { public void testCompactTypeNames() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/CompactTypeNames.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/CompactTypeNames.kt");
@@ -1809,6 +1809,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/namedArguments"), Pattern.compile("^(.+)\\.kt$"), true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/namedArguments"), Pattern.compile("^(.+)\\.kt$"), true);
} }
@TestMetadata("BooleanArgumentExpected.kt")
public void testBooleanArgumentExpected() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/BooleanArgumentExpected.kt");
doTest(fileName);
}
@TestMetadata("CompactTypeNames.kt") @TestMetadata("CompactTypeNames.kt")
public void testCompactTypeNames() throws Exception { public void testCompactTypeNames() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/CompactTypeNames.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/namedArguments/CompactTypeNames.kt");
@@ -83,12 +83,24 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest(fileName); doTest(fileName);
} }
@TestMetadata("BooleanArgumentExpected.kt")
public void testBooleanArgumentExpected() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/BooleanArgumentExpected.kt");
doTest(fileName);
}
@TestMetadata("BooleanExpected.kt") @TestMetadata("BooleanExpected.kt")
public void testBooleanExpected() throws Exception { public void testBooleanExpected() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/BooleanExpected.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/BooleanExpected.kt");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("BooleanOrNullableArgumentExpected.kt")
public void testBooleanOrNullableArgumentExpected() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/BooleanOrNullableArgumentExpected.kt");
doTest(fileName);
}
@TestMetadata("ChainedCall.kt") @TestMetadata("ChainedCall.kt")
public void testChainedCall() throws Exception { public void testChainedCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/ChainedCall.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/ChainedCall.kt");
@@ -635,6 +635,12 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
doTest(fileName); doTest(fileName);
} }
@TestMetadata("NamedBooleanArgument.kt")
public void testNamedBooleanArgument() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NamedBooleanArgument.kt");
doTest(fileName);
}
@TestMetadata("NestedDataClass.kt") @TestMetadata("NestedDataClass.kt")
public void testNestedDataClass() throws Exception { public void testNestedDataClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NestedDataClass.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/NestedDataClass.kt");