Extension callables index and its use in completion

This commit is contained in:
Valentin Kipyatkov
2015-03-30 22:45:14 +03:00
parent 546af84435
commit 5ba5618718
15 changed files with 245 additions and 75 deletions
@@ -98,7 +98,7 @@ public class JetProperty extends JetTypeParameterListOwnerStub<KotlinPropertyStu
public JetTypeReference getReceiverTypeReference() {
KotlinPropertyStub stub = getStub();
if (stub != null) {
if (!stub.hasReceiverTypeRef()) {
if (!stub.isExtension()) {
return null;
}
else {
@@ -134,7 +134,7 @@ public class JetProperty extends JetTypeParameterListOwnerStub<KotlinPropertyStu
}
else {
List<JetTypeReference> typeReferences = getStubOrPsiChildrenAsList(JetStubElementTypes.TYPE_REFERENCE);
int returnTypeRefPositionInPsi = stub.hasReceiverTypeRef() ? 1 : 0;
int returnTypeRefPositionInPsi = stub.isExtension() ? 1 : 0;
if (typeReferences.size() <= returnTypeRefPositionInPsi) {
LOG.error("Invalid stub structure built for property:\n" + getText());
return null;
@@ -56,13 +56,10 @@ public trait KotlinAnnotationEntryStub : StubElement<JetAnnotationEntry> {
public fun hasValueArguments(): Boolean
}
public trait KotlinFunctionStub : KotlinStubWithFqName<JetNamedFunction> {
public fun isTopLevel(): Boolean
public fun isExtension(): Boolean
public trait KotlinFunctionStub : KotlinCallableStubBase<JetNamedFunction> {
public fun hasBlockBody(): Boolean
public fun hasBody(): Boolean
public fun hasTypeParameterListBeforeFunctionName(): Boolean
public fun isProbablyNothingType(): Boolean
}
public trait KotlinImportDirectiveStub : StubElement<JetImportDirective> {
@@ -92,14 +89,17 @@ public trait KotlinPropertyAccessorStub : StubElement<JetPropertyAccessor> {
public fun hasBlockBody(): Boolean
}
public trait KotlinPropertyStub : KotlinStubWithFqName<JetProperty> {
public trait KotlinPropertyStub : KotlinCallableStubBase<JetProperty> {
public fun isVar(): Boolean
public fun isTopLevel(): Boolean
public fun hasDelegate(): Boolean
public fun hasDelegateExpression(): Boolean
public fun hasInitializer(): Boolean
public fun hasReceiverTypeRef(): Boolean
public fun hasReturnTypeRef(): Boolean
}
public trait KotlinCallableStubBase<TDeclaration: JetCallableDeclaration> : KotlinStubWithFqName<TDeclaration> {
public fun isTopLevel(): Boolean
public fun isExtension(): Boolean
public fun isProbablyNothingType(): Boolean
}
@@ -61,7 +61,7 @@ public class JetPropertyElementType extends JetStubElementType<KotlinPropertyStu
dataStream.writeBoolean(stub.hasDelegate());
dataStream.writeBoolean(stub.hasDelegateExpression());
dataStream.writeBoolean(stub.hasInitializer());
dataStream.writeBoolean(stub.hasReceiverTypeRef());
dataStream.writeBoolean(stub.isExtension());
dataStream.writeBoolean(stub.hasReturnTypeRef());
dataStream.writeBoolean(stub.isProbablyNothingType());
@@ -32,7 +32,7 @@ public class KotlinPropertyStubImpl(
private val hasDelegate: Boolean,
private val hasDelegateExpression: Boolean,
private val hasInitializer: Boolean,
private val hasReceiverTypeRef: Boolean,
private val isExtension: Boolean,
private val hasReturnTypeRef: Boolean,
private val isProbablyNothingType: Boolean,
private val fqName: FqName?
@@ -53,7 +53,7 @@ public class KotlinPropertyStubImpl(
override fun hasDelegate() = hasDelegate
override fun hasDelegateExpression() = hasDelegateExpression
override fun hasInitializer() = hasInitializer
override fun hasReceiverTypeRef() = hasReceiverTypeRef
override fun isExtension() = isExtension
override fun hasReturnTypeRef() = hasReturnTypeRef
override fun getName() = StringRef.toString(name)
override fun isProbablyNothingType() = isProbablyNothingType
@@ -133,7 +133,7 @@ private class CallableClsStubBuilder(
hasDelegate = false,
hasDelegateExpression = false,
hasInitializer = false,
hasReceiverTypeRef = callableProto.hasReceiverType(),
isExtension = callableProto.hasReceiverType(),
hasReturnTypeRef = true,
fqName = c.containerFqName.child(callableName),
isProbablyNothingType = isProbablyNothing(callableProto)
@@ -0,0 +1,66 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.stubindex
import com.intellij.psi.stubs.IndexSink
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.stubs.KotlinCallableStubBase
fun indexTopLevelExtension<TDeclaration : JetCallableDeclaration>(stub: KotlinCallableStubBase<TDeclaration>, sink: IndexSink) {
if (stub.isExtension()) {
val declaration = stub.getPsi()
declaration.getReceiverTypeReference()!!.getTypeElement()?.index(declaration, sink)
}
}
private fun <TDeclaration : JetCallableDeclaration> JetTypeElement.index(declaration: TDeclaration, sink: IndexSink) {
fun occurrence(typeName: String) {
val name = declaration.getName() ?: return
sink.occurrence(JetTopLevelExtensionsByReceiverTypeIndex.INSTANCE.getKey(),
JetTopLevelExtensionsByReceiverTypeIndex.buildKey(typeName, name))
}
when (this) {
is JetUserType -> {
//TODO: aliases
var typeName = getReferencedName() ?: return
if (declaration is JetNamedFunction) {
val typeParameter = declaration.getTypeParameters().firstOrNull { it.getName() == typeName }
if (typeParameter != null) {
val bound = typeParameter.getExtendsBound()
if (bound != null) {
bound.getTypeElement()?.index(declaration, sink)
return
}
typeName = "Any"
}
}
occurrence(typeName)
}
is JetNullableType -> getInnerType()?.index(declaration, sink)
is JetFunctionType -> {
val typeName = (if (getReceiverTypeReference() != null) "ExtensionFunction" else "Function") + getParameters().size()
occurrence(typeName)
}
else -> occurrence("Any")
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.stubindex
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.stubs.StringStubIndexExtension
import com.intellij.psi.stubs.StubIndexKey
import org.jetbrains.kotlin.psi.JetCallableDeclaration
public class JetTopLevelExtensionsByReceiverTypeIndex private() : StringStubIndexExtension<JetCallableDeclaration>() {
override fun getKey() = KEY
override fun get(s: String, project: Project, scope: GlobalSearchScope)
= super.get(s, project, JetSourceFilterScope.kotlinSourcesAndLibraries(scope, project))
companion object {
private val KEY = KotlinIndexUtil.createIndexKey<String, JetCallableDeclaration>(javaClass<JetTopLevelExtensionsByReceiverTypeIndex>())
public val INSTANCE: JetTopLevelExtensionsByReceiverTypeIndex = JetTopLevelExtensionsByReceiverTypeIndex()
public fun buildKey(receiverTypeName: String, callableName: String): String = receiverTypeName + "\n" + callableName
public fun receiverTypeNameFromKey(key: String): String = key.substringBefore('\n', "")
public fun callableNameFromKey(key: String): String = key.substringAfter('\n', "")
}
}
@@ -17,10 +17,7 @@
package org.jetbrains.kotlin.idea.stubindex;
import com.intellij.psi.stubs.IndexSink;
import com.intellij.psi.stubs.StubElement;
import org.jetbrains.kotlin.load.java.JvmAbi;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.JetClassOrObject;
import org.jetbrains.kotlin.psi.stubs.*;
import org.jetbrains.kotlin.psi.stubs.elements.StubIndexService;
@@ -95,6 +92,7 @@ public class StubIndexServiceImpl implements StubIndexService {
if (fqName != null) {
sink.occurrence(JetTopLevelFunctionFqnNameIndex.getInstance().getKey(), fqName.asString());
sink.occurrence(JetTopLevelFunctionByPackageIndex.getInstance().getKey(), fqName.parent().asString());
StubindexPackage.indexTopLevelExtension(stub, sink);
}
}
}
@@ -116,6 +114,7 @@ public class StubIndexServiceImpl implements StubIndexService {
if (fqName != null) {
sink.occurrence(JetTopLevelPropertyFqnNameIndex.getInstance().getKey(), fqName.asString());
sink.occurrence(JetTopLevelPropertyByPackageIndex.getInstance().getKey(), fqName.parent().asString());
StubindexPackage.indexTopLevelExtension(stub, sink);
}
}
}
+1
View File
@@ -428,6 +428,7 @@
<stubIndex implementation="org.jetbrains.kotlin.idea.stubindex.JetSuperClassIndex"/>
<stubIndex implementation="org.jetbrains.kotlin.idea.stubindex.JetTopLevelFunctionFqnNameIndex"/>
<stubIndex implementation="org.jetbrains.kotlin.idea.stubindex.JetTopLevelPropertyFqnNameIndex"/>
<stubIndex implementation="org.jetbrains.kotlin.idea.stubindex.JetTopLevelExtensionsByReceiverTypeIndex"/>
<stubIndex implementation="org.jetbrains.kotlin.idea.stubindex.JetAnnotationsIndex"/>
<stubIndex implementation="org.jetbrains.kotlin.idea.stubindex.JetProbablyNothingFunctionShortNameIndex"/>
<stubIndex implementation="org.jetbrains.kotlin.idea.stubindex.JetProbablyNothingPropertyShortNameIndex"/>
@@ -16,28 +16,31 @@
package org.jetbrains.kotlin.idea.caches
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.stubs.StringStubIndexExtension
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.lazy.ResolveSessionUtils
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.stubindex.*
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstance
import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.scopes.JetScope
import com.intellij.openapi.project.Project
import java.util.HashSet
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.resolve.QualifiedExpressionResolver.LookupMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import com.intellij.psi.stubs.StringStubIndexExtension
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable
import org.jetbrains.kotlin.idea.util.CallType
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastUtils
import org.jetbrains.kotlin.resolve.lazy.ResolveSessionUtils
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstance
import org.jetbrains.kotlin.idea.stubindex.*
import java.util.HashSet
import java.util.LinkedHashSet
public class KotlinIndicesHelper(
private val project: Project,
@@ -78,74 +81,95 @@ public class KotlinIndicesHelper(
}
public fun getCallableTopLevelExtensions(nameFilter: (String) -> Boolean, expression: JetSimpleNameExpression): Collection<CallableDescriptor> {
val receiverValues = receiverValues(expression)
if (receiverValues.isEmpty()) return emptyList()
val dataFlowInfo = bindingContext.getDataFlowInfo(expression)
val functionsIndex = JetTopLevelFunctionFqnNameIndex.getInstance()
val propertiesIndex = JetTopLevelPropertyFqnNameIndex.getInstance()
val receiverTypeNames = possibleReceiverTypeNames(receiverValues.map { it.first }, dataFlowInfo)
val functionFqNames = functionsIndex.getAllKeys(project).sequence().map { FqName(it) }
val propertyFqNames = propertiesIndex.getAllKeys(project).sequence().map { FqName(it) }
val index = JetTopLevelExtensionsByReceiverTypeIndex.INSTANCE
val result = HashSet<CallableDescriptor>()
result.fqNamesToSuitableExtensions(functionFqNames, nameFilter, functionsIndex, expression, bindingContext, dataFlowInfo)
result.fqNamesToSuitableExtensions(propertyFqNames, nameFilter, propertiesIndex, expression, bindingContext, dataFlowInfo)
val declarations = index.getAllKeys(project)
.sequence()
.filter {
JetTopLevelExtensionsByReceiverTypeIndex.receiverTypeNameFromKey(it) in receiverTypeNames
&& nameFilter(JetTopLevelExtensionsByReceiverTypeIndex.callableNameFromKey(it))
}
.flatMap { index.get(it, project, scope).sequence() }
return findSuitableExtensions(declarations, receiverValues, dataFlowInfo, bindingContext)
}
private fun possibleReceiverTypeNames(receiverValues: Collection<ReceiverValue>, dataFlowInfo: DataFlowInfo): Set<String> {
val result = LinkedHashSet<String>()
for (receiverValue in receiverValues) {
for (type in SmartCastUtils.getSmartCastVariants(receiverValue, bindingContext, dataFlowInfo)) {
result.addTypeNames(type)
}
}
return result
}
private fun MutableCollection<CallableDescriptor>.fqNamesToSuitableExtensions(
fqNames: Sequence<FqName>,
nameFilter: (String) -> Boolean,
index: StringStubIndexExtension<out JetCallableDeclaration>,
expression: JetSimpleNameExpression,
bindingContext: BindingContext,
dataFlowInfo: DataFlowInfo) {
val matchingNames = fqNames.filter { nameFilter(it.shortName().asString()) }
private fun receiverValues(expression: JetSimpleNameExpression): Collection<Pair<ReceiverValue, CallType>> {
val receiverPair = ReferenceVariantsHelper.getExplicitReceiverData(expression)
if (receiverPair != null) {
val (receiverExpression, callType) = receiverPair
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE, receiverExpression]
if (expressionType == null || expressionType.isError()) return
if (expressionType == null || expressionType.isError()) return emptyList()
val receiverValue = ExpressionReceiver(receiverExpression, expressionType)
matchingNames.flatMapTo(this) {
findSuitableExtensions(it, index, receiverValue, dataFlowInfo, callType, bindingContext)
}
return listOf(receiverValue to callType)
}
else {
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, expression] ?: return
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, expression] ?: return emptyList()
for (receiver in resolutionScope.getImplicitReceiversWithInstance()) {
matchingNames.flatMapTo(this) {
findSuitableExtensions(it, index, receiver.getValue(), dataFlowInfo, CallType.NORMAL, bindingContext)
}
}
return resolutionScope.getImplicitReceiversWithInstance().map { it.getValue() to CallType.NORMAL }
}
}
private fun MutableCollection<String>.addTypeNames(type: JetType) {
val constructor = type.getConstructor()
addIfNotNull(constructor.getDeclarationDescriptor()?.getName()?.asString())
constructor.getSupertypes().forEach { addTypeNames(it) }
}
/**
* Check that function or property with the given qualified name can be resolved in given scope and called on given receiver
*/
private fun findSuitableExtensions(callableFQN: FqName,
index: StringStubIndexExtension<out JetCallableDeclaration>,
receiverValue: ReceiverValue,
dataFlowInfo: DataFlowInfo,
callType: CallType,
bindingContext: BindingContext): Sequence<CallableDescriptor> {
val extensions = index.get(callableFQN.asString(), project, scope).filter { it.getReceiverTypeReference() != null }
val descriptors = if (extensions.any { it.getContainingJetFile().isCompiled() } ) { //TODO: it's temporary while resolveToDescriptor does not work for compiled declarations
analyzeImportReference(callableFQN)
.filterIsInstance<CallableDescriptor>()
.filter { it.getExtensionReceiverParameter() != null }
private fun findSuitableExtensions(
declarations: Sequence<JetCallableDeclaration>,
receiverValues: Collection<Pair<ReceiverValue, CallType>>,
dataFlowInfo: DataFlowInfo,
bindingContext: BindingContext
): Collection<CallableDescriptor> {
val result = LinkedHashSet<CallableDescriptor>()
fun processDescriptor(descriptor: CallableDescriptor) {
if (visibilityFilter(descriptor)) {
for ((receiverValue, callType) in receiverValues) {
result.addAll(descriptor.substituteExtensionIfCallable(receiverValue, callType, bindingContext, dataFlowInfo))
}
}
}
else {
extensions.map { resolutionFacade.resolveToDescriptor(it) as CallableDescriptor }
for (declaration in declarations) {
if (declaration.getContainingJetFile().isCompiled()) {
//TODO: it's temporary while resolveToDescriptor does not work for compiled declarations
for (descriptor in analyzeImportReference(declaration.getFqName()!!)) {
if (descriptor is CallableDescriptor && descriptor.getExtensionReceiverParameter() != null) {
processDescriptor(descriptor)
}
}
}
else {
processDescriptor(resolutionFacade.resolveToDescriptor(declaration) as CallableDescriptor)
}
}
return descriptors.sequence()
.filter(visibilityFilter)
.flatMap { it.substituteExtensionIfCallable(receiverValue, callType, bindingContext, dataFlowInfo).sequence() }
return result
}
public fun getClassDescriptors(nameFilter: (String) -> Boolean, kindFilter: (ClassKind) -> Boolean): Collection<ClassDescriptor> {
@@ -1,6 +1,6 @@
package second
fun String.helloFun() {
fun String?.helloFun() {
}
fun String.helloWithParams(i : Int) : String {
@@ -10,5 +10,8 @@ fun String.helloWithParams(i : Int) : String {
fun String.helloFunPreventAutoInsert() {
}
fun <T: CharSequence> T.helloFunGeneric() {
}
fun Int.helloFake() {
}
@@ -8,5 +8,6 @@ fun firstFun() {
// EXIST: helloFun
// EXIST: helloFunPreventAutoInsert
// EXIST: helloWithParams
// EXIST: helloFunGeneric
// ABSENT: helloFake
// NUMBER: 3
// NUMBER: 4
@@ -0,0 +1,16 @@
package second
fun (() -> Unit)?.helloFun1() {
}
fun Function0<Unit>.helloFun2() {
}
fun ExtensionFunction0<String, Unit>.helloFun3() {
}
fun Function1<String, Unit>.helloFun4() {
}
fun Any.helloAny() {
}
@@ -0,0 +1,12 @@
package first
fun firstFun(p: () -> Unit) {
p.hello<caret>
}
// EXIST: helloFun1
// EXIST: helloFun2
// EXIST: helloAny
// ABSENT: helloFun3
// ABSENT: helloFun4
// NUMBER: 3
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.completion;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.InnerTestClasses;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.JetTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
@@ -144,6 +143,12 @@ public class MultiFileJvmBasicCompletionTestGenerated extends AbstractMultiFileJ
doTest(fileName);
}
@TestMetadata("NotImportedExtensionFunction2")
public void testNotImportedExtensionFunction2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NotImportedExtensionFunction2/");
doTest(fileName);
}
@TestMetadata("NotImportedExtensionProperty")
public void testNotImportedExtensionProperty() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/multifile/NotImportedExtensionProperty/");