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 c5461b94530..8ec6d22fd0b 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 @@ -1091,6 +1091,7 @@ fun main(args: Array) { model("intentions/specifyTypeExplicitly", pattern = pattern) model("intentions/importAllMembers", pattern = pattern) model("intentions/importMember", pattern = pattern) + model("intentions/convertToBlockBody", pattern = pattern) } testClass { diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/intentions/HLConvertToBlockBodyIntention.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/intentions/HLConvertToBlockBodyIntention.kt new file mode 100644 index 00000000000..9fe1f033067 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/intentions/HLConvertToBlockBodyIntention.kt @@ -0,0 +1,124 @@ +/* + * 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.fir.intentions + +import com.intellij.psi.PsiElement +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.AbstractHLIntention +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicabilityRange +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInputProvider +import org.jetbrains.kotlin.idea.fir.api.applicator.inputProvider +import org.jetbrains.kotlin.idea.fir.applicators.ApplicabilityRanges +import org.jetbrains.kotlin.idea.formatter.adjustLineIndent +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.types.KtClassErrorType +import org.jetbrains.kotlin.idea.frontend.api.types.KtType +import org.jetbrains.kotlin.idea.util.resultingWhens +import org.jetbrains.kotlin.idea.util.setType +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +class HLConvertToBlockBodyIntention : + AbstractHLIntention(KtDeclarationWithBody::class, applicator) { + + class Input( + val returnType: KtType, + val returnTypeIsUnit: Boolean, + val returnTypeIsNothing: Boolean, + val returnTypeString: String, + val returnTypeClassId: ClassId?, + val bodyTypeIsUnit: Boolean, + val bodyTypeIsNothing: Boolean, + val reformat: Boolean, + ) : HLApplicatorInput + + override fun allowCaretInsideElement(element: PsiElement) = element !is KtDeclaration && super.allowCaretInsideElement(element) + + override val applicabilityRange: HLApplicabilityRange get() = ApplicabilityRanges.SELF + + override val inputProvider: HLApplicatorInputProvider + get() = inputProvider { psi -> + if (psi is KtNamedFunction) { + val returnType = psi.getReturnKtType() + if (!psi.hasDeclaredReturnType() && returnType is KtClassErrorType) return@inputProvider null + } + createInputForDeclaration(psi, true) + } + + companion object { + fun KtAnalysisSession.createInputForDeclaration(declaration: KtDeclarationWithBody, reformat: Boolean): Input? { + val body = declaration.bodyExpression ?: return null + val returnType = declaration.getReturnKtType().approximateToSuperPublicDenotableOrSelf() + val bodyType = body.getKtType() + return Input( + returnType, + returnType.isUnit, + returnType.isNothing, + returnType.render(), + returnType.expandedClassSymbol?.classIdIfNonLocal, + bodyType.isUnit, + bodyType.isNothing, + reformat, + ) + } + + val applicator = applicator { + familyAndActionName(KotlinBundle.lazyMessage(("convert.to.block.body"))) + isApplicableByPsi { (it is KtNamedFunction || it is KtPropertyAccessor) && !it.hasBlockBody() && it.hasBody() } + applyTo { declaration, input -> + val body = declaration.bodyExpression!! + + val newBody = when (declaration) { + is KtNamedFunction -> { + if (!declaration.hasDeclaredReturnType() && !input.returnTypeIsUnit) { + declaration.setType(input.returnTypeString, input.returnTypeClassId) + } + generateBody(body, input, !input.returnTypeIsUnit && !input.returnTypeIsNothing) + } + + is KtPropertyAccessor -> { + val parent = declaration.parent + if (parent is KtProperty && parent.typeReference == null) { + parent.setType(input.returnTypeString, input.returnTypeClassId) + } + + generateBody(body, input, declaration.isGetter) + } + + else -> throw RuntimeException("Unknown declaration type: $declaration") + } + + declaration.equalsToken!!.delete() + val replaced = body.replace(newBody) + if (input.reformat) declaration.containingKtFile.adjustLineIndent(replaced.startOffset, replaced.endOffset) + } + } + + private fun generateBody(body: KtExpression, input: Input, returnsValue: Boolean): KtExpression { + val factory = KtPsiFactory(body) + if (input.bodyTypeIsUnit && body is KtNameReferenceExpression) return factory.createEmptyBody() + val unitWhenAsResult = input.bodyTypeIsUnit && body.resultingWhens().isNotEmpty() + val needReturn = returnsValue && (!input.bodyTypeIsUnit && !input.bodyTypeIsNothing) + return if (needReturn || unitWhenAsResult) { + val annotatedExpr = body as? KtAnnotatedExpression + val returnedExpr = annotatedExpr?.baseExpression ?: body + val block = factory.createSingleStatementBlock(factory.createExpressionByPattern("return $0", returnedExpr)) + val statement = block.firstStatement + annotatedExpr?.annotationEntries?.forEach { + block.addBefore(it, statement) + block.addBefore(factory.createNewLine(), statement) + } + block + } else { + factory.createSingleStatementBlock(body) + } + } + } +} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/util/ktPsiUtils.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/util/ktPsiUtils.kt new file mode 100644 index 00000000000..bbc05fe5d65 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/util/ktPsiUtils.kt @@ -0,0 +1,29 @@ +/* + * 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.util + +import org.jetbrains.kotlin.idea.frontend.api.analyse +import org.jetbrains.kotlin.idea.frontend.api.tokens.HackToForceAllowRunningAnalyzeOnEDT +import org.jetbrains.kotlin.idea.frontend.api.tokens.hackyAllowRunningOnEdt +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtPsiFactory + +@OptIn(HackToForceAllowRunningAnalyzeOnEDT::class) +fun KtCallableDeclaration.setType(typeString: String, classId: ClassId?, shortenReferences: Boolean = true) { + val typeReference = KtPsiFactory(project).createType(typeString) + setTypeReference(typeReference) + if (shortenReferences && classId != null) { + hackyAllowRunningOnEdt { + analyse(this) { + collectPossibleReferenceShortenings( + containingKtFile, + getTypeReference()!!.textRange, + ).invokeShortening() + } + } + } +} diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/intentions/AbstractHLIntentionTest.kt b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/intentions/AbstractHLIntentionTest.kt index 5f5e8fe1166..6cacecab005 100644 --- a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/intentions/AbstractHLIntentionTest.kt +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/intentions/AbstractHLIntentionTest.kt @@ -7,7 +7,9 @@ package org.jetbrains.kotlin.idea.fir.intentions import com.intellij.codeInsight.intention.IntentionAction import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.idea.fir.invalidateCaches import org.jetbrains.kotlin.idea.intentions.AbstractIntentionTest +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.test.utils.IgnoreTests import java.io.File @@ -30,6 +32,11 @@ abstract class AbstractHLIntentionTest : AbstractIntentionTest() { override fun checkForErrorsAfter(fileText: String) {} override fun checkForErrorsBefore(fileText: String) {} + override fun tearDown() { + project.invalidateCaches(file as? KtFile) + super.tearDown() + } + companion object { private const val AFTER_FIR_EXTENSION = ".after.fir" } diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/intentions/HLIntentionTestGenerated.java b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/intentions/HLIntentionTestGenerated.java index 0a4bd2be898..6acef81fd1c 100644 --- a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/intentions/HLIntentionTestGenerated.java +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/intentions/HLIntentionTestGenerated.java @@ -679,4 +679,172 @@ public class HLIntentionTestGenerated extends AbstractHLIntentionTest { runTest("idea/testData/intentions/importMember/TopLevelFun.kt"); } } + + @TestMetadata("idea/testData/intentions/convertToBlockBody") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConvertToBlockBody extends AbstractHLIntentionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("addSpace.kt") + public void testAddSpace() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/addSpace.kt"); + } + + @TestMetadata("adjustLineIndent.kt") + public void testAdjustLineIndent() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/adjustLineIndent.kt"); + } + + public void testAllFilesPresentInConvertToBlockBody() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/convertToBlockBody"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true); + } + + @TestMetadata("annotatedExpr.kt") + public void testAnnotatedExpr() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/annotatedExpr.kt"); + } + + @TestMetadata("annotatedExpr2.kt") + public void testAnnotatedExpr2() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/annotatedExpr2.kt"); + } + + @TestMetadata("annotatedExprInParentheses.kt") + public void testAnnotatedExprInParentheses() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/annotatedExprInParentheses.kt"); + } + + @TestMetadata("explicitlyNonUnitFun.kt") + public void testExplicitlyNonUnitFun() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/explicitlyNonUnitFun.kt"); + } + + @TestMetadata("explicitlyTypedFunWithUnresolvedExpression.kt") + public void testExplicitlyTypedFunWithUnresolvedExpression() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/explicitlyTypedFunWithUnresolvedExpression.kt"); + } + + @TestMetadata("explicitlyTypedFunWithUnresolvedType.kt") + public void testExplicitlyTypedFunWithUnresolvedType() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/explicitlyTypedFunWithUnresolvedType.kt"); + } + + @TestMetadata("explicitlyUnitFun.kt") + public void testExplicitlyUnitFun() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/explicitlyUnitFun.kt"); + } + + @TestMetadata("explicitlyUnitFunWithUnresolvedExpression.kt") + public void testExplicitlyUnitFunWithUnresolvedExpression() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/explicitlyUnitFunWithUnresolvedExpression.kt"); + } + + @TestMetadata("funWithCustomUnitClass.kt") + public void testFunWithCustomUnitClass() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/funWithCustomUnitClass.kt"); + } + + @TestMetadata("funWithThrow.kt") + public void testFunWithThrow() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/funWithThrow.kt"); + } + + @TestMetadata("funWithUnit.kt") + public void testFunWithUnit() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/funWithUnit.kt"); + } + + @TestMetadata("getter.kt") + public void testGetter() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/getter.kt"); + } + + @TestMetadata("getterTypeInferred.kt") + public void testGetterTypeInferred() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/getterTypeInferred.kt"); + } + + @TestMetadata("getterWithThrow.kt") + public void testGetterWithThrow() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/getterWithThrow.kt"); + } + + @TestMetadata("ifWhenUnit.kt") + public void testIfWhenUnit() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/ifWhenUnit.kt"); + } + + @TestMetadata("implicitlyNonUnitFun.kt") + public void testImplicitlyNonUnitFun() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun.kt"); + } + + @TestMetadata("implicitlyNonUnitFun2.kt") + public void testImplicitlyNonUnitFun2() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt"); + } + + @TestMetadata("implicitlyTypedFunWithUnresolvedType.kt") + public void testImplicitlyTypedFunWithUnresolvedType() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/implicitlyTypedFunWithUnresolvedType.kt"); + } + + @TestMetadata("implicitlyUnitFun.kt") + public void testImplicitlyUnitFun() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/implicitlyUnitFun.kt"); + } + + @TestMetadata("labeledExpr.kt") + public void testLabeledExpr() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/labeledExpr.kt"); + } + + @TestMetadata("labeledExprInParentheses.kt") + public void testLabeledExprInParentheses() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/labeledExprInParentheses.kt"); + } + + @TestMetadata("nothingFun.kt") + public void testNothingFun() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/nothingFun.kt"); + } + + @TestMetadata("overrideWithPlatformType.kt") + public void testOverrideWithPlatformType() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/overrideWithPlatformType.kt"); + } + + @TestMetadata("setter.kt") + public void testSetter() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/setter.kt"); + } + + @TestMetadata("valueIsAnonymousObject.kt") + public void testValueIsAnonymousObject() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/valueIsAnonymousObject.kt"); + } + + @TestMetadata("valueIsAnonymousObject2.kt") + public void testValueIsAnonymousObject2() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/valueIsAnonymousObject2.kt"); + } + + @TestMetadata("valueIsAnonymousObject3.kt") + public void testValueIsAnonymousObject3() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/valueIsAnonymousObject3.kt"); + } + + @TestMetadata("valueIsAnonymousObject4.kt") + public void testValueIsAnonymousObject4() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/valueIsAnonymousObject4.kt"); + } + + @TestMetadata("whenUnit.kt") + public void testWhenUnit() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/whenUnit.kt"); + } + } } diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt index ea9530ea804..5b8f4ccc6a1 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt @@ -6,6 +6,8 @@ package org.jetbrains.kotlin.idea.frontend.api.components import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtTypeAliasSymbol import org.jetbrains.kotlin.idea.frontend.api.types.* import org.jetbrains.kotlin.name.ClassId @@ -37,12 +39,25 @@ interface KtTypeInfoProviderMixIn : KtAnalysisSessionMixIn { val KtType.isString: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.STRING) val KtType.isCharSequence: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.CHAR_SEQUENCE) val KtType.isAny: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.ANY) + val KtType.isNothing: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.NOTHING) val KtType.isUInt: Boolean get() = isClassTypeWithClassId(StandardNames.FqNames.uInt) val KtType.isULong: Boolean get() = isClassTypeWithClassId(StandardNames.FqNames.uLong) val KtType.isUShort: Boolean get() = isClassTypeWithClassId(StandardNames.FqNames.uShort) val KtType.isUByte: Boolean get() = isClassTypeWithClassId(StandardNames.FqNames.uByte) + /** Gets the class symbol backing the given type, if available. */ + val KtType.expandedClassSymbol: KtClassOrObjectSymbol? + get() { + return when (this) { + is KtNonErrorClassType -> when (val classSymbol = classSymbol) { + is KtClassOrObjectSymbol -> classSymbol + is KtTypeAliasSymbol -> classSymbol.expandedType.expandedClassSymbol + } + else -> null + } + } + fun KtType.isClassTypeWithClassId(classId: ClassId): Boolean { if (this !is KtNonErrorClassType) return false return this.classId == classId @@ -85,5 +100,6 @@ object DefaultTypeClassIds { val STRING = ClassId.topLevel(StandardNames.FqNames.string.toSafe()) val CHAR_SEQUENCE = ClassId.topLevel(StandardNames.FqNames.charSequence.toSafe()) val ANY = ClassId.topLevel(StandardNames.FqNames.any.toSafe()) + val NOTHING = ClassId.topLevel(StandardNames.FqNames.nothing.toSafe()) val PRIMITIVES = setOf(INT, LONG, SHORT, BYTE, FLOAT, DOUBLE, CHAR, BOOLEAN) } diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeProvider.kt index 9212f00b5c6..55d2c02ed96 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeProvider.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeProvider.kt @@ -33,6 +33,8 @@ interface KtTypeProviderMixIn : KtAnalysisSessionMixIn { fun KtType.approximateToSuperPublicDenotable(): KtType? = analysisSession.typeProvider.approximateToSuperPublicDenotableType(this) + fun KtType.approximateToSuperPublicDenotableOrSelf(): KtType = approximateToSuperPublicDenotable() ?: this + fun KtNamedClassOrObjectSymbol.buildSelfClassType(): KtType = analysisSession.typeProvider.buildSelfClassType(this) diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/util/psiUtils.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/util/psiUtils.kt index 4ecfcdbc7ba..2d510887a7d 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/util/psiUtils.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/util/psiUtils.kt @@ -52,3 +52,11 @@ fun PsiClass.classIdIfNonLocal(): ClassId? { return ClassId(packageFqName, FqName(classesNames.joinToString(separator = ".")), false) } +fun KtExpression.resultingWhens(): List = when (this) { + is KtWhenExpression -> listOf(this) + entries.map { it.expression?.resultingWhens() ?: listOf() }.flatten() + is KtIfExpression -> (then?.resultingWhens() ?: listOf()) + (`else`?.resultingWhens() ?: listOf()) + is KtBinaryExpression -> (left?.resultingWhens() ?: listOf()) + (right?.resultingWhens() ?: listOf()) + is KtUnaryExpression -> this.baseExpression?.resultingWhens() ?: listOf() + is KtBlockExpression -> statements.lastOrNull()?.resultingWhens() ?: listOf() + else -> listOf() +} diff --git a/idea/resources-en/intentionDescriptions/HLConvertToBlockBodyIntention/after.kt.template b/idea/resources-en/intentionDescriptions/HLConvertToBlockBodyIntention/after.kt.template new file mode 100644 index 00000000000..08215d4cf58 --- /dev/null +++ b/idea/resources-en/intentionDescriptions/HLConvertToBlockBodyIntention/after.kt.template @@ -0,0 +1,3 @@ +fun foo(): String { + return "abc" +} \ No newline at end of file diff --git a/idea/resources-en/intentionDescriptions/HLConvertToBlockBodyIntention/before.kt.template b/idea/resources-en/intentionDescriptions/HLConvertToBlockBodyIntention/before.kt.template new file mode 100644 index 00000000000..830d7bea5c8 --- /dev/null +++ b/idea/resources-en/intentionDescriptions/HLConvertToBlockBodyIntention/before.kt.template @@ -0,0 +1 @@ +fun foo() = "abc" \ No newline at end of file diff --git a/idea/resources-en/intentionDescriptions/HLConvertToBlockBodyIntention/description.html b/idea/resources-en/intentionDescriptions/HLConvertToBlockBodyIntention/description.html new file mode 100644 index 00000000000..94f4c909cee --- /dev/null +++ b/idea/resources-en/intentionDescriptions/HLConvertToBlockBodyIntention/description.html @@ -0,0 +1,5 @@ + + +This intention converts an expression body (single expression after = sign) to a block body with a return statement. + + \ No newline at end of file diff --git a/idea/resources-fir/META-INF/firIntentions.xml b/idea/resources-fir/META-INF/firIntentions.xml index 6e23bcf1763..202f474c8cd 100644 --- a/idea/resources-fir/META-INF/firIntentions.xml +++ b/idea/resources-fir/META-INF/firIntentions.xml @@ -30,5 +30,10 @@ org.jetbrains.kotlin.idea.fir.intentions.HLImportMemberIntention Kotlin + + + org.jetbrains.kotlin.idea.fir.intentions.HLConvertToBlockBodyIntention + Kotlin + \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UseExpressionBodyInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UseExpressionBodyInspection.kt index 72479208134..aaa44708364 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UseExpressionBodyInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UseExpressionBodyInspection.kt @@ -24,8 +24,8 @@ import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.core.setType import org.jetbrains.kotlin.idea.core.util.isOneLiner import org.jetbrains.kotlin.idea.intentions.hasResultingIfWithoutElse -import org.jetbrains.kotlin.idea.intentions.resultingWhens import org.jetbrains.kotlin.idea.util.CommentSaver +import org.jetbrains.kotlin.idea.util.resultingWhens import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToBlockBodyIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToBlockBodyIntention.kt index 0ef3bd6526f..b12aa0e4948 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToBlockBodyIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToBlockBodyIntention.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.setType import org.jetbrains.kotlin.idea.formatter.adjustLineIndent +import org.jetbrains.kotlin.idea.util.resultingWhens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt index 1d2cbb8af6b..35633696d6b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt @@ -124,15 +124,6 @@ fun KtExpression.negate(reformat: Boolean = true): KtExpression { return KtPsiFactory(this).createExpressionByPattern("!$0", this, reformat = reformat) } -fun KtExpression.resultingWhens(): List = when (this) { - is KtWhenExpression -> listOf(this) + entries.map { it.expression?.resultingWhens() ?: listOf() }.flatten() - is KtIfExpression -> (then?.resultingWhens() ?: listOf()) + (`else`?.resultingWhens() ?: listOf()) - is KtBinaryExpression -> (left?.resultingWhens() ?: listOf()) + (right?.resultingWhens() ?: listOf()) - is KtUnaryExpression -> this.baseExpression?.resultingWhens() ?: listOf() - is KtBlockExpression -> statements.lastOrNull()?.resultingWhens() ?: listOf() - else -> listOf() -} - fun KtExpression?.hasResultingIfWithoutElse(): Boolean = when (this) { is KtIfExpression -> `else` == null || then.hasResultingIfWithoutElse() || `else`.hasResultingIfWithoutElse() is KtWhenExpression -> entries.any { it.expression.hasResultingIfWithoutElse() } diff --git a/idea/testData/intentions/convertToBlockBody/.firIntention b/idea/testData/intentions/convertToBlockBody/.firIntention new file mode 100644 index 00000000000..90c2e35723c --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/.firIntention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.fir.intentions.HLConvertToBlockBodyIntention diff --git a/idea/testData/intentions/convertToBlockBody/annotatedExprInParentheses.kt b/idea/testData/intentions/convertToBlockBody/annotatedExprInParentheses.kt new file mode 100644 index 00000000000..3c0cffa1232 --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/annotatedExprInParentheses.kt @@ -0,0 +1,5 @@ +@Target(AnnotationTarget.EXPRESSION) +@Retention(AnnotationRetention.SOURCE) +annotation class ann + +fun foo(): Int = (@ann 1) \ No newline at end of file diff --git a/idea/testData/intentions/convertToBlockBody/annotatedExprInParentheses.kt.after b/idea/testData/intentions/convertToBlockBody/annotatedExprInParentheses.kt.after new file mode 100644 index 00000000000..3869b539535 --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/annotatedExprInParentheses.kt.after @@ -0,0 +1,7 @@ +@Target(AnnotationTarget.EXPRESSION) +@Retention(AnnotationRetention.SOURCE) +annotation class ann + +fun foo(): Int { + return (@ann 1) +} diff --git a/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun.kt.after.fir b/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun.kt.after.fir new file mode 100644 index 00000000000..2d8e4d84d30 --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun.kt.after.fir @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(): String { + return java.io.File("x").getAbsolutePath() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt b/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt new file mode 100644 index 00000000000..e12113d30ce --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt @@ -0,0 +1,3 @@ +// WITH_RUNTIME + +fun foo() = java.io.File("x").list() \ No newline at end of file diff --git a/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt.after b/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt.after new file mode 100644 index 00000000000..c8c7f9eafbd --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(): Array? { + return java.io.File("x").list() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt.after.fir b/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt.after.fir new file mode 100644 index 00000000000..a779340426f --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt.after.fir @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +fun foo(): Array? { + return java.io.File("x").list() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToBlockBody/labeledExprInParentheses.kt b/idea/testData/intentions/convertToBlockBody/labeledExprInParentheses.kt new file mode 100644 index 00000000000..42c84eadbef --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/labeledExprInParentheses.kt @@ -0,0 +1 @@ +fun foo(): Int = (ann@ 1) \ No newline at end of file diff --git a/idea/testData/intentions/convertToBlockBody/labeledExprInParentheses.kt.after b/idea/testData/intentions/convertToBlockBody/labeledExprInParentheses.kt.after new file mode 100644 index 00000000000..829ebc0b65f --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/labeledExprInParentheses.kt.after @@ -0,0 +1,3 @@ +fun foo(): Int { + return (ann@ 1) +} diff --git a/idea/testData/intentions/convertToBlockBody/valueIsAnonymousObject3.kt.after.fir b/idea/testData/intentions/convertToBlockBody/valueIsAnonymousObject3.kt.after.fir new file mode 100644 index 00000000000..7e2181dd1ac --- /dev/null +++ b/idea/testData/intentions/convertToBlockBody/valueIsAnonymousObject3.kt.after.fir @@ -0,0 +1,8 @@ +interface I1 +interface I2 + +fun f() { + fun g(): I1 { + return object : I1, I2 { } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index a592e0d94ac..649f067dada 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -7119,6 +7119,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/convertToBlockBody/annotatedExpr2.kt"); } + @TestMetadata("annotatedExprInParentheses.kt") + public void testAnnotatedExprInParentheses() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/annotatedExprInParentheses.kt"); + } + @TestMetadata("explicitlyNonUnitFun.kt") public void testExplicitlyNonUnitFun() throws Exception { runTest("idea/testData/intentions/convertToBlockBody/explicitlyNonUnitFun.kt"); @@ -7184,6 +7189,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun.kt"); } + @TestMetadata("implicitlyNonUnitFun2.kt") + public void testImplicitlyNonUnitFun2() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/implicitlyNonUnitFun2.kt"); + } + @TestMetadata("implicitlyTypedFunWithUnresolvedType.kt") public void testImplicitlyTypedFunWithUnresolvedType() throws Exception { runTest("idea/testData/intentions/convertToBlockBody/implicitlyTypedFunWithUnresolvedType.kt"); @@ -7199,6 +7209,11 @@ public class IntentionTestGenerated extends AbstractIntentionTest { runTest("idea/testData/intentions/convertToBlockBody/labeledExpr.kt"); } + @TestMetadata("labeledExprInParentheses.kt") + public void testLabeledExprInParentheses() throws Exception { + runTest("idea/testData/intentions/convertToBlockBody/labeledExprInParentheses.kt"); + } + @TestMetadata("nothingFun.kt") public void testNothingFun() throws Exception { runTest("idea/testData/intentions/convertToBlockBody/nothingFun.kt");