KT-6376 Smart completion should work after "in" and "!in"
#KT-6376 Fixed
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user