FIR IDE: introduce return statement target provider
This commit is contained in:
@@ -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.sessions.AbstractSessionsInvalidationTest
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.AbstractProjectWideOutOfBlockKotlinModificationTrackerTest
|
import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.AbstractProjectWideOutOfBlockKotlinModificationTrackerTest
|
||||||
import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
|
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.fir.AbstractResolveCallTest
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.scopes.AbstractMemberScopeByFqNameTest
|
import org.jetbrains.kotlin.idea.frontend.api.scopes.AbstractMemberScopeByFqNameTest
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
|
||||||
@@ -1021,6 +1022,10 @@ fun main(args: Array<String>) {
|
|||||||
testClass<AbstractMemoryLeakInSymbolsTest> {
|
testClass<AbstractMemoryLeakInSymbolsTest> {
|
||||||
model("symbolMemoryLeak")
|
model("symbolMemoryLeak")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testClass<AbstractReturnExpressionTargetTest> {
|
||||||
|
model("components/returnExpressionTarget")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") {
|
testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") {
|
||||||
|
|||||||
+11
@@ -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.KtSymbol
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol
|
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.symbols.markers.isExtension
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
|
||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
@@ -148,3 +149,13 @@ private class KotlinAvailableScopesCompletionProvider(prefixMatcher: PrefixMatch
|
|||||||
collectTypesCompletion(result, implicitScopes)
|
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
|
protected abstract val symbolDeclarationOverridesProvider: KtSymbolDeclarationOverridesProvider
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
protected open val typeRenderer: KtTypeRenderer = KtDefaultTypeRenderer(this, token)
|
protected open val typeRenderer: KtTypeRenderer = KtDefaultTypeRenderer(this, token)
|
||||||
|
protected abstract val expressionHandlingComponent: KtExpressionHandlingComponent
|
||||||
|
|
||||||
/// TODO: get rid of
|
/// TODO: get rid of
|
||||||
@Deprecated("Used only in completion now, temporary")
|
@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 =
|
fun KtType.render(options: KtTypeRendererOptions = KtTypeRendererOptions.DEFAULT): String =
|
||||||
typeRenderer.render(this, options)
|
typeRenderer.render(this, options)
|
||||||
|
|
||||||
|
fun KtReturnExpression.getReturnTargetSymbol(): KtFunctionLikeSymbol? =
|
||||||
|
expressionHandlingComponent.getReturnExpressionTargetSymbol(this)
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -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?
|
||||||
|
}
|
||||||
+2
@@ -45,6 +45,8 @@ private constructor(
|
|||||||
override val symbolDeclarationOverridesProvider: KtSymbolDeclarationOverridesProvider =
|
override val symbolDeclarationOverridesProvider: KtSymbolDeclarationOverridesProvider =
|
||||||
KtFirSymbolDeclarationOverridesProvider(this, token)
|
KtFirSymbolDeclarationOverridesProvider(this, token)
|
||||||
|
|
||||||
|
override val expressionHandlingComponent: KtExpressionHandlingComponent = KtFirExpressionHandlingComponent(this, token)
|
||||||
|
|
||||||
override fun createContextDependentCopy(): KtAnalysisSession {
|
override fun createContextDependentCopy(): KtAnalysisSession {
|
||||||
check(!isContextSession) { "Cannot create context-dependent copy of KtAnalysis session from a context dependent one" }
|
check(!isContextSession) { "Cannot create context-dependent copy of KtAnalysis session from a context dependent one" }
|
||||||
val contextResolveState = LowLevelFirApiFacadeForCompletion.getResolveStateForCompletion(firResolveState)
|
val contextResolveState = LowLevelFirApiFacadeForCompletion.getResolveStateForCompletion(firResolveState)
|
||||||
|
|||||||
+26
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
fun /* EXPECTED_TARGET */x(): Int {
|
||||||
|
receiveLambda {
|
||||||
|
return<caret> 1
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun receiveLambda(x: () -> Unit){}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun /* EXPECTED_TARGET */x(): Int {
|
||||||
|
return<caret> 1
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun /* EXPECTED_TARGET */x(): Int {
|
||||||
|
return<caret> 1
|
||||||
|
}
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
fun x(): Int {
|
||||||
|
receiveLambda { /* EXPECTED_TARGET */
|
||||||
|
return@receiveLambda<caret> 1
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fun receiveLambda(x: () -> Int){}
|
||||||
+72
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user