From e887542c3ed26b62b4a5c47d2fab6c3a8e2f973b Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 3 Nov 2014 15:00:03 +0100 Subject: [PATCH] suggest correctly shaped functional types if extracting functional expression without context: initial (IDEA-125510) --- .../codeInsight/ExpectedTypeInfo.java | 1 + .../codeInsight/ExpectedTypeUtil.java | 2 + .../codeInsight/ExpectedTypesProvider.java | 14 ++++ .../FunctionalInterfaceSuggester.java | 78 +++++++++++++++++++ .../ui/TypeSelectorManagerImpl.java | 14 ++++ .../refactoring/util/RefactoringUtil.java | 7 +- .../LambdaNotInContext.after.java | 6 ++ .../introduceVariable/LambdaNotInContext.java | 5 ++ .../MethodRefNotInContext.after.java | 7 ++ .../MethodRefNotInContext.java | 5 ++ .../refactoring/IntroduceVariableTest.java | 15 ++++ 11 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java create mode 100644 java/java-tests/testData/refactoring/introduceVariable/LambdaNotInContext.after.java create mode 100644 java/java-tests/testData/refactoring/introduceVariable/LambdaNotInContext.java create mode 100644 java/java-tests/testData/refactoring/introduceVariable/MethodRefNotInContext.after.java create mode 100644 java/java-tests/testData/refactoring/introduceVariable/MethodRefNotInContext.java diff --git a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypeInfo.java b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypeInfo.java index a3378742f76e..bdd66cce0ed1 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypeInfo.java +++ b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypeInfo.java @@ -28,6 +28,7 @@ public interface ExpectedTypeInfo { int TYPE_OR_SUBTYPE = 1; int TYPE_OR_SUPERTYPE = 2; int TYPE_BETWEEN = 3; + int TYPE_SAME_SHAPED = 4; @MagicConstant(valuesFromClass = ExpectedTypeInfo.class) @interface Type {} diff --git a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypeUtil.java b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypeUtil.java index b9626c1a1e9c..c038f36aca3f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypeUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypeUtil.java @@ -129,6 +129,8 @@ public class ExpectedTypeUtil { return type.isAssignableFrom(infoType); case ExpectedTypeInfo.TYPE_BETWEEN: return type.isAssignableFrom(info.getDefaultType()) && infoType.isAssignableFrom(type); + case ExpectedTypeInfo.TYPE_SAME_SHAPED: + return true; } LOG.error("Unexpected ExpectedInfo kind"); diff --git a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java index c0535f2803f6..1ca5a42e09c8 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/ExpectedTypesProvider.java @@ -129,6 +129,20 @@ public class ExpectedTypesProvider { final boolean voidable, boolean usedAfter) { if (expr == null) return ExpectedTypeInfo.EMPTY_ARRAY; PsiElement parent = expr.getParent(); + if (expr instanceof PsiFunctionalExpression && parent instanceof PsiExpressionStatement) { + final Collection types = FunctionalInterfaceSuggester.suggestFunctionalInterfaces((PsiFunctionalExpression)expr); + if (types.isEmpty()) { + return ExpectedTypeInfo.EMPTY_ARRAY; + } + else { + final ExpectedTypeInfo[] result = new ExpectedTypeInfo[types.size()]; + int i = 0; + for (PsiType type : types) { + result[i++] = new ExpectedTypeInfoImpl(type, ExpectedTypeInfo.TYPE_SAME_SHAPED, type, TailType.NONE, null, ExpectedTypeInfoImpl.NULL); + } + return result; + } + } MyParentVisitor visitor = new MyParentVisitor(expr, forCompletion, classProvider, voidable, usedAfter); if (parent != null) { parent.accept(visitor); diff --git a/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java b/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java new file mode 100644 index 000000000000..645a36739b73 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2014 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 com.intellij.codeInsight; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.searches.AnnotatedMembersSearch; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +public class FunctionalInterfaceSuggester { + public static Collection suggestFunctionalInterfaces(final @NotNull PsiFunctionalExpression expression) { + + final Project project = expression.getProject(); + final PsiClass functionalInterfaceClass = JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, GlobalSearchScope.allScope(project)); + if (functionalInterfaceClass == null) { + return Collections.emptyList(); + } + final Set types = new LinkedHashSet(); + final String uniqueExprName = JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName("l", expression, true); + AnnotatedMembersSearch.search(functionalInterfaceClass, expression.getResolveScope()).forEach(new Processor() { + @Override + public boolean process(PsiMember member) { + if (member instanceof PsiClass) { + final PsiType type = getAcceptableType((PsiClass)member, expression, uniqueExprName); + if (type != null) { + types.add(type); + } + } + return true; + } + }); + return types; + } + + private static PsiType getAcceptableType(PsiClass interface2Consider, PsiFunctionalExpression expression, String uniqueExprName) { + final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(interface2Consider.getProject()); + //todo try to infer type + final PsiDeclarationStatement exprDeclaration = (PsiDeclarationStatement)elementFactory + .createStatementFromText(interface2Consider.getQualifiedName() + " " + uniqueExprName + " = " + expression.getText() + ";", expression); + + final PsiLocalVariable var = (PsiLocalVariable)exprDeclaration.getDeclaredElements()[0]; + final PsiExpression exprAsInitializer = var.getInitializer(); + if (exprAsInitializer instanceof PsiFunctionalExpression) { + + if (!((PsiFunctionalExpression)exprAsInitializer).isAcceptable(var.getType())) { + return null; + } + final PsiType type = ((PsiFunctionalExpression)exprAsInitializer).getFunctionalInterfaceType(); + if (type instanceof PsiLambdaExpressionType || type instanceof PsiLambdaParameterType || type instanceof PsiMethodReferenceType) { + return null; + } + return type; + } + + return null; + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/ui/TypeSelectorManagerImpl.java b/java/java-impl/src/com/intellij/refactoring/ui/TypeSelectorManagerImpl.java index b17e856b380d..63782c5bb806 100644 --- a/java/java-impl/src/com/intellij/refactoring/ui/TypeSelectorManagerImpl.java +++ b/java/java-impl/src/com/intellij/refactoring/ui/TypeSelectorManagerImpl.java @@ -191,10 +191,20 @@ public class TypeSelectorManagerImpl implements TypeSelectorManager { } }); + collectAllSameShapedTypes(expectedTypes, allowedTypes); + ArrayList result = normalizeTypeList(allowedTypes); return result.toArray(PsiType.createArray(result.size())); } + private void collectAllSameShapedTypes(ExpectedTypeInfo[] expectedTypes, ArrayList allowedTypes) { + for (ExpectedTypeInfo info : expectedTypes) { + if (info.getKind() == ExpectedTypeInfo.TYPE_SAME_SHAPED) { + allowedTypes.add(info.getDefaultType()); + } + } + } + private PsiType[] getTypesForAll(final boolean areTypesDirected) { final ArrayList expectedTypesFromAll = new ArrayList(); for (PsiExpression occurrence : myOccurrences) { @@ -228,6 +238,10 @@ public class TypeSelectorManagerImpl implements TypeSelectorManager { } }); + for (ExpectedTypeInfo[] typeInfos : expectedTypesFromAll) { + collectAllSameShapedTypes(typeInfos, allowedTypes); + } + final ArrayList result = normalizeTypeList(allowedTypes); if (!areTypesDirected) { Collections.reverse(result); diff --git a/java/java-impl/src/com/intellij/refactoring/util/RefactoringUtil.java b/java/java-impl/src/com/intellij/refactoring/util/RefactoringUtil.java index 6dbb4fbc3948..fa7c9d0ca3fc 100644 --- a/java/java-impl/src/com/intellij/refactoring/util/RefactoringUtil.java +++ b/java/java-impl/src/com/intellij/refactoring/util/RefactoringUtil.java @@ -386,9 +386,12 @@ public class RefactoringUtil { public static PsiType getTypeByExpressionWithExpectedType(PsiExpression expr) { PsiType type = getTypeByExpression(expr); - if (type != null) return type; + final boolean isFunctionalType = type instanceof PsiLambdaExpressionType || type instanceof PsiMethodReferenceType; + if (type != null && !isFunctionalType) { + return type; + } ExpectedTypeInfo[] expectedTypes = ExpectedTypesProvider.getInstance(expr.getProject()).getExpectedTypes(expr, false); - if (expectedTypes.length == 1) { + if (expectedTypes.length == 1 || isFunctionalType && expectedTypes.length > 0) { type = expectedTypes[0].getType(); if (!type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) return type; } diff --git a/java/java-tests/testData/refactoring/introduceVariable/LambdaNotInContext.after.java b/java/java-tests/testData/refactoring/introduceVariable/LambdaNotInContext.after.java new file mode 100644 index 000000000000..5af526a9b606 --- /dev/null +++ b/java/java-tests/testData/refactoring/introduceVariable/LambdaNotInContext.after.java @@ -0,0 +1,6 @@ +class Foo { + void test() { + Runnable l = () -> { + }; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/introduceVariable/LambdaNotInContext.java b/java/java-tests/testData/refactoring/introduceVariable/LambdaNotInContext.java new file mode 100644 index 000000000000..1bfee500f229 --- /dev/null +++ b/java/java-tests/testData/refactoring/introduceVariable/LambdaNotInContext.java @@ -0,0 +1,5 @@ +class Foo { + void test() { + () -> {}; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/introduceVariable/MethodRefNotInContext.after.java b/java/java-tests/testData/refactoring/introduceVariable/MethodRefNotInContext.after.java new file mode 100644 index 000000000000..b69e23e3cacd --- /dev/null +++ b/java/java-tests/testData/refactoring/introduceVariable/MethodRefNotInContext.after.java @@ -0,0 +1,7 @@ +import java.util.function.IntConsumer; + +class Foo { + void test() { + IntConsumer l = System::exit; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/introduceVariable/MethodRefNotInContext.java b/java/java-tests/testData/refactoring/introduceVariable/MethodRefNotInContext.java new file mode 100644 index 000000000000..267d8208b594 --- /dev/null +++ b/java/java-tests/testData/refactoring/introduceVariable/MethodRefNotInContext.java @@ -0,0 +1,5 @@ +class Foo { + void test() { + System::exit; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/refactoring/IntroduceVariableTest.java b/java/java-tests/testSrc/com/intellij/refactoring/IntroduceVariableTest.java index 2e2a6cc488a3..d3b7df2f8592 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/IntroduceVariableTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/IntroduceVariableTest.java @@ -18,6 +18,7 @@ package com.intellij.refactoring; import com.intellij.JavaTestUtil; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; @@ -27,6 +28,7 @@ import com.intellij.refactoring.introduceVariable.InputValidator; import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; import com.intellij.refactoring.introduceVariable.IntroduceVariableSettings; import com.intellij.refactoring.ui.TypeSelectorManagerImpl; +import com.intellij.testFramework.IdeaTestUtil; import com.intellij.testFramework.LightCodeInsightTestCase; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; @@ -430,6 +432,14 @@ public class IntroduceVariableTest extends LightCodeInsightTestCase { doTest(new MockIntroduceVariableHandler("c", false, false, false, "SAM")); } + public void testLambdaNotInContext() { + doTest(new MockIntroduceVariableHandler("l", false, false, false, CommonClassNames.JAVA_LANG_RUNNABLE)); + } + + public void testMethodRefNotInContext() { + doTest(new MockIntroduceVariableHandler("l", false, false, false, "java.util.function.IntConsumer")); + } + public void testOneLineLambdaVoidCompatible() { doTest(new MockIntroduceVariableHandler("c", false, false, false, CommonClassNames.JAVA_LANG_STRING)); } @@ -472,4 +482,9 @@ public class IntroduceVariableTest extends LightCodeInsightTestCase { testMe.invoke(getProject(), getEditor(), getFile(), null); checkResultByFile(baseName + ".after.java"); } + + @Override + protected Sdk getProjectJDK() { + return IdeaTestUtil.getMockJdk18(); + } }