From 2ad981d5c9ed3cb01103fd3159940d351885022c Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Tue, 21 Jul 2015 11:22:35 +0300 Subject: [PATCH] Smart completion: don't use outer call's expected type too much --- .../jetbrains/kotlin/idea/util/FuzzyType.kt | 14 ++++++--- .../kotlin/idea/completion/ExpectedInfos.kt | 31 ++++++++++++------- .../idea/completion/SmartCompletionSession.kt | 2 +- .../completion/handlers/GenerateLambda.kt | 3 +- .../idea/completion/smart/SmartCompletion.kt | 28 +++++++---------- .../testData/smart/LambdaValue4.kt | 11 +++++++ idea/idea-completion/testData/smart/MapTo.kt | 2 +- .../test/JvmSmartCompletionTestGenerated.java | 6 ++++ 8 files changed, 61 insertions(+), 36 deletions(-) create mode 100644 idea/idea-completion/testData/smart/LambdaValue4.kt diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/FuzzyType.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/FuzzyType.kt index 628133c78f1..b1535681860 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/FuzzyType.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/FuzzyType.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.idea.util +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemImpl @@ -24,11 +25,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.registerTypeVariables import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.TypeSubstitutor import org.jetbrains.kotlin.types.Variance -import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf -import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind -import org.jetbrains.kotlin.types.typeUtil.makeNotNullable -import org.jetbrains.kotlin.types.typeUtil.makeNullable -import org.jetbrains.kotlin.types.typeUtil.nullability +import org.jetbrains.kotlin.types.typeUtil.* import java.util.HashSet fun CallableDescriptor.fuzzyReturnType(): FuzzyType? { @@ -45,6 +42,13 @@ fun FuzzyType.makeNotNullable() = FuzzyType(type.makeNotNullable(), freeParamete fun FuzzyType.makeNullable() = FuzzyType(type.makeNullable(), freeParameters) fun FuzzyType.nullability() = type.nullability() +fun FuzzyType.isAlmostAnyType(): Boolean { + if (freeParameters.isEmpty()) return false + val typeParameter = type.constructor.declarationDescriptor as? TypeParameterDescriptor ?: return false + if (typeParameter !in freeParameters) return false + return typeParameter.upperBoundsAsType.isAnyOrNullableAny() +} + class FuzzyType( val type: JetType, freeParameters: Collection diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt index 8c9970defab..a3ade125b0a 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/ExpectedInfos.kt @@ -24,6 +24,8 @@ 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.idea.util.fuzzyReturnType +import org.jetbrains.kotlin.idea.util.isAlmostAnyType import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* @@ -48,9 +50,8 @@ 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 +import java.util.* enum class Tail { COMMA, @@ -93,12 +94,12 @@ class ReturnValueExpectedInfo(type: JetType, val callable: CallableDescriptor) : = callable.hashCode() } - class ExpectedInfos( val bindingContext: BindingContext, val resolutionFacade: ResolutionFacade, val moduleDescriptor: ModuleDescriptor, - val useHeuristicSignatures: Boolean + val useHeuristicSignatures: Boolean = true, + val useOuterCallsExpectedTypeCount: Int = 0 ) { public fun calculate(expressionWithType: JetExpression): Collection? { return calculateForArgument(expressionWithType) @@ -136,16 +137,22 @@ class ExpectedInfos( } public fun calculateForArgument(call: Call, argument: ValueArgument): Collection? { - //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.fuzzyType.type } - if (expectedTypes == null || expectedTypes.isEmpty()) { - expectedTypes = listOf(TypeUtils.NO_EXPECTED_TYPE) + val results = calculateForArgument(call, TypeUtils.NO_EXPECTED_TYPE, argument) ?: return null + + if (useOuterCallsExpectedTypeCount > 0 && results.any { it.fuzzyType.freeParameters.isNotEmpty() && it.function.fuzzyReturnType()?.freeParameters?.isNotEmpty() ?: false }) { + val callExpression = (call.callElement as? JetExpression)?.getQualifiedExpressionForSelectorOrThis() ?: return results + val expectedFuzzyTypes = ExpectedInfos(bindingContext, resolutionFacade, moduleDescriptor, useHeuristicSignatures, useOuterCallsExpectedTypeCount - 1) + .calculate(callExpression) + ?.map { it.fuzzyType } ?: return results + if (expectedFuzzyTypes.isEmpty() || expectedFuzzyTypes.any { it.freeParameters.isNotEmpty() }) return results + + return expectedFuzzyTypes.flatMap { calculateForArgument(call, it.type, argument) ?: emptyList() }.toSet() } - return expectedTypes.flatMap { calculateForArgument(call, it, argument) ?: emptyList() }.toSet() + + return results } - private fun calculateForArgument(call: Call, callExpectedType: JetType, argument: ValueArgument): Collection? { + private fun calculateForArgument(call: Call, callExpectedType: JetType, argument: ValueArgument): Collection? { val argumentIndex = call.getValueArguments().indexOf(argument) assert(argumentIndex >= 0) { "Could not find argument '$argument' among arguments of call: $call" @@ -176,7 +183,7 @@ class ExpectedInfos( val callResolver = createContainerForMacros(project, moduleDescriptor).callResolver val results: OverloadResolutionResults = callResolver.resolveFunctionCall(callResolutionContext) - val expectedInfos = HashSet() + val expectedInfos = LinkedHashSet() for (candidate: ResolvedCall in results.getAllCandidates()!!) { val status = candidate.getStatus() if (status == ResolutionStatus.RECEIVER_TYPE_ERROR || status == ResolutionStatus.RECEIVER_PRESENCE_ERROR) continue diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/SmartCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/SmartCompletionSession.kt index cf55743eb26..3fa39e03845 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/SmartCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/SmartCompletionSession.kt @@ -107,7 +107,7 @@ class SmartCompletionSession(configuration: CompletionSessionConfiguration, para override fun getValueArgumentList() = throw UnsupportedOperationException() } - val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, moduleDescriptor, true) + val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, moduleDescriptor, useHeuristicSignatures = false) .calculateForArgument(dummyCall, dummyArgument) if (expectedInfos != null) { collector.addElements(LambdaItems.collect(expectedInfos)) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/GenerateLambda.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/GenerateLambda.kt index 881d06b66af..8e3c266c762 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/GenerateLambda.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/GenerateLambda.kt @@ -79,7 +79,8 @@ 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 expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, resolutionFacade.findModuleDescriptor(file), useHeuristicSignatures = false) + .calculate(expression) ?: return false val functionTypes = expectedInfos.map { it.fuzzyType.type }.filter { KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(it) }.toSet() if (functionTypes.size() <= 1) return false diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt index 820e9e96b2d..082876c6ed4 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt @@ -39,10 +39,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.TypeUtils -import org.jetbrains.kotlin.types.typeUtil.TypeNullability -import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf -import org.jetbrains.kotlin.types.typeUtil.makeNotNullable -import org.jetbrains.kotlin.types.typeUtil.makeNullable +import org.jetbrains.kotlin.types.typeUtil.* import java.util.ArrayList import java.util.HashSet @@ -211,8 +208,9 @@ class SmartCompletion( private fun DeclarationDescriptor.fuzzyTypes(smartCastTypes: (VariableDescriptor) -> Collection): Collection { if (this is CallableDescriptor) { var returnType = fuzzyReturnType() ?: return listOf() + // skip declarations of type Nothing or of generic parameter type which has no real bounds //TODO: maybe we should include them on the second press? - if (shouldSkipDeclarationsOfType(returnType)) return listOf() + if (returnType.type.isNothing() || returnType.isAlmostAnyType()) return listOf() if (this is VariableDescriptor) { return smartCastTypes(this).map { FuzzyType(it, listOf()) } @@ -229,16 +227,6 @@ class SmartCompletion( } } - // skip declarations of type Nothing or of generic parameter type which has no real bounds - private fun shouldSkipDeclarationsOfType(type: FuzzyType): Boolean { - if (KotlinBuiltIns.isNothing(type.type)) return true - if (type.freeParameters.isEmpty()) return false - val typeParameter = type.type.getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor ?: return false - if (!type.freeParameters.contains(typeParameter)) return false - return KotlinBuiltIns.isAnyOrNullableAny(typeParameter.getUpperBoundsAsType()) - //TODO: check for companion object constraint when they are supported - } - private fun calcExpectedInfos(expression: JetExpression): Collection? { // if our expression is initializer of implicitly typed variable - take type of variable from original file (+ the same for function) val declaration = implicitlyTypedDeclarationFromInitializer(expression) @@ -251,7 +239,15 @@ class SmartCompletion( } } - return ExpectedInfos(bindingContext, resolutionFacade, moduleDescriptor, true).calculate(expression) + // if expected types are too general, try to use expected type from outer calls + var count = 0 + while (true) { + val infos = ExpectedInfos(bindingContext, resolutionFacade, moduleDescriptor, useOuterCallsExpectedTypeCount = count) + .calculate(expression) ?: return null + if (count == 2 /* use two outer calls maximum */ || infos.none { it.fuzzyType.isAlmostAnyType() }) return infos + count++ + } + //TODO: we could always give higher priority to results with outer call expected type used } private fun implicitlyTypedDeclarationFromInitializer(expression: JetExpression): JetDeclaration? { diff --git a/idea/idea-completion/testData/smart/LambdaValue4.kt b/idea/idea-completion/testData/smart/LambdaValue4.kt new file mode 100644 index 00000000000..98edf5703b3 --- /dev/null +++ b/idea/idea-completion/testData/smart/LambdaValue4.kt @@ -0,0 +1,11 @@ +fun foo(list: List, list2: List) { + bar(list.map { it?.let { list2. } }) +} + +fun bar(p: Collection) { +} + +// EXIST: size +// EXIST: indexOf +// ABSENT: isEmpty +// ABSENT: toString diff --git a/idea/idea-completion/testData/smart/MapTo.kt b/idea/idea-completion/testData/smart/MapTo.kt index bff8dfe0464..59877a17126 100644 --- a/idea/idea-completion/testData/smart/MapTo.kt +++ b/idea/idea-completion/testData/smart/MapTo.kt @@ -4,4 +4,4 @@ fun foo(list: List, intList: MutableList, stringList: MutableList