diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/TypesUtil.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/TypesUtil.java index be56602bc084..e629466b8242 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/TypesUtil.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/TypesUtil.java @@ -29,7 +29,6 @@ import com.intellij.util.containers.ComparatorUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import gnu.trove.THashMap; -import gnu.trove.TIntObjectHashMap; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NonNls; @@ -54,6 +53,7 @@ import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; import java.util.Iterator; +import java.util.List; import java.util.Map; import static org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.HardcodedGroovyMethodConstants.*; @@ -179,6 +179,15 @@ public class TypesUtil { TYPE_TO_RANK.put(CommonClassNames.JAVA_LANG_NUMBER, 9); } + private static final List LUB_NUMERIC_TYPES = ContainerUtil.newArrayList( + PsiType.BYTE, + PsiType.SHORT, + PsiType.INT, + PsiType.LONG, + PsiType.FLOAT, + PsiType.DOUBLE + ); + static { ourQNameToUnboxed.put(CommonClassNames.JAVA_LANG_BOOLEAN, PsiType.BOOLEAN); ourQNameToUnboxed.put(CommonClassNames.JAVA_LANG_BYTE, PsiType.BYTE); @@ -191,21 +200,6 @@ public class TypesUtil { ourQNameToUnboxed.put(CommonClassNames.JAVA_LANG_VOID, PsiType.VOID); } - - private static final TIntObjectHashMap RANK_TO_TYPE = new TIntObjectHashMap<>(); - - static { - RANK_TO_TYPE.put(1, CommonClassNames.JAVA_LANG_INTEGER); - RANK_TO_TYPE.put(2, CommonClassNames.JAVA_LANG_INTEGER); - RANK_TO_TYPE.put(3, CommonClassNames.JAVA_LANG_INTEGER); - RANK_TO_TYPE.put(4, CommonClassNames.JAVA_LANG_LONG); - RANK_TO_TYPE.put(5, GroovyCommonClassNames.JAVA_MATH_BIG_INTEGER); - RANK_TO_TYPE.put(6, GroovyCommonClassNames.JAVA_MATH_BIG_DECIMAL); - RANK_TO_TYPE.put(7, CommonClassNames.JAVA_LANG_DOUBLE); - RANK_TO_TYPE.put(8, CommonClassNames.JAVA_LANG_DOUBLE); - RANK_TO_TYPE.put(9, CommonClassNames.JAVA_LANG_NUMBER); - } - /** * @deprecated see {@link #canAssign} */ @@ -499,6 +493,10 @@ public class TypesUtil { @Nullable public static PsiType getLeastUpperBound(@NotNull PsiType type1, @NotNull PsiType type2, PsiManager manager) { + { + PsiType numericLUB = getNumericLUB(type1, type2); + if (numericLUB != null) return numericLUB; + } if (type1 instanceof GrTupleType && type2 instanceof GrTupleType) { GrTupleType tuple1 = (GrTupleType)type1; GrTupleType tuple2 = (GrTupleType)type2; @@ -565,6 +563,21 @@ public class TypesUtil { return GenericsUtil.getLeastUpperBound(type1, type2, manager); } + @Nullable + private static PsiType getNumericLUB(@Nullable PsiType type1, @Nullable PsiType type2) { + PsiPrimitiveType unboxedType1 = PsiPrimitiveType.getUnboxedType(type1); + PsiPrimitiveType unboxedType2 = PsiPrimitiveType.getUnboxedType(type2); + if (unboxedType1 != null && unboxedType2 != null) { + int i1 = LUB_NUMERIC_TYPES.indexOf(unboxedType1); + int i2 = LUB_NUMERIC_TYPES.indexOf(unboxedType2); + if (i1 >= 0 && i2 >= 0) { + if (i1 > i2) return type1; + if (i2 > i1) return type2; + } + } + return null; + } + private static boolean checkEmptyListAndList(PsiType type1, PsiType type2) { if (type1 instanceof GrTupleType) { PsiType[] types = ((GrTupleType)type1).getComponentTypes(); diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/GrNumericLUBTypeTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/GrNumericLUBTypeTest.groovy new file mode 100644 index 000000000000..677ae95aeb65 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/GrNumericLUBTypeTest.groovy @@ -0,0 +1,135 @@ +/* + * Copyright 2000-2016 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.plugins.groovy.lang.resolve + +import com.intellij.testFramework.LightProjectDescriptor +import groovy.transform.CompileStatic +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor +import org.jetbrains.plugins.groovy.LightGroovyTestCase +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression + +@CompileStatic +class GrNumericLUBTypeTest extends LightGroovyTestCase { + + final LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST + + @Override + void setUp() throws Exception { + super.setUp() + fixture.addFileToProject '''constants.groovy''', '''\ +interface Constants { + Byte nB = 0 + Short nS = 0 + Integer nI = 0 + Long nL = 0 + Float nF = 0 + Double nD = 0 +} +''' + } + + private void doTest(String expressionText, String expectedType) { + def file = fixture.configureByText('_.groovy', """\ +import static Constants.* +$expressionText +""") as GroovyFile + def expression = file.statements.last() as GrExpression + assert expression.type.equalsToText(expectedType): "'$expression.text' : $expression.type" + } + + void 'test elvis types'() { + [ + // Byte + 'nB ?: nB': 'java.lang.Byte', + 'nB ?: nS': 'java.lang.Short', + 'nB ?: nI': 'java.lang.Integer', + 'nB ?: nL': 'java.lang.Long', + 'nB ?: nF': 'java.lang.Float', + 'nB ?: nD': 'java.lang.Double', + + // Short + 'nS ?: nB': 'java.lang.Short', + 'nS ?: nS': 'java.lang.Short', + 'nS ?: nI': 'java.lang.Integer', + 'nS ?: nL': 'java.lang.Long', + 'nS ?: nF': 'java.lang.Float', + 'nS ?: nD': 'java.lang.Double', + + // Integer + 'nI ?: nB': 'java.lang.Integer', + 'nI ?: nS': 'java.lang.Integer', + 'nI ?: nI': 'java.lang.Integer', + 'nI ?: nL': 'java.lang.Long', + 'nI ?: nF': 'java.lang.Float', + 'nI ?: nD': 'java.lang.Double', + + // Long + 'nL ?: nB': 'java.lang.Long', + 'nL ?: nS': 'java.lang.Long', + 'nL ?: nI': 'java.lang.Long', + 'nL ?: nL': 'java.lang.Long', + 'nL ?: nF': 'java.lang.Float', + 'nL ?: nD': 'java.lang.Double', + + // Float + 'nF ?: nB': 'java.lang.Float', + 'nF ?: nS': 'java.lang.Float', + 'nF ?: nI': 'java.lang.Float', + 'nF ?: nL': 'java.lang.Float', + 'nF ?: nF': 'java.lang.Float', + 'nF ?: nD': 'java.lang.Double', + + // Double + 'nD ?: nB': 'java.lang.Double', + 'nD ?: nS': 'java.lang.Double', + 'nD ?: nI': 'java.lang.Double', + 'nD ?: nL': 'java.lang.Double', + 'nD ?: nF': 'java.lang.Double', + 'nD ?: nD': 'java.lang.Double', + ].each { + doTest it.key, it.value + } + } + + // same as elvis + void 'test ternary types'() { + [ + '42 ? nB : nB': 'java.lang.Byte', + '42 ? nB : nS': 'java.lang.Short', + '42 ? nB : nI': 'java.lang.Integer', + '42 ? nB : nL': 'java.lang.Long', + '42 ? nB : nF': 'java.lang.Float', + '42 ? nB : nD': 'java.lang.Double', + ].each { + doTest it.key, it.value + } + } + + // same as elvis + void 'test if branches assignment'() { + [ + 'def a; if (42) { a = nB } else { a = nB }; a': 'java.lang.Byte', + 'def a; if (42) { a = nB } else { a = nS }; a': 'java.lang.Short', + 'def a; if (42) { a = nB } else { a = nI }; a': 'java.lang.Integer', + 'def a; if (42) { a = nB } else { a = nL }; a': 'java.lang.Long', + 'def a; if (42) { a = nB } else { a = nF }; a': 'java.lang.Float', + 'def a; if (42) { a = nB } else { a = nD }; a': 'java.lang.Double', + ].each { + doTest it.key, it.value + } + } +}