KT-6376 Smart completion should work after "in" and "!in"

#KT-6376 Fixed
This commit is contained in:
Valentin Kipyatkov
2015-01-16 23:01:12 +03:00
parent 8f688ab7b7
commit 6d06436c34
14 changed files with 264 additions and 2 deletions
@@ -43,7 +43,7 @@ fun FuzzyType.makeNotNullable() = FuzzyType(type.makeNotNullable(), freeParamete
fun FuzzyType.makeNullable() = FuzzyType(type.makeNullable(), freeParameters)
fun FuzzyType.nullability() = type.nullability()
class FuzzyType(
data class FuzzyType(
val type: JetType,
val freeParameters: Collection<TypeParameterDescriptor>
) {
@@ -117,6 +117,9 @@ class SmartCompletion(
val loopRangePositionResult = buildForLoopRangePosition(expressionWithType, receiver)
if (loopRangePositionResult != null) return loopRangePositionResult
val inOperatorArgumentResult = buildForInOperatorArgument(expressionWithType, receiver)
if (inOperatorArgumentResult != null) return inOperatorArgumentResult
val allExpectedInfos = calcExpectedInfos(expressionWithType) ?: return null
val filteredExpectedInfos = allExpectedInfos.filter { !it.type.isError() }
if (filteredExpectedInfos.isEmpty()) return null
@@ -409,6 +412,39 @@ class SmartCompletion(
return Result(::filterDeclaration, listOf(), null)
}
//TODO: reuse code from above
private fun buildForInOperatorArgument(expressionWithType: JetExpression, receiver: JetExpression?): Result? {
val binaryExpression = expressionWithType.getParent() as? JetBinaryExpression ?: return null
val operationToken = binaryExpression.getOperationToken()
if (operationToken != JetTokens.IN_KEYWORD && operationToken != JetTokens.NOT_IN || expressionWithType != binaryExpression.getRight()) return null
val leftOperandType = bindingContext.get(BindingContext.EXPRESSION_TYPE, binaryExpression.getLeft()) ?: return null
val smartCastTypes: (VariableDescriptor) -> Collection<JetType> = SmartCastCalculator(bindingContext).calculate(expressionWithType, receiver)
val scope = bindingContext.get(BindingContext.RESOLUTION_SCOPE, expressionWithType)
val detector = TypesWithContainsDetector(scope, leftOperandType)
fun filterDeclaration(descriptor: DeclarationDescriptor): Collection<LookupElement> {
val types = descriptor.fuzzyTypes(smartCastTypes)
fun createLookupElement() = lookupElementFactory.createLookupElement(descriptor, resolutionFacade, bindingContext, true)
if (types.any { detector.hasContains(it) }) {
return listOf(createLookupElement())
}
if (types.any { it.nullability() == TypeNullability.NULLABLE && detector.hasContains(it.makeNotNullable()) }) {
return lookupElementsForNullable(::createLookupElement)
}
return listOf()
}
return Result(::filterDeclaration, listOf(), null)
}
private fun lookupElementForType(jetType: JetType): LookupElement? {
if (jetType.isError()) return null
val classifier = jetType.getConstructor().getDeclarationDescriptor() ?: return null
@@ -0,0 +1,60 @@
/*
* 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.smart
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.resolve.scopes.JetScope
import java.util.HashMap
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.idea.util.nullability
import org.jetbrains.kotlin.idea.util.TypeNullability
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
//TODO: heuristics for collection's?
class TypesWithContainsDetector(
private val scope: JetScope,
private val argumentType: JetType
) {
private val cache = HashMap<FuzzyType, Boolean>()
private val containsName = Name.identifier("contains")
private val booleanType = KotlinBuiltIns.getInstance().getBooleanType()
private val typesWithExtensionContains: Collection<JetType> = scope.getFunctions(containsName)
.filter { it.getExtensionReceiverParameter() != null && isGoodContainsFunction(it, listOf()) }
.map { it.getExtensionReceiverParameter()!!.getType() }
public fun hasContains(type: FuzzyType): Boolean {
return cache.getOrPut(type, { hasContainsNoCache(type) })
}
private fun hasContainsNoCache(type: FuzzyType): Boolean {
return type.nullability() != TypeNullability.NULLABLE && type.type.getMemberScope().getFunctions(containsName).any { isGoodContainsFunction(it, type.freeParameters) }
|| typesWithExtensionContains.any { type.checkIsSubtypeOf(it) != null }
}
private fun isGoodContainsFunction(function: FunctionDescriptor, freeTypeParams: Collection<TypeParameterDescriptor>): Boolean {
if (!TypeUtils.equalTypes(function.getReturnType(), booleanType)) return false
val parameter = function.getValueParameters().singleOrNull() ?: return false
val parameterType = FuzzyType(parameter.getType(), function.getTypeParameters() + freeTypeParams)
return parameterType.checkIsSuperTypeOf(argumentType) != null
}
}
@@ -0,0 +1,13 @@
trait X
trait Y : X
trait Z
fun X.contains(s: String): Boolean
fun foo(s: String, x: X, y: Y, z: Z) {
if (s in <caret>)
}
// EXIST: x
// EXIST: y
// ABSENT: z
@@ -0,0 +1,19 @@
trait X {
fun contains(s: String): Boolean
}
trait Y {
fun contains(i: Int): Boolean
}
trait Z {
fun contains(o: Any): Boolean
}
fun foo(s: String, x: X, y: Y, z: Z) {
if (s in <caret>)
}
// EXIST: x
// ABSENT: y
// EXIST: z
@@ -0,0 +1,5 @@
fun foo(s: String) {
if (s in <caret>)
}
// EXIST: { lookupString:"listOf", itemText: "listOf", tailText: "(vararg values: T) (kotlin)", typeText:"List<T>" }
@@ -0,0 +1,13 @@
trait X<T> {
fun contains(t: T): Boolean
}
trait A {
fun<T> createX(t: T): X<T>
fun foo(s: String) {
if (s in <caret>)
}
}
// EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: T)", typeText:"X<T>" }
@@ -0,0 +1,13 @@
trait X<T>
fun<T> X<T>.contains(t: T): Boolean
trait A {
fun<T> createX(t: T): X<T>
fun foo(s: String) {
if (s in <caret>)
}
}
// EXIST: { lookupString:"createX", itemText: "createX", tailText: "(t: T)", typeText:"X<T>" }
@@ -0,0 +1,9 @@
trait X {
fun contains(s: String): Boolean?
}
fun foo(s: String, x: X) {
if (s in <caret>)
}
// ABSENT: x
@@ -0,0 +1,6 @@
fun foo(s: String, list: List<String>, o: Any) {
if (s !in <caret>)
}
// EXIST: list
// ABSENT: o
@@ -0,0 +1,7 @@
fun foo(s: String, list: List<String>?) {
if (s in <caret>)
}
// ABSENT: { lookupString:"list", itemText: "list" }
// EXIST: { lookupString:"list", itemText: "!! list", typeText:"List<String>?" }
// EXIST: { lookupString:"list", itemText: "?: list", typeText:"List<String>?" }
@@ -0,0 +1,6 @@
fun foo(s: String, list: List<String>, o: Any) {
if (s in <caret>)
}
// EXIST: list
// ABSENT: o
@@ -0,0 +1,6 @@
fun foo(s: String, o1: Any, o2: Any) {
if (o1 is Collection<String> && s in <caret>
}
// EXIST: o1
// ABSENT: o2
@@ -30,7 +30,7 @@ import java.util.regex.Pattern;
@SuppressWarnings("all")
@TestMetadata("idea/testData/completion/smart")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({JvmSmartCompletionTestGenerated.AfterAs.class, JvmSmartCompletionTestGenerated.AnonymousObject.class, JvmSmartCompletionTestGenerated.Constructor.class, JvmSmartCompletionTestGenerated.ForLoopRange.class, JvmSmartCompletionTestGenerated.FunctionLiterals.class, JvmSmartCompletionTestGenerated.FunctionReference.class, JvmSmartCompletionTestGenerated.Generics.class, JvmSmartCompletionTestGenerated.IfValue.class, JvmSmartCompletionTestGenerated.InElvisOperator.class, JvmSmartCompletionTestGenerated.Inheritors.class, JvmSmartCompletionTestGenerated.MultipleArgsItem.class, JvmSmartCompletionTestGenerated.SmartCasts.class, JvmSmartCompletionTestGenerated.This.class, JvmSmartCompletionTestGenerated.WhenEntry.class})
@InnerTestClasses({JvmSmartCompletionTestGenerated.AfterAs.class, JvmSmartCompletionTestGenerated.AnonymousObject.class, JvmSmartCompletionTestGenerated.Constructor.class, JvmSmartCompletionTestGenerated.ForLoopRange.class, JvmSmartCompletionTestGenerated.FunctionLiterals.class, JvmSmartCompletionTestGenerated.FunctionReference.class, JvmSmartCompletionTestGenerated.Generics.class, JvmSmartCompletionTestGenerated.IfValue.class, JvmSmartCompletionTestGenerated.InElvisOperator.class, JvmSmartCompletionTestGenerated.InOperator.class, JvmSmartCompletionTestGenerated.Inheritors.class, JvmSmartCompletionTestGenerated.MultipleArgsItem.class, JvmSmartCompletionTestGenerated.SmartCasts.class, JvmSmartCompletionTestGenerated.This.class, JvmSmartCompletionTestGenerated.WhenEntry.class})
@RunWith(JUnit3RunnerWithInners.class)
public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionTest {
@TestMetadata("AfterExclSign.kt")
@@ -826,6 +826,75 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
}
}
@TestMetadata("idea/testData/completion/smart/inOperator")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class InOperator extends AbstractJvmSmartCompletionTest {
public void testAllFilesPresentInInOperator() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/completion/smart/inOperator"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("ExtensionContains.kt")
public void testExtensionContains() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/ExtensionContains.kt");
doTest(fileName);
}
@TestMetadata("FilterByArgumentType.kt")
public void testFilterByArgumentType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/FilterByArgumentType.kt");
doTest(fileName);
}
@TestMetadata("GenericMethod.kt")
public void testGenericMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/GenericMethod.kt");
doTest(fileName);
}
@TestMetadata("GenericMethod2.kt")
public void testGenericMethod2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/GenericMethod2.kt");
doTest(fileName);
}
@TestMetadata("GenericMethod3.kt")
public void testGenericMethod3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/GenericMethod3.kt");
doTest(fileName);
}
@TestMetadata("NonBooleanContains.kt")
public void testNonBooleanContains() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/NonBooleanContains.kt");
doTest(fileName);
}
@TestMetadata("NotIn.kt")
public void testNotIn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/NotIn.kt");
doTest(fileName);
}
@TestMetadata("Nullable.kt")
public void testNullable() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/Nullable.kt");
doTest(fileName);
}
@TestMetadata("Simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/Simple.kt");
doTest(fileName);
}
@TestMetadata("SmartCasts.kt")
public void testSmartCasts() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/smart/inOperator/SmartCasts.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/completion/smart/inheritors")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)