[groovy] properly compute result of +,-,* on BigInteger-s (IDEA-139913)

This commit is contained in:
Daniil Ovchinnikov
2017-01-24 23:02:38 +03:00
parent 6d7144fa10
commit 37af4a2d81
5 changed files with 194 additions and 26 deletions
@@ -20,8 +20,7 @@ import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.Nullable;
import static org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.TYPE_TO_RANK;
import static org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.getQualifiedName;
import static org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.*;
public interface TypeConstants {
@@ -44,4 +43,9 @@ public interface TypeConstants {
}
return 0;
}
@Nullable
static String getTypeFqn(int rank) {
return RANK_TO_TYPE.get(rank);
}
}
@@ -29,6 +29,7 @@ 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;
@@ -180,6 +181,15 @@ public class TypesUtil implements TypeConstants {
TYPE_TO_RANK.put(CommonClassNames.JAVA_LANG_NUMBER, 10);
}
static final TIntObjectHashMap<String> RANK_TO_TYPE = new TIntObjectHashMap<>();
static {
TYPE_TO_RANK.forEachEntry((fqn, rank) -> {
RANK_TO_TYPE.put(rank, fqn);
return true;
});
}
private static final List<PsiType> LUB_NUMERIC_TYPES = ContainerUtil.newArrayList(
PsiType.BYTE,
PsiType.SHORT,
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -18,19 +18,35 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.binary
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiType;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrOperatorExpression;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import static org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypeConstants.*;
import static org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.createTypeByFQClassName;
/**
* Created by Max Medvedev on 12/20/13
*/
public class GrBinaryExpressionUtil {
private static final int[] RANKS = new int[]{
INTEGER_RANK, LONG_RANK, BIG_INTEGER_RANK, BIG_DECIMAL_RANK, DOUBLE_RANK
};
public static PsiType getDefaultNumericResultType(PsiType ltype, PsiType rtype, GrOperatorExpression e) {
if (isBigDecimal(ltype, rtype)) return createBigDecimal(e);
if (isFloatOrDouble(ltype, rtype)) return createDouble(e);
if (isLong(ltype, rtype)) return createLong(e);
return createInteger(e);
int lRank = getTypeRank(ltype);
int rRank = getTypeRank(rtype);
int resultRank = getResultTypeRank(lRank, rRank);
String fqn = getTypeFqn(resultRank);
return fqn == null ? null : createTypeByFQClassName(fqn, e);
}
private static int getResultTypeRank(int lRank, int rRank) {
for (int rank : RANKS) {
if (lRank <= rank && rRank <= rank) {
return rank;
}
}
return 0;
}
public static PsiType createDouble(GrOperatorExpression e) {
@@ -63,6 +79,6 @@ public class GrBinaryExpressionUtil {
}
public static PsiType getTypeByFQName(String fqn, GrOperatorExpression e) {
return TypesUtil.createTypeByFQClassName(fqn, e);
return createTypeByFQClassName(fqn, e);
}
}
@@ -0,0 +1,154 @@
/*
* Copyright 2000-2017 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.psi.PsiClassType
import com.intellij.testFramework.LightProjectDescriptor
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
class NumberMathTypingTest extends LightGroovyTestCase {
final LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST
@Override
void setUp() throws Exception {
super.setUp();
addBigInteger()
addBigDecimal()
}
void 'test +'() {
def data = [
['Byte', 'Byte', 'Integer'],
['Byte', 'Character', 'Integer'],
['Byte', 'Short', 'Integer'],
['Byte', 'Integer', 'Integer'],
['Byte', 'Long', 'Long'],
['Byte', 'BigInteger', 'BigInteger'],
['Byte', 'BigDecimal', 'BigDecimal'],
['Byte', 'Float', 'Double'],
['Byte', 'Double', 'Double'],
['Character', 'Byte', 'Integer'],
['Character', 'Character', 'Integer'],
['Character', 'Short', 'Integer'],
['Character', 'Integer', 'Integer'],
['Character', 'Long', 'Long'],
['Character', 'BigInteger', 'BigInteger'],
['Character', 'BigDecimal', 'BigDecimal'],
['Character', 'Float', 'Double'],
['Character', 'Double', 'Double'],
['Short', 'Byte', 'Integer'],
['Short', 'Character', 'Integer'],
['Short', 'Short', 'Integer'],
['Short', 'Integer', 'Integer'],
['Short', 'Long', 'Long'],
['Short', 'BigInteger', 'BigInteger'],
['Short', 'BigDecimal', 'BigDecimal'],
['Short', 'Float', 'Double'],
['Short', 'Double', 'Double'],
['Integer', 'Byte', 'Integer'],
['Integer', 'Character', 'Integer'],
['Integer', 'Short', 'Integer'],
['Integer', 'Integer', 'Integer'],
['Integer', 'Long', 'Long'],
['Integer', 'BigInteger', 'BigInteger'],
['Integer', 'BigDecimal', 'BigDecimal'],
['Integer', 'Float', 'Double'],
['Integer', 'Double', 'Double'],
['Long', 'Byte', 'Long'],
['Long', 'Character', 'Long'],
['Long', 'Short', 'Long'],
['Long', 'Integer', 'Long'],
['Long', 'Long', 'Long'],
['Long', 'BigInteger', 'BigInteger'],
['Long', 'BigDecimal', 'BigDecimal'],
['Long', 'Float', 'Double'],
['Long', 'Double', 'Double'],
['BigInteger', 'Byte', 'BigInteger'],
['BigInteger', 'Character', 'BigInteger'],
['BigInteger', 'Short', 'BigInteger'],
['BigInteger', 'Integer', 'BigInteger'],
['BigInteger', 'Long', 'BigInteger'],
['BigInteger', 'BigInteger', 'BigInteger'],
['BigInteger', 'BigDecimal', 'BigDecimal'],
['BigInteger', 'Float', 'Double'],
['BigInteger', 'Double', 'Double'],
['BigDecimal', 'Byte', 'BigDecimal'],
['BigDecimal', 'Character', 'BigDecimal'],
['BigDecimal', 'Short', 'BigDecimal'],
['BigDecimal', 'Integer', 'BigDecimal'],
['BigDecimal', 'Long', 'BigDecimal'],
['BigDecimal', 'BigInteger', 'BigDecimal'],
['BigDecimal', 'BigDecimal', 'BigDecimal'],
['BigDecimal', 'Float', 'Double'],
['BigDecimal', 'Double', 'Double'],
['Float', 'Byte', 'Double'],
['Float', 'Character', 'Double'],
['Float', 'Short', 'Double'],
['Float', 'Integer', 'Double'],
['Float', 'Long', 'Double'],
['Float', 'BigInteger', 'Double'],
['Float', 'BigDecimal', 'Double'],
['Float', 'Float', 'Double'],
['Float', 'Double', 'Double'],
['Double', 'Byte', 'Double'],
['Double', 'Character', 'Double'],
['Double', 'Short', 'Double'],
['Double', 'Integer', 'Double'],
['Double', 'Long', 'Double'],
['Double', 'BigInteger', 'Double'],
['Double', 'BigDecimal', 'Double'],
['Double', 'Float', 'Double'],
['Double', 'Double', 'Double'],
]
for (row in data) {
def (left, right, type) = row
doTest left, '+', right, type
}
}
private void doTest(String left, String operator, String right, String expected) {
fixture.with {
def file = configureByText('_.groovy', """\
def foo($left a, $right b) {
a $operator b
}
""") as GroovyFile
try {
def expr = file.methods.first().block.statements.first()
assert expr instanceof GrExpression
def type = expr.type
assert type instanceof PsiClassType
def name = type.className
assert name == expected
}
catch (Throwable e) {
throw new RuntimeException("$left $operator $right (expected: $expected)", e)
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -614,22 +614,6 @@ class Any {
''', 'java.lang.String')
}
void testPlus1() {
doExprTest('2+2', 'java.lang.Integer')
}
void testPlus2() {
doExprTest('2f+2', 'java.lang.Double')
}
void testPlus3() {
doExprTest('2f+2f', 'java.lang.Double')
}
void testPlus4() {
doExprTest('2.5+2', 'java.math.BigDecimal')
}
void testMultiply1() {
doExprTest('2*2', 'java.lang.Integer')
}