FIR IDE: introduce return statement target provider

This commit is contained in:
Ilya Kirillov
2020-12-11 17:56:09 +01:00
parent a0ed14eafe
commit c61d4b5f9c
12 changed files with 205 additions and 0 deletions
@@ -88,6 +88,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileSt
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.AbstractSessionsInvalidationTest
import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.AbstractProjectWideOutOfBlockKotlinModificationTrackerTest
import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
import org.jetbrains.kotlin.idea.frontend.api.components.AbstractReturnExpressionTargetTest
import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest
import org.jetbrains.kotlin.idea.frontend.api.scopes.AbstractMemberScopeByFqNameTest
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
@@ -1021,6 +1022,10 @@ fun main(args: Array<String>) {
testClass<AbstractMemoryLeakInSymbolsTest> {
model("symbolMemoryLeak")
}
testClass<AbstractReturnExpressionTargetTest> {
model("components/returnExpressionTarget")
}
}
testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") {
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.isExtension
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
@@ -148,3 +149,13 @@ private class KotlinAvailableScopesCompletionProvider(prefixMatcher: PrefixMatch
collectTypesCompletion(result, implicitScopes)
}
}
private object ExpectedTypeProvider {
fun KtAnalysisSession.getExpectedType(nameExpression: KtSimpleNameExpression, parameters: CompletionParameters): KtType? =
getExpectedTypeByReturnExpression(nameExpression)
private fun KtAnalysisSession.getExpectedTypeByReturnExpression(nameExpression: KtSimpleNameExpression): KtType? {
val parentReturn = nameExpression.parent as? KtReturnExpression ?: return null
TODO()
}
}
@@ -45,6 +45,7 @@ abstract class KtAnalysisSession(final override val token: ValidityToken) : Vali
protected abstract val symbolDeclarationOverridesProvider: KtSymbolDeclarationOverridesProvider
@Suppress("LeakingThis")
protected open val typeRenderer: KtTypeRenderer = KtDefaultTypeRenderer(this, token)
protected abstract val expressionHandlingComponent: KtExpressionHandlingComponent
/// TODO: get rid of
@Deprecated("Used only in completion now, temporary")
@@ -148,4 +149,7 @@ abstract class KtAnalysisSession(final override val token: ValidityToken) : Vali
fun KtType.render(options: KtTypeRendererOptions = KtTypeRendererOptions.DEFAULT): String =
typeRenderer.render(this, options)
fun KtReturnExpression.getReturnTargetSymbol(): KtFunctionLikeSymbol? =
expressionHandlingComponent.getReturnExpressionTargetSymbol(this)
}
@@ -0,0 +1,13 @@
/*
* 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.components
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.psi.KtReturnExpression
abstract class KtExpressionHandlingComponent : KtAnalysisSessionComponent() {
abstract fun getReturnExpressionTargetSymbol(returnExpression: KtReturnExpression): KtFunctionLikeSymbol?
}
@@ -45,6 +45,8 @@ private constructor(
override val symbolDeclarationOverridesProvider: KtSymbolDeclarationOverridesProvider =
KtFirSymbolDeclarationOverridesProvider(this, token)
override val expressionHandlingComponent: KtExpressionHandlingComponent = KtFirExpressionHandlingComponent(this, token)
override fun createContextDependentCopy(): KtAnalysisSession {
check(!isContextSession) { "Cannot create context-dependent copy of KtAnalysis session from a context dependent one" }
val contextResolveState = LowLevelFirApiFacadeForCompletion.getResolveStateForCompletion(firResolveState)
@@ -0,0 +1,26 @@
/*
* 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.components
import org.jetbrains.kotlin.fir.expressions.FirReturnExpression
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirSafe
import org.jetbrains.kotlin.idea.frontend.api.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.components.KtExpressionHandlingComponent
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.psi.KtReturnExpression
internal class KtFirExpressionHandlingComponent(
override val analysisSession: KtFirAnalysisSession,
override val token: ValidityToken,
) : KtExpressionHandlingComponent(), KtFirAnalysisSessionComponent {
override fun getReturnExpressionTargetSymbol(returnExpression: KtReturnExpression): KtFunctionLikeSymbol? {
val fir = returnExpression.getOrBuildFirSafe<FirReturnExpression>(firResolveState) ?: return null
val firTargetSymbol = fir.target.labeledElement
return firSymbolBuilder.buildCallableSymbol(firTargetSymbol) as KtFunctionLikeSymbol
}
}
@@ -0,0 +1,8 @@
fun /* EXPECTED_TARGET */x(): Int {
receiveLambda {
return<caret> 1
}
return 2
}
inline fun receiveLambda(x: () -> Unit){}
@@ -0,0 +1,3 @@
fun /* EXPECTED_TARGET */x(): Int {
return<caret> 1
}
@@ -0,0 +1,3 @@
fun /* EXPECTED_TARGET */x(): Int {
return<caret> 1
}
@@ -0,0 +1,8 @@
fun x(): Int {
receiveLambda { /* EXPECTED_TARGET */
return@receiveLambda<caret> 1
}
return 2
}
fun receiveLambda(x: () -> Int){}
@@ -0,0 +1,72 @@
/*
* 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.components
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiComment
import com.intellij.psi.util.parentOfType
import junit.framework.Assert
import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.idea.frontend.api.analyze
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.util.application.executeOnPooledThread
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import java.io.File
abstract class AbstractReturnExpressionTargetTest : KotlinLightCodeInsightFixtureTestCase() {
override fun isFirPlugin() = true
protected fun doTest(path: String) {
val testDataFile = File(path)
val ktFile = myFixture.configureByText(testDataFile.name, FileUtil.loadFile(testDataFile)) as KtFile
val ktReturnExpressionAtCaret = ktFile.findElementAt(myFixture.caretOffset)?.parentOfType<KtReturnExpression>()
?: error("No element was found at caret or no <caret> is present in the test file")
val expectedReturnTarget = ktFile.getExpectedReturnTarget()
val actualReturnTargetPsi: KtDeclaration? = executeOnPooledThreadInReadAction {
analyze(ktFile) {
val actualReturnTargetSymbol = ktReturnExpressionAtCaret.getReturnTargetSymbol() ?: return@analyze null
actualReturnTargetSymbol.psi as KtDeclaration
}
}
Assert.assertEquals(expectedReturnTarget?.text, actualReturnTargetPsi?.text)
}
private fun KtFile.getExpectedReturnTarget(): KtDeclaration? {
var declaration: KtDeclaration? = null
forEachDescendantOfType<PsiComment> { comment ->
if (comment.text == EXPECTED_RETURN_TARGET_COMMENT) {
if (declaration != null) {
error("More than one $EXPECTED_RETURN_TARGET_COMMENT found")
}
declaration = comment.parentOfType()
}
}
val noDeclarationExpected = InTextDirectivesUtils.findStringWithPrefixes(text, NO_TARGET_EXPECTED_PREFIX) != null
return when {
noDeclarationExpected && declaration != null -> {
error("$noDeclarationExpected was present together with $EXPECTED_RETURN_TARGET_COMMENT")
}
!noDeclarationExpected && declaration == null -> {
error("No $EXPECTED_RETURN_TARGET_COMMENT present, but $NO_TARGET_EXPECTED_PREFIX is not provided")
}
else -> declaration
}
}
companion object {
private const val EXPECTED_RETURN_TARGET_COMMENT = "/* EXPECTED_TARGET */"
private const val NO_TARGET_EXPECTED_PREFIX = "// NO_TARGET_EXPECTED"
}
}
@@ -0,0 +1,50 @@
/*
* 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.components;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/idea-frontend-fir/testData/components/returnExpressionTarget")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class ReturnExpressionTargetTestGenerated extends AbstractReturnExpressionTargetTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInReturnExpressionTarget() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/components/returnExpressionTarget"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("returnFromFunctionViaLambdaWithoutLabel.kt")
public void testReturnFromFunctionViaLambdaWithoutLabel() throws Exception {
runTest("idea/idea-frontend-fir/testData/components/returnExpressionTarget/returnFromFunctionViaLambdaWithoutLabel.kt");
}
@TestMetadata("returnFromFunctionWithLabel.kt")
public void testReturnFromFunctionWithLabel() throws Exception {
runTest("idea/idea-frontend-fir/testData/components/returnExpressionTarget/returnFromFunctionWithLabel.kt");
}
@TestMetadata("returnFromFunctionWithoutLabel.kt")
public void testReturnFromFunctionWithoutLabel() throws Exception {
runTest("idea/idea-frontend-fir/testData/components/returnExpressionTarget/returnFromFunctionWithoutLabel.kt");
}
@TestMetadata("returnFromLambdaWithLabel.kt")
public void testReturnFromLambdaWithLabel() throws Exception {
runTest("idea/idea-frontend-fir/testData/components/returnExpressionTarget/returnFromLambdaWithLabel.kt");
}
}