Smart completion works for cases when no exact type known (generic parameter substitution is not known)

This commit is contained in:
Valentin Kipyatkov
2015-07-21 01:20:50 +03:00
parent 7b0b2d38c5
commit a5384cdc28
12 changed files with 93 additions and 49 deletions
@@ -128,4 +128,6 @@ fun JetType.getNestedArguments(): List<TypeProjection> {
return result
}
fun JetType.containsError() = ErrorUtils.containsErrorType(this)
public fun JetType.isDefaultBound(): Boolean = KotlinBuiltIns.isDefaultBound(getSupertypeRepresentative())
@@ -78,20 +78,26 @@ class FuzzyType(
}
}
public fun checkIsSubtypeOf(otherType: JetType): TypeSubstitutor?
public fun checkIsSubtypeOf(otherType: FuzzyType): TypeSubstitutor?
= matchedSubstitutor(otherType, MatchKind.IS_SUBTYPE)
public fun checkIsSuperTypeOf(otherType: JetType): TypeSubstitutor?
public fun checkIsSuperTypeOf(otherType: FuzzyType): TypeSubstitutor?
= matchedSubstitutor(otherType, MatchKind.IS_SUPERTYPE)
public fun checkIsSubtypeOf(otherType: JetType): TypeSubstitutor?
= checkIsSubtypeOf(FuzzyType(otherType, emptyList()))
public fun checkIsSuperTypeOf(otherType: JetType): TypeSubstitutor?
= checkIsSuperTypeOf(FuzzyType(otherType, emptyList()))
private enum class MatchKind {
IS_SUBTYPE,
IS_SUPERTYPE
}
private fun matchedSubstitutor(otherType: JetType, matchKind: MatchKind): TypeSubstitutor? {
private fun matchedSubstitutor(otherType: FuzzyType, matchKind: MatchKind): TypeSubstitutor? {
if (type.isError()) return null
if (otherType.isError()) return null
if (otherType.type.isError()) return null
fun JetType.checkInheritance(otherType: JetType): Boolean {
return when (matchKind) {
@@ -100,16 +106,17 @@ class FuzzyType(
}
}
if (freeParameters.isEmpty()) {
return if (type.checkInheritance(otherType)) TypeSubstitutor.EMPTY else null
if (freeParameters.isEmpty() && otherType.freeParameters.isEmpty()) {
return if (type.checkInheritance(otherType.type)) TypeSubstitutor.EMPTY else null
}
val constraintSystem = ConstraintSystemImpl()
constraintSystem.registerTypeVariables(freeParameters, { Variance.INVARIANT })
constraintSystem.registerTypeVariables(otherType.freeParameters, { Variance.INVARIANT })
when (matchKind) {
MatchKind.IS_SUBTYPE -> constraintSystem.addSubtypeConstraint(type, otherType, ConstraintPositionKind.RECEIVER_POSITION.position())
MatchKind.IS_SUPERTYPE -> constraintSystem.addSubtypeConstraint(otherType, type, ConstraintPositionKind.RECEIVER_POSITION.position())
MatchKind.IS_SUBTYPE -> constraintSystem.addSubtypeConstraint(type, otherType.type, ConstraintPositionKind.RECEIVER_POSITION.position())
MatchKind.IS_SUPERTYPE -> constraintSystem.addSubtypeConstraint(otherType.type, type, ConstraintPositionKind.RECEIVER_POSITION.position())
}
constraintSystem.fixVariables()
@@ -119,7 +126,11 @@ class FuzzyType(
// that's why we have to check subtyping manually
val substitutor = constraintSystem.getResultingSubstitutor()
val substitutedType = substitutor.substitute(type, Variance.INVARIANT)
return if (substitutedType != null && substitutedType.checkInheritance(otherType)) substitutor else null
val otherSubstitutedType = substitutor.substitute(otherType.type, Variance.INVARIANT)
return if (substitutedType != null && otherSubstitutedType != null && substitutedType.checkInheritance(otherSubstitutedType))
substitutor
else
null
}
else {
return null
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.frontend.di.createContainerForMacros
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.completion.smart.toList
import org.jetbrains.kotlin.idea.core.mapArgumentsToParameters
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
@@ -46,6 +47,7 @@ import org.jetbrains.kotlin.resolve.validation.SymbolUsageValidator
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
import org.jetbrains.kotlin.types.typeUtil.containsError
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import java.util.HashSet
@@ -64,7 +66,9 @@ data class ItemOptions(val starPrefix: Boolean) {
}
}
open data class ExpectedInfo(val type: JetType, val expectedName: String?, val tail: Tail?, val itemOptions: ItemOptions = ItemOptions.DEFAULT)
open data class ExpectedInfo(val fuzzyType: FuzzyType, val expectedName: String?, val tail: Tail?, val itemOptions: ItemOptions = ItemOptions.DEFAULT) {
constructor(type: JetType, expectedName: String?, tail: Tail?) : this(FuzzyType(type, emptyList()), expectedName, tail)
}
data class ArgumentPosition(val argumentIndex: Int, val argumentName: Name?, val isFunctionLiteralArgument: Boolean) {
constructor(argumentIndex: Int, isFunctionLiteralArgument: Boolean = false) : this(argumentIndex, null, isFunctionLiteralArgument)
@@ -72,7 +76,7 @@ data class ArgumentPosition(val argumentIndex: Int, val argumentName: Name?, val
}
class ArgumentExpectedInfo(type: JetType, name: String?, tail: Tail?, val function: FunctionDescriptor, val position: ArgumentPosition, itemOptions: ItemOptions = ItemOptions.DEFAULT)
: ExpectedInfo(type, name, tail, itemOptions) {
: ExpectedInfo(FuzzyType(type, function.typeParameters), name, tail, itemOptions) {
override fun equals(other: Any?)
= other is ArgumentExpectedInfo && super.equals(other) && function == other.function && position == other.position
@@ -132,8 +136,9 @@ class ExpectedInfos(
}
public fun calculateForArgument(call: Call, argument: ValueArgument): Collection<ExpectedInfo>? {
//TODO: it can too slow for deep nested calls (esp DSL's)
val callExpression = (call.callElement as? JetExpression)?.getQualifiedExpressionForSelectorOrThis()
var expectedTypes = callExpression?.let { calculate(it) }?.map { it.type }
var expectedTypes = callExpression?.let { calculate(it) }?.map { it.fuzzyType.type }
if (expectedTypes == null || expectedTypes.isEmpty()) {
expectedTypes = listOf(TypeUtils.NO_EXPECTED_TYPE)
}
@@ -179,18 +184,25 @@ class ExpectedInfos(
// check that all arguments before the current one matched
if (!candidate.noErrorsInValueArguments()) continue
val descriptor = candidate.getResultingDescriptor()
val parameters = descriptor.getValueParameters()
if (parameters.isEmpty()) continue
val argumentToParameter = call.mapArgumentsToParameters(descriptor)
val parameter = argumentToParameter[argument] ?: continue
var descriptor = candidate.getResultingDescriptor()
if (descriptor.valueParameters.isEmpty()) continue
val thisReceiver = ExpressionTypingUtils.normalizeReceiverValueForVisibility(candidate.getDispatchReceiver(), bindingContext)
if (!Visibilities.isVisible(thisReceiver, descriptor, resolutionScope.getContainingDeclaration())) continue
var argumentToParameter = call.mapArgumentsToParameters(descriptor)
var parameter = argumentToParameter[argument] ?: continue
if (parameter.type.containsError()) {
parameter = parameter.original
descriptor = descriptor.original
argumentToParameter = call.mapArgumentsToParameters(descriptor)
}
val expectedName = if (descriptor.hasSynthesizedParameterNames()) null else parameter.getName().asString()
val parameters = descriptor.valueParameters
fun needCommaForParameter(parameter: ValueParameterDescriptor): Boolean {
if (parameter.hasDefaultValue()) return false // parameter is optional
if (parameter.getVarargElementType() != null) return false // vararg arguments list can be empty
@@ -243,7 +255,6 @@ class ExpectedInfos(
}
}
else {
expectedInfos.add(ArgumentExpectedInfo(parameterType, expectedName, tail, descriptor, argumentPosition))
}
}
@@ -283,13 +294,13 @@ class ExpectedInfos(
return when (expressionWithType) {
ifExpression.getCondition() -> listOf(ExpectedInfo(KotlinBuiltIns.getInstance().getBooleanType(), null, Tail.RPARENTH))
ifExpression.getThen() -> calculate(ifExpression)?.map { ExpectedInfo(it.type, it.expectedName, Tail.ELSE) }
ifExpression.getThen() -> calculate(ifExpression)?.map { ExpectedInfo(it.fuzzyType, it.expectedName, Tail.ELSE) }
ifExpression.getElse() -> {
val ifExpectedInfo = calculate(ifExpression)
val thenType = ifExpression.getThen()?.let { bindingContext.getType(it) }
if (thenType != null)
ifExpectedInfo?.filter { it.type.isSubtypeOf(thenType) }
if (thenType != null && !thenType.isError())
ifExpectedInfo?.filter { it.fuzzyType.checkIsSubtypeOf(thenType) != null }
else
ifExpectedInfo
}
@@ -309,7 +320,7 @@ class ExpectedInfos(
val expectedInfos = calculate(binaryExpression)
if (expectedInfos != null) {
return if (leftTypeNotNullable != null)
expectedInfos.filter { leftTypeNotNullable.isSubtypeOf(it.type) }
expectedInfos.filter { it.fuzzyType.checkIsSuperTypeOf(leftTypeNotNullable) != null }
else
expectedInfos
}
@@ -329,13 +340,15 @@ class ExpectedInfos(
if (functionLiteral != null) {
val literalExpression = functionLiteral.parent as JetFunctionLiteralExpression
return calculate(literalExpression)
?.map { it.type }
?.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it) }
?.map { KotlinBuiltIns.getReturnTypeFromFunctionType(it) }
?.map { ExpectedInfo(it, null, Tail.RBRACE) }
?.map { it.fuzzyType }
?.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it.type) }
?.map {
val returnType = KotlinBuiltIns.getReturnTypeFromFunctionType(it.type)
ExpectedInfo(FuzzyType(returnType, it.freeParameters), null, Tail.RBRACE)
}
}
else {
return calculate(block)?.map { ExpectedInfo(it.type, it.expectedName, null) }
return calculate(block)?.map { ExpectedInfo(it.fuzzyType, it.expectedName, null) }
}
}
@@ -80,7 +80,7 @@ private fun needExplicitParameterTypes(context: InsertionContext, placeholderRan
val resolutionFacade = file.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(expression, BodyResolveMode.PARTIAL)
val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, resolutionFacade.findModuleDescriptor(file), false).calculate(expression) ?: return false
val functionTypes = expectedInfos.map { it.type }.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it) }.toSet()
val functionTypes = expectedInfos.map { it.fuzzyType.type }.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it) }.toSet()
if (functionTypes.size() <= 1) return false
val lambdaParameterCount = KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(lambdaType).size()
@@ -50,13 +50,13 @@ object KeywordValues {
if (!skipTrueFalse) {
val booleanInfoClassifier = { info: ExpectedInfo ->
if (info.type == KotlinBuiltIns.getInstance().getBooleanType()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches
if (info.fuzzyType.type == KotlinBuiltIns.getInstance().getBooleanType()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches
}
collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("true").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.TRUE) }
collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("false").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.FALSE) }
}
val classifier = { info: ExpectedInfo -> if (info.type.isMarkedNullable()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches }
val classifier = { info: ExpectedInfo -> if (info.fuzzyType.type.isMarkedNullable()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches }
collection.addLookupElements(null, expectedInfos, classifier) {
LookupElementBuilder.create("null").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.NULL)
}
@@ -34,7 +34,7 @@ object LambdaItems {
}
public fun addToCollection(collection: MutableCollection<LookupElement>, functionExpectedInfos: Collection<ExpectedInfo>) {
val distinctTypes = functionExpectedInfos.map { it.type }.toSet()
val distinctTypes = functionExpectedInfos.map { it.fuzzyType.type }.toSet()
val singleType = if (distinctTypes.size() == 1) distinctTypes.single() else null
val singleSignatureLength = singleType?.let { KotlinBuiltIns.getParameterTypeProjectionsFromFunctionType(it).size() }
@@ -60,7 +60,7 @@ object LambdaItems {
})
.suppressAutoInsertion()
.assignSmartCompletionPriority(SmartCompletionItemPriority.LAMBDA)
.addTailAndNameSimilarity(functionExpectedInfos.filter { it.type == functionType })
.addTailAndNameSimilarity(functionExpectedInfos.filter { it.fuzzyType.type == functionType })
collection.add(lookupElement)
}
}
@@ -31,10 +31,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.completion.*
import org.jetbrains.kotlin.idea.core.IterableTypesDetector
import org.jetbrains.kotlin.idea.core.SmartCastCalculator
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.fuzzyReturnType
import org.jetbrains.kotlin.idea.util.makeNotNullable
import org.jetbrains.kotlin.idea.util.nullability
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
@@ -129,21 +126,21 @@ class SmartCompletion(
if (inOperatorArgumentResult != null) return inOperatorArgumentResult
val allExpectedInfos = calcExpectedInfos(expressionWithType) ?: return null
val filteredExpectedInfos = allExpectedInfos.filter { !it.type.isError() }
val filteredExpectedInfos = allExpectedInfos.filter { !it.fuzzyType.type.isError() }
if (filteredExpectedInfos.isEmpty()) return null
// if we complete argument of == or !=, make types in expected info's nullable to allow nullable items too
val expectedInfos = if ((expressionWithType.getParent() as? JetBinaryExpression)?.getOperationToken() in COMPARISON_TOKENS)
filteredExpectedInfos.map { ExpectedInfo(it.type.makeNullable(), it.expectedName, it.tail) }
filteredExpectedInfos.map { ExpectedInfo(it.fuzzyType.makeNullable(), it.expectedName, it.tail) }
else
filteredExpectedInfos
val smartCastTypes: (VariableDescriptor) -> Collection<JetType> = SmartCastCalculator(
bindingContext, moduleDescriptor).calculate(expressionWithType, receiver)
val smartCastTypes: (VariableDescriptor) -> Collection<JetType>
= SmartCastCalculator(bindingContext, moduleDescriptor).calculate(expressionWithType, receiver)
val itemsToSkip = calcItemsToSkip(expressionWithType)
val functionExpectedInfos = expectedInfos.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it.type) }
val functionExpectedInfos = expectedInfos.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it.fuzzyType.type) }
fun filterDeclaration(descriptor: DeclarationDescriptor): Collection<LookupElement> {
if (descriptor in itemsToSkip) return listOf()
@@ -324,7 +321,7 @@ class SmartCompletion(
fun toLookupElement(descriptor: FunctionDescriptor): LookupElement? {
val functionType = functionType(descriptor) ?: return null
val matchedExpectedInfos = functionExpectedInfos.filter { functionType.isSubtypeOf(it.type) }
val matchedExpectedInfos = functionExpectedInfos.filter { it.fuzzyType.checkIsSuperTypeOf(functionType) != null }
if (matchedExpectedInfos.isEmpty()) return null
var lookupElement = lookupElementFactory.createLookupElement(descriptor, bindingContext, true)
@@ -372,7 +369,7 @@ class SmartCompletion(
if (elementType != JetTokens.AS_KEYWORD && elementType != JetTokens.AS_SAFE) return null
val expectedInfos = calcExpectedInfos(binaryExpression) ?: return null
val expectedInfosGrouped: Map<JetType, List<ExpectedInfo>> = expectedInfos.groupBy { it.type.makeNotNullable() }
val expectedInfosGrouped: Map<JetType, List<ExpectedInfo>> = expectedInfos.groupBy { it.fuzzyType.type.makeNotNullable() }
val items = ArrayList<LookupElement>()
for ((type, infos) in expectedInfosGrouped) {
@@ -46,7 +46,7 @@ class StaticMembers(
context: JetSimpleNameExpression,
enumEntriesToSkip: Set<DeclarationDescriptor>) {
val expectedInfosByClass = expectedInfos.groupBy { TypeUtils.getClassDescriptor(it.type) }
val expectedInfosByClass = expectedInfos.groupBy { TypeUtils.getClassDescriptor(it.fuzzyType.type) }
for ((classDescriptor, expectedInfosForClass) in expectedInfosByClass) {
if (classDescriptor != null && !classDescriptor.getName().isSpecial()) {
addToCollection(collection, classDescriptor, expectedInfosForClass, context, enumEntriesToSkip)
@@ -64,7 +64,7 @@ class TypeInstantiationItems(
inheritanceSearchers: MutableCollection<InheritanceItemsSearcher>,
expectedInfos: Collection<ExpectedInfo>
) {
val expectedInfosGrouped: Map<JetType, List<ExpectedInfo>> = expectedInfos.groupBy { it.type.makeNotNullable() }
val expectedInfosGrouped: Map<JetType, List<ExpectedInfo>> = expectedInfos.groupBy { it.fuzzyType.type.makeNotNullable() }
for ((type, infos) in expectedInfosGrouped) {
val tail = mergeTails(infos.map { it.tail })
addTo(items, inheritanceSearchers, type, tail)
@@ -21,6 +21,7 @@ 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 com.intellij.debugger.ui.tree.LocalVariableDescriptor
import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.completion.*
@@ -132,13 +133,13 @@ private constructor(
fun Collection<FuzzyType>.classifyExpectedInfo(expectedInfo: ExpectedInfo): ExpectedInfoClassification {
val sequence = asSequence()
val substitutor = sequence.map { it.checkIsSubtypeOf(expectedInfo.type) }.firstOrNull()
val substitutor = sequence.map { it.checkIsSubtypeOf(expectedInfo.fuzzyType) }.firstOrNull()
if (substitutor != null) {
return ExpectedInfoClassification.matches(substitutor)
}
if (sequence.any { it.nullability() == TypeNullability.NULLABLE }) {
val substitutor2 = sequence.map { it.makeNotNullable().checkIsSubtypeOf(expectedInfo.type) }.firstOrNull()
val substitutor2 = sequence.map { it.makeNotNullable().checkIsSubtypeOf(expectedInfo.fuzzyType) }.firstOrNull()
if (substitutor2 != null) {
return ExpectedInfoClassification.matchesIfNotNullable(substitutor2)
}
@@ -170,7 +171,7 @@ fun<TDescriptor: DeclarationDescriptor?> MutableCollection<LookupElement>.addLoo
val classification = infoClassifier(info)
if (classification.substitutor != null) {
@suppress("UNCHECKED_CAST")
val substitutedDescriptor = descriptor?.substitute(classification.substitutor) as TDescriptor
val substitutedDescriptor = descriptor.substituteFixed(classification.substitutor)
val map = if (classification.makeNotNullable) makeNullableInfos else matchedInfos
map.getOrPut(ItemData(substitutedDescriptor, info.itemOptions)) { ArrayList() }.add(info)
}
@@ -196,6 +197,13 @@ fun<TDescriptor: DeclarationDescriptor?> MutableCollection<LookupElement>.addLoo
}
}
private fun <T : DeclarationDescriptor?> T.substituteFixed(substitutor: TypeSubstitutor): T {
if (this is LocalVariableDescriptor || this is ValueParameterDescriptor || this is TypeParameterDescriptor) { // TODO: it's not implemented for them
return this
}
return this?.substitute(substitutor) as T
}
private fun MutableCollection<LookupElement>.addLookupElementsForNullable(factory: () -> LookupElement?, matchedInfos: Collection<ExpectedInfo>) {
for (element in lookupElementsForNullable(factory)) {
add(element.addTailAndNameSimilarity(matchedInfos))
+7
View File
@@ -0,0 +1,7 @@
fun foo(list: List<String>, intList: MutableList<Int>, stringList: MutableList<String>): Collection<Int> {
list.mapTo(<caret>)
}
// EXIST: intList
// EXIST: stringList
// EXIST: arrayListOf
@@ -281,6 +281,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest(fileName);
}
@TestMetadata("MapTo2.kt")
public void testMapTo2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/MapTo2.kt");
doTest(fileName);
}
@TestMetadata("MethodCallArgument.kt")
public void testMethodCallArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/MethodCallArgument.kt");