Extension methods shown with type arguments substituted in completion list

This commit is contained in:
Valentin Kipyatkov
2014-12-02 13:23:20 +03:00
parent 3df3e57b05
commit 2c08b3e229
9 changed files with 79 additions and 36 deletions
@@ -32,12 +32,12 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.jet.lang.resolve.scopes.getDescriptorsFiltered import org.jetbrains.jet.lang.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.jet.lang.resolve.scopes.DescriptorKindFilter import org.jetbrains.jet.lang.resolve.scopes.DescriptorKindFilter
import org.jetbrains.jet.lang.resolve.scopes.DescriptorKindExclude import org.jetbrains.jet.lang.resolve.scopes.DescriptorKindExclude
import org.jetbrains.jet.plugin.util.extensionsUtils.isExtensionCallable
import org.jetbrains.jet.lang.types.JetType import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.types.TypeUtils import org.jetbrains.jet.lang.types.TypeUtils
import org.jetbrains.jet.lang.types.checker.JetTypeChecker import org.jetbrains.jet.lang.types.checker.JetTypeChecker
import org.jetbrains.jet.lexer.JetTokens import org.jetbrains.jet.lexer.JetTokens
import org.jetbrains.jet.lang.resolve.calls.callUtil.getCall import org.jetbrains.jet.utils.addIfNotNull
import org.jetbrains.jet.plugin.util.extensionsUtils.substituteExtensionIfCallable
public class ReferenceVariantsHelper( public class ReferenceVariantsHelper(
private val context: BindingContext, private val context: BindingContext,
@@ -131,11 +131,13 @@ public class ReferenceVariantsHelper(
val dataFlowInfo = context.getDataFlowInfo(expression) val dataFlowInfo = context.getDataFlowInfo(expression)
val receiverValues = receivers.map { it.getValue() } val receiverValues = receivers.map { it.getValue() }
resolutionScope.getDescriptorsFiltered(kindFilter, nameFilter).filterTo(descriptorsSet) { for (descriptor in resolutionScope.getDescriptorsFiltered(kindFilter, nameFilter)) {
if (it is CallableDescriptor && it.getExtensionReceiverParameter() != null) if (descriptor is CallableDescriptor && descriptor.getExtensionReceiverParameter() != null) {
it.isExtensionCallable(receiverValues, context, dataFlowInfo, false) descriptorsSet.addIfNotNull(descriptor.substituteExtensionIfCallable(receiverValues, context, dataFlowInfo, false))
else }
true else {
descriptorsSet.add(descriptor)
}
} }
return descriptorsSet return descriptorsSet
@@ -198,8 +200,10 @@ public class ReferenceVariantsHelper(
nameFilter: (Name) -> Boolean nameFilter: (Name) -> Boolean
) { ) {
if (!kindFilter.excludes.contains(DescriptorKindExclude.Extensions)) { if (!kindFilter.excludes.contains(DescriptorKindExclude.Extensions)) {
resolutionScope.getDescriptorsFiltered(kindFilter.exclude(DescriptorKindExclude.NonExtensions), nameFilter) for (callable in resolutionScope.getDescriptorsFiltered(kindFilter.exclude(DescriptorKindExclude.NonExtensions), nameFilter)) {
.filterTo(this) { (it as CallableDescriptor).isExtensionCallable(receiver, isInfixCall, context, dataFlowInfo) } val substituted = (callable as CallableDescriptor).substituteExtensionIfCallable(receiver, isInfixCall, context, dataFlowInfo)
addIfNotNull(substituted)
}
} }
} }
@@ -33,52 +33,69 @@ import org.jetbrains.jet.lang.resolve.calls.inference.ConstraintsUtil
import org.jetbrains.jet.utils.addIfNotNull import org.jetbrains.jet.utils.addIfNotNull
import java.util.HashSet import java.util.HashSet
import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor import org.jetbrains.jet.lang.descriptors.ReceiverParameterDescriptor
import org.jetbrains.jet.lang.types.TypeSubstitutor
public fun CallableDescriptor.isExtensionCallable(receivers: Collection<ReceiverValue>, //TODO: what if multiple receiver types match? this can result in different substitutions
public fun CallableDescriptor.substituteExtensionIfCallable(receivers: Collection<ReceiverValue>,
context: BindingContext, context: BindingContext,
dataFlowInfo: DataFlowInfo, dataFlowInfo: DataFlowInfo,
isInfixCall: Boolean): Boolean isInfixCall: Boolean): CallableDescriptor? {
= receivers.any { isExtensionCallable(it, isInfixCall, context, dataFlowInfo) } return receivers.stream()
.map { substituteExtensionIfCallable(it, isInfixCall, context, dataFlowInfo) }
.firstOrNull { it != null }
}
public fun CallableDescriptor.isExtensionCallableWithImplicitReceiver(scope: JetScope, context: BindingContext, dataFlowInfo: DataFlowInfo): Boolean public fun CallableDescriptor.substituteExtensionIfCallableWithImplicitReceiver(scope: JetScope, context: BindingContext, dataFlowInfo: DataFlowInfo): CallableDescriptor?
= isExtensionCallable(scope.getImplicitReceiversHierarchy().map { it.getValue() }, context, dataFlowInfo, false) = substituteExtensionIfCallable(scope.getImplicitReceiversHierarchy().map { it.getValue() }, context, dataFlowInfo, false)
public fun CallableDescriptor.isExtensionCallable( public fun CallableDescriptor.substituteExtensionIfCallable(
receiver: ReceiverValue, receiver: ReceiverValue,
isInfixCall: Boolean, isInfixCall: Boolean,
bindingContext: BindingContext, bindingContext: BindingContext,
dataFlowInfo: DataFlowInfo dataFlowInfo: DataFlowInfo
): Boolean { ): CallableDescriptor? {
val receiverParameter = getExtensionReceiverParameter()!! val receiverParameter = getExtensionReceiverParameter()!!
if (!receiver.exists()) return false if (!receiver.exists()) return null
if (isInfixCall && (this !is SimpleFunctionDescriptor || getValueParameters().size() != 1)) { if (isInfixCall && (this !is SimpleFunctionDescriptor || getValueParameters().size() != 1)) {
return false return null
} }
return SmartCastUtils.getSmartCastVariants(receiver, bindingContext, dataFlowInfo) for (type in SmartCastUtils.getSmartCastVariants(receiver, bindingContext, dataFlowInfo)) {
.any { checkReceiverResolution(it, receiverParameter, getTypeParameters()) } val substitutor = checkReceiverResolution(type, receiverParameter, getTypeParameters())
if (substitutor != null) {
return substitute(substitutor)
}
}
return null
} }
private fun checkReceiverResolution( private fun checkReceiverResolution(
receiverType: JetType, receiverType: JetType,
receiverParameter: ReceiverParameterDescriptor, receiverParameter: ReceiverParameterDescriptor,
typeParameters: List<TypeParameterDescriptor> typeParameters: List<TypeParameterDescriptor>
): Boolean { ): TypeSubstitutor? {
val typeNamesInReceiver = HashSet<TypeParameterDescriptor>() val typeParamsInReceiver = HashSet<TypeParameterDescriptor>()
typeNamesInReceiver.addUsedTypeParameters(receiverParameter.getType()) typeParamsInReceiver.addUsedTypeParameters(receiverParameter.getType())
val constraintSystem = ConstraintSystemImpl() val constraintSystem = ConstraintSystemImpl()
val typeVariables = LinkedHashMap<TypeParameterDescriptor, Variance>() val typeVariables = LinkedHashMap<TypeParameterDescriptor, Variance>()
for (typeParameter in typeParameters) { for (typeParameter in typeParameters) {
if (typeNamesInReceiver.contains(typeParameter)) { if (typeParamsInReceiver.contains(typeParameter)) {
typeVariables[typeParameter] = Variance.INVARIANT typeVariables[typeParameter] = Variance.INVARIANT
} }
} }
constraintSystem.registerTypeVariables(typeVariables) constraintSystem.registerTypeVariables(typeVariables)
constraintSystem.addSubtypeConstraint(receiverType, receiverParameter.getType(), ConstraintPosition.RECEIVER_POSITION) constraintSystem.addSubtypeConstraint(receiverType, receiverParameter.getType(), ConstraintPosition.RECEIVER_POSITION)
return constraintSystem.getStatus().isSuccessful() && ConstraintsUtil.checkBoundsAreSatisfied(constraintSystem, true)
if (constraintSystem.getStatus().isSuccessful() && ConstraintsUtil.checkBoundsAreSatisfied(constraintSystem, true)) {
return constraintSystem.getResultingSubstitutor()
}
else {
return null
}
} }
private fun MutableSet<TypeParameterDescriptor>.addUsedTypeParameters(jetType: JetType) { private fun MutableSet<TypeParameterDescriptor>.addUsedTypeParameters(jetType: JetType) {
@@ -35,7 +35,7 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.jet.lang.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.jet.lang.resolve.calls.smartcasts.DataFlowInfo
import com.intellij.psi.stubs.StringStubIndexExtension import com.intellij.psi.stubs.StringStubIndexExtension
import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade import org.jetbrains.jet.plugin.caches.resolve.ResolutionFacade
import org.jetbrains.jet.plugin.util.extensionsUtils.isExtensionCallable import org.jetbrains.jet.plugin.util.extensionsUtils.substituteExtensionIfCallable
public class KotlinIndicesHelper( public class KotlinIndicesHelper(
private val project: Project, private val project: Project,
@@ -122,7 +122,7 @@ public class KotlinIndicesHelper(
val receiverValue = ExpressionReceiver(receiverExpression, expressionType) val receiverValue = ExpressionReceiver(receiverExpression, expressionType)
matchingNames.flatMapTo(this) { matchingNames.flatMapTo(this) {
findSuitableExtensions(it, index, receiverValue, dataFlowInfo, isInfixCall, resolutionScope, moduleDescriptor, bindingContext).stream() findSuitableExtensions(it, index, receiverValue, dataFlowInfo, isInfixCall, resolutionScope, moduleDescriptor, bindingContext)
} }
} }
else { else {
@@ -130,7 +130,7 @@ public class KotlinIndicesHelper(
for (receiver in resolutionScope.getImplicitReceiversHierarchy()) { for (receiver in resolutionScope.getImplicitReceiversHierarchy()) {
matchingNames.flatMapTo(this) { matchingNames.flatMapTo(this) {
findSuitableExtensions(it, index, receiver.getValue(), dataFlowInfo, false, resolutionScope, moduleDescriptor, bindingContext).stream() findSuitableExtensions(it, index, receiver.getValue(), dataFlowInfo, false, resolutionScope, moduleDescriptor, bindingContext)
} }
} }
} }
@@ -146,7 +146,7 @@ public class KotlinIndicesHelper(
isInfixCall: Boolean, isInfixCall: Boolean,
resolutionScope: JetScope, resolutionScope: JetScope,
module: ModuleDescriptor, module: ModuleDescriptor,
bindingContext: BindingContext): Collection<CallableDescriptor> { bindingContext: BindingContext): Stream<CallableDescriptor> {
val fqnString = callableFQN.asString() val fqnString = callableFQN.asString()
val descriptors = if (index != null) { val descriptors = if (index != null) {
index.get(fqnString, project, scope) index.get(fqnString, project, scope)
@@ -160,9 +160,10 @@ public class KotlinIndicesHelper(
.filter { it.getExtensionReceiverParameter() != null } .filter { it.getExtensionReceiverParameter() != null }
} }
return descriptors.filter { return descriptors.stream()
visibilityFilter(it) && it.isExtensionCallable(receiverValue, isInfixCall, bindingContext, dataFlowInfo) .filter(visibilityFilter)
} .map { it.substituteExtensionIfCallable(receiverValue, isInfixCall, bindingContext, dataFlowInfo) }
.filterNotNull()
} }
public fun getClassDescriptors(nameFilter: (String) -> Boolean, kindFilter: (ClassKind) -> Boolean): Collection<ClassDescriptor> { public fun getClassDescriptors(nameFilter: (String) -> Boolean, kindFilter: (ClassKind) -> Boolean): Collection<ClassDescriptor> {
@@ -35,7 +35,7 @@ public class DeclarationDescriptorLookupObject(
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return descriptor.hashCode() return descriptor.getOriginal().hashCode()
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@@ -49,7 +49,8 @@ public class DeclarationDescriptorLookupObject(
return false return false
} }
return lookupObject.descriptor == descriptor //TODO: different substitutions
return lookupObject.descriptor.getOriginal() == descriptor.getOriginal()
} }
class object { class object {
@@ -83,7 +83,10 @@ public abstract class BaseJetVariableMacro extends Macro {
VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor; VariableDescriptor variableDescriptor = (VariableDescriptor) declarationDescriptor;
if (variableDescriptor.getExtensionReceiverParameter() != null if (variableDescriptor.getExtensionReceiverParameter() != null
&& !ExtensionsUtilsPackage.isExtensionCallableWithImplicitReceiver(variableDescriptor, scope, bindingContext, dataFlowInfo)) continue; && ExtensionsUtilsPackage.substituteExtensionIfCallableWithImplicitReceiver(
variableDescriptor, scope, bindingContext, dataFlowInfo) == null) {
continue;
}
if (isSuitable(variableDescriptor, scope, project, components)) { if (isSuitable(variableDescriptor, scope, project, components)) {
filteredDescriptors.add(variableDescriptor); filteredDescriptors.add(variableDescriptor);
@@ -0,0 +1,5 @@
fun foo(list: List<String>) {
list.<caret>
}
// EXIST: { itemText: "firstOrNull", tailText: "(predicate: (String) -> Boolean) for Iterable<String> in kotlin", typeText: "String?" }
// EXIST: { itemText: "toMap", tailText: "(selector: (String) -> K) for Iterable<String> in kotlin", typeText: "Map<K, String>" }
@@ -8,5 +8,5 @@ fun firstFun() {
} }
// INVOCATION_COUNT: 1 // INVOCATION_COUNT: 1
// EXIST: { lookupString:"toLinkedList", itemText:"toLinkedList", tailText:"() for Iterable<T> in kotlin" } // EXIST: { lookupString:"toLinkedList", itemText:"toLinkedList", tailText:"() for Iterable<Int!> in kotlin" }
// NUMBER: 1 // NUMBER: 1
@@ -814,6 +814,12 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest(fileName); doTest(fileName);
} }
@TestMetadata("SubstitutedSignature3.kt")
public void testSubstitutedSignature3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SubstitutedSignature3.kt");
doTest(fileName);
}
@TestMetadata("TopLevelClassCompletionInQualifiedCall.kt") @TestMetadata("TopLevelClassCompletionInQualifiedCall.kt")
public void testTopLevelClassCompletionInQualifiedCall() throws Exception { public void testTopLevelClassCompletionInQualifiedCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/TopLevelClassCompletionInQualifiedCall.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/TopLevelClassCompletionInQualifiedCall.kt");
@@ -814,6 +814,12 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName); doTest(fileName);
} }
@TestMetadata("SubstitutedSignature3.kt")
public void testSubstitutedSignature3() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/SubstitutedSignature3.kt");
doTest(fileName);
}
@TestMetadata("TopLevelClassCompletionInQualifiedCall.kt") @TestMetadata("TopLevelClassCompletionInQualifiedCall.kt")
public void testTopLevelClassCompletionInQualifiedCall() throws Exception { public void testTopLevelClassCompletionInQualifiedCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/TopLevelClassCompletionInQualifiedCall.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/TopLevelClassCompletionInQualifiedCall.kt");