From ec072798b191b06d51a6be09ed03de6d9b26fd47 Mon Sep 17 00:00:00 2001 From: Pavel Kirpichenkov Date: Wed, 19 Aug 2020 13:18:53 +0300 Subject: [PATCH] [FIR-IDE] Hack for checking single candidate resolver in tests --- idea/build.gradle.kts | 1 + .../basic/common/ConsiderExtensionType.kt | 20 +++++++ .../ExtensionsWithTypeParameterConstraints.kt | 11 ++++ .../basic/common/GenericCompanionExtension.kt | 24 ++++++++ .../test/JSBasicCompletionTestGenerated.java | 15 +++++ .../test/JvmBasicCompletionTestGenerated.java | 15 +++++ .../idea/frontend/api/KtAnalysisSession.kt | 7 +++ .../frontend/api/fir/KtFirAnalysisSession.kt | 60 +++++++++++++++++++ .../api/fir/components/KtFirScopeProvider.kt | 6 +- .../KtFirMemberTypeParameterStubbingScope.kt | 36 +++++++++++ 10 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 idea/idea-completion/testData/basic/common/ConsiderExtensionType.kt create mode 100644 idea/idea-completion/testData/basic/common/ExtensionsWithTypeParameterConstraints.kt create mode 100644 idea/idea-completion/testData/basic/common/GenericCompanionExtension.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/scopes/KtFirMemberTypeParameterStubbingScope.kt diff --git a/idea/build.gradle.kts b/idea/build.gradle.kts index 5a2bd8feb18..e0d2427732a 100644 --- a/idea/build.gradle.kts +++ b/idea/build.gradle.kts @@ -187,6 +187,7 @@ dependencies { testRuntime(intellijPluginDep("smali")) testRuntime(intellijPluginDep("testng")) + testRuntime(project(":idea:idea-fir")) testRuntime(project(":idea:idea-frontend-fir")) if (Ide.AS36.orHigher()) { diff --git a/idea/idea-completion/testData/basic/common/ConsiderExtensionType.kt b/idea/idea-completion/testData/basic/common/ConsiderExtensionType.kt new file mode 100644 index 00000000000..0de335d689e --- /dev/null +++ b/idea/idea-completion/testData/basic/common/ConsiderExtensionType.kt @@ -0,0 +1,20 @@ +class A +open class B + +class C : B() +interface D + +fun T.ext1() where T : B {} +fun B.ext2() {} +fun A.ext3() {} +fun T.ext4() where T : B, T : D {} + +fun usage() { + val c = C() + c.ext +} + +// EXIST: ext1 +// EXIST: ext2 +// ABSENT: ext3 +// ABSENT: ext4 \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/ExtensionsWithTypeParameterConstraints.kt b/idea/idea-completion/testData/basic/common/ExtensionsWithTypeParameterConstraints.kt new file mode 100644 index 00000000000..43a46982edd --- /dev/null +++ b/idea/idea-completion/testData/basic/common/ExtensionsWithTypeParameterConstraints.kt @@ -0,0 +1,11 @@ +fun T.ext1() {} +fun T.ext2() {} + +fun test() { + with(42) { + ext + } +} + +// EXIST: ext1 +// ABSENT: ext2 \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/GenericCompanionExtension.kt b/idea/idea-completion/testData/basic/common/GenericCompanionExtension.kt new file mode 100644 index 00000000000..ac3cc465ee4 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/GenericCompanionExtension.kt @@ -0,0 +1,24 @@ +class A { + fun foo1() {} + + class B { + fun foo2() {} + + fun foo3() { + foo + } + } + + class C + + companion object { + fun B.foo4() {} + fun C.foo5() {} + } +} + +// ABSENT: foo1 +// EXIST: foo2 +// EXIST: foo3 +// EXIST: foo4 +// ABSENT: foo5 diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index fea699bbb3e..22269f0a6b0 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -85,6 +85,11 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes runTest("idea/idea-completion/testData/basic/common/ComponentFunction.kt"); } + @TestMetadata("ConsiderExtensionType.kt") + public void testConsiderExtensionType() throws Exception { + runTest("idea/idea-completion/testData/basic/common/ConsiderExtensionType.kt"); + } + @TestMetadata("CoroutineContext.kt") public void testCoroutineContext() throws Exception { runTest("idea/idea-completion/testData/basic/common/CoroutineContext.kt"); @@ -185,6 +190,11 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes runTest("idea/idea-completion/testData/basic/common/ExtensionWithManyTypeParamsInReceiver.kt"); } + @TestMetadata("ExtensionsWithTypeParameterConstraints.kt") + public void testExtensionsWithTypeParameterConstraints() throws Exception { + runTest("idea/idea-completion/testData/basic/common/ExtensionsWithTypeParameterConstraints.kt"); + } + @TestMetadata("FromImports.kt") public void testFromImports() throws Exception { runTest("idea/idea-completion/testData/basic/common/FromImports.kt"); @@ -200,6 +210,11 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes runTest("idea/idea-completion/testData/basic/common/FunctionVariableCallArgument.kt"); } + @TestMetadata("GenericCompanionExtension.kt") + public void testGenericCompanionExtension() throws Exception { + runTest("idea/idea-completion/testData/basic/common/GenericCompanionExtension.kt"); + } + @TestMetadata("GenericExtensionFunReceiver1.kt") public void testGenericExtensionFunReceiver1() throws Exception { runTest("idea/idea-completion/testData/basic/common/GenericExtensionFunReceiver1.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index 6c1cb0c5074..9af0da044a0 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -85,6 +85,11 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT runTest("idea/idea-completion/testData/basic/common/ComponentFunction.kt"); } + @TestMetadata("ConsiderExtensionType.kt") + public void testConsiderExtensionType() throws Exception { + runTest("idea/idea-completion/testData/basic/common/ConsiderExtensionType.kt"); + } + @TestMetadata("CoroutineContext.kt") public void testCoroutineContext() throws Exception { runTest("idea/idea-completion/testData/basic/common/CoroutineContext.kt"); @@ -185,6 +190,11 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT runTest("idea/idea-completion/testData/basic/common/ExtensionWithManyTypeParamsInReceiver.kt"); } + @TestMetadata("ExtensionsWithTypeParameterConstraints.kt") + public void testExtensionsWithTypeParameterConstraints() throws Exception { + runTest("idea/idea-completion/testData/basic/common/ExtensionsWithTypeParameterConstraints.kt"); + } + @TestMetadata("FromImports.kt") public void testFromImports() throws Exception { runTest("idea/idea-completion/testData/basic/common/FromImports.kt"); @@ -200,6 +210,11 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT runTest("idea/idea-completion/testData/basic/common/FunctionVariableCallArgument.kt"); } + @TestMetadata("GenericCompanionExtension.kt") + public void testGenericCompanionExtension() throws Exception { + runTest("idea/idea-completion/testData/basic/common/GenericCompanionExtension.kt"); + } + @TestMetadata("GenericExtensionFunReceiver1.kt") public void testGenericExtensionFunReceiver1() throws Exception { runTest("idea/idea-completion/testData/basic/common/GenericExtensionFunReceiver1.kt"); diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt index ed66efd4eca..fdd24a71902 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt @@ -118,4 +118,11 @@ abstract class KtAnalysisSession(override val token: ValidityToken) : ValidityTo check(this is KtSymbolBasedReference) { "To get reference symbol the one should be KtSymbolBasedReference but was ${this::class}" } return resolveToSymbols().singleOrNull() } + + abstract fun resolveAndCheckReceivers( + firSymbolForCandidate: KtCallableSymbol, + originalFile: KtFile, + nameExpression: KtSimpleNameExpression, + possibleReceiver: KtExpression?, + ): Boolean } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt index d702d3fe0a0..a4c7f6bf6b0 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt @@ -5,6 +5,9 @@ package org.jetbrains.kotlin.idea.frontend.api.fir +import com.jetbrains.rd.util.getOrCreate +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.resolve.firSymbolProvider import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveState import org.jetbrains.kotlin.idea.fir.low.level.api.LowLevelFirApiFacade @@ -17,10 +20,20 @@ import org.jetbrains.kotlin.idea.frontend.api.components.KtDiagnosticProvider import org.jetbrains.kotlin.idea.frontend.api.components.KtSmartCastProvider import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeProvider import org.jetbrains.kotlin.idea.frontend.api.fir.components.* +import org.jetbrains.kotlin.idea.fir.* +import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.SingleCandidateResolver +import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.SingleCandidateResolutionMode +import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.ResolutionParameters +import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirFunctionSymbol import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbolProvider import org.jetbrains.kotlin.idea.frontend.api.fir.utils.threadLocal import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolProvider import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.idea.frontend.api.symbols.* +import org.jetbrains.kotlin.idea.util.getElementTextInContext +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.utils.addToStdlib.safeAs internal class KtFirAnalysisSession private constructor( @@ -58,6 +71,53 @@ private constructor( ) } + private val completionContextCache = HashMap, LowLevelFirApiFacade.FirCompletionContext>() + + // Temporary kludge to check single candidate resolver in tests + override fun resolveAndCheckReceivers( + firSymbolForCandidate: KtCallableSymbol, + originalFile: KtFile, + nameExpression: KtSimpleNameExpression, + possibleReceiver: KtExpression?, + ): Boolean { + firSymbolForCandidate.safeAs()?.firRef?.withFir { firFunction -> + val file = originalFile.getOrBuildFirOfType(firResolveState) + val explicitReceiverExpression = possibleReceiver?.getOrBuildFirOfType(firResolveState) + val resolver = SingleCandidateResolver(firResolveState.firIdeSourcesSession, file) + val enclosingFunction = nameExpression.getNonStrictParentOfType() + ?: error("Cannot find enclosing function for ${nameExpression.getElementTextInContext()}") + + val completionContext = completionContextCache.getOrCreate(file to enclosingFunction) { + LowLevelFirApiFacade.buildCompletionContextForFunction( + file, + enclosingFunction, + enclosingFunction, + state = firResolveState + ) + } + + val towerDataContext = completionContext.getTowerDataContext(nameExpression) + + val implicitReceivers = sequence { + yield(null) + yieldAll(towerDataContext.implicitReceiverStack) + } + for (implicitReceiverValue in implicitReceivers) { + val resolutionParameters = ResolutionParameters( + singleCandidateResolutionMode = SingleCandidateResolutionMode.CHECK_EXTENSION_FOR_COMPLETION, + callableSymbol = firFunction.symbol, + implicitReceiver = implicitReceiverValue, + explicitReceiver = explicitReceiverExpression + ) + resolver.resolveSingleCandidate(resolutionParameters)?.let { + // not null if resolved and completed successfully + return true + } + } + } + return false + } + companion object { @Deprecated("Please use org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSessionProviderKt.analyze") internal fun createForElement(element: KtElement): KtFirAnalysisSession { diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirScopeProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirScopeProvider.kt index c324baa49e8..58d2e0c0968 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirScopeProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirScopeProvider.kt @@ -13,10 +13,7 @@ import org.jetbrains.kotlin.fir.resolve.ScopeSession import org.jetbrains.kotlin.fir.resolve.scope import org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScope import org.jetbrains.kotlin.fir.scopes.FirScope -import org.jetbrains.kotlin.fir.scopes.impl.FirAbstractSimpleImportingScope -import org.jetbrains.kotlin.fir.scopes.impl.FirAbstractStarImportingScope -import org.jetbrains.kotlin.fir.scopes.impl.FirPackageMemberScope -import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope +import org.jetbrains.kotlin.fir.scopes.impl.* import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope import org.jetbrains.kotlin.idea.fir.getOrBuildFirOfType import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveState @@ -155,6 +152,7 @@ internal class KtFirScopeProvider( is FirAbstractStarImportingScope -> KtFirStarImportingScope(firScope, builder, project, token) is FirPackageMemberScope -> KtFirPackageScope(firScope, project, builder, token) is FirContainingNamesAwareScope -> KtFirDelegatingScopeImpl(firScope, builder, token) + is FirMemberTypeParameterScope -> KtFirMemberTypeParameterStubbingScope(firScope, token) else -> TODO(firScope::class.toString()) } } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/scopes/KtFirMemberTypeParameterStubbingScope.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/scopes/KtFirMemberTypeParameterStubbingScope.kt new file mode 100644 index 00000000000..3cf4979e6cc --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/scopes/KtFirMemberTypeParameterStubbingScope.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.frontend.api.fir.scopes + +import org.jetbrains.kotlin.fir.scopes.impl.FirMemberTypeParameterScope +import org.jetbrains.kotlin.idea.frontend.api.ValidityToken +import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScope +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassLikeSymbol +import org.jetbrains.kotlin.name.Name + +// Does nothing, it is used to close the hole in KtFirScopeProvider +// TODO: check if this scope should provide something useful +class KtFirMemberTypeParameterStubbingScope( + val firMemberTypeParameterScope: FirMemberTypeParameterScope, + override val token: ValidityToken, +) : KtScope { + override fun getCallableNames(): Set { + return emptySet() + } + + override fun getClassLikeSymbolNames(): Set { + return emptySet() + } + + override fun getCallableSymbols(): Sequence { + return emptySequence() + } + + override fun getClassClassLikeSymbols(): Sequence { + return emptySequence() + } +} \ No newline at end of file