diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/PsiPolyExpressionUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/PsiPolyExpressionUtil.java new file mode 100644 index 000000000000..f3611e939bc4 --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/PsiPolyExpressionUtil.java @@ -0,0 +1,146 @@ +/* + * Copyright 2000-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 com.intellij.psi.impl.source.resolve.graphInference; + +import com.intellij.psi.*; +import com.intellij.psi.util.TypeConversionUtil; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * User: anna + */ +public class PsiPolyExpressionUtil { + public static boolean hasStandaloneForm(PsiExpression expression) { + if (expression instanceof PsiLambdaExpression || + expression instanceof PsiMethodReferenceExpression || + expression instanceof PsiParenthesizedExpression || + expression instanceof PsiConditionalExpression || + expression instanceof PsiCallExpression) { + return false; + } + return true; + } + + public static boolean isPolyExpression(PsiExpression expression) { + if (expression instanceof PsiLambdaExpression || expression instanceof PsiMethodReferenceExpression) { + return true; + } + else if (expression instanceof PsiParenthesizedExpression) { + return isPolyExpression(((PsiParenthesizedExpression)expression).getExpression()); + } + else if (expression instanceof PsiNewExpression) { + final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)expression).getClassReference(); + if (classReference != null) { + final PsiReferenceParameterList parameterList = classReference.getParameterList(); + if (parameterList != null) { + final PsiTypeElement[] typeElements = parameterList.getTypeParameterElements(); + if (typeElements.length == 1 && typeElements[0].getType() instanceof PsiDiamondType) { + return isAssignmentOrInvocationContext(expression.getParent()); + } + } + } + } else if (expression instanceof PsiMethodCallExpression) { + if (isAssignmentOrInvocationContext(expression.getParent()) && ((PsiMethodCallExpression)expression).getTypeArguments().length == 0) { + final PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod(); + if (method != null) { + final Set typeParameters = new HashSet(Arrays.asList(method.getTypeParameters())); + if (typeParameters.size() > 0) { + final PsiType returnType = method.getReturnType(); + if (returnType != null) { + return returnTypeMentionsTypeParameters(typeParameters, returnType); + } + } + } + } + } + else if (expression instanceof PsiConditionalExpression) { + final ConditionalKind conditionalKind = isBooleanOrNumeric(expression); + if (conditionalKind == null) { + return isAssignmentOrInvocationContext(expression.getParent()); + } + } + return false; + } + + private static Boolean returnTypeMentionsTypeParameters(final Set typeParameters, PsiType returnType) { + return returnType.accept(new PsiTypeVisitor() { + @Nullable + @Override + public Boolean visitType(PsiType type) { + return false; + } + + @Nullable + @Override + public Boolean visitClassType(PsiClassType classType) { + for (PsiType type : classType.getParameters()) { + if (type.accept(this)) return true; + } + final PsiClass psiClass = classType.resolve(); + return psiClass instanceof PsiTypeParameter && typeParameters.contains(psiClass); + } + + @Nullable + @Override + public Boolean visitArrayType(PsiArrayType arrayType) { + return arrayType.getComponentType().accept(this); + } + }); + } + + private static boolean isAssignmentOrInvocationContext(PsiElement context) { + return context instanceof PsiExpressionList || isAssignmentContext(context); + } + + private static boolean isAssignmentContext(PsiElement context) { + return context instanceof PsiReturnStatement || + context instanceof PsiAssignmentExpression || + context instanceof PsiVariable; + } + + private enum ConditionalKind { + BOOLEAN, NUMERIC + } + + private static ConditionalKind isBooleanOrNumeric(PsiExpression expr) { + if (expr instanceof PsiParenthesizedExpression) { + return isBooleanOrNumeric(((PsiParenthesizedExpression)expr).getExpression()); + } + PsiType type = null; + if (expr instanceof PsiNewExpression || hasStandaloneForm(expr)) { + type = expr.getType(); + } else if (expr instanceof PsiMethodCallExpression) { + final PsiMethod method = ((PsiMethodCallExpression)expr).resolveMethod(); + if (method != null) { + type = method.getReturnType(); + } + } + if (TypeConversionUtil.isNumericType(type)) return ConditionalKind.NUMERIC; + if (TypeConversionUtil.isBooleanType(type)) return ConditionalKind.BOOLEAN; + if (expr instanceof PsiConditionalExpression) { + final PsiExpression thenExpression = ((PsiConditionalExpression)expr).getThenExpression(); + final PsiExpression elseExpression = ((PsiConditionalExpression)expr).getElseExpression(); + final ConditionalKind thenKind = isBooleanOrNumeric(thenExpression); + final ConditionalKind elseKind = isBooleanOrNumeric(elseExpression); + if (thenKind == elseKind) return thenKind; + } + return null; + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/PsiPolyExpressionUtilTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/PsiPolyExpressionUtilTest.java new file mode 100644 index 000000000000..b892318943e0 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/PsiPolyExpressionUtilTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-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 com.intellij.codeInsight.daemon.lambda; + +import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; + +/** + * User: anna + */ +public class PsiPolyExpressionUtilTest extends LightCodeInsightFixtureTestCase { + public void testPrefixExpression() throws Exception { + final PsiExpression psiExpression = findExpression(" int j = i++;"); + assertInstanceOf(psiExpression, PsiPostfixExpression.class); + assertTrue(PsiPolyExpressionUtil.hasStandaloneForm(psiExpression)); + assertFalse(PsiPolyExpressionUtil.isPolyExpression(psiExpression)); + } + + public void testNumericConditionExpression() throws Exception { + final PsiExpression psiExpression = findExpression(" int j = i == 0 ? i + 1 : i - 1;"); + assertInstanceOf(psiExpression, PsiConditionalExpression.class); + assertFalse(PsiPolyExpressionUtil.hasStandaloneForm(psiExpression)); + assertFalse(PsiPolyExpressionUtil.isPolyExpression(psiExpression)); + } + + public void testPolyConditionExpression() throws Exception { + myFixture.configureByText("Foo.java", "import java.util.*;" + + "class Foo {" + + " String foo(int i) {" + + " return i == 0 ? bar() : bar();" + + " }" + + " String bar() {return null;}" + + "}"); + final PsiElement elementAtCaret = myFixture.getFile().findElementAt(myFixture.getCaretOffset()); + assertNotNull(elementAtCaret); + final PsiExpression psiExpression = PsiTreeUtil.getParentOfType(elementAtCaret, PsiExpression.class); + assertInstanceOf(psiExpression, PsiConditionalExpression.class); + assertFalse(PsiPolyExpressionUtil.hasStandaloneForm(psiExpression)); + assertTrue(PsiPolyExpressionUtil.isPolyExpression(psiExpression)); + } + + public void testNewExpressionDiamond() throws Exception { + final PsiExpression psiExpression = findExpression(" List l = new ArrayList<>();"); + assertInstanceOf(psiExpression, PsiNewExpression.class); + assertFalse(PsiPolyExpressionUtil.hasStandaloneForm(psiExpression)); + assertTrue(PsiPolyExpressionUtil.isPolyExpression(psiExpression)); + } + + public void testNewExpression() throws Exception { + final PsiExpression psiExpression = findExpression(" List l = new ArrayList();"); + assertInstanceOf(psiExpression, PsiNewExpression.class); + assertFalse(PsiPolyExpressionUtil.hasStandaloneForm(psiExpression)); + assertFalse(PsiPolyExpressionUtil.isPolyExpression(psiExpression)); + } + + private PsiExpression findExpression(String textWithExpression) { + myFixture.configureByText("Foo.java", "import java.util.*;" + + "class Foo {" + + " void foo(int i) {" + + textWithExpression + + " }" + + "}"); + final PsiElement elementAtCaret = myFixture.getFile().findElementAt(myFixture.getCaretOffset()); + assertNotNull(elementAtCaret); + return PsiTreeUtil.getParentOfType(elementAtCaret, PsiExpression.class); + } +}