Skip to content

Commit

Permalink
GROOVY-11348: STC: check loss of precision for (short) 1234 literals
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Jul 9, 2024
1 parent 95e4dbf commit 65ce840
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -767,74 +767,65 @@ public static boolean isBeingCompiled(final ClassNode node) {
return (node.getCompileUnit() != null);
}

@Deprecated(forRemoval = true, since = "3.0.0")
static boolean checkPossibleLooseOfPrecision(final ClassNode left, final ClassNode right, final Expression rightExpr) {
return checkPossibleLossOfPrecision(left, right, rightExpr);
}

static boolean checkPossibleLossOfPrecision(final ClassNode left, final ClassNode right, final Expression rightExpr) {
if (left == right || left.equals(right)) return false; // identical types
int leftIndex = NUMBER_TYPES.get(left);
int rightIndex = NUMBER_TYPES.get(right);
if (leftIndex >= rightIndex) return false;
// here we must check if the right number is short enough to fit in the left type
if (rightExpr instanceof ConstantExpression) {
Object value = ((ConstantExpression) rightExpr).getValue();
if (!(value instanceof Number)) return true;

// check if the right number will fit in the left type
Expression valueExpr = rightExpr;
if (valueExpr instanceof CastExpression) // "(short) 1234"
valueExpr = ((CastExpression) valueExpr).getExpression();
if (valueExpr instanceof ConstantExpression) {
Object value = ((ConstantExpression) valueExpr).getValue();
if (!(value instanceof Number)) return true; // null or ...
Number number = (Number) value;
switch (leftIndex) {
case 0: { // byte
byte val = number.byteValue();
if (number instanceof Short) {
return !Short.valueOf(val).equals(number);
}
if (number instanceof Integer) {
return !Integer.valueOf(val).equals(number);
}
if (number instanceof Long) {
return !Long.valueOf(val).equals(number);
}
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
case 0: // byte
switch (rightIndex) {
case 1:
return Short .compare(number.byteValue(), number. shortValue()) != 0;
case 2:
return Integer.compare(number.byteValue(), number. intValue()) != 0;
case 3:
return Long .compare(number.byteValue(), number. longValue()) != 0;
case 4:
return Float .compare(number.byteValue(), number. floatValue()) != 0;
default:
return Double .compare(number.byteValue(), number.doubleValue()) != 0;
}
case 1: { // short
short val = number.shortValue();
if (number instanceof Integer) {
return !Integer.valueOf(val).equals(number);
}
if (number instanceof Long) {
return !Long.valueOf(val).equals(number);
}
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
case 1: // short
switch (rightIndex) {
case 2:
return Integer.compare(number.shortValue(), number. intValue()) != 0;
case 3:
return Long .compare(number.shortValue(), number. longValue()) != 0;
case 4:
return Float .compare(number.shortValue(), number. floatValue()) != 0;
default:
return Double .compare(number.shortValue(), number.doubleValue()) != 0;
}
case 2: { // integer
int val = number.intValue();
if (number instanceof Long) {
return !Long.valueOf(val).equals(number);
}
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
}
case 3: { // long
long val = number.longValue();
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
case 2: // int
switch (rightIndex) {
case 3:
return Long .compare(number.intValue(), number. longValue()) != 0;
case 4:
return Float .compare(number.intValue(), number. floatValue()) != 0;
default:
return Double.compare(number.intValue(), number.doubleValue()) != 0;
}
case 4: { // float
float val = number.floatValue();
return !Double.valueOf(val).equals(number);
case 3: // long
switch (rightIndex) {
case 4:
return Float .compare(number.longValue(), number. floatValue()) != 0;
default:
return Double.compare(number.longValue(), number.doubleValue()) != 0;
}
default: // double
return false; // no possible loss here
case 4: // float
return Double.compare(number.floatValue(), number.doubleValue()) != 0;
default: // double
return false; // no possible loss here
}
}
return true; // possible loss of precision
Expand Down
80 changes: 53 additions & 27 deletions src/test/groovy/transform/stc/STCAssignmentTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
'''
}

void testAssignmentToNumbers() {
for (type in ['byte','short','int','long','float','double',
'java.lang.Byte','java.lang.Short','java.lang.Integer',
'java.lang.Long','java.lang.Float','java.lang.Double']) {
boolean primitive = !type.contains('.')
shouldFailWithMessages """
$type x = 0
x = (byte)1
x = (char)2 // cannot assign
x =(short)3 // possible loss -- GROOVY-11348
x = 4
x = 5L
x = 6f
x = 7d
x = 8g // cannot assign
//x = 9.0g // okay for float and double
x = (Number)10
""",
"Cannot assign value of type ${primitive ? 'char' : 'java.lang.Character'} to variable of type $type",
"Cannot assign value of type java.math.BigInteger to variable of type $type",
"Cannot assign value of type java.lang.Number to variable of type $type"
}
}

void testAssignmentToBoolean() {
assertScript '''
boolean b = new Object()
Expand Down Expand Up @@ -219,70 +243,72 @@ class STCAssignmentTest extends StaticTypeCheckingTestCase {
}

void testPossibleLossOfPrecision1() {
shouldFailWithMessages '''
long a = Long.MAX_VALUE
int b = a
''',
'Possible loss of precision from long to int'
}

void testPossibleLossOfPrecision2() {
assertScript '''
int b = 0L
'''
}

void testPossibleLossOfPrecision3() {
assertScript '''
byte b = 127
'''
}

void testPossibleLossOfPrecision4() {
void testPossibleLossOfPrecision2() {
shouldFailWithMessages '''
byte b = 128 // will not fit in a byte
''',
'Possible loss of precision from int to byte'
}

void testPossibleLossOfPrecision5() {
void testPossibleLossOfPrecision3() {
assertScript '''
short b = 128
short s = 128
'''
}

void testPossibleLossOfPrecision6() {
void testPossibleLossOfPrecision4() {
shouldFailWithMessages '''
short b = 32768 // will not fit in a short
short s = 32768 // will not fit in a short
''',
'Possible loss of precision from int to short'
}

void testPossibleLossOfPrecision7() {
void testPossibleLossOfPrecision5() {
assertScript '''
int b = 32768L // mark it as a long, but it fits into an int
int i = 32768L // mark it as a long, but it fits into an int
'''
}

void testPossibleLossOfPrecision8() {
void testPossibleLossOfPrecision6() {
assertScript '''
int b = 32768.0f // mark it as a float, but it fits into an int
int i = 32768f // mark it as a float, but it fits into an int
'''
}

void testPossibleLossOfPrecision9() {
void testPossibleLossOfPrecision7() {
assertScript '''
int b = 32768.0d // mark it as a double, but it fits into an int
int i = 32768d // mark it as a double, but it fits into an int
'''
}

void testPossibleLossOfPrecision10() {
void testPossibleLossOfPrecision8() {
shouldFailWithMessages '''
int b = 32768.1d
int i = 32768.1d
''',
'Possible loss of precision from double to int'
}

void testPossibleLossOfPrecision9() {
shouldFailWithMessages '''
int i = Long.MAX_VALUE
''',
'Possible loss of precision from long to int'
}

void testPossibleLossOfPrecision10() {
assertScript '''
byte b = 0L
short s = 0L
int i = 0L
float f = 0L
'''
}

//--------------------------------------------------------------------------

void testPlusEqualsOnInt() {
Expand Down

0 comments on commit 65ce840

Please sign in to comment.