KT-5072 Values of extension function type are not seen in completion

#KT-5072 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-11-13 19:42:43 +03:00
parent 2e2dfcb4a0
commit 927df1c4cd
23 changed files with 305 additions and 11 deletions
@@ -34,13 +34,13 @@ import org.jetbrains.kotlin.types.typeUtil.TypeNullability
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.types.typeUtil.nullability
public fun CallableDescriptor.substituteExtensionIfCallable(
public fun <TCallable : CallableDescriptor> TCallable.substituteExtensionIfCallable(
receivers: Collection<ReceiverValue>,
context: BindingContext,
dataFlowInfo: DataFlowInfo,
callType: CallType<*>,
containingDeclarationOrModule: DeclarationDescriptor
): Collection<CallableDescriptor> {
): Collection<TCallable> {
val sequence = receivers.asSequence().flatMap { substituteExtensionIfCallable(it, callType, context, dataFlowInfo, containingDeclarationOrModule).asSequence() }
if (getTypeParameters().isEmpty()) { // optimization for non-generic callables
return sequence.firstOrNull()?.let { listOf(it) } ?: listOf()
@@ -50,32 +50,32 @@ public fun CallableDescriptor.substituteExtensionIfCallable(
}
}
public fun CallableDescriptor.substituteExtensionIfCallableWithImplicitReceiver(
public fun <TCallable : CallableDescriptor> TCallable.substituteExtensionIfCallableWithImplicitReceiver(
scope: LexicalScope,
context: BindingContext,
dataFlowInfo: DataFlowInfo
): Collection<CallableDescriptor> {
): Collection<TCallable> {
val receiverValues = scope.getImplicitReceiversWithInstance().map { it.getValue() }
return substituteExtensionIfCallable(receiverValues, context, dataFlowInfo, CallType.DEFAULT, scope.ownerDescriptor)
}
public fun CallableDescriptor.substituteExtensionIfCallable(
public fun <TCallable : CallableDescriptor> TCallable.substituteExtensionIfCallable(
receiver: ReceiverValue,
callType: CallType<*>,
bindingContext: BindingContext,
dataFlowInfo: DataFlowInfo,
containingDeclarationOrModule: DeclarationDescriptor
): Collection<CallableDescriptor> {
): Collection<TCallable> {
if (!receiver.exists()) return listOf()
var types = SmartCastManager().getSmartCastVariants(receiver, bindingContext, containingDeclarationOrModule, dataFlowInfo)
return substituteExtensionIfCallable(types, callType)
}
public fun CallableDescriptor.substituteExtensionIfCallable(
public fun <TCallable : CallableDescriptor> TCallable.substituteExtensionIfCallable(
receiverTypes: Collection<KotlinType>,
callType: CallType<*>
): Collection<CallableDescriptor> {
): Collection<TCallable> {
if (!callType.descriptorKindFilter.accepts(this)) return listOf()
var types = receiverTypes.asSequence()
@@ -98,7 +98,7 @@ public fun CallableDescriptor.substituteExtensionIfCallable(
return if (substitutors.any()) listOf(this) else listOf()
}
else {
return substitutors.map { substitute(it)!! }.toList()
return substitutors.map { @Suppress("UNCHECKED_CAST") (substitute(it) as TCallable) }.toList()
}
}
@@ -207,6 +207,15 @@ class BasicCompletionSession(
val contextVariablesProvider = RealContextVariablesProvider(referenceVariantsHelper, position)
withContextVariablesProvider(contextVariablesProvider) { lookupElementFactory ->
if (receiverTypes != null) {
ExtensionFunctionTypeValueCompletion(receiverTypes, callTypeAndReceiver.callType, lookupElementFactory)
.processVariables(contextVariablesProvider)
.forEach {
val lookupElements = it.factory.createStandardLookupElementsForDescriptor(it.invokeDescriptor, useReceiverTypes = true)
collector.addElements(lookupElements)
}
}
if (contextVariableTypesForSmartCompletion.any { contextVariablesProvider.functionTypeVariables(it).isNotEmpty() }) {
completeWithSmartCompletion(lookupElementFactory)
}
@@ -37,7 +37,8 @@ class RealContextVariablesProvider(
private val referenceVariantsHelper: ReferenceVariantsHelper,
private val contextElement: PsiElement
) : ContextVariablesProvider {
private val functionTypeVariables by lazy {
val allFunctionTypeVariables by lazy {
collectVariables().filter { KotlinBuiltIns.isFunctionOrExtensionFunctionType(it.type) }
}
@@ -49,7 +50,7 @@ class RealContextVariablesProvider(
override fun functionTypeVariables(requiredType: FuzzyType): Collection<Pair<VariableDescriptor, TypeSubstitutor>> {
val result = SmartList<Pair<VariableDescriptor, TypeSubstitutor>>()
for (variable in functionTypeVariables) {
for (variable in allFunctionTypeVariables) {
val substitutor = variable.fuzzyReturnType()?.checkIsSubtypeOf(requiredType) ?: continue
result.add(variable to substitutor)
}
@@ -0,0 +1,95 @@
/*
* Copyright 2010-2015 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.kotlin.idea.completion
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.resolve.calls.tasks.createSynthesizedInvokes
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.util.OperatorNameConventions
import java.util.*
class ExtensionFunctionTypeValueCompletion(
private val receiverTypes: Collection<KotlinType>,
private val callType: CallType<*>,
private val lookupElementFactory: LookupElementFactory
) {
data class Result(val invokeDescriptor: FunctionDescriptor, val factory: AbstractLookupElementFactory)
fun processVariables(variablesProvider: RealContextVariablesProvider): Collection<Result> {
if (callType != CallType.DOT && callType != CallType.SAFE) return emptyList()
val results = ArrayList<Result>()
for (variable in variablesProvider.allFunctionTypeVariables) {
val variableType = variable.type
if (!KotlinBuiltIns.isExtensionFunctionType(variableType)) continue
val invokes = variableType.memberScope.getContributedFunctions(OperatorNameConventions.INVOKE, NoLookupLocation.FROM_IDE)
for (invoke in createSynthesizedInvokes(invokes)) {
for (substituted in invoke.substituteExtensionIfCallable(receiverTypes, callType)) {
val factory = object : AbstractLookupElementFactory {
override fun createStandardLookupElementsForDescriptor(descriptor: DeclarationDescriptor, useReceiverTypes: Boolean): Collection<LookupElement> {
if (!useReceiverTypes) return emptyList()
descriptor as FunctionDescriptor // should be descriptor for "invoke"
val invokeLookupElement = lookupElementFactory.createLookupElement(substituted, useReceiverTypes = true)
val variableLookupElement = lookupElementFactory.createLookupElement(variable, useReceiverTypes = false)
val insertHandler = lookupElementFactory.insertHandlerProvider.insertHandler(invoke)
val lookupElement = object : LookupElementDecorator<LookupElement>(variableLookupElement) {
override fun renderElement(presentation: LookupElementPresentation) {
invokeLookupElement.renderElement(presentation)
presentation.itemText = variable.name.asString()
val parameterTail = presentation.tailFragments.first()
presentation.clearTail()
presentation.appendTailText(parameterTail.text, false)
}
override fun handleInsert(context: InsertionContext?) {
insertHandler.handleInsert(context, this)
}
}
return listOf(lookupElement)
}
override fun createLookupElement(
descriptor: DeclarationDescriptor,
useReceiverTypes: Boolean,
qualifyNestedClasses: Boolean,
includeClassTypeArguments: Boolean,
parametersAndTypeGrayed: Boolean): LookupElement? = null
}
results.add(Result(substituted, factory))
}
}
}
return results
}
}
@@ -97,6 +97,14 @@ class SmartCompletionSession(
val contextVariablesProvider = RealContextVariablesProvider(referenceVariantsHelper, position)
withContextVariablesProvider(contextVariablesProvider) { lookupElementFactory ->
if (filter != null && receiverTypes != null) {
val results = ExtensionFunctionTypeValueCompletion(receiverTypes, callTypeAndReceiver.callType, lookupElementFactory)
.processVariables(contextVariablesProvider)
for ((invokeDescriptor, factory) in results) {
collector.addElements(filter(invokeDescriptor, factory))
}
}
if (contextVariableTypesForAdditionalItems.any { contextVariablesProvider.functionTypeVariables(it).isNotEmpty() }) {
val additionalItems = smartCompletion!!.additionalItems(lookupElementFactory).first
collector.addElements(additionalItems)
@@ -0,0 +1,8 @@
class C
fun C.test(foo: C.() -> Unit) {
fo<caret>
}
// EXIST: { lookupString: "foo", itemText: "foo", tailText: null, typeText: "C.() -> Unit" }
// ABSENT: { itemText: "foo", typeText: "Unit" }
@@ -0,0 +1,5 @@
fun test(i: Int?, foo: Int.(String) -> Char) {
i?.fo<caret>
}
// EXIST: { lookupString: "foo", itemText: "foo", tailText: "(String)", typeText: "Char", attributes: "bold" }
@@ -0,0 +1,6 @@
fun test(i: Int, foo: Int.(String) -> Char, fooAny: Any.() -> Unit) {
i.fo<caret>
}
// EXIST: { lookupString: "foo", itemText: "foo", tailText: "(String)", typeText: "Char", attributes: "bold" }
// EXIST: { lookupString: "fooAny", itemText: "fooAny", tailText: "()", typeText: "Unit", attributes: "" }
@@ -0,0 +1,11 @@
interface I
interface J
fun test(p: I, fooI: I.() -> Unit, fooJ: J.() -> Unit) {
if (p is J) {
p.fo<caret>
}
}
// EXIST: { lookupString: "fooI", itemText: "fooI", tailText: "()", typeText: "Unit", attributes: "bold" }
// EXIST: { lookupString: "fooJ", itemText: "fooJ", tailText: "()", typeText: "Unit", attributes: "bold" }
@@ -0,0 +1,5 @@
fun test(i: Int, foo: Int.() -> Char) {
i.<caret>
}
// ELEMENT: foo
@@ -0,0 +1,5 @@
fun test(i: Int, foo: Int.() -> Char) {
i.foo()<caret>
}
// ELEMENT: foo
@@ -0,0 +1,5 @@
fun test(i: Int, foo: Int.(String) -> Char) {
i.<caret>
}
// ELEMENT: foo
@@ -0,0 +1,5 @@
fun test(i: Int, foo: Int.(String) -> Char) {
i.foo(<caret>)
}
// ELEMENT: foo
@@ -0,0 +1,7 @@
fun test(i: Int, foo: Int.() -> Char) {
bar(i.<caret>)
}
fun bar(p1: Char, p2: Int){}
// ELEMENT: foo
@@ -0,0 +1,7 @@
fun test(i: Int, foo: Int.() -> Char) {
bar(i.foo(), <caret>)
}
fun bar(p1: Char, p2: Int){}
// ELEMENT: foo
@@ -0,0 +1,7 @@
fun test(i: Int, foo: Int.(String) -> Char) {
bar(i.<caret>)
}
fun bar(p1: Char, p2: Int){}
// ELEMENT: foo
@@ -0,0 +1,7 @@
fun test(i: Int, foo: Int.(String) -> Char) {
bar(i.foo(<caret>), )
}
fun bar(p1: Char, p2: Int){}
// ELEMENT: foo
@@ -0,0 +1,7 @@
fun test(i: Int, foo1: Int.(String) -> Char, foo2: Int.() -> Int, foo3: String.() -> Char): Char {
return i.<caret>
}
// EXIST: { lookupString: "foo1", itemText: "foo1", tailText: "(String)", typeText: "Char", attributes: "bold" }
// ABSENT: foo2
// ABSENT: foo3
@@ -1201,6 +1201,39 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ExtensionFunctionTypeValues extends AbstractJSBasicCompletionTest {
public void testAllFilesPresentInExtensionFunctionTypeValues() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("ImplicitReceiver.kt")
public void testImplicitReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/ImplicitReceiver.kt");
doTest(fileName);
}
@TestMetadata("SafeCall.kt")
public void testSafeCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/SafeCall.kt");
doTest(fileName);
}
@TestMetadata("Simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/Simple.kt");
doTest(fileName);
}
@TestMetadata("SmartCast.kt")
public void testSmartCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/SmartCast.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/extensions")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -1201,6 +1201,39 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ExtensionFunctionTypeValues extends AbstractJvmBasicCompletionTest {
public void testAllFilesPresentInExtensionFunctionTypeValues() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("ImplicitReceiver.kt")
public void testImplicitReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/ImplicitReceiver.kt");
doTest(fileName);
}
@TestMetadata("SafeCall.kt")
public void testSafeCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/SafeCall.kt");
doTest(fileName);
}
@TestMetadata("Simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/Simple.kt");
doTest(fileName);
}
@TestMetadata("SmartCast.kt")
public void testSmartCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/SmartCast.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/extensions")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -131,6 +131,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest(fileName);
}
@TestMetadata("ExtensionFunctionTypeVariables.kt")
public void testExtensionFunctionTypeVariables() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/ExtensionFunctionTypeVariables.kt");
doTest(fileName);
}
@TestMetadata("FilterTo.kt")
public void testFilterTo() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smart/FilterTo.kt");
@@ -71,6 +71,18 @@ public class BasicCompletionHandlerTestGenerated extends AbstractBasicCompletion
doTest(fileName);
}
@TestMetadata("ExtensionFunctionTypeVariable1.kt")
public void testExtensionFunctionTypeVariable1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable1.kt");
doTest(fileName);
}
@TestMetadata("ExtensionFunctionTypeVariable2.kt")
public void testExtensionFunctionTypeVariable2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt");
doTest(fileName);
}
@TestMetadata("ExtensionReceiverTypeArg.kt")
public void testExtensionReceiverTypeArg() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/basic/ExtensionReceiverTypeArg.kt");
@@ -383,6 +383,18 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
doTest(fileName);
}
@TestMetadata("ExtensionFunctionTypeVariable1.kt")
public void testExtensionFunctionTypeVariable1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/ExtensionFunctionTypeVariable1.kt");
doTest(fileName);
}
@TestMetadata("ExtensionFunctionTypeVariable2.kt")
public void testExtensionFunctionTypeVariable2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/ExtensionFunctionTypeVariable2.kt");
doTest(fileName);
}
@TestMetadata("ForLoopRange.kt")
public void testForLoopRange() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/handlers/smart/ForLoopRange.kt");