Smart completion: don't use outer call's expected type too much

This commit is contained in:
Valentin Kipyatkov
2015-07-21 11:22:35 +03:00
parent a5384cdc28
commit 2ad981d5c9
8 changed files with 61 additions and 36 deletions
@@ -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<TypeParameterDescriptor>
@@ -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<ExpectedInfo>? {
return calculateForArgument(expressionWithType)
@@ -136,16 +137,22 @@ 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.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<ExpectedInfo>? {
private fun calculateForArgument(call: Call, callExpectedType: JetType, argument: ValueArgument): Collection<ArgumentExpectedInfo>? {
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<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
val expectedInfos = HashSet<ExpectedInfo>()
val expectedInfos = LinkedHashSet<ArgumentExpectedInfo>()
for (candidate: ResolvedCall<FunctionDescriptor> in results.getAllCandidates()!!) {
val status = candidate.getStatus()
if (status == ResolutionStatus.RECEIVER_TYPE_ERROR || status == ResolutionStatus.RECEIVER_PRESENCE_ERROR) continue
@@ -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))
@@ -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
@@ -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<JetType>): Collection<FuzzyType> {
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<ExpectedInfo>? {
// 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? {
+11
View File
@@ -0,0 +1,11 @@
fun foo(list: List<String?>, list2: List<Int>) {
bar(list.map { it?.let { list2.<caret> } })
}
fun bar(p: Collection<Int?>) {
}
// EXIST: size
// EXIST: indexOf
// ABSENT: isEmpty
// ABSENT: toString
+1 -1
View File
@@ -4,4 +4,4 @@ fun foo(list: List<String>, intList: MutableList<Int>, stringList: MutableList<S
// EXIST: intList
// EXIST: arrayListOf
// ABSENT: stringList
// EXIST: stringList
@@ -275,6 +275,12 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest(fileName);
}
@TestMetadata("LambdaValue4.kt")
public void testLambdaValue4() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/LambdaValue4.kt");
doTest(fileName);
}
@TestMetadata("MapTo.kt")
public void testMapTo() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/smart/MapTo.kt");