From 71c5c9f6c52f815f27a01cbe967b647b327ea47e Mon Sep 17 00:00:00 2001 From: Tianyu Geng Date: Thu, 6 May 2021 09:43:04 -0700 Subject: [PATCH] FIR IDE: add quickfix to change function return type --- .../diagnostics/FirDiagnosticsList.kt | 1 + .../fir/analysis/diagnostics/FirErrors.kt | 3 +- .../FirFunctionReturnTypeMismatchChecker.kt | 5 +- .../diagnostics/FirDefaultErrorMessages.kt | 2 +- .../generators/tests/idea/GenerateTests.kt | 2 + .../idea/quickfix/MainKtQuickFixRegistrar.kt | 14 +- ...kFix.kt => ChangeTypeQuickFixFactories.kt} | 104 +++++++++++--- .../HighLevelQuickFixTestGenerated.java | 136 ++++++++++++++++++ .../fir/generator/HLDiagnosticConverter.kt | 5 + ...deDeserializedDeclarationSourceProvider.kt | 6 +- .../diagnostics/KtFirDataClassConverters.kt | 1 + .../api/fir/diagnostics/KtFirDiagnostics.kt | 1 + .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 1 + .../idea/quickfix/ChangeTypeFixUtils.kt | 66 +++++++++ .../quickfix/ChangeCallableReturnTypeFix.kt | 65 +-------- .../assignmentTypeMismatch.kt | 3 +- .../assignmentTypeMismatch.kt.after | 3 +- .../literalPropertyWithGetter.kt | 3 +- .../literalPropertyWithGetter.kt.after | 3 +- .../propertyGetterInitializerTypeMismatch.kt | 3 +- ...essionTypeMismatchFunctionParameterType.kt | 3 +- ...eMismatchInIfStatementReturnedByLiteral.kt | 3 +- 22 files changed, 340 insertions(+), 93 deletions(-) rename idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/{ChangeReturnTypeOnOverrideQuickFix.kt => ChangeTypeQuickFixFactories.kt} (52%) create mode 100644 idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/ChangeTypeFixUtils.kt diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 0535c3fd68b..5b321e1543e 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -418,6 +418,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val RETURN_TYPE_MISMATCH by error(PositioningStrategy.WHOLE_ELEMENT) { parameter("expectedType") parameter("actualType") + parameter("targetFunction") } val CYCLIC_GENERIC_UPPER_BOUND by error() diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 4012cb5d084..e90687f01c6 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirEnumEntry import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration +import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction import org.jetbrains.kotlin.fir.declarations.FirValueParameter import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol @@ -288,7 +289,7 @@ object FirErrors { val REIFIED_TYPE_PARAMETER_NO_INLINE by error0(SourceElementPositioningStrategies.REIFIED_MODIFIER) val TYPE_PARAMETERS_NOT_ALLOWED by error0(SourceElementPositioningStrategies.TYPE_PARAMETERS_LIST) val TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER by error0() - val RETURN_TYPE_MISMATCH by error2(SourceElementPositioningStrategies.WHOLE_ELEMENT) + val RETURN_TYPE_MISMATCH by error3(SourceElementPositioningStrategies.WHOLE_ELEMENT) val CYCLIC_GENERIC_UPPER_BOUND by error0() val DEPRECATED_TYPE_PARAMETER_SYNTAX by error0(SourceElementPositioningStrategies.TYPE_PARAMETERS_LIST) val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirFunctionReturnTypeMismatchChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirFunctionReturnTypeMismatchChecker.kt index 0ead64d87e5..503eeee20d5 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirFunctionReturnTypeMismatchChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirFunctionReturnTypeMismatchChecker.kt @@ -36,7 +36,10 @@ object FirFunctionReturnTypeMismatchChecker : FirReturnExpressionChecker() { if (resultExpression.isNullLiteral && functionReturnType.nullability == ConeNullability.NOT_NULL) { reporter.reportOn(resultExpression.source, NULL_FOR_NONNULL_TYPE, context) } else { - reporter.report(RETURN_TYPE_MISMATCH.on(returnExpressionSource, functionReturnType, returnExpressionType), context) + reporter.report( + RETURN_TYPE_MISMATCH.on(returnExpressionSource, functionReturnType, returnExpressionType, targetElement), + context + ) } } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index ceb67931558..af91891a44f 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -667,7 +667,7 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { map.put(TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER, "Type parameter of a property must be used in its receiver type") - map.put(RETURN_TYPE_MISMATCH, "Return type mismatch: expected {0}, actual {1}", RENDER_TYPE, RENDER_TYPE) + map.put(RETURN_TYPE_MISMATCH, "Return type mismatch: expected {0}, actual {1}", RENDER_TYPE, RENDER_TYPE, NOT_RENDERED) map.put(CYCLIC_GENERIC_UPPER_BOUND, "Type parameter has cyclic upper bounds") diff --git a/generators/idea-generator/tests/org/jetbrains/kotlin/generators/tests/idea/GenerateTests.kt b/generators/idea-generator/tests/org/jetbrains/kotlin/generators/tests/idea/GenerateTests.kt index 5c712cf898b..4bde0eb70c3 100644 --- a/generators/idea-generator/tests/org/jetbrains/kotlin/generators/tests/idea/GenerateTests.kt +++ b/generators/idea-generator/tests/org/jetbrains/kotlin/generators/tests/idea/GenerateTests.kt @@ -1148,6 +1148,8 @@ fun main(args: Array) { model("quickfix/variables/changeMutability", pattern = pattern, filenameStartsLowerCase = true) model("quickfix/when", pattern = pattern, filenameStartsLowerCase = true) model("quickfix/wrapWithSafeLetCall", pattern = pattern, filenameStartsLowerCase = true) + model("quickfix/typeMismatch/componentFunctionReturnTypeMismatch", pattern = pattern, filenameStartsLowerCase = true) + model("quickfix/typeMismatch/typeMismatchOnReturnedExpression", pattern = pattern, filenameStartsLowerCase = true) } testClass { diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt index 62f77507554..41ba8862e03 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt @@ -12,8 +12,6 @@ import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixesListBuilder import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic import org.jetbrains.kotlin.idea.quickfix.fixes.* import org.jetbrains.kotlin.idea.quickfix.fixes.InitializePropertyQuickFixFactory -import org.jetbrains.kotlin.idea.quickfix.fixes.ChangeTypeQuickFix -import org.jetbrains.kotlin.idea.quickfix.fixes.ReplaceCallFixFactories class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { private val modifiers = KtQuickFixesListBuilder.registerPsiQuickFix { @@ -78,9 +76,9 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { } private val overrides = KtQuickFixesListBuilder.registerPsiQuickFix { - registerApplicator(ChangeTypeQuickFix.changeFunctionReturnTypeOnOverride) - registerApplicator(ChangeTypeQuickFix.changePropertyReturnTypeOnOverride) - registerApplicator(ChangeTypeQuickFix.changeVariableReturnTypeOnOverride) + registerApplicator(ChangeTypeQuickFixFactories.changeFunctionReturnTypeOnOverride) + registerApplicator(ChangeTypeQuickFixFactories.changePropertyReturnTypeOnOverride) + registerApplicator(ChangeTypeQuickFixFactories.changeVariableReturnTypeOnOverride) registerApplicator(MemberNotImplementedQuickfixFactories.abstractMemberNotImplemented) registerApplicator(MemberNotImplementedQuickfixFactories.abstractClassMemberNotImplemented) registerApplicator(MemberNotImplementedQuickfixFactories.manyInterfacesMemberNotImplemented) @@ -120,6 +118,11 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { registerApplicator(WrapWithSafeLetCallFixFactories.forArgumentTypeMismatch) } + private val returnTypes = KtQuickFixesListBuilder.registerPsiQuickFix { + registerApplicator(ChangeTypeQuickFixFactories.componentFunctionReturnTypeMismatch) + registerApplicator(ChangeTypeQuickFixFactories.returnTypeMismatch) + } + override val list: KtQuickFixesList = KtQuickFixesList.createCombined( modifiers, propertyInitialization, @@ -127,5 +130,6 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { imports, mutability, expressions, + returnTypes ) } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeTypeQuickFixFactories.kt similarity index 52% rename from idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt rename to idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeTypeQuickFixFactories.kt index 74433d1f0c9..02603f6e8dd 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeReturnTypeOnOverrideQuickFix.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/fixes/ChangeTypeQuickFixFactories.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.quickfix.fixes import com.intellij.psi.PsiElement import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.api.applicator.HLApplicatorInput import org.jetbrains.kotlin.idea.api.applicator.applicator import org.jetbrains.kotlin.idea.fir.api.fixes.HLApplicatorTargetWithInput @@ -16,17 +17,24 @@ import org.jetbrains.kotlin.idea.fir.applicators.CallableReturnTypeUpdaterApplic import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic -import org.jetbrains.kotlin.idea.frontend.api.symbols.* +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtPropertySymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithMembers +import org.jetbrains.kotlin.idea.frontend.api.symbols.psiSafe +import org.jetbrains.kotlin.idea.frontend.api.types.KtClassType import org.jetbrains.kotlin.idea.frontend.api.types.KtType -import org.jetbrains.kotlin.idea.quickfix.ChangeCallableReturnTypeFix +import org.jetbrains.kotlin.idea.quickfix.ChangeTypeFixUtils +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType -object ChangeTypeQuickFix { +object ChangeTypeQuickFixFactories { val applicator = applicator { familyName(CallableReturnTypeUpdaterApplicator.applicator.getFamilyName()) - actionName { declaration, (updateBaseFunction, type) -> - val presentation = getPresentation(updateBaseFunction, declaration) + actionName { declaration, (targetType, type) -> + val presentation = getPresentation(targetType, declaration) getActionName(declaration, presentation, type) } @@ -39,7 +47,7 @@ object ChangeTypeQuickFix { declaration: KtCallableDeclaration, presentation: String?, type: CallableReturnTypeUpdaterApplicator.Type - ) = ChangeCallableReturnTypeFix.StringPresentation.getTextForQuickFix( + ) = ChangeTypeFixUtils.getTextForQuickFix( declaration, presentation, type.isUnit, @@ -47,21 +55,48 @@ object ChangeTypeQuickFix { ) private fun getPresentation( - updateBaseFunction: Boolean, + targetType: TargetType, declaration: KtCallableDeclaration - ) = when { - updateBaseFunction -> { - val containerName = declaration.parentOfType()?.nameAsName?.takeUnless { it.isSpecial } - ChangeCallableReturnTypeFix.StringPresentation.baseFunctionOrConstructorParameterPresentation( - declaration, - containerName + ): String? { + return when (targetType) { + TargetType.CURRENT_DECLARATION -> null + TargetType.BASE_DECLARATION -> KotlinBundle.message( + "fix.change.return.type.presentation.base", + declaration.presentationForQuickfix ?: return null ) + TargetType.ENCLOSING_DECLARATION -> KotlinBundle.message( + "fix.change.return.type.presentation.enclosing", + declaration.presentationForQuickfix ?: return KotlinBundle.message("fix.change.return.type.presentation.enclosing.function") + ) + TargetType.CALLED_FUNCTION -> { + val presentation = + declaration.presentationForQuickfix + ?: return KotlinBundle.message("fix.change.return.type.presentation.called.function") + when (declaration) { + is KtParameter -> KotlinBundle.message("fix.change.return.type.presentation.accessed", presentation) + else -> KotlinBundle.message("fix.change.return.type.presentation.called", presentation) + } + } + TargetType.VARIABLE -> return "'${declaration.name}'" } - else -> null + } + + private val KtCallableDeclaration.presentationForQuickfix: String? + get() { + val containerName = parentOfType()?.nameAsName?.takeUnless { it.isSpecial } + return ChangeTypeFixUtils.functionOrConstructorParameterPresentation(this, containerName?.asString()) + } + + enum class TargetType { + CURRENT_DECLARATION, + BASE_DECLARATION, + ENCLOSING_DECLARATION, + CALLED_FUNCTION, + VARIABLE, } data class Input( - val updateBaseFunction: Boolean, + val targetType: TargetType, val type: CallableReturnTypeUpdaterApplicator.Type ) : HLApplicatorInput { override fun isValidFor(psi: PsiElement): Boolean = type.isValidFor(psi) @@ -82,6 +117,31 @@ object ChangeTypeQuickFix { it.variable as? KtPropertySymbol } + val returnTypeMismatch = + diagnosticFixFactory(applicator) { diagnostic -> + val function = diagnostic.targetFunction.psi as? KtCallableDeclaration ?: return@diagnosticFixFactory emptyList() + listOf(function withInput Input(TargetType.ENCLOSING_DECLARATION, createTypeInfo(diagnostic.actualType))) + } + + @OptIn(ExperimentalStdlibApi::class) + val componentFunctionReturnTypeMismatch = + diagnosticFixFactory(applicator) { diagnostic -> + val entryWithWrongType = + getDestructuringDeclarationEntryThatTypeMismatchComponentFunction( + diagnostic.componentFunctionName, + diagnostic.psi + ) + ?: return@diagnosticFixFactory emptyList() + buildList> { + add(entryWithWrongType withInput Input(TargetType.VARIABLE, createTypeInfo(diagnostic.destructingType))) + val classSymbol = (diagnostic.psi.getKtType() as? KtClassType)?.classSymbol as? KtSymbolWithMembers ?: return@buildList + val componentFunction = classSymbol.getMemberScope() + .getCallableSymbols { it == diagnostic.componentFunctionName } + .firstOrNull()?.psi as? KtCallableDeclaration + ?: return@buildList + add(componentFunction withInput Input(TargetType.CALLED_FUNCTION, createTypeInfo(diagnostic.expectedType))) + } + } private inline fun > changeReturnTypeOnOverride( crossinline getCallableSymbol: (DIAGNOSTIC) -> KtCallableSymbol? @@ -100,7 +160,7 @@ object ChangeTypeQuickFix { ): HLApplicatorTargetWithInput? { val lowerSuperType = findLowerBoundOfOverriddenCallablesReturnTypes(callable) ?: return null val changeToTypeInfo = createTypeInfo(lowerSuperType) - return declaration withInput Input(updateBaseFunction = false, changeToTypeInfo) + return declaration withInput Input(TargetType.CURRENT_DECLARATION, changeToTypeInfo) } private fun KtAnalysisSession.createChangeOverriddenFunctionQuickFix( @@ -110,7 +170,8 @@ object ChangeTypeQuickFix { val singleNonMatchingOverriddenFunction = findSingleNonMatchingOverriddenFunction(callable, type) ?: return null val singleMatchingOverriddenFunctionPsi = singleNonMatchingOverriddenFunction.psiSafe() ?: return null val changeToTypeInfo = createTypeInfo(type) - return singleMatchingOverriddenFunctionPsi withInput Input(updateBaseFunction = true, changeToTypeInfo) + if (!singleMatchingOverriddenFunctionPsi.isWritable) return null + return singleMatchingOverriddenFunctionPsi withInput Input(TargetType.BASE_DECLARATION, changeToTypeInfo) } private fun KtAnalysisSession.findSingleNonMatchingOverriddenFunction( @@ -143,4 +204,13 @@ object ChangeTypeQuickFix { } return lowestType } + + private fun getDestructuringDeclarationEntryThatTypeMismatchComponentFunction( + componentName: Name, + rhsExpression: KtExpression + ): KtDestructuringDeclarationEntry? { + val componentIndex = componentName.asString().removePrefix("component").toIntOrNull() ?: return null + val destructuringDeclaration = rhsExpression.getParentOfType(strict = true) ?: return null + return destructuringDeclaration.entries[componentIndex - 1] + } } diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/quickfix/HighLevelQuickFixTestGenerated.java b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/quickfix/HighLevelQuickFixTestGenerated.java index 1dc7779a842..e86fb5bddfb 100644 --- a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/quickfix/HighLevelQuickFixTestGenerated.java +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/quickfix/HighLevelQuickFixTestGenerated.java @@ -1813,4 +1813,140 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes runTest("idea/testData/quickfix/wrapWithSafeLetCall/wrapAllNonNullablePositions6.kt"); } } + + @TestMetadata("idea/testData/quickfix/typeMismatch/componentFunctionReturnTypeMismatch") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ComponentFunctionReturnTypeMismatch extends AbstractHighLevelQuickFixTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInComponentFunctionReturnTypeMismatch() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/typeMismatch/componentFunctionReturnTypeMismatch"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true); + } + + @TestMetadata("componentFunctionReturnTypeMismatch1.kt") + public void testComponentFunctionReturnTypeMismatch1() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/componentFunctionReturnTypeMismatch/componentFunctionReturnTypeMismatch1.kt"); + } + + @TestMetadata("componentFunctionReturnTypeMismatch2.kt") + public void testComponentFunctionReturnTypeMismatch2() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/componentFunctionReturnTypeMismatch/componentFunctionReturnTypeMismatch2.kt"); + } + + @TestMetadata("componentFunctionReturnTypeMismatch3.kt") + public void testComponentFunctionReturnTypeMismatch3() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/componentFunctionReturnTypeMismatch/componentFunctionReturnTypeMismatch3.kt"); + } + + @TestMetadata("componentFunctionReturnTypeMismatch4.kt") + public void testComponentFunctionReturnTypeMismatch4() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/componentFunctionReturnTypeMismatch/componentFunctionReturnTypeMismatch4.kt"); + } + + @TestMetadata("componentFunctionReturnTypeMismatch5.kt") + public void testComponentFunctionReturnTypeMismatch5() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/componentFunctionReturnTypeMismatch/componentFunctionReturnTypeMismatch5.kt"); + } + + @TestMetadata("dataClass.kt") + public void testDataClass() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/componentFunctionReturnTypeMismatch/dataClass.kt"); + } + } + + @TestMetadata("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class TypeMismatchOnReturnedExpression extends AbstractHighLevelQuickFixTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInTypeMismatchOnReturnedExpression() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), null, true); + } + + @TestMetadata("assignmentTypeMismatch.kt") + public void testAssignmentTypeMismatch() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/assignmentTypeMismatch.kt"); + } + + @TestMetadata("changeFunctionReturnTypeToFunctionType.kt") + public void testChangeFunctionReturnTypeToFunctionType() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/changeFunctionReturnTypeToFunctionType.kt"); + } + + @TestMetadata("changeFunctionReturnTypeToMatchReturnTypeOfReturnedLiteral.kt") + public void testChangeFunctionReturnTypeToMatchReturnTypeOfReturnedLiteral() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/changeFunctionReturnTypeToMatchReturnTypeOfReturnedLiteral.kt"); + } + + @TestMetadata("dontChangeFunctionReturnTypeToErrorType.kt") + public void testDontChangeFunctionReturnTypeToErrorType() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/dontChangeFunctionReturnTypeToErrorType.kt"); + } + + @TestMetadata("literalPropertyWithGetter.kt") + public void testLiteralPropertyWithGetter() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/literalPropertyWithGetter.kt"); + } + + @TestMetadata("multiFakeOverride.kt") + public void testMultiFakeOverride() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/multiFakeOverride.kt"); + } + + @TestMetadata("multiFakeOverrideForOperatorConvention.kt") + public void testMultiFakeOverrideForOperatorConvention() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/multiFakeOverrideForOperatorConvention.kt"); + } + + @TestMetadata("nonLocalReturnRuntime.kt") + public void testNonLocalReturnRuntime() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/nonLocalReturnRuntime.kt"); + } + + @TestMetadata("nonLocalReturnWithLabelRuntime.kt") + public void testNonLocalReturnWithLabelRuntime() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/nonLocalReturnWithLabelRuntime.kt"); + } + + @TestMetadata("notApplicableToConstructor.kt") + public void testNotApplicableToConstructor() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/notApplicableToConstructor.kt"); + } + + @TestMetadata("propertyGetterInitializerTypeMismatch.kt") + public void testPropertyGetterInitializerTypeMismatch() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/propertyGetterInitializerTypeMismatch.kt"); + } + + @TestMetadata("returnedExpressionTypeMismatchFunctionParameterType.kt") + public void testReturnedExpressionTypeMismatchFunctionParameterType() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/returnedExpressionTypeMismatchFunctionParameterType.kt"); + } + + @TestMetadata("typeMismatchInIfStatementReturnedByFunction.kt") + public void testTypeMismatchInIfStatementReturnedByFunction() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInIfStatementReturnedByFunction.kt"); + } + + @TestMetadata("typeMismatchInIfStatementReturnedByLiteral.kt") + public void testTypeMismatchInIfStatementReturnedByLiteral() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInIfStatementReturnedByLiteral.kt"); + } + + @TestMetadata("typeMismatchInInitializer.kt") + public void testTypeMismatchInInitializer() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInInitializer.kt"); + } + + @TestMetadata("typeMismatchInReturnStatement.kt") + public void testTypeMismatchInReturnStatement() throws Exception { + runTest("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInReturnStatement.kt"); + } + } } diff --git a/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt b/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt index d31e4670d75..46943a7c336 100644 --- a/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt +++ b/idea/idea-frontend-fir/idea-frontend-fir-generator/src/org/jetbrains/kotlin/idea/frontend/api/fir/generator/HLDiagnosticConverter.kt @@ -217,6 +217,11 @@ private object FirToKtConversionCreator { KtSymbol::class.createType(), importsToAdd = listOf("org.jetbrains.kotlin.fir.declarations.FirDeclaration") ), + FirSimpleFunction::class to HLFunctionCallConversion( + "firSymbolBuilder.buildSymbol({0})", + KtSymbol::class.createType(), + importsToAdd = listOf("org.jetbrains.kotlin.fir.declarations.FirSimpleFunction") + ), FirNamedFunctionSymbol::class to HLFunctionCallConversion( "firSymbolBuilder.functionLikeBuilder.buildFunctionSymbol({0}.fir)", KtFunctionLikeSymbol::class.createType(), diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirIdeDeserializedDeclarationSourceProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirIdeDeserializedDeclarationSourceProvider.kt index 56fa42908c2..8cdf4c8b3c9 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirIdeDeserializedDeclarationSourceProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirIdeDeserializedDeclarationSourceProvider.kt @@ -137,7 +137,11 @@ object FirIdeDeserializedDeclarationSourceProvider { private fun KtElement.isCompiled(): Boolean = containingKtFile.isCompiled -private val allowedFakeElementKinds = setOf(FirFakeSourceElementKind.PropertyFromParameter, FirFakeSourceElementKind.ItLambdaParameter) +private val allowedFakeElementKinds = setOf( + FirFakeSourceElementKind.PropertyFromParameter, + FirFakeSourceElementKind.ItLambdaParameter, + FirFakeSourceElementKind.DataClassGeneratedMembers +) private fun FirElement.getAllowedPsi() = when (val source = source) { null -> null diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index b4067642e3b..4ca387b5a21 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -1289,6 +1289,7 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert ReturnTypeMismatchImpl( firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a), firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b), + firSymbolBuilder.buildSymbol(firDiagnostic.c), firDiagnostic as FirPsiDiagnostic<*>, token, ) diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 859bb687927..ce3b40c74b6 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -910,6 +910,7 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = ReturnTypeMismatch::class abstract val expectedType: KtType abstract val actualType: KtType + abstract val targetFunction: KtSymbol } abstract class CyclicGenericUpperBound : KtFirDiagnostic() { diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index dcf4b37ea1f..984aa9bd246 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -1471,6 +1471,7 @@ internal class TypeParameterOfPropertyNotUsedInReceiverImpl( internal class ReturnTypeMismatchImpl( override val expectedType: KtType, override val actualType: KtType, + override val targetFunction: KtSymbol, firDiagnostic: FirPsiDiagnostic<*>, override val token: ValidityToken, ) : KtFirDiagnostic.ReturnTypeMismatch(), KtAbstractFirDiagnostic { diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/ChangeTypeFixUtils.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/ChangeTypeFixUtils.kt new file mode 100644 index 00000000000..0e642c7600e --- /dev/null +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/quickfix/ChangeTypeFixUtils.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2021 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.quickfix + +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtProperty + +object ChangeTypeFixUtils { + fun familyName(): String = KotlinBundle.message("fix.change.return.type.family") + + fun functionOrConstructorParameterPresentation(element: KtCallableDeclaration, containerName: String?): String? { + val name = element.name + return if (name != null) { + val fullName = if (containerName != null) "'${containerName}.$name'" else "'$name'" + when (element) { + is KtParameter -> KotlinBundle.message("fix.change.return.type.presentation.property", fullName) + is KtProperty -> KotlinBundle.message("fix.change.return.type.presentation.property", fullName) + else -> KotlinBundle.message("fix.change.return.type.presentation.function", fullName) + } + } else null + } + + + fun baseFunctionOrConstructorParameterPresentation(presentation: String): String = + KotlinBundle.message("fix.change.return.type.presentation.base", presentation) + + fun baseFunctionOrConstructorParameterPresentation(element: KtCallableDeclaration, containerName: String?): String? { + val presentation = functionOrConstructorParameterPresentation(element, containerName) ?: return null + return baseFunctionOrConstructorParameterPresentation(presentation) + } + + fun getTextForQuickFix( + element: KtCallableDeclaration, + presentation: String?, + isUnitType: Boolean, + typePresentation: String + ): String { + if (isUnitType && element is KtFunction && element.hasBlockBody()) { + return if (presentation == null) + KotlinBundle.message("fix.change.return.type.remove.explicit.return.type") + else + KotlinBundle.message("fix.change.return.type.remove.explicit.return.type.of", presentation) + } + + return when (element) { + is KtFunction -> { + if (presentation != null) + KotlinBundle.message("fix.change.return.type.return.type.text.of", presentation, typePresentation) + else + KotlinBundle.message("fix.change.return.type.return.type.text", typePresentation) + } + else -> { + if (presentation != null) + KotlinBundle.message("fix.change.return.type.type.text.of", presentation, typePresentation) + else + KotlinBundle.message("fix.change.return.type.type.text", typePresentation) + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeCallableReturnTypeFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeCallableReturnTypeFix.kt index ab2061bc3c2..ad80366a070 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeCallableReturnTypeFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeCallableReturnTypeFix.kt @@ -34,7 +34,6 @@ import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil import org.jetbrains.kotlin.idea.project.builtIns import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.resolve.BindingContext @@ -74,8 +73,8 @@ abstract class ChangeCallableReturnTypeFix( val element = element!! if (element.name == null) return null val container = element.unsafeResolveToDescriptor().containingDeclaration as? ClassDescriptor - val containerName = container?.name?.takeUnless { it.isSpecial } - return StringPresentation.functionOrConstructorParameterPresentation(element, containerName) + val containerName = container?.name?.takeUnless { it.isSpecial }?.asString() + return ChangeTypeFixUtils.functionOrConstructorParameterPresentation(element, containerName) } class OnType(element: KtFunction, type: KotlinType) : ChangeCallableReturnTypeFix(element, type), HighPriorityAction { @@ -104,7 +103,7 @@ abstract class ChangeCallableReturnTypeFix( class ForOverridden(element: KtFunction, type: KotlinType) : ChangeCallableReturnTypeFix(element, type) { override fun functionPresentation(): String? { val presentation = super.functionPresentation() ?: return null - return StringPresentation.baseFunctionOrConstructorParameterPresentation(presentation) + return ChangeTypeFixUtils.baseFunctionOrConstructorParameterPresentation(presentation) } } @@ -115,10 +114,10 @@ abstract class ChangeCallableReturnTypeFix( return changeFunctionLiteralReturnTypeFix.text } - return StringPresentation.getTextForQuickFix(element, functionPresentation(), isUnitType, typePresentation) + return ChangeTypeFixUtils.getTextForQuickFix(element, functionPresentation(), isUnitType, typePresentation) } - override fun getFamilyName(): String = StringPresentation.familyName() + override fun getFamilyName(): String = ChangeTypeFixUtils.familyName() override fun isAvailable(project: Project, editor: Editor?, file: KtFile): Boolean { return !typeContainsError && @@ -234,59 +233,5 @@ abstract class ChangeCallableReturnTypeFix( return multiDeclaration.entries[componentIndex - 1] } } - - object StringPresentation { - fun familyName(): String = KotlinBundle.message("fix.change.return.type.family") - - fun functionOrConstructorParameterPresentation(element: KtCallableDeclaration, containerName: Name?): String? { - val name = element.name - return if (name != null) { - val fullName = if (containerName != null) "'${containerName.asString()}.$name'" else "'$name'" - when (element) { - is KtParameter -> KotlinBundle.message("fix.change.return.type.presentation.property", fullName) - is KtProperty -> KotlinBundle.message("fix.change.return.type.presentation.property", fullName) - else -> KotlinBundle.message("fix.change.return.type.presentation.function", fullName) - } - } else null - } - - - fun baseFunctionOrConstructorParameterPresentation(presentation: String): String = - KotlinBundle.message("fix.change.return.type.presentation.base", presentation) - - fun baseFunctionOrConstructorParameterPresentation(element: KtCallableDeclaration, containerName: Name?): String? { - val presentation = functionOrConstructorParameterPresentation(element, containerName) ?: return null - return baseFunctionOrConstructorParameterPresentation(presentation) - } - - fun getTextForQuickFix( - element: KtCallableDeclaration, - presentation: String?, - isUnitType: Boolean, - typePresentation: String - ): String { - if (isUnitType && element is KtFunction && element.hasBlockBody()) { - return if (presentation == null) - KotlinBundle.message("fix.change.return.type.remove.explicit.return.type") - else - KotlinBundle.message("fix.change.return.type.remove.explicit.return.type.of", presentation) - } - - return when (element) { - is KtFunction -> { - if (presentation != null) - KotlinBundle.message("fix.change.return.type.return.type.text.of", presentation, typePresentation) - else - KotlinBundle.message("fix.change.return.type.return.type.text", typePresentation) - } - else -> { - if (presentation != null) - KotlinBundle.message("fix.change.return.type.type.text.of", presentation, typePresentation) - else - KotlinBundle.message("fix.change.return.type.type.text", typePresentation) - } - } - } - } } diff --git a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/assignmentTypeMismatch.kt b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/assignmentTypeMismatch.kt index 0d3ed11bf0b..3cf58142b57 100644 --- a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/assignmentTypeMismatch.kt +++ b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/assignmentTypeMismatch.kt @@ -4,4 +4,5 @@ fun foo() { var x = 1 x += 21 } -} \ No newline at end of file +} +/* IGNORE_FIR */ diff --git a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/assignmentTypeMismatch.kt.after b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/assignmentTypeMismatch.kt.after index 397fa0eef5f..41fcd2a48bf 100644 --- a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/assignmentTypeMismatch.kt.after +++ b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/assignmentTypeMismatch.kt.after @@ -4,4 +4,5 @@ fun foo() { var x = 1 x += 21 } -} \ No newline at end of file +} +/* IGNORE_FIR */ diff --git a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/literalPropertyWithGetter.kt b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/literalPropertyWithGetter.kt index 3d42263b312..6b1d85afcf6 100644 --- a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/literalPropertyWithGetter.kt +++ b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/literalPropertyWithGetter.kt @@ -1,4 +1,5 @@ // "Change type of 'complex' to '(Int) -> Long'" "true" val complex: (Int) -> String - get() = { it.toLong() } \ No newline at end of file + get() = { it.toLong() } +/* IGNORE_FIR */ diff --git a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/literalPropertyWithGetter.kt.after b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/literalPropertyWithGetter.kt.after index a616495e321..a0aacf3dff0 100644 --- a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/literalPropertyWithGetter.kt.after +++ b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/literalPropertyWithGetter.kt.after @@ -1,4 +1,5 @@ // "Change type of 'complex' to '(Int) -> Long'" "true" val complex: (Int) -> Long - get() = { it.toLong() } \ No newline at end of file + get() = { it.toLong() } +/* IGNORE_FIR */ diff --git a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/propertyGetterInitializerTypeMismatch.kt b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/propertyGetterInitializerTypeMismatch.kt index 19572cd7386..eed74655fc3 100644 --- a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/propertyGetterInitializerTypeMismatch.kt +++ b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/propertyGetterInitializerTypeMismatch.kt @@ -4,4 +4,5 @@ class A { var x: Int get(): Int = if (true) { {42} } else { {24} } set(i: Int) {} -} \ No newline at end of file +} +/* IGNORE_FIR */ diff --git a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/returnedExpressionTypeMismatchFunctionParameterType.kt b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/returnedExpressionTypeMismatchFunctionParameterType.kt index cb39787a072..7e2ebb18c78 100644 --- a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/returnedExpressionTypeMismatchFunctionParameterType.kt +++ b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/returnedExpressionTypeMismatchFunctionParameterType.kt @@ -3,4 +3,5 @@ fun foo(f: () -> Int) { foo { "" } -} \ No newline at end of file +} +/* IGNORE_FIR */ diff --git a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInIfStatementReturnedByLiteral.kt b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInIfStatementReturnedByLiteral.kt index 358ef194410..6142902545c 100644 --- a/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInIfStatementReturnedByLiteral.kt +++ b/idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression/typeMismatchInIfStatementReturnedByLiteral.kt @@ -9,4 +9,5 @@ fun foo() { if (true) x else x } } -} \ No newline at end of file +} +/* IGNORE_FIR */