diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties index c640470d94b..eb1f9fda628 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties +++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties @@ -62,6 +62,7 @@ change.no.name.function.return.type=Change function return type to ''{0}'' remove.function.return.type=Remove explicitly specified return type in ''{0}'' function remove.no.name.function.return.type=Remove explicitly specified function return type change.element.type=Change ''{0}'' type to ''{1}'' +change.function.parameter.type=Change parameter ''{0}'' type of function ''{1}'' to ''{2}'' change.type=Change type from ''{0}'' to ''{1}'' change.type.family=Change Type add.parameters.to.function=Add parameter{0} to function ''{1}'' diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/ChangeFunctionParameterTypeFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/ChangeFunctionParameterTypeFix.java new file mode 100644 index 00000000000..0c7e797928f --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/ChangeFunctionParameterTypeFix.java @@ -0,0 +1,110 @@ +/* + * 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.PsiElement; +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.descriptors.CallableDescriptor; +import org.jetbrains.jet.lang.diagnostics.Diagnostic; +import org.jetbrains.jet.lang.psi.*; +import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.BindingContextUtils; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; +import org.jetbrains.jet.lang.resolve.name.FqName; +import org.jetbrains.jet.lang.resolve.name.NameUtils; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.plugin.JetBundle; +import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache; +import org.jetbrains.jet.renderer.DescriptorRenderer; + +public class ChangeFunctionParameterTypeFix extends JetIntentionAction { + private final String renderedType; + private final String containingFunctionName; + + public ChangeFunctionParameterTypeFix(@NotNull JetParameter element, @NotNull JetType type) { + super(element); + renderedType = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(type); + JetFunction function = PsiTreeUtil.getParentOfType(element, JetFunction.class); + FqName functionFQName = function == null ? null : JetPsiUtil.getFQName(function); + containingFunctionName = functionFQName == null ? null : functionFQName.getFqName(); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return super.isAvailable(project, editor, file) && containingFunctionName != null; + } + + @NotNull + @Override + public String getText() { + return JetBundle.message("change.function.parameter.type", element.getName(), containingFunctionName, renderedType); + } + + @NotNull + @Override + public String getFamilyName() { + return JetBundle.message("change.type.family"); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, JetFile file) throws IncorrectOperationException { + JetTypeReference typeReference = element.getTypeReference(); + assert typeReference != null : "Parameter without type annotation cannot cause type mismatch"; + typeReference.replace(JetPsiFactory.createType(project, renderedType)); + } + + @NotNull + public static JetIntentionActionFactory createFactory() { + return new JetIntentionActionFactory() { + @Nullable + @Override + public IntentionAction createAction(Diagnostic diagnostic) { + // Change type of function parameter in case TYPE_MISMATCH is reported on expression passed as value argument of call + JetParameter correspondingParameter = null; + JetType type = null; + + BindingContext context = AnalyzerFacadeWithCache.analyzeFileWithCache((JetFile) diagnostic.getPsiFile()).getBindingContext(); + JetFunctionLiteralExpression functionLiteralExpression = + QuickFixUtil.getParentElementOfType(diagnostic, JetFunctionLiteralExpression.class); + + if (functionLiteralExpression != null && diagnostic.getPsiElement() == functionLiteralExpression.getBodyExpression()) { + correspondingParameter = + QuickFixUtil.getFunctionParameterCorrespondingToFunctionLiteralPassedOutsideArgumentList(functionLiteralExpression); + type = context.get(BindingContext.EXPRESSION_TYPE, functionLiteralExpression); + } + else { + JetValueArgument valueArgument = QuickFixUtil.getParentElementOfType(diagnostic, JetValueArgument.class); + if (valueArgument != null && valueArgument.getArgumentExpression() == diagnostic.getPsiElement()) { + correspondingParameter = QuickFixUtil.getFunctionParameterCorrespondingToValueArgumentPassedInCall(valueArgument); + type = context.get(BindingContext.EXPRESSION_TYPE, valueArgument.getArgumentExpression()); + } + } + if (correspondingParameter != null && type != null) { + return new ChangeFunctionParameterTypeFix(correspondingParameter, type); + } + return null; + } + }; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixUtil.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixUtil.java index a1d9aa40425..7c3f2971576 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixUtil.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixUtil.java @@ -26,10 +26,10 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.CallableDescriptor; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.diagnostics.Diagnostic; -import org.jetbrains.jet.lang.psi.JetDeclaration; -import org.jetbrains.jet.lang.psi.JetFile; -import org.jetbrains.jet.lang.psi.JetNamedDeclaration; +import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.BindingContextUtils; +import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; import org.jetbrains.jet.lang.types.DeferredType; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.checker.JetTypeChecker; @@ -93,4 +93,60 @@ public class QuickFixUtil { public static boolean canModifyElement(@NotNull PsiElement element) { return element.isWritable() && !BuiltInsReferenceResolver.isFromBuiltIns(element); } + + @Nullable + public static JetParameterList getParameterListOfCalledFunction(@NotNull JetCallExpression callExpression) { + BindingContext context = AnalyzerFacadeWithCache.analyzeFileWithCache((JetFile) callExpression.getContainingFile()).getBindingContext(); + ResolvedCall resolvedCall = context.get(BindingContext.RESOLVED_CALL, callExpression.getCalleeExpression()); + if (resolvedCall == null) return null; + PsiElement functionDeclaration = BindingContextUtils.descriptorToDeclaration(context, resolvedCall.getCandidateDescriptor()); + if (functionDeclaration instanceof JetFunction) { + return ((JetFunction) functionDeclaration).getValueParameterList(); + } + return null; + } + + @Nullable + public static JetParameter getFunctionParameterCorrespondingToFunctionLiteralPassedOutsideArgumentList(@NotNull JetFunctionLiteralExpression functionLiteralExpression) { + if (!(functionLiteralExpression.getParent() instanceof JetCallExpression)) { + return null; + } + JetCallExpression callExpression = (JetCallExpression) functionLiteralExpression.getParent(); + JetParameterList parameterList = getParameterListOfCalledFunction(callExpression); + if (parameterList == null) return null; + return parameterList.getParameters().get(parameterList.getParameters().size() - 1); + } + + @Nullable + public static JetParameter getFunctionParameterCorrespondingToValueArgumentPassedInCall(@NotNull JetValueArgument valueArgument) { + if (!(valueArgument.getParent() instanceof JetValueArgumentList)) { + return null; + } + JetValueArgumentList valueArgumentList = (JetValueArgumentList) valueArgument.getParent(); + if (!(valueArgumentList.getParent() instanceof JetCallExpression)) { + return null; + } + JetCallExpression callExpression = (JetCallExpression) valueArgumentList.getParent(); + JetParameterList parameterList = getParameterListOfCalledFunction(callExpression); + if (parameterList == null) return null; + int position = valueArgumentList.getArguments().indexOf(valueArgument); + if (position == -1) return null; + + if (valueArgument.isNamed()) { + JetValueArgumentName valueArgumentName = valueArgument.getArgumentName(); + JetSimpleNameExpression referenceExpression = valueArgumentName == null ? null : valueArgumentName.getReferenceExpression(); + String valueArgumentNameAsString = referenceExpression == null ? null : referenceExpression.getReferencedName(); + if (valueArgumentNameAsString == null) return null; + + for (JetParameter parameter: parameterList.getParameters()) { + if (valueArgumentNameAsString.equals(parameter.getName())) { + return parameter; + } + } + return null; + } + else { + return parameterList.getParameters().get(position); + } + } } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java index be8f7fb5b30..c1fd41dab2e 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/QuickFixes.java @@ -235,7 +235,9 @@ public class QuickFixes { factories.put(EXPECTED_PARAMETER_TYPE_MISMATCH, ChangeTypeFix.createFactoryForExpectedParameterTypeMismatch()); factories.put(EXPECTED_RETURN_TYPE_MISMATCH, ChangeTypeFix.createFactoryForExpectedReturnTypeMismatch()); - + + factories.put(TYPE_MISMATCH, ChangeFunctionParameterTypeFix.createFactory()); + factories.put(AUTOCAST_IMPOSSIBLE, CastExpressionFix.createFactoryForAutoCastImpossible()); factories.put(TYPE_MISMATCH, CastExpressionFix.createFactoryForTypeMismatch()); diff --git a/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/afterChangeFunctionParameterType1.kt b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/afterChangeFunctionParameterType1.kt new file mode 100644 index 00000000000..b253e8b1782 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/afterChangeFunctionParameterType1.kt @@ -0,0 +1,5 @@ +// "Change parameter 'x' type of function 'bar.foo' to '(String) -> Int'" "true" +package bar +fun foo(w: Int = 0, x: (String) -> Int, y: Int = 0, z: (Int) -> Int = {42}) { + foo(1, {(a: String) -> 42}, 1) +} diff --git a/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/afterChangeFunctionParameterType2.kt b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/afterChangeFunctionParameterType2.kt new file mode 100644 index 00000000000..c1b3343642c --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/afterChangeFunctionParameterType2.kt @@ -0,0 +1,4 @@ +// "Change parameter 'y' type of function 'foo' to 'String'" "true" +fun foo(v: Int, w: Int = 0, x: Int = 0, y: String, z: (Int) -> Int = {42}) { + foo(0, 1, y = "") +} diff --git a/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/afterChangeFunctionParameterType3.kt b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/afterChangeFunctionParameterType3.kt new file mode 100644 index 00000000000..30b490ba5ed --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/afterChangeFunctionParameterType3.kt @@ -0,0 +1,4 @@ +// "Change parameter 'z' type of function 'foo' to '(Int) -> Unit'" "true" +fun foo(w: Int = 0, x: Int, y: Int = 0, z: (Int) -> Unit) { + foo(0, 1) {} +} diff --git a/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType1.kt b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType1.kt new file mode 100644 index 00000000000..deb5313f041 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType1.kt @@ -0,0 +1,5 @@ +// "Change parameter 'x' type of function 'bar.foo' to '(String) -> Int'" "true" +package bar +fun foo(w: Int = 0, x: Int, y: Int = 0, z: (Int) -> Int = {42}) { + foo(1, {(a: String) -> 42}, 1) +} diff --git a/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType2.kt b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType2.kt new file mode 100644 index 00000000000..c5e45b514be --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType2.kt @@ -0,0 +1,4 @@ +// "Change parameter 'y' type of function 'foo' to 'String'" "true" +fun foo(v: Int, w: Int = 0, x: Int = 0, y: Int, z: (Int) -> Int = {42}) { + foo(0, 1, y = "") +} diff --git a/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType3.kt b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType3.kt new file mode 100644 index 00000000000..9d5f48d961d --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType3.kt @@ -0,0 +1,4 @@ +// "Change parameter 'z' type of function 'foo' to '(Int) -> Unit'" "true" +fun foo(w: Int = 0, x: Int, y: Int = 0, z: (Int) -> String) { + foo(0, 1) {} +} diff --git a/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType4.kt b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType4.kt new file mode 100644 index 00000000000..a4c9f02baaf --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType4.kt @@ -0,0 +1,8 @@ +// "Change parameter 'z' type of function 'foo' to '(Int) -> String'" "false" +// ERROR: Type mismatch.
Required:jet.Int
Found:jet.String
+fun foo(y: Int = 0, z: (Int) -> String = {""}) { + foo { + "": Int + "" + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType5.kt b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType5.kt new file mode 100644 index 00000000000..80f37c15a85 --- /dev/null +++ b/idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType5.kt @@ -0,0 +1,5 @@ +// "Change parameter 'y' type of function 'foo' to 'Int'" "false" +// ERROR: Type mismatch.
Required:jet.Int
Found:jet.String
+fun foo(y: Int = 0, z: (Int) -> String = {""}) { + foo("": Int) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java index fb646cabc37..20ca2288240 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/QuickFixTestGenerated.java @@ -1240,7 +1240,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } @TestMetadata("idea/testData/quickfix/typeMismatch") - @InnerTestClasses({TypeMismatch.Casts.class, TypeMismatch.ComponentFunctionReturnTypeMismatch.class, TypeMismatch.TypeMismatchOnReturnedExpression.class}) + @InnerTestClasses({TypeMismatch.Casts.class, TypeMismatch.ComponentFunctionReturnTypeMismatch.class, TypeMismatch.FunctionParameterTypeMismatch.class, TypeMismatch.TypeMismatchOnReturnedExpression.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); @@ -1377,6 +1377,39 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } + @TestMetadata("idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch") + public static class FunctionParameterTypeMismatch extends AbstractQuickFixTest { + public void testAllFilesPresentInFunctionParameterTypeMismatch() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch"), Pattern.compile("^before(\\w+)\\.kt$"), true); + } + + @TestMetadata("beforeChangeFunctionParameterType1.kt") + public void testChangeFunctionParameterType1() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType1.kt"); + } + + @TestMetadata("beforeChangeFunctionParameterType2.kt") + public void testChangeFunctionParameterType2() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType2.kt"); + } + + @TestMetadata("beforeChangeFunctionParameterType3.kt") + public void testChangeFunctionParameterType3() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType3.kt"); + } + + @TestMetadata("beforeChangeFunctionParameterType4.kt") + public void testChangeFunctionParameterType4() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType4.kt"); + } + + @TestMetadata("beforeChangeFunctionParameterType5.kt") + public void testChangeFunctionParameterType5() throws Exception { + doTest("idea/testData/quickfix/typeMismatch/functionParameterTypeMismatch/beforeChangeFunctionParameterType5.kt"); + } + + } + @TestMetadata("idea/testData/quickfix/typeMismatch/typeMismatchOnReturnedExpression") public static class TypeMismatchOnReturnedExpression extends AbstractQuickFixTest { public void testAllFilesPresentInTypeMismatchOnReturnedExpression() throws Exception { @@ -1405,6 +1438,7 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { suite.addTestSuite(TypeMismatch.class); suite.addTestSuite(Casts.class); suite.addTestSuite(ComponentFunctionReturnTypeMismatch.class); + suite.addTestSuite(FunctionParameterTypeMismatch.class); suite.addTestSuite(TypeMismatchOnReturnedExpression.class); return suite; }