diff --git a/generators/org/jetbrains/jet/generators/tests/GenerateTests.java b/generators/org/jetbrains/jet/generators/tests/GenerateTests.java index 60f122de194..03953c2fb15 100644 --- a/generators/org/jetbrains/jet/generators/tests/GenerateTests.java +++ b/generators/org/jetbrains/jet/generators/tests/GenerateTests.java @@ -375,6 +375,7 @@ public class GenerateTests { testModel("idea/testData/intentions/declarations/split", "doTestSplitProperty"), testModel("idea/testData/intentions/declarations/join", "doTestJoinProperty"), testModel("idea/testData/intentions/declarations/convertMemberToExtension", "doTestConvertMemberToExtension"), + testModel("idea/testData/intentions/reconstructedType", "doTestReconstructType"), testModel("idea/testData/intentions/removeUnnecessaryParentheses", "doTestRemoveUnnecessaryParentheses") ); diff --git a/idea/resources/intentionDescriptions/ReconstructTypeInCastOrIsAction/after.kt.template b/idea/resources/intentionDescriptions/ReconstructTypeInCastOrIsAction/after.kt.template new file mode 100644 index 00000000000..fc5ee713e50 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReconstructTypeInCastOrIsAction/after.kt.template @@ -0,0 +1 @@ +foo as Map \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReconstructTypeInCastOrIsAction/before.kt.template b/idea/resources/intentionDescriptions/ReconstructTypeInCastOrIsAction/before.kt.template new file mode 100644 index 00000000000..e328ad61a68 --- /dev/null +++ b/idea/resources/intentionDescriptions/ReconstructTypeInCastOrIsAction/before.kt.template @@ -0,0 +1 @@ +foo as Map \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ReconstructTypeInCastOrIsAction/description.html b/idea/resources/intentionDescriptions/ReconstructTypeInCastOrIsAction/description.html new file mode 100644 index 00000000000..a1acebdebde --- /dev/null +++ b/idea/resources/intentionDescriptions/ReconstructTypeInCastOrIsAction/description.html @@ -0,0 +1,5 @@ + + +This intention replaces a shortened type on the right-hand side of as/as?/is/!is by its complete version + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 8d7726e87b8..e0d75a22dfb 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -426,6 +426,11 @@ Kotlin + + org.jetbrains.jet.plugin.intentions.ReconstructTypeInCastOrIsAction + Kotlin + + diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index ebf7126a735..da67c03c6c3 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -276,4 +276,6 @@ find.what.constructor.usages.checkbox=Usages of &constructor find.what.derived.traits.checkbox=&Derived traits find.what.derived.classes.checkbox=&Derived classes -convert.to.extension=Convert to extension \ No newline at end of file +convert.to.extension=Convert to extension +replace.by.reconstructed.type.family.name=Replace by Reconstructed Type +replace.by.reconstructed.type=Replace by ''{0}'' \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/intentions/ReconstructTypeInCastOrIsAction.java b/idea/src/org/jetbrains/jet/plugin/intentions/ReconstructTypeInCastOrIsAction.java new file mode 100644 index 00000000000..ae4c5d793fa --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/intentions/ReconstructTypeInCastOrIsAction.java @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.intentions; + +import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.types.ErrorUtils; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.plugin.JetBundle; +import org.jetbrains.jet.plugin.codeInsight.ReferenceToClassesShortening; +import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache; +import org.jetbrains.jet.renderer.DescriptorRenderer; + +import java.util.Collections; + +public class ReconstructTypeInCastOrIsAction extends PsiElementBaseIntentionAction { + @NotNull + @Override + public String getFamilyName() { + return JetBundle.message("replace.by.reconstructed.type.family.name"); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) { + JetTypeReference typeRef = PsiTreeUtil.getTopmostParentOfType(element, JetTypeReference.class); + assert typeRef != null : "Must be checked by isAvailable(): " + element; + + JetType type = getReconstructedType(typeRef); + JetTypeReference newType = JetPsiFactory.createType(project, DescriptorRenderer.SOURCE_CODE.renderType(type)); + JetTypeReference replaced = (JetTypeReference) typeRef.replace(newType); + ReferenceToClassesShortening.compactReferenceToClasses(Collections.singletonList(replaced)); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) { + JetTypeReference typeRef = PsiTreeUtil.getTopmostParentOfType(element, JetTypeReference.class); + if (typeRef == null) return false; + + // Only user types (like Foo) are interesting + JetTypeElement typeElement = typeRef.getTypeElement(); + if (!(typeElement instanceof JetUserType)) return false; + + // If there are generic arguments already, there's nothing to reconstruct + if (!((JetUserType) typeElement).getTypeArguments().isEmpty()) return false; + + // We must be on the RHS of as/as?/is/!is or inside an is/!is-condition in when() + JetExpression outerExpression = PsiTreeUtil.getParentOfType(typeRef, JetExpression.class); + if (!(outerExpression instanceof JetBinaryExpressionWithTypeRHS)) { + JetWhenConditionIsPattern outerIsCondition = PsiTreeUtil.getParentOfType(typeRef, JetWhenConditionIsPattern.class); + if (outerIsCondition == null) return false; + } + + JetType type = getReconstructedType(typeRef); + if (type == null) return false; + if (ErrorUtils.isErrorType(type)) return false; + + // No type parameters expected => nothing to reconstruct + if (type.getConstructor().getParameters().isEmpty()) return false; + + setText(JetBundle.message("replace.by.reconstructed.type", type)); + + return true; + } + + private static JetType getReconstructedType(JetTypeReference typeRef) { + BindingContext bindingContext = AnalyzerFacadeWithCache.analyzeFileWithCache((JetFile) typeRef.getContainingFile()).getBindingContext(); + return bindingContext.get(BindingContext.TYPE, typeRef); + } +} diff --git a/idea/testData/intentions/reconstructedType/completeGenericType.kt b/idea/testData/intentions/reconstructedType/completeGenericType.kt new file mode 100644 index 00000000000..5169032f1af --- /dev/null +++ b/idea/testData/intentions/reconstructedType/completeGenericType.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false +class G + +fun foo(a: Any) = a as G<*> \ No newline at end of file diff --git a/idea/testData/intentions/reconstructedType/completeType.kt b/idea/testData/intentions/reconstructedType/completeType.kt new file mode 100644 index 00000000000..9cf04bfcdf4 --- /dev/null +++ b/idea/testData/intentions/reconstructedType/completeType.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false +class G + +fun foo(a: Any) = a as G \ No newline at end of file diff --git a/idea/testData/intentions/reconstructedType/errorType.kt b/idea/testData/intentions/reconstructedType/errorType.kt new file mode 100644 index 00000000000..6e2deb6e00c --- /dev/null +++ b/idea/testData/intentions/reconstructedType/errorType.kt @@ -0,0 +1,2 @@ +// IS_APPLICABLE: false +fun foo(a: Any) = a as G \ No newline at end of file diff --git a/idea/testData/intentions/reconstructedType/reconstructableType.kt b/idea/testData/intentions/reconstructedType/reconstructableType.kt new file mode 100644 index 00000000000..fdc206ab427 --- /dev/null +++ b/idea/testData/intentions/reconstructedType/reconstructableType.kt @@ -0,0 +1,5 @@ +// IS_APPLICABLE: true +class B +class G: B + +fun foo(a: B) = a as G \ No newline at end of file diff --git a/idea/testData/intentions/reconstructedType/reconstructableType.kt.after b/idea/testData/intentions/reconstructedType/reconstructableType.kt.after new file mode 100644 index 00000000000..43667449d20 --- /dev/null +++ b/idea/testData/intentions/reconstructedType/reconstructableType.kt.after @@ -0,0 +1,5 @@ +// IS_APPLICABLE: true +class B +class G: B + +fun foo(a: B) = a as G \ No newline at end of file diff --git a/idea/testData/intentions/reconstructedType/typeInDeclaration.kt b/idea/testData/intentions/reconstructedType/typeInDeclaration.kt new file mode 100644 index 00000000000..0dccfec39ff --- /dev/null +++ b/idea/testData/intentions/reconstructedType/typeInDeclaration.kt @@ -0,0 +1,2 @@ +// IS_APPLICABLE: false +fun foo(a: Any) = 1 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/intentions/AbstractCodeTransformationTest.java b/idea/tests/org/jetbrains/jet/plugin/intentions/AbstractCodeTransformationTest.java index dd61009c664..e5aee15a1fe 100644 --- a/idea/tests/org/jetbrains/jet/plugin/intentions/AbstractCodeTransformationTest.java +++ b/idea/tests/org/jetbrains/jet/plugin/intentions/AbstractCodeTransformationTest.java @@ -110,6 +110,10 @@ public abstract class AbstractCodeTransformationTest extends LightCodeInsightTes doTestIntention(path, new ConvertMemberToExtension()); } + public void doTestReconstructType(@NotNull String path) throws Exception { + doTestIntention(path, new ReconstructTypeInCastOrIsAction()); + } + private void doTestIntention(@NotNull String path, @NotNull IntentionAction intentionAction) throws Exception { configureByFile(path); diff --git a/idea/tests/org/jetbrains/jet/plugin/intentions/CodeTransformationsTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/intentions/CodeTransformationsTestGenerated.java index 60c0a9116ea..615bdf7c68c 100644 --- a/idea/tests/org/jetbrains/jet/plugin/intentions/CodeTransformationsTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/intentions/CodeTransformationsTestGenerated.java @@ -30,7 +30,7 @@ import org.jetbrains.jet.plugin.intentions.AbstractCodeTransformationTest; /** This class is generated by {@link org.jetbrains.jet.generators.tests.GenerateTests}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") -@InnerTestClasses({CodeTransformationsTestGenerated.IfToAssignment.class, CodeTransformationsTestGenerated.IfToReturn.class, CodeTransformationsTestGenerated.IfToReturnAsymmetrically.class, CodeTransformationsTestGenerated.WhenToAssignment.class, CodeTransformationsTestGenerated.WhenToReturn.class, CodeTransformationsTestGenerated.AssignmentToIf.class, CodeTransformationsTestGenerated.AssignmentToWhen.class, CodeTransformationsTestGenerated.PropertyToIf.class, CodeTransformationsTestGenerated.PropertyToWhen.class, CodeTransformationsTestGenerated.ReturnToIf.class, CodeTransformationsTestGenerated.ReturnToWhen.class, CodeTransformationsTestGenerated.IfToWhen.class, CodeTransformationsTestGenerated.WhenToIf.class, CodeTransformationsTestGenerated.Flatten.class, CodeTransformationsTestGenerated.IntroduceSubject.class, CodeTransformationsTestGenerated.EliminateSubject.class, CodeTransformationsTestGenerated.Split.class, CodeTransformationsTestGenerated.Join.class, CodeTransformationsTestGenerated.ConvertMemberToExtension.class, CodeTransformationsTestGenerated.RemoveUnnecessaryParentheses.class}) +@InnerTestClasses({CodeTransformationsTestGenerated.IfToAssignment.class, CodeTransformationsTestGenerated.IfToReturn.class, CodeTransformationsTestGenerated.IfToReturnAsymmetrically.class, CodeTransformationsTestGenerated.WhenToAssignment.class, CodeTransformationsTestGenerated.WhenToReturn.class, CodeTransformationsTestGenerated.AssignmentToIf.class, CodeTransformationsTestGenerated.AssignmentToWhen.class, CodeTransformationsTestGenerated.PropertyToIf.class, CodeTransformationsTestGenerated.PropertyToWhen.class, CodeTransformationsTestGenerated.ReturnToIf.class, CodeTransformationsTestGenerated.ReturnToWhen.class, CodeTransformationsTestGenerated.IfToWhen.class, CodeTransformationsTestGenerated.WhenToIf.class, CodeTransformationsTestGenerated.Flatten.class, CodeTransformationsTestGenerated.IntroduceSubject.class, CodeTransformationsTestGenerated.EliminateSubject.class, CodeTransformationsTestGenerated.Split.class, CodeTransformationsTestGenerated.Join.class, CodeTransformationsTestGenerated.ConvertMemberToExtension.class, CodeTransformationsTestGenerated.ReconstructedType.class, CodeTransformationsTestGenerated.RemoveUnnecessaryParentheses.class}) public class CodeTransformationsTestGenerated extends AbstractCodeTransformationTest { @TestMetadata("idea/testData/intentions/branched/folding/ifToAssignment") public static class IfToAssignment extends AbstractCodeTransformationTest { @@ -974,6 +974,39 @@ public class CodeTransformationsTestGenerated extends AbstractCodeTransformation } + @TestMetadata("idea/testData/intentions/reconstructedType") + public static class ReconstructedType extends AbstractCodeTransformationTest { + public void testAllFilesPresentInReconstructedType() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/intentions/reconstructedType"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("completeGenericType.kt") + public void testCompleteGenericType() throws Exception { + doTestReconstructType("idea/testData/intentions/reconstructedType/completeGenericType.kt"); + } + + @TestMetadata("completeType.kt") + public void testCompleteType() throws Exception { + doTestReconstructType("idea/testData/intentions/reconstructedType/completeType.kt"); + } + + @TestMetadata("errorType.kt") + public void testErrorType() throws Exception { + doTestReconstructType("idea/testData/intentions/reconstructedType/errorType.kt"); + } + + @TestMetadata("reconstructableType.kt") + public void testReconstructableType() throws Exception { + doTestReconstructType("idea/testData/intentions/reconstructedType/reconstructableType.kt"); + } + + @TestMetadata("typeInDeclaration.kt") + public void testTypeInDeclaration() throws Exception { + doTestReconstructType("idea/testData/intentions/reconstructedType/typeInDeclaration.kt"); + } + + } + @TestMetadata("idea/testData/intentions/removeUnnecessaryParentheses") public static class RemoveUnnecessaryParentheses extends AbstractCodeTransformationTest { public void testAllFilesPresentInRemoveUnnecessaryParentheses() throws Exception { @@ -1063,6 +1096,7 @@ public class CodeTransformationsTestGenerated extends AbstractCodeTransformation suite.addTestSuite(Split.class); suite.addTestSuite(Join.class); suite.addTestSuite(ConvertMemberToExtension.class); + suite.addTestSuite(ReconstructedType.class); suite.addTestSuite(RemoveUnnecessaryParentheses.class); return suite; }