From 14ca2d207d00984c6d8ec8ef6dfcb1198fa0edca Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Thu, 18 Mar 2021 07:33:09 +0000 Subject: [PATCH] FIR IDE: Additional support for KtFirExpressionTypeProvider.getExpectedType(): - Infix function parameter - Variable assignment - Property declaration - Function expression body --- .../components/KtExpressionTypeProvider.kt | 5 +- .../components/KtFirExpressionTypeProvider.kt | 52 +++++++++++++- .../functionExpressionBody.kt | 3 + .../functionExpressionBodyQualified.kt | 3 + .../functionExpressionBodyWithTypeFromRHS.kt | 5 ++ ...nctionExpressionBodyWithoutExplicitType.kt | 3 + .../functionPositionalParamQualified.kt | 7 ++ .../infixFunctionAsRegularCallParam.kt | 7 ++ .../infixFunctionParam.kt | 7 ++ .../infixFunctionParamQualified.kt | 7 ++ .../propertyDeclaration.kt | 5 ++ .../propertyDeclarationQualified.kt | 5 ++ .../propertyDeclarationWithTypeFromRHS.kt | 6 ++ .../propertyDeclarationWithoutExplicitType.kt | 5 ++ .../variableAssignment.kt | 6 ++ .../variableAssignmentQualified.kt | 6 ++ .../ExpectedExpressionTypeTestGenerated.java | 70 +++++++++++++++++++ 17 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBody.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyQualified.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithTypeFromRHS.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithoutExplicitType.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/functionPositionalParamQualified.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionAsRegularCallParam.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParam.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParamQualified.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclaration.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationQualified.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithTypeFromRHS.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithoutExplicitType.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignment.kt create mode 100644 idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignmentQualified.kt diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtExpressionTypeProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtExpressionTypeProvider.kt index 96b66e5a110..3a9486dc06f 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtExpressionTypeProvider.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtExpressionTypeProvider.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.idea.frontend.api.components import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.idea.frontend.api.ValidityTokenOwner import org.jetbrains.kotlin.idea.frontend.api.types.KtType import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtExpression @@ -25,6 +24,10 @@ interface KtExpressionTypeProviderMixIn : KtAnalysisSessionMixIn { fun KtDeclaration.getReturnKtType(): KtType = analysisSession.expressionTypeProvider.getReturnTypeForKtDeclaration(this) + /** + * Returns the expected [KtType] of this [PsiElement] if it is an expression. The returned value should not be a + * [org.jetbrains.kotlin.idea.frontend.api.types.KtErrorType]. + */ fun PsiElement.getExpectedType(): KtType? = analysisSession.expressionTypeProvider.getExpectedType(this) } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirExpressionTypeProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirExpressionTypeProvider.kt index 4bf5c8831bb..1ae202cc7bb 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirExpressionTypeProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirExpressionTypeProvider.kt @@ -7,7 +7,10 @@ package org.jetbrains.kotlin.idea.frontend.api.fir.components import com.intellij.psi.PsiElement import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration -import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.expressions.FirFunctionCall +import org.jetbrains.kotlin.fir.expressions.FirStatement +import org.jetbrains.kotlin.fir.expressions.argumentMapping import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.references.FirErrorNamedReference import org.jetbrains.kotlin.fir.references.FirNamedReference @@ -23,9 +26,11 @@ import org.jetbrains.kotlin.idea.frontend.api.ValidityToken import org.jetbrains.kotlin.idea.frontend.api.components.KtExpressionTypeProvider import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtTypedSymbol +import org.jetbrains.kotlin.idea.frontend.api.types.KtErrorType import org.jetbrains.kotlin.idea.frontend.api.types.KtType import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* internal class KtFirExpressionTypeProvider( @@ -57,10 +62,16 @@ internal class KtFirExpressionTypeProvider( ?: ConeClassErrorType(ConeUnresolvedNameError(name)) } - override fun getExpectedType(expression: PsiElement): KtType? = - getExpectedTypeByReturnExpression(expression) + override fun getExpectedType(expression: PsiElement): KtType? { + val expectedType = getExpectedTypeByReturnExpression(expression) ?: getExpressionTypeByIfOrBooleanCondition(expression) ?: getExpectedTypeOfFunctionParameter(expression) + ?: getExpectedTypeOfInfixFunctionParameter(expression) + ?: getExpectedTypeByVariableAssignment(expression) + ?: getExpectedTypeByPropertyDeclaration(expression) + ?: getExpectedTypeByFunctionExpressionBody(expression) + return expectedType.takeIf { it !is KtErrorType } + } private fun getExpectedTypeOfFunctionParameter(expression: PsiElement): KtType? { val (ktCallExpression, ktArgument) = expression.getFunctionCallAsWithThisAsParameter() ?: return null @@ -78,6 +89,17 @@ internal class KtFirExpressionTypeProvider( return KtCallWithArgument(callExpression, argumentExpression) } + private fun getExpectedTypeOfInfixFunctionParameter(expression: PsiElement): KtType? { + val infixCallExpression = + expression.unwrapQualified { binaryExpr, expr -> binaryExpr.right == expr } ?: return null + val firCall = infixCallExpression.getOrBuildFirSafe(firResolveState) ?: return null + + // There is only one parameter for infix functions; get its type + val arguments = firCall.argumentMapping ?: return null + val firParameterForExpression = arguments.values.singleOrNull() ?: return null + return firParameterForExpression.returnTypeRef.coneType.asKtType() + } + private fun getExpectedTypeByReturnExpression(expression: PsiElement): KtType? { val returnParent = expression.getReturnExpressionWithThisType() ?: return null val targetSymbol = with(analysisSession) { returnParent.getReturnTargetSymbol() } ?: return null @@ -92,6 +114,30 @@ internal class KtFirExpressionTypeProvider( else -> null } + private fun getExpectedTypeByVariableAssignment(expression: PsiElement): KtType? { + // Given: `x = expression` + // Expected type of `expression` is type of `x` + val assignmentExpression = + expression.unwrapQualified { binaryExpr, expr -> binaryExpr.right == expr && binaryExpr.operationToken == KtTokens.EQ } + ?: return null + val variableExpression = assignmentExpression.left as? KtNameReferenceExpression ?: return null + return getKtExpressionType(variableExpression) + } + + private fun getExpectedTypeByPropertyDeclaration(expression: PsiElement): KtType? { + // Given: `val x: T = expression` + // Expected type of `expression` is `T` + val property = expression.unwrapQualified { property, expr -> property.initializer == expr } ?: return null + return getReturnTypeForKtDeclaration(property) + } + + private fun getExpectedTypeByFunctionExpressionBody(expression: PsiElement): KtType? { + // Given: `fun f(): T = expression` + // Expected type of `expression` is `T` + val function = expression.unwrapQualified { function, expr -> function.bodyExpression == expr } ?: return null + return getReturnTypeForKtDeclaration(function) + } + private fun PsiElement.isWhileLoopCondition() = unwrapQualified { whileExpr, cond -> whileExpr.condition == cond } != null diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBody.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBody.kt new file mode 100644 index 00000000000..f16d598ffac --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBody.kt @@ -0,0 +1,3 @@ +fun foo(): String = av + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyQualified.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyQualified.kt new file mode 100644 index 00000000000..daada518e90 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyQualified.kt @@ -0,0 +1,3 @@ +fun foo(): String = x.av + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithTypeFromRHS.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithTypeFromRHS.kt new file mode 100644 index 00000000000..b31dc99753a --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithTypeFromRHS.kt @@ -0,0 +1,5 @@ +val av = "foo" + +fun foo() = av + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithoutExplicitType.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithoutExplicitType.kt new file mode 100644 index 00000000000..24b167a7781 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithoutExplicitType.kt @@ -0,0 +1,3 @@ +fun foo() = av + +// EXPECTED_TYPE: null \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionPositionalParamQualified.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionPositionalParamQualified.kt new file mode 100644 index 00000000000..0eb15c6db94 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/functionPositionalParamQualified.kt @@ -0,0 +1,7 @@ +fun x() { + toCall(1, x.av, true) +} + +fun toCall(x: Int, y: String, z: Boolean): Char = 'a' + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionAsRegularCallParam.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionAsRegularCallParam.kt new file mode 100644 index 00000000000..8082921ead7 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionAsRegularCallParam.kt @@ -0,0 +1,7 @@ +fun x() { + 1.toCall(x.av) +} + +infix fun Int.toCall(y: String): Char = 'a' + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParam.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParam.kt new file mode 100644 index 00000000000..aafb326c2f2 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParam.kt @@ -0,0 +1,7 @@ +fun x() { + 1 toCall av +} + +infix fun Int.toCall(y: String): Char = 'a' + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParamQualified.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParamQualified.kt new file mode 100644 index 00000000000..643f208a6ca --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParamQualified.kt @@ -0,0 +1,7 @@ +fun x() { + 1 toCall x.av +} + +infix fun Int.toCall(y: String): Char = 'a' + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclaration.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclaration.kt new file mode 100644 index 00000000000..4b4fdb0e583 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclaration.kt @@ -0,0 +1,5 @@ +class C { + var y: String = av +} + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationQualified.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationQualified.kt new file mode 100644 index 00000000000..dce43297677 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationQualified.kt @@ -0,0 +1,5 @@ +class C { + var y: String = x.av +} + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithTypeFromRHS.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithTypeFromRHS.kt new file mode 100644 index 00000000000..cd49ca2e2da --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithTypeFromRHS.kt @@ -0,0 +1,6 @@ +class C { + val av = "foo" + var y = av +} + +// EXPECTED_TYPE: kotlin/String \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithoutExplicitType.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithoutExplicitType.kt new file mode 100644 index 00000000000..a269fb01461 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithoutExplicitType.kt @@ -0,0 +1,5 @@ +class C { + var y = x.av +} + +// EXPECTED_TYPE: null \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignment.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignment.kt new file mode 100644 index 00000000000..444ef1a3eae --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignment.kt @@ -0,0 +1,6 @@ +fun foo() { + var y = 1 + y = av +} + +// EXPECTED_TYPE: kotlin/Int \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignmentQualified.kt b/idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignmentQualified.kt new file mode 100644 index 00000000000..7cb81a8f0f1 --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignmentQualified.kt @@ -0,0 +1,6 @@ +fun foo() { + var y = 1 + y = x.av +} + +// EXPECTED_TYPE: kotlin/Int \ No newline at end of file diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/ExpectedExpressionTypeTestGenerated.java b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/ExpectedExpressionTypeTestGenerated.java index aaad4c25a98..bca7de75118 100644 --- a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/ExpectedExpressionTypeTestGenerated.java +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/ExpectedExpressionTypeTestGenerated.java @@ -29,6 +29,26 @@ public class ExpectedExpressionTypeTestGenerated extends AbstractExpectedExpress KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/components/expectedExpressionType"), Pattern.compile("^(.+)\\.kt$"), null, true); } + @TestMetadata("functionExpressionBody.kt") + public void testFunctionExpressionBody() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBody.kt"); + } + + @TestMetadata("functionExpressionBodyQualified.kt") + public void testFunctionExpressionBodyQualified() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyQualified.kt"); + } + + @TestMetadata("functionExpressionBodyWithTypeFromRHS.kt") + public void testFunctionExpressionBodyWithTypeFromRHS() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithTypeFromRHS.kt"); + } + + @TestMetadata("functionExpressionBodyWithoutExplicitType.kt") + public void testFunctionExpressionBodyWithoutExplicitType() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionExpressionBodyWithoutExplicitType.kt"); + } + @TestMetadata("functionLambdaParam.kt") public void testFunctionLambdaParam() throws Exception { runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionLambdaParam.kt"); @@ -49,6 +69,11 @@ public class ExpectedExpressionTypeTestGenerated extends AbstractExpectedExpress runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionPositionalParam.kt"); } + @TestMetadata("functionPositionalParamQualified.kt") + public void testFunctionPositionalParamQualified() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/functionPositionalParamQualified.kt"); + } + @TestMetadata("ifCondition.kt") public void testIfCondition() throws Exception { runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/ifCondition.kt"); @@ -59,6 +84,41 @@ public class ExpectedExpressionTypeTestGenerated extends AbstractExpectedExpress runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/ifConditionQualified.kt"); } + @TestMetadata("infixFunctionAsRegularCallParam.kt") + public void testInfixFunctionAsRegularCallParam() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionAsRegularCallParam.kt"); + } + + @TestMetadata("infixFunctionParam.kt") + public void testInfixFunctionParam() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParam.kt"); + } + + @TestMetadata("infixFunctionParamQualified.kt") + public void testInfixFunctionParamQualified() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/infixFunctionParamQualified.kt"); + } + + @TestMetadata("propertyDeclaration.kt") + public void testPropertyDeclaration() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclaration.kt"); + } + + @TestMetadata("propertyDeclarationQualified.kt") + public void testPropertyDeclarationQualified() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationQualified.kt"); + } + + @TestMetadata("propertyDeclarationWithTypeFromRHS.kt") + public void testPropertyDeclarationWithTypeFromRHS() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithTypeFromRHS.kt"); + } + + @TestMetadata("propertyDeclarationWithoutExplicitType.kt") + public void testPropertyDeclarationWithoutExplicitType() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/propertyDeclarationWithoutExplicitType.kt"); + } + @TestMetadata("returnFromFunction.kt") public void testReturnFromFunction() throws Exception { runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/returnFromFunction.kt"); @@ -79,6 +139,16 @@ public class ExpectedExpressionTypeTestGenerated extends AbstractExpectedExpress runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/returnFromLambda.kt"); } + @TestMetadata("variableAssignment.kt") + public void testVariableAssignment() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignment.kt"); + } + + @TestMetadata("variableAssignmentQualified.kt") + public void testVariableAssignmentQualified() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/variableAssignmentQualified.kt"); + } + @TestMetadata("whileCondition.kt") public void testWhileCondition() throws Exception { runTest("idea/idea-frontend-fir/testData/components/expectedExpressionType/whileCondition.kt");