FIR IDE: add expected type calculation for function calls
does not work in case of incomplete call expression :(
This commit is contained in:
@@ -32,6 +32,7 @@ dependencies {
|
|||||||
testCompile(projectTests(":idea:idea-test-framework"))
|
testCompile(projectTests(":idea:idea-test-framework"))
|
||||||
testCompile(project(":kotlin-test:kotlin-test-junit"))
|
testCompile(project(":kotlin-test:kotlin-test-junit"))
|
||||||
testCompile(commonDep("junit:junit"))
|
testCompile(commonDep("junit:junit"))
|
||||||
|
testCompile(projectTests(":idea:idea-frontend-independent"))
|
||||||
|
|
||||||
compile(intellijPluginDep("java"))
|
compile(intellijPluginDep("java"))
|
||||||
}
|
}
|
||||||
|
|||||||
+27
@@ -6,12 +6,19 @@
|
|||||||
package org.jetbrains.kotlin.idea.frontend.api.fir.components
|
package org.jetbrains.kotlin.idea.frontend.api.fir.components
|
||||||
|
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.jetbrains.rd.util.firstOrNull
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
|
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.FirCall
|
||||||
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
|
||||||
|
import org.jetbrains.kotlin.fir.expressions.argumentMapping
|
||||||
|
import org.jetbrains.kotlin.fir.psi
|
||||||
import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
|
import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
|
||||||
import org.jetbrains.kotlin.fir.types.ConeTypeCheckerContext
|
import org.jetbrains.kotlin.fir.types.ConeTypeCheckerContext
|
||||||
import org.jetbrains.kotlin.fir.types.coneType
|
import org.jetbrains.kotlin.fir.types.coneType
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFir
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirOfType
|
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirOfType
|
||||||
|
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.ValidityToken
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.assertIsValid
|
import org.jetbrains.kotlin.idea.frontend.api.assertIsValid
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtBuiltinTypes
|
import org.jetbrains.kotlin.idea.frontend.api.components.KtBuiltinTypes
|
||||||
@@ -23,6 +30,7 @@ import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
|
|||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeCheckerContext
|
import org.jetbrains.kotlin.types.AbstractTypeCheckerContext
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
internal class KtFirTypeProvider(
|
internal class KtFirTypeProvider(
|
||||||
@@ -41,6 +49,23 @@ internal class KtFirTypeProvider(
|
|||||||
override fun getExpectedType(expression: PsiElement): KtType? =
|
override fun getExpectedType(expression: PsiElement): KtType? =
|
||||||
getExpectedTypeByReturnExpression(expression)
|
getExpectedTypeByReturnExpression(expression)
|
||||||
?: getExpressionTypeByIfOrBooleanCondition(expression)
|
?: getExpressionTypeByIfOrBooleanCondition(expression)
|
||||||
|
?: getExpectedTypeOfFunctionParameter(expression)
|
||||||
|
|
||||||
|
private fun getExpectedTypeOfFunctionParameter(expression: PsiElement): KtType? {
|
||||||
|
val (ktCallExpression, ktArgument) = expression.getFunctionCallAsWithThisAsParameter() ?: return null
|
||||||
|
val firCall = ktCallExpression.getOrBuildFirSafe<FirFunctionCall>(firResolveState) ?: return null
|
||||||
|
val arguments = firCall.argumentMapping ?: return null
|
||||||
|
val firParameterForExpression = arguments.entries.firstOrNull { (arg, _) -> arg.psi == ktArgument }?.value ?: return null
|
||||||
|
return firParameterForExpression.returnTypeRef.coneType.asKtType()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.getFunctionCallAsWithThisAsParameter(): KtCallWithArgument? {
|
||||||
|
val valueArgument = unwrapQualified<KtValueArgument> { valueArg, expr -> valueArg.getArgumentExpression() == expr } ?: return null
|
||||||
|
val argumentsList = valueArgument.parent as? KtValueArgumentList ?: return null
|
||||||
|
val callExpression = argumentsList.parent as? KtCallExpression ?: return null
|
||||||
|
val argumentExpression = valueArgument.getArgumentExpression() ?: return null
|
||||||
|
return KtCallWithArgument(callExpression, argumentExpression)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getExpectedTypeByReturnExpression(expression: PsiElement): KtType? {
|
private fun getExpectedTypeByReturnExpression(expression: PsiElement): KtType? {
|
||||||
val returnParent = expression.getReturnExpressionWithThisType() ?: return null
|
val returnParent = expression.getReturnExpressionWithThisType() ?: return null
|
||||||
@@ -99,6 +124,8 @@ internal class KtFirTypeProvider(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class KtCallWithArgument(val call: KtCallExpression, val argument: KtExpression)
|
||||||
|
|
||||||
private inline fun <reified R : Any> PsiElement.unwrapQualified(check: (R, PsiElement) -> Boolean): R? {
|
private inline fun <reified R : Any> PsiElement.unwrapQualified(check: (R, PsiElement) -> Boolean): R? {
|
||||||
val parent = nonContainerParent
|
val parent = nonContainerParent
|
||||||
return when {
|
return when {
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
fun x() {
|
||||||
|
toCall(1, "", a<caret>v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toCall(x: Int, y: String, lambda: (Int) -> String): Char = 'a'
|
||||||
|
|
||||||
|
// EXPECTED_TYPE: kotlin/Function1<kotlin/Int, kotlin/String>
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// FIX_ME: should work on non fully resolved calls
|
||||||
|
|
||||||
|
fun x() {
|
||||||
|
toCall(1, z = a<caret>v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toCall(x: Int, y: String, z: Boolean): Char = 'a'
|
||||||
|
|
||||||
|
// EXPECTED_TYPE: kotlin/Boolean
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// FIX_ME: should work on non fully resolved calls
|
||||||
|
|
||||||
|
fun x() {
|
||||||
|
toCall(a<caret>b, 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> toCall(x: T, y: T): Char = 'a'
|
||||||
|
|
||||||
|
// EXPECTED_TYPE: kotlin/Int
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
fun x() {
|
||||||
|
toCall(1, a<caret>v, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toCall(x: Int, y: String, z: Boolean): Char = 'a'
|
||||||
|
|
||||||
|
// EXPECTED_TYPE: kotlin/String
|
||||||
+4
-1
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.psi.KtReturnExpression
|
|||||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
|
import org.jetbrains.kotlin.test.uitls.IgnoreTests
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
abstract class AbstractExpectedExpressionTypeTest : KotlinLightCodeInsightFixtureTestCase() {
|
abstract class AbstractExpectedExpressionTypeTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||||
@@ -37,7 +38,9 @@ abstract class AbstractExpectedExpressionTypeTest : KotlinLightCodeInsightFixtur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KotlinTestUtils.assertEqualsToFile(File(path), testDataFile.getTextWithActualType(actualExpectedTypeText))
|
IgnoreTests.runTestWithFixMeSupport(testDataFile.toPath()) {
|
||||||
|
KotlinTestUtils.assertEqualsToFile(File(path), testDataFile.getTextWithActualType(actualExpectedTypeText))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun File.getTextWithActualType(actualType: String?) : String {
|
private fun File.getTextWithActualType(actualType: String?) : String {
|
||||||
|
|||||||
+20
@@ -28,6 +28,26 @@ public class ExpectedExpressionTypeTestGenerated extends AbstractExpectedExpress
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/components/expectedExpressionType"), Pattern.compile("^(.+)\\.kt$"), null, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/components/expectedExpressionType"), Pattern.compile("^(.+)\\.kt$"), null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("functionLambdaParam.kt")
|
||||||
|
public void testFunctionLambdaParam() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionLambdaParam.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("functionNamedlParam.kt")
|
||||||
|
public void testFunctionNamedlParam() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionNamedlParam.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("functionParamWithTypeParam.kt")
|
||||||
|
public void testFunctionParamWithTypeParam() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionParamWithTypeParam.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("functionPositionalParam.kt")
|
||||||
|
public void testFunctionPositionalParam() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionPositionalParam.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("ifCondition.kt")
|
@TestMetadata("ifCondition.kt")
|
||||||
public void testIfCondition() throws Exception {
|
public void testIfCondition() throws Exception {
|
||||||
runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/ifCondition.kt");
|
runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/ifCondition.kt");
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ object IgnoreTests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun runTestWithFixMeSupport(testFile: Path, test: () -> Unit) {
|
||||||
|
runTestIfEnabledByDirective(
|
||||||
|
testFile,
|
||||||
|
EnableOrDisableTestDirective.Disable(DIRECTIVES.FIX_ME),
|
||||||
|
additionalFilesExtensions = emptyList(),
|
||||||
|
test = test
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun runTestIfNotDisabledByFileDirective(
|
fun runTestIfNotDisabledByFileDirective(
|
||||||
testFile: Path,
|
testFile: Path,
|
||||||
disableTestDirective: String,
|
disableTestDirective: String,
|
||||||
@@ -128,5 +137,6 @@ object IgnoreTests {
|
|||||||
object DIRECTIVES {
|
object DIRECTIVES {
|
||||||
const val FIR_COMPARISON = "// FIR_COMPARISON"
|
const val FIR_COMPARISON = "// FIR_COMPARISON"
|
||||||
const val IGNORE_FIR = "// IGNORE_FIR"
|
const val IGNORE_FIR = "// IGNORE_FIR"
|
||||||
|
const val FIX_ME = "// FIX_ME: "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user