Smart completion: multiple arguments at once

This commit is contained in:
Valentin Kipyatkov
2014-09-27 08:18:07 +04:00
parent a04efad059
commit 267aabae12
24 changed files with 315 additions and 25 deletions
@@ -39,6 +39,7 @@ public final class JetDescriptorIconProvider {
private JetDescriptorIconProvider() {
}
@NotNull
public static Icon getIcon(@NotNull DeclarationDescriptor descriptor, @Nullable PsiElement declaration, @Iconable.IconFlags int flags) {
if (declaration != null && !isKotlinDeclaration(declaration)) {
return declaration.getIcon(flags);
@@ -30,6 +30,7 @@ import org.jetbrains.jet.lang.descriptors.ClassKind
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
enum class ItemPriority {
MULTIPLE_ARGUMENTS_ITEM
DEFAULT
NAMED_PARAMETER
}
@@ -26,7 +26,6 @@ import com.intellij.lang.ASTNode
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.jet.lang.resolve.calls.util.CallMaker
import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo
import org.jetbrains.jet.lang.resolve.calls.context.ContextDependency
import org.jetbrains.jet.lang.resolve.calls.context.CheckValueArgumentsMode
import org.jetbrains.jet.lang.resolve.calls.CompositeExtension
@@ -35,7 +34,6 @@ import org.jetbrains.jet.lang.resolve.calls.results.OverloadResolutionResults
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import java.util.HashSet
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall
import org.jetbrains.jet.lang.descriptors.ModuleDescriptor
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.psi.JetBinaryExpression
import org.jetbrains.jet.lexer.JetTokens
@@ -44,7 +42,6 @@ import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lang.psi.JetContainerNode
import org.jetbrains.jet.plugin.completion.smart.isSubtypeOf
import org.jetbrains.jet.lang.resolve.calls.callUtil.noErrorsInValueArguments
import org.jetbrains.jet.lang.resolve.calls.callUtil.hasUnmappedParameters
import org.jetbrains.jet.lang.descriptors.Visibilities
import org.jetbrains.jet.lang.psi.JetBlockExpression
import org.jetbrains.jet.plugin.util.makeNotNullable
@@ -62,16 +59,14 @@ import org.jetbrains.jet.lang.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import org.jetbrains.jet.lang.psi.JetArrayAccessExpression
import org.jetbrains.jet.lang.psi.JetProperty
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor
import org.jetbrains.jet.lang.descriptors.VariableDescriptor
import org.jetbrains.jet.lang.psi.JetFunction
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody
import org.jetbrains.jet.lang.descriptors.PropertyAccessorDescriptor
import org.jetbrains.jet.lang.psi.JetReturnExpression
import org.jetbrains.jet.lang.resolve.bindingContextUtil.getTargetFunctionDescriptor
import org.jetbrains.jet.plugin.completion.smart.toList
import org.jetbrains.jet.lang.descriptors.PropertyGetterDescriptor
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor
import org.jetbrains.jet.plugin.project.ResolveSessionForBodies
import org.jetbrains.jet.lang.descriptors.VariableDescriptor
enum class Tail {
COMMA
@@ -79,9 +74,19 @@ enum class Tail {
ELSE
}
data class ExpectedInfo(val `type`: JetType, val name: String?, val tail: Tail?)
open data class ExpectedInfo(val `type`: JetType, val name: String?, val tail: Tail?)
class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: ModuleDescriptor) {
class PositionalArgumentExpectedInfo(`type`: JetType, name: String?, tail: Tail?, val function: FunctionDescriptor, val argumentIndex: Int)
: ExpectedInfo(`type`, name, tail) {
override fun equals(other: Any?)
= other is PositionalArgumentExpectedInfo && super.equals(other) && function == other.function && argumentIndex == other.argumentIndex
override fun hashCode()
= function.hashCode()
}
class ExpectedInfos(val bindingContext: BindingContext, val resolveSession: ResolveSessionForBodies) {
public fun calculate(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
return calculateForArgument(expressionWithType)
?: calculateForFunctionLiteralArgument(expressionWithType)
@@ -156,7 +161,7 @@ class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: Mo
CheckValueArgumentsMode.ENABLED,
CompositeExtension(listOf()),
false).replaceCollectAllCandidates(true)
val callResolver = InjectorForMacros(callElement.getProject(), moduleDescriptor).getCallResolver()!!
val callResolver = InjectorForMacros(callElement.getProject(), resolveSession.getModuleDescriptor()).getCallResolver()!!
val results: OverloadResolutionResults<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
val expectedInfos = HashSet<ExpectedInfo>()
@@ -172,7 +177,7 @@ class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: Mo
val tail = if (isFunctionLiteralArgument)
null
else if (argumentIndex == parameters.lastIndex)
Tail.RPARENTH
Tail.RPARENTH //TODO: support square brackets
else if (parameters.drop(argumentIndex + 1).all { it.hasDefaultValue() || it.getVarargElementType() != null })
null
else
@@ -180,7 +185,7 @@ class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: Mo
val parameter = parameters[argumentIndex]
val expectedName = if (descriptor.hasSynthesizedParameterNames()) null else parameter.getName().asString()
expectedInfos.add(ExpectedInfo(parameter.getType(), expectedName, tail))
expectedInfos.add(PositionalArgumentExpectedInfo(parameter.getType(), expectedName, tail, descriptor, argumentIndex))
}
}
return expectedInfos
@@ -273,14 +278,14 @@ class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: Mo
private fun calculateForInitializer(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val property = expressionWithType.getParent() as? JetProperty ?: return null
if (expressionWithType != property.getInitializer()) return null
val propertyDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, property] as? VariableDescriptor ?: return null
val propertyDescriptor = resolveSession.resolveToDescriptor(property) as? VariableDescriptor ?: return null
return listOf(ExpectedInfo(propertyDescriptor.getType(), propertyDescriptor.getName().asString(), null))
}
private fun calculateForExpressionBody(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val declaration = expressionWithType.getParent() as? JetDeclarationWithBody ?: return null
if (expressionWithType != declaration.getBodyExpression() || declaration.hasBlockBody()) return null
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] as? FunctionDescriptor ?: return null
val descriptor = resolveSession.resolveToDescriptor(declaration) as? FunctionDescriptor ?: return null
return functionReturnValueExpectedInfo(descriptor).toList()
}
@@ -88,7 +88,7 @@ private fun needExplicitParameterTypes(context: InsertionContext, placeholderRan
val resolveSession = file.getLazyResolveSession()
val bindingContext = resolveSession.resolveToElement(expression)
val expectedInfos = ExpectedInfos(bindingContext, resolveSession.getModuleDescriptor()).calculate(expression) ?: return false
val expectedInfos = ExpectedInfos(bindingContext, resolveSession).calculate(expression) ?: return false
val functionTypes = expectedInfos.map { it.`type` }.filter { KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(it) }.toSet()
if (functionTypes.size <= 1) return false
@@ -0,0 +1,93 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.plugin.completion.smart
import org.jetbrains.jet.plugin.completion.ExpectedInfo
import org.jetbrains.jet.lang.psi.JetExpression
import com.intellij.codeInsight.lookup.LookupElement
import org.jetbrains.jet.lang.types.checker.JetTypeChecker
import com.intellij.ui.LayeredIcon
import org.jetbrains.jet.renderer.DescriptorRenderer
import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.jet.plugin.completion.Tail
import org.jetbrains.jet.plugin.completion.ItemPriority
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.resolve.BindingContext
import java.util.HashSet
import org.jetbrains.jet.lang.resolve.scopes.JetScope
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor
import org.jetbrains.jet.plugin.JetDescriptorIconProvider
import org.jetbrains.jet.lang.descriptors.VariableDescriptor
import org.jetbrains.jet.plugin.completion.PositionalArgumentExpectedInfo
import org.jetbrains.jet.plugin.completion.assignPriority
import java.util.ArrayList
class MultipleArgumentsItemProvider(val bindingContext: BindingContext,
val typesWithAutoCasts: (DeclarationDescriptor) -> Iterable<JetType>) {
public fun addToCollection(collection: MutableCollection<LookupElement>,
expectedInfos: Collection<ExpectedInfo>,
context: JetExpression) {
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, context] ?: return
val added = HashSet<String>()
for (expectedInfo in expectedInfos) {
if (expectedInfo is PositionalArgumentExpectedInfo && expectedInfo.argumentIndex == 0) {
val parameters = expectedInfo.function.getValueParameters()
if (parameters.size > 1) {
val variables = ArrayList<VariableDescriptor>()
for ((i, parameter) in parameters.withIndices()) {
val variable = variableInScope(parameter, resolutionScope) ?: break
variables.add(variable) // TODO: cannot inline variable because of KT-5890
if (i > 0 && parameters.stream().drop(i + 1).all { it.hasDefaultValue() }) { // this is the last parameter or all others have default values
val lookupElement = createParametersLookupElement(variables)
if (added.add(lookupElement.getLookupString())) { // check that we don't already have item with the same text
collection.add(lookupElement)
}
}
}
}
}
}
}
private fun createParametersLookupElement(variables: List<VariableDescriptor>): LookupElement {
val compoundIcon = LayeredIcon(2)
val firstIcon = JetDescriptorIconProvider.getIcon(variables.first(), null, 0)
val lastIcon = JetDescriptorIconProvider.getIcon(variables.last(), null, 0)
compoundIcon.setIcon(lastIcon, 0, 2 * firstIcon.getIconWidth() / 5, 0)
compoundIcon.setIcon(firstIcon, 1, 0, 0)
return LookupElementBuilder
.create(variables.map { DescriptorRenderer.SOURCE_CODE.renderName(it.getName()) }.joinToString(", "))
.withIcon(compoundIcon)
.addTail(Tail.RPARENTH) //TODO: support square brackets
.assignPriority(ItemPriority.MULTIPLE_ARGUMENTS_ITEM)
}
private fun variableInScope(parameter: ValueParameterDescriptor, scope: JetScope): VariableDescriptor? {
val name = parameter.getName()
//TODO: there can be more than one property with such name in scope and we should be able to select one (but we need API for this)
val variable = scope.getLocalVariable(name) ?: scope.getProperties(name).singleOrNull() ?: return null
return if (typesWithAutoCasts(variable).any { JetTypeChecker.DEFAULT.isSubtypeOf(it, parameter.getType()) })
variable
else
null
}
}
@@ -39,7 +39,6 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
val visibilityFilter: (DeclarationDescriptor) -> Boolean,
val originalFile: JetFile) {
private val bindingContext = resolveSession.resolveToElement(expression)
private val moduleDescriptor = resolveSession.getModuleDescriptor()
private val project = expression.getProject()
public data class Result(val declarationFilter: ((DeclarationDescriptor) -> Collection<LookupElement>)?,
@@ -136,6 +135,8 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
LambdaItems.addToCollection(additionalItems, functionExpectedInfos)
KeywordValues.addToCollection(additionalItems, filteredExpectedInfos/* use filteredExpectedInfos to not include null after == */, expressionWithType)
MultipleArgumentsItemProvider(bindingContext, typesWithAutoCasts).addToCollection(additionalItems, expectedInfos, expression)
}
return Result(::filterDeclaration, additionalItems)
@@ -154,7 +155,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
}
}
return ExpectedInfos(bindingContext, moduleDescriptor).calculate(expression)
return ExpectedInfos(bindingContext, resolveSession).calculate(expression)
}
private fun implicitlyTypedDeclarationFromInitializer(expression: JetExpression): JetDeclaration? {
@@ -172,7 +173,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
is JetProperty -> {
//TODO: this can be filtered out by ordinary completion
if (expression == parent.getInitializer()) {
return resolveSession.resolveToElement(parent)[BindingContext.DECLARATION_TO_DESCRIPTOR, parent].toSet()
return resolveSession.resolveToDescriptor(parent).toSet()
}
}
@@ -0,0 +1,7 @@
fun foo(a: Int, b: String, c: String) {}
fun bar(b: String, a: Int, c: String) {
foo(<caret>)
}
// ELEMENT: "a, b, c"
@@ -0,0 +1,7 @@
fun foo(a: Int, b: String, c: String) {}
fun bar(b: String, a: Int, c: String) {
foo(a, b, c)<caret>
}
// ELEMENT: "a, b, c"
@@ -0,0 +1,7 @@
fun foo(a: Int, b: String, c: String) {}
fun bar(b: String, a: Int, c: String) {
foo(<caret>)
}
// EXIST: "a, b, c"
@@ -0,0 +1,11 @@
fun f(p1: String, p2: Int, p3: Any){}
class C(val p1: String) {
val p3: String = ""
fun foo(p2: Int) {
f(<caret>)
}
}
// EXIST: "p1, p2, p3"
@@ -0,0 +1,9 @@
fun foo(a: Int, b: String, c: String = "", d: Int= 0) {}
fun bar(b: String, a: Int, c: String, d: Int) {
foo(<caret>)
}
// EXIST: "a, b"
// EXIST: "a, b, c"
// EXIST: "a, b, c, d"
@@ -0,0 +1,11 @@
class B {
open fun foo(p1: String, p2: String) { }
}
class C : B() {
override fun foo(p1: String, p2: String) {
super.foo(<caret>)
}
}
// EXIST: "p1, p2"
@@ -0,0 +1,5 @@
class B(p1: String, p2: String)
class C(p1: String, p2: String, p3: String) : B(<caret>)
// EXIST: "p1, p2"
@@ -0,0 +1,7 @@
fun foo(a: Int, b: Any, c: String?) {}
fun bar(b: String, a: Int, c: String) {
foo(<caret>)
}
// EXIST: "a, b, c"
@@ -0,0 +1,7 @@
fun foo(a: Int, b: String, c: String) {}
fun bar(b: String, a: Int, c: String?) {
foo(<caret>)
}
// ABSENT: "a, b, c"
@@ -0,0 +1,9 @@
fun foo(a: Int, b: String, c: String) {}
fun bar(b: String, a: Int, c: String?) {
if (c != null) {
foo(<caret>)
}
}
// EXIST: "a, b, c"
@@ -0,0 +1,9 @@
fun foo(a: Int, b: String, c: String) {}
fun foo(a: Int, b: String) {}
fun bar(b: String, a: Int, c: String) {
foo(<caret>)
}
// EXIST: "a, b, c"
// EXIST: "a, b"
@@ -0,0 +1,9 @@
fun foo(a: Int, b: String) {}
fun f(p: (Int, String) -> Unit){}
fun bar() {
f { (a, b) -> foo(<caret>) }
}
// EXIST: "a, b"
@@ -0,0 +1,9 @@
fun foo(a: Int, b: String) {}
fun bar(a: Int, b: String) {
fun local() {
foo(<caret>)
}
}
// EXIST: "a, b"
@@ -0,0 +1,8 @@
fun foo(a: Int, b: String, c: String) {}
fun bar(b: String, a: Int, c: String) {
foo(<caret>)
}
// ORDER: "a, b, c"
// ORDER: a
@@ -14,10 +14,10 @@
* limitations under the License.
*/
fun f(fooBar1: String, fooBar2: String){}
fun f(x: Int, fooBar1: String, fooBar2: String){}
fun g(someBar0: String, someBar1: String, someBar2: String, fooBar: String, fooBar0: String, fooBar1: String, fooBar2: String) {
f(<caret>)
f(1, <caret>)
}
// ORDER: fooBar1, fooBar, fooBar0, fooBar2, someBar1, someBar0, someBar2
@@ -17,13 +17,9 @@
package org.jetbrains.jet.completion;
import com.intellij.testFramework.TestDataPath;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.junit.runner.RunWith;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.jetbrains.jet.JUnit3RunnerWithInners;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
@@ -446,6 +442,72 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest(fileName);
}
@TestMetadata("MultipleArgsItem1.kt")
public void testMultipleArgsItem1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem1.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem10.kt")
public void testMultipleArgsItem10() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem10.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem11.kt")
public void testMultipleArgsItem11() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem11.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem2.kt")
public void testMultipleArgsItem2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem2.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem3.kt")
public void testMultipleArgsItem3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem3.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem4.kt")
public void testMultipleArgsItem4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem4.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem5.kt")
public void testMultipleArgsItem5() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem5.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem6.kt")
public void testMultipleArgsItem6() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem6.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem7.kt")
public void testMultipleArgsItem7() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem7.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem8.kt")
public void testMultipleArgsItem8() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem8.kt");
doTest(fileName);
}
@TestMetadata("MultipleArgsItem9.kt")
public void testMultipleArgsItem9() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/MultipleArgsItem9.kt");
doTest(fileName);
}
@TestMetadata("NoConstructorWithQualifier.kt")
public void testNoConstructorWithQualifier() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/NoConstructorWithQualifier.kt");
@@ -452,6 +452,12 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
doTest(fileName);
}
@TestMetadata("MultipleArgsItem.kt")
public void testMultipleArgsItem() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/smart/MultipleArgsItem.kt");
doTest(fileName);
}
@TestMetadata("NullableValue1.kt")
public void testNullableValue1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/handlers/smart/NullableValue1.kt");
@@ -38,6 +38,12 @@ public class SmartCompletionWeigherTestGenerated extends AbstractSmartCompletion
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/completion/weighers/smart"), Pattern.compile("^([^\\.]+)\\.kt$"), true);
}
@TestMetadata("MultipleArgsItem.kt")
public void testMultipleArgsItem() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/weighers/smart/MultipleArgsItem.kt");
doTest(fileName);
}
@TestMetadata("NameSimilarity1.kt")
public void testNameSimilarity1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/weighers/smart/NameSimilarity1.kt");