From 5ba561871888ae1d45826bd54908f481d30ab155 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Mon, 30 Mar 2015 22:45:14 +0300 Subject: [PATCH] Extension callables index and its use in completion --- .../org/jetbrains/kotlin/psi/JetProperty.java | 4 +- .../kotlin/psi/stubs/StubInterfaces.kt | 14 +- .../elements/JetPropertyElementType.java | 2 +- .../psi/stubs/impl/KotlinPropertyStubImpl.kt | 4 +- .../stubBuilder/CallableClsStubBuilder.kt | 2 +- .../kotlin/idea/stubindex/IndexUtils.kt | 66 +++++++++ ...etTopLevelExtensionsByReceiverTypeIndex.kt | 43 ++++++ .../idea/stubindex/StubIndexServiceImpl.java | 5 +- idea/src/META-INF/plugin.xml | 1 + .../kotlin/idea/caches/KotlinIndicesHelper.kt | 136 ++++++++++-------- ...NotImportedExtensionFunction.dependency.kt | 5 +- .../NotImportedExtensionFunction.kt | 3 +- ...otImportedExtensionFunction2.dependency.kt | 16 +++ .../NotImportedExtensionFunction2.kt | 12 ++ ...tiFileJvmBasicCompletionTestGenerated.java | 7 +- 15 files changed, 245 insertions(+), 75 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/IndexUtils.kt create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/JetTopLevelExtensionsByReceiverTypeIndex.kt create mode 100644 idea/testData/completion/basic/multifile/NotImportedExtensionFunction2/NotImportedExtensionFunction2.dependency.kt create mode 100644 idea/testData/completion/basic/multifile/NotImportedExtensionFunction2/NotImportedExtensionFunction2.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetProperty.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetProperty.java index 68274253dc4..d8370711fdc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetProperty.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetProperty.java @@ -98,7 +98,7 @@ public class JetProperty extends JetTypeParameterListOwnerStub 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; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/stubs/StubInterfaces.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/stubs/StubInterfaces.kt index fe1dd77ce39..e3dec570d36 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/stubs/StubInterfaces.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/stubs/StubInterfaces.kt @@ -56,13 +56,10 @@ public trait KotlinAnnotationEntryStub : StubElement { public fun hasValueArguments(): Boolean } -public trait KotlinFunctionStub : KotlinStubWithFqName { - public fun isTopLevel(): Boolean - public fun isExtension(): Boolean +public trait KotlinFunctionStub : KotlinCallableStubBase { public fun hasBlockBody(): Boolean public fun hasBody(): Boolean public fun hasTypeParameterListBeforeFunctionName(): Boolean - public fun isProbablyNothingType(): Boolean } public trait KotlinImportDirectiveStub : StubElement { @@ -92,14 +89,17 @@ public trait KotlinPropertyAccessorStub : StubElement { public fun hasBlockBody(): Boolean } -public trait KotlinPropertyStub : KotlinStubWithFqName { +public trait KotlinPropertyStub : KotlinCallableStubBase { 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 : KotlinStubWithFqName { + public fun isTopLevel(): Boolean + public fun isExtension(): Boolean public fun isProbablyNothingType(): Boolean } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/stubs/elements/JetPropertyElementType.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/stubs/elements/JetPropertyElementType.java index e02b93952af..ad402085d78 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/stubs/elements/JetPropertyElementType.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/stubs/elements/JetPropertyElementType.java @@ -61,7 +61,7 @@ public class JetPropertyElementType extends JetStubElementType(stub: KotlinCallableStubBase, sink: IndexSink) { + if (stub.isExtension()) { + val declaration = stub.getPsi() + declaration.getReceiverTypeReference()!!.getTypeElement()?.index(declaration, sink) + } +} + +private fun 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") + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/JetTopLevelExtensionsByReceiverTypeIndex.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/JetTopLevelExtensionsByReceiverTypeIndex.kt new file mode 100644 index 00000000000..12425eca0ac --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/JetTopLevelExtensionsByReceiverTypeIndex.kt @@ -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() { + + 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(javaClass()) + + 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', "") + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/StubIndexServiceImpl.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/StubIndexServiceImpl.java index c914fa66a7d..f61c2fa3c28 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/StubIndexServiceImpl.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/StubIndexServiceImpl.java @@ -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); } } } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index d707bab0997..5d0004ceab9 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -428,6 +428,7 @@ + diff --git a/idea/src/org/jetbrains/kotlin/idea/caches/KotlinIndicesHelper.kt b/idea/src/org/jetbrains/kotlin/idea/caches/KotlinIndicesHelper.kt index 4cd87189be0..cb5d90a301b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/caches/KotlinIndicesHelper.kt +++ b/idea/src/org/jetbrains/kotlin/idea/caches/KotlinIndicesHelper.kt @@ -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 { + 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() - 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, dataFlowInfo: DataFlowInfo): Set { + val result = LinkedHashSet() + for (receiverValue in receiverValues) { + for (type in SmartCastUtils.getSmartCastVariants(receiverValue, bindingContext, dataFlowInfo)) { + result.addTypeNames(type) + } + } return result } - private fun MutableCollection.fqNamesToSuitableExtensions( - fqNames: Sequence, - nameFilter: (String) -> Boolean, - index: StringStubIndexExtension, - expression: JetSimpleNameExpression, - bindingContext: BindingContext, - dataFlowInfo: DataFlowInfo) { - val matchingNames = fqNames.filter { nameFilter(it.shortName().asString()) } - + private fun receiverValues(expression: JetSimpleNameExpression): Collection> { 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.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, - receiverValue: ReceiverValue, - dataFlowInfo: DataFlowInfo, - callType: CallType, - bindingContext: BindingContext): Sequence { - 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() - .filter { it.getExtensionReceiverParameter() != null } + private fun findSuitableExtensions( + declarations: Sequence, + receiverValues: Collection>, + dataFlowInfo: DataFlowInfo, + bindingContext: BindingContext + ): Collection { + val result = LinkedHashSet() + + 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 { diff --git a/idea/testData/completion/basic/multifile/NotImportedExtensionFunction/NotImportedExtensionFunction.dependency.kt b/idea/testData/completion/basic/multifile/NotImportedExtensionFunction/NotImportedExtensionFunction.dependency.kt index 0d782748e87..1e1ee43305e 100644 --- a/idea/testData/completion/basic/multifile/NotImportedExtensionFunction/NotImportedExtensionFunction.dependency.kt +++ b/idea/testData/completion/basic/multifile/NotImportedExtensionFunction/NotImportedExtensionFunction.dependency.kt @@ -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.helloFunGeneric() { +} + fun Int.helloFake() { } \ No newline at end of file diff --git a/idea/testData/completion/basic/multifile/NotImportedExtensionFunction/NotImportedExtensionFunction.kt b/idea/testData/completion/basic/multifile/NotImportedExtensionFunction/NotImportedExtensionFunction.kt index dda68250d5e..68cf2a3dc82 100644 --- a/idea/testData/completion/basic/multifile/NotImportedExtensionFunction/NotImportedExtensionFunction.kt +++ b/idea/testData/completion/basic/multifile/NotImportedExtensionFunction/NotImportedExtensionFunction.kt @@ -8,5 +8,6 @@ fun firstFun() { // EXIST: helloFun // EXIST: helloFunPreventAutoInsert // EXIST: helloWithParams +// EXIST: helloFunGeneric // ABSENT: helloFake -// NUMBER: 3 \ No newline at end of file +// NUMBER: 4 \ No newline at end of file diff --git a/idea/testData/completion/basic/multifile/NotImportedExtensionFunction2/NotImportedExtensionFunction2.dependency.kt b/idea/testData/completion/basic/multifile/NotImportedExtensionFunction2/NotImportedExtensionFunction2.dependency.kt new file mode 100644 index 00000000000..daad50bf546 --- /dev/null +++ b/idea/testData/completion/basic/multifile/NotImportedExtensionFunction2/NotImportedExtensionFunction2.dependency.kt @@ -0,0 +1,16 @@ +package second + +fun (() -> Unit)?.helloFun1() { +} + +fun Function0.helloFun2() { +} + +fun ExtensionFunction0.helloFun3() { +} + +fun Function1.helloFun4() { +} + +fun Any.helloAny() { +} \ No newline at end of file diff --git a/idea/testData/completion/basic/multifile/NotImportedExtensionFunction2/NotImportedExtensionFunction2.kt b/idea/testData/completion/basic/multifile/NotImportedExtensionFunction2/NotImportedExtensionFunction2.kt new file mode 100644 index 00000000000..e00e1cffc07 --- /dev/null +++ b/idea/testData/completion/basic/multifile/NotImportedExtensionFunction2/NotImportedExtensionFunction2.kt @@ -0,0 +1,12 @@ +package first + +fun firstFun(p: () -> Unit) { + p.hello +} + +// EXIST: helloFun1 +// EXIST: helloFun2 +// EXIST: helloAny +// ABSENT: helloFun3 +// ABSENT: helloFun4 +// NUMBER: 3 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/completion/MultiFileJvmBasicCompletionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/completion/MultiFileJvmBasicCompletionTestGenerated.java index 8caff0bdc5c..96f63264e82 100644 --- a/idea/tests/org/jetbrains/kotlin/completion/MultiFileJvmBasicCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/completion/MultiFileJvmBasicCompletionTestGenerated.java @@ -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/");