Smart completion: don't use outer call's expected type too much
This commit is contained in:
@@ -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
|
||||
|
||||
+1
-1
@@ -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))
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
|
||||
+12
-16
@@ -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? {
|
||||
|
||||
@@ -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
@@ -4,4 +4,4 @@ fun foo(list: List<String>, intList: MutableList<Int>, stringList: MutableList<S
|
||||
|
||||
// EXIST: intList
|
||||
// EXIST: arrayListOf
|
||||
// ABSENT: stringList
|
||||
// EXIST: stringList
|
||||
|
||||
+6
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user