diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java index 6bebfd7c7a6..88ea338827b 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java @@ -495,7 +495,7 @@ public interface Errors { // Type mismatch - DiagnosticFactory2 TYPE_MISMATCH = DiagnosticFactory2.create(ERROR); + DiagnosticFactory2 TYPE_MISMATCH = DiagnosticFactory2.create(ERROR); DiagnosticFactory1 RETURN_TYPE_MISMATCH = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 EXPECTED_TYPE_MISMATCH = DiagnosticFactory1.create(ERROR); DiagnosticFactory1 ASSIGNMENT_TYPE_MISMATCH = DiagnosticFactory1.create(ERROR); diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index ee84d2f38ec..30011027704 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -64,6 +64,8 @@ remove.no.name.function.return.type=Remove explicitly specified function return change.element.type=Change ''{0}'' type to ''{1}'' change.type=Change type from ''{0}'' to ''{1}'' change.type.family=Change Type +cast.expression.to.type=Cast expression ''{0}'' to ''{1}'' +cast.expression.family=Cast Expression add.kotlin.signature.action.family.name=Specify Custom Kotlin Signature add.kotlin.signature.action.text=Specify custom Kotlin signature diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/CastExpressionFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/CastExpressionFix.java new file mode 100644 index 00000000000..1b4cc20a134 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/CastExpressionFix.java @@ -0,0 +1,122 @@ +/* + * 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.quickfix; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.diagnostics.Diagnostic; +import org.jetbrains.jet.lang.diagnostics.DiagnosticWithParameters2; +import org.jetbrains.jet.lang.diagnostics.Errors; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.checker.JetTypeChecker; +import org.jetbrains.jet.plugin.JetBundle; +import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache; + +public class CastExpressionFix extends JetIntentionAction { + private final JetType type; + + public CastExpressionFix(@NotNull JetExpression element, @NotNull JetType type) { + super(element); + this.type = type; + } + + @NotNull + @Override + public String getText() { + return JetBundle.message("cast.expression.to.type", element.getText(), type.toString()); + } + + @NotNull + @Override + public String getFamilyName() { + return JetBundle.message("cast.expression.family"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + BindingContext context = AnalyzerFacadeWithCache.analyzeFileWithCache((JetFile) file).getBindingContext(); + JetType expressionType = context.get(BindingContext.EXPRESSION_TYPE, element); + return super.isAvailable(project, editor, file) && expressionType != null && JetTypeChecker.INSTANCE.isSubtypeOf(type, expressionType); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + JetBinaryExpressionWithTypeRHS castedExpression = + (JetBinaryExpressionWithTypeRHS) JetPsiFactory.createExpression(project, "(" + element.getText() + ") as " + type.toString()); + if (JetPsiUtil.areParenthesesUseless((JetParenthesizedExpression) castedExpression.getLeft())) { + castedExpression = (JetBinaryExpressionWithTypeRHS) JetPsiFactory.createExpression(project, element.getText() + " as " + type.toString()); + } + + JetParenthesizedExpression castedExpressionInParentheses = + (JetParenthesizedExpression) element.replace(JetPsiFactory.createExpression(project, "(" + castedExpression.getText() + ")")); + + if (JetPsiUtil.areParenthesesUseless(castedExpressionInParentheses)) { + castedExpressionInParentheses.replace(castedExpression); + } + } + + @NotNull + public static JetIntentionActionFactory createFactoryForAutoCastImpossible() { + return new JetIntentionActionFactory() { + @Nullable + @Override + public IntentionAction createAction(Diagnostic diagnostic) { + assert diagnostic.getFactory() == Errors.AUTOCAST_IMPOSSIBLE; + @SuppressWarnings("unchecked") + DiagnosticWithParameters2 diagnosticWithParameters = + (DiagnosticWithParameters2) diagnostic; + return new CastExpressionFix(diagnosticWithParameters.getPsiElement(), diagnosticWithParameters.getA()); + } + }; + } + + @NotNull + public static JetIntentionActionFactory createFactoryForTypeMismatch() { + return new JetIntentionActionFactory() { + @Nullable + @Override + public IntentionAction createAction(Diagnostic diagnostic) { + assert diagnostic.getFactory() == Errors.TYPE_MISMATCH; + @SuppressWarnings("unchecked") + DiagnosticWithParameters2 diagnosticWithParameters = + (DiagnosticWithParameters2) diagnostic; + JetExpression expression = diagnosticWithParameters.getPsiElement(); + + // we don't want to cast a cast: + if (expression instanceof JetBinaryExpressionWithTypeRHS) { + return null; + } + + // 'x: Int' - TYPE_MISMATCH might be reported on 'x', and we don't want this quickfix to be available: + JetBinaryExpressionWithTypeRHS parentExpressionWithTypeRHS = + PsiTreeUtil.getParentOfType(expression, JetBinaryExpressionWithTypeRHS.class, true); + if (parentExpressionWithTypeRHS != null && parentExpressionWithTypeRHS.getLeft() == expression) { + return null; + } + return new CastExpressionFix(expression, diagnosticWithParameters.getA()); + } + }; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index d67c7059070..e9ad461fbaa 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -226,6 +226,9 @@ public class QuickFixes { factories.put(EXPECTED_PARAMETER_TYPE_MISMATCH, ChangeTypeFix.createFactoryForExpectedParameterTypeMismatch()); factories.put(EXPECTED_RETURN_TYPE_MISMATCH, ChangeTypeFix.createFactoryForExpectedReturnTypeMismatch()); + + factories.put(AUTOCAST_IMPOSSIBLE, CastExpressionFix.createFactoryForAutoCastImpossible()); + factories.put(TYPE_MISMATCH, CastExpressionFix.createFactoryForTypeMismatch()); factories.put(PLATFORM_CLASS_MAPPED_TO_KOTLIN, MapPlatformClassToKotlinFix.createFactory()); diff --git a/idea/testData/quickfix/typeMismatch/casts/afterAutocastImpossible1.kt b/idea/testData/quickfix/typeMismatch/casts/afterAutocastImpossible1.kt new file mode 100644 index 00000000000..2b25f84c063 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/afterAutocastImpossible1.kt @@ -0,0 +1,12 @@ +// "Cast expression 'a' to 'Foo'" "true" +trait Foo { + fun plus(x: Any) : Foo +} + +fun foo(_a: Any): Any { + var a = _a + if (a is Foo) { + return a as Foo + a + } + return 42 +} diff --git a/idea/testData/quickfix/typeMismatch/casts/afterAutocastImpossible2.kt b/idea/testData/quickfix/typeMismatch/casts/afterAutocastImpossible2.kt new file mode 100644 index 00000000000..07c07301d94 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/afterAutocastImpossible2.kt @@ -0,0 +1,12 @@ +// "Cast expression 'a' to 'Foo'" "true" +trait Foo { + fun not() : Foo +} + +fun foo(_a: Any): Any { + var a = _a + if (a is Foo) { + return !(a as Foo) + } + return 42 +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/afterAutocastImpossible3.kt b/idea/testData/quickfix/typeMismatch/casts/afterAutocastImpossible3.kt new file mode 100644 index 00000000000..91679b39d10 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/afterAutocastImpossible3.kt @@ -0,0 +1,11 @@ +// "Cast expression 'x' to 'Foo'" "true" +trait Foo { + fun foo(x: T) +} + +fun bar(_x: Any) { + var x = _x + if (x is Foo<*>) { + (x as Foo).foo(42) + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/afterTypeMismatch1.kt b/idea/testData/quickfix/typeMismatch/casts/afterTypeMismatch1.kt new file mode 100644 index 00000000000..cf7c3d9c45c --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/afterTypeMismatch1.kt @@ -0,0 +1,6 @@ +// "Cast expression 'Foo()' to 'Foo'" "true" +class Foo + +fun foo(): Foo { + return Foo() as Foo +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/afterTypeMismatch3.kt b/idea/testData/quickfix/typeMismatch/casts/afterTypeMismatch3.kt new file mode 100644 index 00000000000..4c0a55f7d31 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/afterTypeMismatch3.kt @@ -0,0 +1,9 @@ +// "Cast expression 'a + a' to 'B'" "true" +trait A { + fun plus(x: Any): A +} +trait B : A + +fun foo(a: A): B { + return (a + a) as B +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible1.kt b/idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible1.kt new file mode 100644 index 00000000000..cb232a56d17 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible1.kt @@ -0,0 +1,12 @@ +// "Cast expression 'a' to 'Foo'" "true" +trait Foo { + fun plus(x: Any) : Foo +} + +fun foo(_a: Any): Any { + var a = _a + if (a is Foo) { + return a + a + } + return 42 +} diff --git a/idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible2.kt b/idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible2.kt new file mode 100644 index 00000000000..55cec269a71 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible2.kt @@ -0,0 +1,12 @@ +// "Cast expression 'a' to 'Foo'" "true" +trait Foo { + fun not() : Foo +} + +fun foo(_a: Any): Any { + var a = _a + if (a is Foo) { + return !a + } + return 42 +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible3.kt b/idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible3.kt new file mode 100644 index 00000000000..fb9b80248b4 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible3.kt @@ -0,0 +1,11 @@ +// "Cast expression 'x' to 'Foo'" "true" +trait Foo { + fun foo(x: T) +} + +fun bar(_x: Any) { + var x = _x + if (x is Foo<*>) { + x.foo(42) + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch1.kt b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch1.kt new file mode 100644 index 00000000000..bad37978777 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch1.kt @@ -0,0 +1,6 @@ +// "Cast expression 'Foo()' to 'Foo'" "true" +class Foo + +fun foo(): Foo { + return Foo() +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch2.kt b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch2.kt new file mode 100644 index 00000000000..f5411450aa9 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch2.kt @@ -0,0 +1,7 @@ +// "Cast expression 'Foo()' to 'Foo'" "false" +// ERROR: Type mismatch.
Required:Foo<jet.Int>
Found:Foo<jet.Number>
+class Foo + +fun foo(): Foo { + return Foo() +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch3.kt b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch3.kt new file mode 100644 index 00000000000..46383dfd385 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch3.kt @@ -0,0 +1,9 @@ +// "Cast expression 'a + a' to 'B'" "true" +trait A { + fun plus(x: Any): A +} +trait B : A + +fun foo(a: A): B { + return a + a +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch4.kt b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch4.kt new file mode 100644 index 00000000000..71fce9c076e --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch4.kt @@ -0,0 +1,8 @@ +// "Cast expression 'a: A' to 'B'" "false" +// ERROR: Type mismatch.
Required:B
Found:A
+open class A +class B : A() + +fun foo(a: A): B { + return a: A +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch5.kt b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch5.kt new file mode 100644 index 00000000000..90dc4c86e3d --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch5.kt @@ -0,0 +1,5 @@ +// "Cast expression 'x' to 'String'" "false" +// ERROR: Type mismatch.
Required:jet.String
Found:jet.Int
+fun foo(x: Int) { + x: String +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java index f9a9c089610..f9fdac26f71 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixMultiFileTestGenerated.java @@ -31,7 +31,7 @@ import org.jetbrains.jet.plugin.quickfix.AbstractQuickFixMultiFileTest; /** This class is generated by {@link org.jetbrains.jet.generators.tests.GenerateTests}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") @TestMetadata("idea/testData/quickfix") -@InnerTestClasses({QuickFixMultiFileTestGenerated.AddStarProjections.class, QuickFixMultiFileTestGenerated.AutoImports.class, QuickFixMultiFileTestGenerated.Modifiers.class, QuickFixMultiFileTestGenerated.Nullables.class, QuickFixMultiFileTestGenerated.Override.class, QuickFixMultiFileTestGenerated.TypeImports.class, QuickFixMultiFileTestGenerated.Variables.class}) +@InnerTestClasses({QuickFixMultiFileTestGenerated.AddStarProjections.class, QuickFixMultiFileTestGenerated.AutoImports.class, QuickFixMultiFileTestGenerated.Modifiers.class, QuickFixMultiFileTestGenerated.Nullables.class, QuickFixMultiFileTestGenerated.Override.class, QuickFixMultiFileTestGenerated.TypeImports.class, QuickFixMultiFileTestGenerated.TypeMismatch.class, QuickFixMultiFileTestGenerated.Variables.class}) public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTest { public void testAllFilesPresentInQuickfix() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true); @@ -229,6 +229,20 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes } + @TestMetadata("idea/testData/quickfix/typeMismatch") + @InnerTestClasses({}) + public static class TypeMismatch extends AbstractQuickFixMultiFileTest { + public void testAllFilesPresentInTypeMismatch() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix/typeMismatch"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true); + } + + public static Test innerSuite() { + TestSuite suite = new TestSuite("TypeMismatch"); + suite.addTestSuite(TypeMismatch.class); + return suite; + } + } + @TestMetadata("idea/testData/quickfix/variables") @InnerTestClasses({}) public static class Variables extends AbstractQuickFixMultiFileTest { @@ -252,6 +266,7 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes suite.addTest(Nullables.innerSuite()); suite.addTest(Override.innerSuite()); suite.addTestSuite(TypeImports.class); + suite.addTest(TypeMismatch.innerSuite()); suite.addTest(Variables.innerSuite()); return suite; } diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java index e26c3ac86b9..0f87cf6a5e8 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java @@ -993,6 +993,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } @TestMetadata("idea/testData/quickfix/typeMismatch") + @InnerTestClasses({TypeMismatch.Casts.class}) public static class TypeMismatch extends AbstractQuickFixTest { public void testAllFilesPresentInTypeMismatch() throws Exception { JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix/typeMismatch"), Pattern.compile("^before(\\w+)\\.kt$"), true); @@ -1073,6 +1074,60 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest("idea/testData/quickfix/typeMismatch/beforeTypeMismatchInReturnStatement.kt"); } + @TestMetadata("idea/testData/quickfix/typeMismatch/casts") + public static class Casts extends AbstractQuickFixTest { + public void testAllFilesPresentInCasts() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix/typeMismatch/casts"), Pattern.compile("^before(\\w+)\\.kt$"), true); + } + + @TestMetadata("beforeAutocastImpossible1.kt") + public void testAutocastImpossible1() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible1.kt"); + } + + @TestMetadata("beforeAutocastImpossible2.kt") + public void testAutocastImpossible2() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible2.kt"); + } + + @TestMetadata("beforeAutocastImpossible3.kt") + public void testAutocastImpossible3() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/casts/beforeAutocastImpossible3.kt"); + } + + @TestMetadata("beforeTypeMismatch1.kt") + public void testTypeMismatch1() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch1.kt"); + } + + @TestMetadata("beforeTypeMismatch2.kt") + public void testTypeMismatch2() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch2.kt"); + } + + @TestMetadata("beforeTypeMismatch3.kt") + public void testTypeMismatch3() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch3.kt"); + } + + @TestMetadata("beforeTypeMismatch4.kt") + public void testTypeMismatch4() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch4.kt"); + } + + @TestMetadata("beforeTypeMismatch5.kt") + public void testTypeMismatch5() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/casts/beforeTypeMismatch5.kt"); + } + + } + + public static Test innerSuite() { + TestSuite suite = new TestSuite("TypeMismatch"); + suite.addTestSuite(TypeMismatch.class); + suite.addTestSuite(Casts.class); + return suite; + } } @TestMetadata("idea/testData/quickfix/typeProjection") @@ -1306,7 +1361,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { suite.addTestSuite(SupertypeInitialization.class); suite.addTestSuite(TypeAddition.class); suite.addTestSuite(TypeImports.class); - suite.addTestSuite(TypeMismatch.class); + suite.addTest(TypeMismatch.innerSuite()); suite.addTestSuite(TypeProjection.class); suite.addTestSuite(UselessImports.class); suite.addTest(Variables.innerSuite());