diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index e8740e7dc0af..5151ce830203 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -31,7 +31,6 @@ import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.FList; import com.siyeh.ig.callMatcher.CallMatcher; -import com.siyeh.ig.numeric.UnnecessaryExplicitNumericCastInspection; import com.siyeh.ig.psiutils.*; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; @@ -1977,23 +1976,15 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { if (operand != null) { operand.accept(this); - generateBoxingUnboxingInstructionFor(operand, castExpression.getType()); + generateBoxingUnboxingInstructionFor(castExpression, operand.getType(), castExpression.getType()); } else { addInstruction(new PushInstruction(myFactory.createTypeValue(castExpression.getType(), Nullability.UNKNOWN), null)); } final PsiTypeElement typeElement = castExpression.getCastType(); - if (typeElement != null && operand != null && operand.getType() != null) { - if (typeElement.getType() instanceof PsiPrimitiveType && - !UnnecessaryExplicitNumericCastInspection.isUnnecessaryPrimitiveNumericCast(castExpression)) { - if (!typeElement.getType().equals(PsiPrimitiveType.getUnboxedType(operand.getType()))) { - addInstruction(new PopInstruction()); - pushUnknown(); - } - } else { - addInstruction(new TypeCastInstruction(castExpression, operand, typeElement.getType())); - } + if (typeElement != null && operand != null && operand.getType() != null && !(typeElement.getType() instanceof PsiPrimitiveType)) { + addInstruction(new TypeCastInstruction(castExpression, operand, typeElement.getType())); } finishElement(castExpression); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index e26733745e9b..2a69f6f76a2f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -541,11 +541,20 @@ public class StandardInstructionVisitor extends InstructionVisitor { if (methodType == MethodCallInstruction.MethodType.CAST) { assert qualifierValue != null; - if (qualifierValue instanceof DfaConstValue && type != null) { - Object casted = TypeConversionUtil.computeCastTo(((DfaConstValue)qualifierValue).getValue(), type); + if (qualifierValue instanceof DfaVariableValue && TypeConversionUtil.isSafeConversion(type, qualifierValue.getType())) { + return qualifierValue; + } + DfaConstValue constValue = state.getConstantValue(qualifierValue); + if (constValue != null && type != null) { + Object casted = TypeConversionUtil.computeCastTo(constValue.getValue(), type); return factory.getConstFactory().createFromValue(casted, type); } - return qualifierValue; + if (type instanceof PsiPrimitiveType && TypeConversionUtil.isIntegralNumberType(type)) { + LongRangeSet range = state.getValueFact(qualifierValue, DfaFactType.RANGE); + if (range == null) range = LongRangeSet.all(); + return factory.getFactValue(DfaFactType.RANGE, range.castTo((PsiPrimitiveType)type)); + } + return DfaUnknownValue.getInstance(); } if (type != null && !(type instanceof PsiPrimitiveType)) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java index 1120e3248abb..bb652b925896 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java @@ -6,6 +6,7 @@ import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.dataFlow.value.*; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.ThreeState; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; @@ -192,6 +193,8 @@ public abstract class LongRangeSet { return null; } + public abstract LongRangeSet castTo(PsiPrimitiveType type); + /** * Returns a range which represents all the possible values after applying {@link Math#abs(int)} or {@link Math#abs(long)} * to the values from this set @@ -404,6 +407,12 @@ public abstract class LongRangeSet { if (leftFrom == leftTo && rightFrom == rightTo) { return point(leftFrom & rightFrom); } + if (leftFrom == leftTo && Long.bitCount(leftFrom+1) == 1) { + return bitwiseMask(rightFrom, rightTo, leftFrom); + } + if (rightFrom == rightTo && Long.bitCount(rightFrom+1) == 1) { + return bitwiseMask(leftFrom, leftTo, rightFrom); + } ThreeState[] leftBits = bits(leftFrom, leftTo); ThreeState[] rightBits = bits(rightFrom, rightTo); ThreeState[] resultBits = new ThreeState[Long.SIZE]; @@ -421,6 +430,22 @@ public abstract class LongRangeSet { return fromBits(resultBits); } + /** + * Returns the range after applying the mask to the input range which looks like 0..01..1 in binary + * @param from input range start + * @param to input range end + * @param mask mask + * @return range set after applying the mask + */ + private static LongRangeSet bitwiseMask(long from, long to, long mask) { + if (to - from > mask) return range(0, mask); + long min = from & mask; + long max = to & mask; + assert min != max; + if (min < max) return range(min, max); + return new RangeSet(new long[] {0, max, min, mask}); + } + /** * Creates a set which contains all the numbers satisfying the supplied bit vector. * Vector format is the same as returned by {@link #bits(long, long)}. The resulting set may @@ -712,6 +737,14 @@ public abstract class LongRangeSet { return other.isEmpty(); } + @Override + public LongRangeSet castTo(PsiPrimitiveType type) { + if (TypeConversionUtil.isIntegralNumberType(type)) { + return this; + } + throw new IllegalArgumentException(type.toString()); + } + @NotNull @Override public LongRangeSet abs(boolean isLong) { @@ -804,6 +837,28 @@ public abstract class LongRangeSet { return other.isEmpty() || equals(other); } + @Override + public LongRangeSet castTo(PsiPrimitiveType type) { + if (PsiType.LONG.equals(type)) return this; + long newValue; + if (PsiType.CHAR.equals(type)) { + newValue = (char)myValue; + } + else if (PsiType.INT.equals(type)) { + newValue = (int)myValue; + } + else if (PsiType.SHORT.equals(type)) { + newValue = (short)myValue; + } + else if (PsiType.BYTE.equals(type)) { + newValue = (byte)myValue; + } + else { + throw new IllegalArgumentException(type.toString()); + } + return newValue == myValue ? this : point(newValue); + } + @NotNull @Override public LongRangeSet abs(boolean isLong) { @@ -924,10 +979,8 @@ public abstract class LongRangeSet { if (from <= myFrom) { return range(to + 1, myTo); } - if (to >= myTo) { - return range(myFrom, from - 1); - } - throw new InternalError("Impossible: " + this + ":" + other); + assert to >= myTo; + return range(myFrom, from - 1); } long[] ranges = ((RangeSet)other).myRanges; LongRangeSet result = this; @@ -998,6 +1051,35 @@ public abstract class LongRangeSet { return other.isEmpty() || other.min() >= myFrom && other.max() <= myTo; } + @Override + public LongRangeSet castTo(PsiPrimitiveType type) { + if (PsiType.LONG.equals(type)) return this; + if (PsiType.BYTE.equals(type)) { + return mask(Byte.SIZE, type); + } + if (PsiType.SHORT.equals(type)) { + return mask(Short.SIZE, type); + } + if (PsiType.INT.equals(type)) { + return mask(Integer.SIZE, type); + } + if (PsiType.CHAR.equals(type)) { + if (myFrom <= Character.MIN_VALUE && myTo >= Character.MAX_VALUE) return CHAR_RANGE; + if (myFrom >= Character.MIN_VALUE && myTo <= Character.MAX_VALUE) return this; + return bitwiseAnd(point(0xFFFF)); + } + throw new IllegalArgumentException(type.toString()); + } + + @NotNull + private LongRangeSet mask(int size, PsiPrimitiveType type) { + long addend = 1L << (size - 1); + if (myFrom <= -addend && myTo >= addend - 1) return Objects.requireNonNull(fromType(type)); + if (myFrom >= -addend && myTo <= addend - 1) return this; + long mask = (1L << size) - 1; + return plus(myFrom, myTo, addend, addend, true).bitwiseAnd(point(mask)).plus(point(-addend), true); + } + @NotNull @Override public LongRangeSet abs(boolean isLong) { @@ -1233,6 +1315,15 @@ public abstract class LongRangeSet { return false; } + @Override + public LongRangeSet castTo(PsiPrimitiveType type) { + LongRangeSet result = all(); + for (int i = 0; i < myRanges.length; i += 2) { + result = result.subtract(range(myRanges[i], myRanges[i + 1]).castTo(type)); + } + return all().subtract(result); + } + @NotNull @Override public LongRangeSet abs(boolean isLong) { diff --git a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java index afa30b16e75e..6fc8624339ed 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java @@ -1477,6 +1477,40 @@ public class TypeConversionUtil { parameter.putUserData(LOWER_BOUND, lowerBound); } + /** + * Returns true if numeric conversion (widening or narrowing) does not lose the information. + * This differs slightly from {@link #isAssignable(PsiType, PsiType)} result as some assignable types + * still may lose the information. E.g. {@code double doubleVar = longVar} may lose round the long value. + * + * @param target target type + * @param source source type + * @return true if numeric conversion (widening or narrowing) does not lose the information. + */ + public static boolean isSafeConversion(PsiType target, PsiType source) { + /* From \ To byte short char int long float double + * byte + + - + + + + + * short - + - + + + + + * char - - + + + + + + * int - - - + + - + + * long - - - - + - - + * float - - - - - + + + * double - - - - - - + + */ + if (target == null || source == null) return false; + if (target.equals(source)) return true; + + int sourceRank = TYPE_TO_RANK_MAP.get(source); + int targetRank = TYPE_TO_RANK_MAP.get(target); + if (sourceRank == 0 || sourceRank > MAX_NUMERIC_RANK || + targetRank == 0 || targetRank > MAX_NUMERIC_RANK || + !IS_ASSIGNABLE_BIT_SET[sourceRank-1][targetRank-1]) { + return false; + } + if (PsiType.INT.equals(source) && PsiType.FLOAT.equals(target)) return false; + if (PsiType.LONG.equals(source) && isFloatOrDoubleType(target)) return false; + return true; + } + @FunctionalInterface private interface Caster { @NotNull diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NumericCast.java b/java/java-tests/testData/inspection/dataFlow/fixture/NumericCast.java new file mode 100644 index 000000000000..558b9c5b0bea --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NumericCast.java @@ -0,0 +1,22 @@ +class NumericCast { + void test(int i) { + if(i > 10 && i < 200) { + byte b = (byte) i; + if (b == 0) { + System.out.println("impossible"); + } + } + byte b = 100; + b+=100; + if (b == -56) { + System.out.println("always"); + } + } + + void testMask(long x) { + int bits = (int) ((x >> 16) & 0xFFFF); + if (bits >= 0) { + System.out.println("Always"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index 7fa1e7431047..bb426d808c6a 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -660,4 +660,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { public void testAlwaysTrueSwitchLabel() { doTest(); } public void testWideningToDouble() { doTest(); } public void testCompoundAssignment() { doTest(); } + public void testNumericCast() { doTest(); } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java index 460bfbc05da1..f32cc31e9274 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java @@ -17,7 +17,10 @@ package com.intellij.java.codeInspection.dataFlow.rangeSet; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType; +import com.intellij.psi.PsiPrimitiveType; import com.intellij.psi.PsiType; +import com.intellij.psi.util.TypeConversionUtil; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.util.HashMap; @@ -25,12 +28,27 @@ import java.util.Random; import java.util.function.Function; import java.util.function.LongBinaryOperator; import java.util.function.LongPredicate; +import java.util.function.LongUnaryOperator; import java.util.stream.Collectors; import static com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet.*; import static org.junit.Assert.*; public class LongRangeSetTest { + @NotNull + private static LongRangeSet fromTypeStrict(PsiType type) { + LongRangeSet range = fromType(type); + assertNotNull(range); + return range; + } + + @NotNull + private static LongRangeSet fromConstantStrict(Object constant) { + LongRangeSet range = fromConstant(constant); + assertNotNull(range); + return range; + } + @Test public void testToString() { assertEquals("{}", empty().toString()); @@ -44,12 +62,12 @@ public class LongRangeSetTest { public void testFromType() { assertNull(fromType(PsiType.FLOAT)); assertNull(fromType(PsiType.NULL)); - assertEquals("{-128..127}", fromType(PsiType.BYTE).toString()); - assertEquals("{0..65535}", fromType(PsiType.CHAR).toString()); - assertEquals("{-32768..32767}", fromType(PsiType.SHORT).toString()); - assertEquals("{Integer.MIN_VALUE..Integer.MAX_VALUE}", fromType(PsiType.INT).toString()); + assertEquals("{-128..127}", fromTypeStrict(PsiType.BYTE).toString()); + assertEquals("{0..65535}", fromTypeStrict(PsiType.CHAR).toString()); + assertEquals("{-32768..32767}", fromTypeStrict(PsiType.SHORT).toString()); + assertEquals("{Integer.MIN_VALUE..Integer.MAX_VALUE}", fromTypeStrict(PsiType.INT).toString()); assertEquals("{0..Integer.MAX_VALUE}", indexRange().toString()); - assertEquals("{Long.MIN_VALUE..Long.MAX_VALUE}", fromType(PsiType.LONG).toString()); + assertEquals("{Long.MIN_VALUE..Long.MAX_VALUE}", fromTypeStrict(PsiType.LONG).toString()); } @Test @@ -84,7 +102,7 @@ public class LongRangeSetTest { assertEquals("{Long.MIN_VALUE}", all().subtract(range(Long.MIN_VALUE + 1, Long.MAX_VALUE)).toString()); assertEquals("{Long.MAX_VALUE}", all().subtract(range(Long.MIN_VALUE, Long.MAX_VALUE - 1)).toString()); assertTrue(all().subtract(range(Long.MIN_VALUE, Long.MAX_VALUE)).isEmpty()); - assertEquals(indexRange(), fromType(PsiType.INT).subtract(range(Long.MIN_VALUE, (long)-1))); + assertEquals(indexRange(), fromTypeStrict(PsiType.INT).subtract(range(Long.MIN_VALUE, (long)-1))); assertTrue(all().subtract(all()).isEmpty()); } @@ -102,9 +120,9 @@ public class LongRangeSetTest { assertEquals("{0..2, 5..15, 19, 20}", range(0, 20).subtract(range(3, 18).subtract(range(5, 15))).toString()); - LongRangeSet first = fromType(PsiType.CHAR).without(45); + LongRangeSet first = fromTypeStrict(PsiType.CHAR).without(45); LongRangeSet second = - fromType(PsiType.CHAR).without(32).without(40).without(44).without(45).without(46).without(58).without(59).without(61); + fromTypeStrict(PsiType.CHAR).without(32).without(40).without(44).without(45).without(46).without(58).without(59).without(61); assertEquals("{0..44, 46..65535}", first.toString()); assertEquals("{0..31, 33..39, 41..43, 47..57, 60, 62..65535}", second.toString()); assertEquals("{32, 40, 44, 46, 58, 59, 61}", first.subtract(second).toString()); @@ -118,7 +136,7 @@ public class LongRangeSetTest { map.put(range(10, 10), "10-10"); map.put(range(10, 11), "10-11"); map.put(range(10, 12), "10-12"); - LongRangeSet longNotChar = fromType(PsiType.LONG).subtract(fromType(PsiType.CHAR)); + LongRangeSet longNotChar = fromTypeStrict(PsiType.LONG).subtract(fromTypeStrict(PsiType.CHAR)); map.put(longNotChar, "longNotChar"); assertEquals("empty", map.get(empty())); @@ -126,13 +144,13 @@ public class LongRangeSetTest { assertEquals("10-11", map.get(range(10, 11))); assertEquals("10-12", map.get(range(10, 12))); assertNull(map.get(range(11, 11))); - assertEquals("longNotChar", map.get(fromType(PsiType.LONG).subtract(fromType(PsiType.CHAR)))); + assertEquals("longNotChar", map.get(fromTypeStrict(PsiType.LONG).subtract(fromTypeStrict(PsiType.CHAR)))); } @Test public void testIntersects() { - assertFalse(empty().intersects(fromType(PsiType.LONG))); - assertTrue(point(Long.MIN_VALUE).intersects(fromType(PsiType.LONG))); + assertFalse(empty().intersects(fromTypeStrict(PsiType.LONG))); + assertTrue(point(Long.MIN_VALUE).intersects(fromTypeStrict(PsiType.LONG))); assertFalse(point(10).intersects(point(11))); assertTrue(point(10).intersects(point(10))); @@ -202,8 +220,8 @@ public class LongRangeSetTest { assertTrue(message, intersection.min() >= Math.max(left.min(), right.min())); assertTrue(message, intersection.max() <= Math.min(left.max(), right.max())); } - assertEquals(message, intersection, right.subtract(fromType(PsiType.LONG).subtract(left))); - assertEquals(message, intersection, left.subtract(fromType(PsiType.LONG).subtract(right))); + assertEquals(message, intersection, right.subtract(fromTypeStrict(PsiType.LONG).subtract(left))); + assertEquals(message, intersection, left.subtract(fromTypeStrict(PsiType.LONG).subtract(right))); intersection.stream().limit(1000).forEach(e -> { assertTrue(left.contains(e)); assertTrue(right.contains(e)); @@ -232,10 +250,10 @@ public class LongRangeSetTest { @Test public void testFromConstant() { - assertEquals("{0}", fromConstant(0).toString()); - assertEquals("{0}", fromConstant(0L).toString()); - assertEquals("{1}", fromConstant((byte)1).toString()); - assertEquals("{97}", fromConstant('a').toString()); + assertEquals("{0}", fromConstantStrict(0).toString()); + assertEquals("{0}", fromConstantStrict(0L).toString()); + assertEquals("{1}", fromConstantStrict((byte)1).toString()); + assertEquals("{97}", fromConstantStrict('a').toString()); assertNull(fromConstant(null)); assertNull(fromConstant(1.0)); } @@ -248,7 +266,7 @@ public class LongRangeSetTest { assertEquals(range(Long.MIN_VALUE, 200), range(100, 200).fromRelation(RelationType.LE)); assertEquals(range(100, 200), range(100, 200).fromRelation(RelationType.EQ)); assertNull(range(100, 200).fromRelation(RelationType.IS)); - assertEquals(fromType(PsiType.LONG), range(100, 200).fromRelation(RelationType.NE)); + assertEquals(fromTypeStrict(PsiType.LONG), range(100, 200).fromRelation(RelationType.NE)); assertEquals("{Long.MIN_VALUE..99, 101..Long.MAX_VALUE}", point(100).fromRelation(RelationType.NE).toString()); } @@ -291,6 +309,37 @@ public class LongRangeSetTest { assertEquals("{-1000..-701, -499..-101, 301..599, 801..900}", set.negate(false).toString()); } + @Test + public void testCastTo() { + PsiPrimitiveType[] types = {PsiType.BYTE, PsiType.SHORT, PsiType.CHAR, PsiType.INT, PsiType.LONG}; + for (PsiPrimitiveType type : types) { + assertTrue(empty().castTo(type).isEmpty()); + assertEquals(point(0), point(0).castTo(type)); + } + assertEquals(point(0x1234_5678_9ABC_DEF0L), point(0x1234_5678_9ABC_DEF0L).castTo(PsiType.LONG)); + assertEquals(point(0x9ABC_DEF0), point(0x1234_5678_9ABC_DEF0L).castTo(PsiType.INT)); + assertEquals(point(0xDEF0), point(0x1234_5678_9ABC_DEF0L).castTo(PsiType.CHAR)); + assertEquals(point(-8464), point(0x1234_5678_9ABC_DEF0L).castTo(PsiType.SHORT)); + assertEquals(point(-16), point(0x1234_5678_9ABC_DEF0L).castTo(PsiType.BYTE)); + LongRangeSet longSet = fromTypeStrict(PsiType.LONG); + assertNotNull(longSet); + LongRangeSet byteSet = fromTypeStrict(PsiType.BYTE); + assertNotNull(byteSet); + for (PsiPrimitiveType type : types) { + LongRangeSet set = fromTypeStrict(type); + assertNotNull(set); + assertEquals(set, set.castTo(type)); + assertEquals(set, longSet.castTo(type)); + assertEquals(type.equals(PsiType.CHAR) ? range(0, 127).unite(range(0xFF80, 0xFFFF)) : byteSet, byteSet.castTo(type)); + } + checkCast(range(-10, 1000), "{-128..127}", PsiType.BYTE); + checkCast(range(-10, 200), "{-128..-56, -10..127}", PsiType.BYTE); + checkCast(range(-1, 255), "{0..255, 65535}", PsiType.CHAR); + checkCast(range(0, 100000), "{-32768..32767}", PsiType.SHORT); + checkCast(range(0, 50000), "{-32768..-15536, 0..32767}", PsiType.SHORT); + assertEquals(fromTypeStrict(PsiType.INT), range(Long.MIN_VALUE, Integer.MAX_VALUE-1).castTo(PsiType.INT)); + } + @Test public void testBitwiseAnd() { assertTrue(empty().bitwiseAnd(all()).isEmpty()); @@ -371,10 +420,10 @@ public class LongRangeSetTest { assertEquals(empty(), empty().shiftRight(all(), true)); assertEquals(empty(), all().shiftRight(empty(), true)); assertEquals(all(), all().shiftRight(all(), true)); - assertEquals(fromType(PsiType.INT), all().shiftRight(point(32), true)); - assertEquals(fromType(PsiType.SHORT), fromType(PsiType.INT).shiftRight(point(16), false)); - assertEquals(fromType(PsiType.BYTE), fromType(PsiType.INT).shiftRight(point(24), false)); - assertEquals(range(-1, 0), fromType(PsiType.INT).shiftRight(point(31), false)); + assertEquals(fromTypeStrict(PsiType.INT), all().shiftRight(point(32), true)); + assertEquals(fromTypeStrict(PsiType.SHORT), fromTypeStrict(PsiType.INT).shiftRight(point(16), false)); + assertEquals(fromTypeStrict(PsiType.BYTE), fromTypeStrict(PsiType.INT).shiftRight(point(24), false)); + assertEquals(range(-1, 0), fromTypeStrict(PsiType.INT).shiftRight(point(31), false)); checkShr(range(-20, 20), point(31), false, "{-1, 0}"); checkShr(range(-20, 20), point(31), true, "{-1, 0}"); @@ -389,9 +438,9 @@ public class LongRangeSetTest { assertEquals(empty(), all().unsignedShiftRight(empty(), true)); assertEquals(all(), all().unsignedShiftRight(all(), true)); assertEquals(range(0, 4294967295L), all().unsignedShiftRight(point(32), true)); - assertEquals(fromType(PsiType.CHAR), fromType(PsiType.INT).unsignedShiftRight(point(16), false)); - assertEquals(range(0, 255), fromType(PsiType.INT).unsignedShiftRight(point(24), false)); - assertEquals(range(0, 1), fromType(PsiType.INT).unsignedShiftRight(point(31), false)); + assertEquals(fromTypeStrict(PsiType.CHAR), fromTypeStrict(PsiType.INT).unsignedShiftRight(point(16), false)); + assertEquals(range(0, 255), fromTypeStrict(PsiType.INT).unsignedShiftRight(point(24), false)); + assertEquals(range(0, 1), fromTypeStrict(PsiType.INT).unsignedShiftRight(point(31), false)); checkUShr(range(-20, 20), point(31), false, "{0, 1}"); checkUShr(range(-20, 20), point(31), true, "{0, 8589934591}"); @@ -495,5 +544,28 @@ public class LongRangeSetTest { } } + void checkCast(LongRangeSet operand, String expected, PsiPrimitiveType castType) { + LongRangeSet result = operand.castTo(castType); + assertEquals(expected, result.toString()); + checkUnOp(operand, result, + castType.equals(PsiType.CHAR) ? x -> (char)x : x -> ((Number)TypeConversionUtil.computeCastTo(x, castType)).longValue(), + expected, castType.getCanonicalText()); + } + + void checkUnOp(LongRangeSet operand, + LongRangeSet result, + LongUnaryOperator operator, + String expected, + String sign) { + assertEquals(expected, result.toString()); + String errors = operand.stream() + .filter(arg -> !result.contains(operator.applyAsLong(arg))) + .mapToObj(arg -> sign + " (" + arg + ") = " + operator.applyAsLong(arg)) + .collect(Collectors.joining("\n")); + if (!errors.isEmpty()) { + fail("Expected range " + expected + " is not satisfied:\n" + errors); + } + } + } \ No newline at end of file