[java-inspections] UseHashCodeMethodInspection: support double type

Also: fix comment processing
Improvement of IDEA-338114

GitOrigin-RevId: 87d057ccbea4262c40e2717c2ea3c004ac9865b0
This commit is contained in:
Tagir Valeev
2023-12-11 17:55:54 +01:00
committed by intellij-monorepo-bot
parent e356e99136
commit 6e1d684df0
14 changed files with 136 additions and 29 deletions

View File

@@ -662,5 +662,4 @@ error.unnamed.variable.without.initializer=Unnamed variable declaration must hav
intention.family.name.move.members.into.class=Move members into class
chooser.popup.title.select.class.to.move.members.to=Select Target Class
intention.family.name.move.members.to=Move members to {0}
inspection.message.can.be.replaced.with.long.hashcode=Can be replaced with 'Long.hashCode()'
inspection.name.can.be.replaced.with.long.hashcode='Long.hashCode()' can be used
inspection.name.can.be.replaced.with.long.hashcode=Standard 'hashCode()' method can be used

View File

@@ -7,10 +7,11 @@ import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiPrecedenceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.EquivalenceChecker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -18,6 +19,9 @@ import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public class UseHashCodeMethodInspection extends AbstractBaseJavaLocalInspectionTool {
private static final CallMatcher DOUBLE_TO_LONG_BITS =
CallMatcher.staticCall("java.lang.Double", "doubleToLongBits").parameterTypes("double");
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
@@ -25,18 +29,46 @@ public class UseHashCodeMethodInspection extends AbstractBaseJavaLocalInspection
return new JavaElementVisitor() {
@Override
public void visitTypeCastExpression(@NotNull PsiTypeCastExpression expression) {
if (getHashCodeOperand(expression) != null) {
holder.registerProblem(expression, JavaAnalysisBundle.message("inspection.message.can.be.replaced.with.long.hashcode"),
new ReplaceWithLongHashCodeFix());
HashCodeModel model = getHashCodeModel(expression);
if (model != null) {
PsiClass containingClass = ClassUtils.getContainingClass(expression);
if (containingClass != null && containingClass.getQualifiedName() != null &&
containingClass.getQualifiedName().startsWith("java.lang.")) {
// Avoid suggesting inside JDK sources
return;
}
holder.registerProblem(expression,
JavaAnalysisBundle.message("inspection.can.be.replaced.with.message", model.type + ".hashCode()"),
new ReplaceWithLongHashCodeFix(model.type));
}
}
};
}
private static @Nullable PsiExpression getHashCodeOperand(PsiTypeCastExpression typeCastExpression) {
if (typeCastExpression == null) return null;
if (!PsiTypes.intType().equals(typeCastExpression.getType())) return null;
PsiExpression operand = PsiUtil.skipParenthesizedExprDown(typeCastExpression.getOperand());
record HashCodeModel(@NotNull PsiExpression completeExpression,
@NotNull PsiExpression argument,
@Nullable PsiLocalVariable intermediateVariable,
@NotNull String type) {
@NotNull HashCodeModel tryReplaceDouble() {
PsiLocalVariable local = ExpressionUtils.resolveLocalVariable(argument);
if (local == null) return this;
if (!(PsiUtil.skipParenthesizedExprDown(local.getInitializer()) instanceof PsiMethodCallExpression call)) return this;
if (!DOUBLE_TO_LONG_BITS.matches(call)) return this;
if (!(local.getParent() instanceof PsiDeclarationStatement decl) || decl.getDeclaredElements().length != 1) return this;
PsiElement nextDeclaration = PsiTreeUtil.skipWhitespacesAndCommentsForward(decl);
if (!PsiTreeUtil.isAncestor(nextDeclaration, completeExpression, true)) return this;
if (ContainerUtil.exists(VariableAccessUtils.getVariableReferences(local, PsiUtil.getVariableCodeBlock(local, null)),
ref -> !PsiTreeUtil.isAncestor(completeExpression, ref, true))) {
return this;
}
return new HashCodeModel(completeExpression, call.getArgumentList().getExpressions()[0], local, "Double");
}
}
private static @Nullable HashCodeModel getHashCodeModel(PsiTypeCastExpression cast) {
if (cast == null) return null;
if (!PsiTypes.intType().equals(cast.getType())) return null;
PsiExpression operand = PsiUtil.skipParenthesizedExprDown(cast.getOperand());
if (!(operand instanceof PsiBinaryExpression binaryExpression)) return null;
PsiJavaToken operationSign = binaryExpression.getOperationSign();
@@ -48,8 +80,8 @@ public class UseHashCodeMethodInspection extends AbstractBaseJavaLocalInspection
if (leftOperand == null || rightOperand == null) return null;
if (!PsiTypes.longType().equals(PsiPrimitiveType.getOptionallyUnboxedType(leftOperand.getType()))) return null;
if (isXorShift(leftOperand, rightOperand)) return leftOperand;
if (isXorShift(rightOperand, leftOperand)) return rightOperand;
if (isXorShift(leftOperand, rightOperand)) return new HashCodeModel(cast, leftOperand, null, "Long").tryReplaceDouble();
if (isXorShift(rightOperand, leftOperand)) return new HashCodeModel(cast, rightOperand, null, "Long").tryReplaceDouble();
return null;
}
@@ -67,25 +99,39 @@ public class UseHashCodeMethodInspection extends AbstractBaseJavaLocalInspection
}
public static class ReplaceWithLongHashCodeFix extends PsiUpdateModCommandQuickFix {
private final String myType;
public ReplaceWithLongHashCodeFix(String type) {
myType = type;
}
@Override
public @NotNull String getName() {
return CommonQuickFixBundle.message("fix.replace.with.x", myType+".hashCode()");
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return CommonQuickFixBundle.message("fix.replace.with.x", "Long.hashCode()");
return CommonQuickFixBundle.message("fix.replace.with.x", "hashCode()");
}
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement startElement, @NotNull ModPsiUpdater updater) {
PsiTypeCastExpression element = (PsiTypeCastExpression)startElement;
PsiExpression operand = getHashCodeOperand(element);
if (operand != null) {
PsiType type = operand.getType();
CommentTracker ct = new CommentTracker();
String call = PsiTypes.longType().equals(type)
? "Long.hashCode(" + ct.text(operand) + ")"
: ct.text(operand, PsiPrecedenceUtil.METHOD_CALL_PRECEDENCE) + ".hashCode()";
ct.replace(element, call);
HashCodeModel model = getHashCodeModel(element);
if (model == null) return;
PsiExpression argument = model.argument();
PsiType type = argument.getType();
CommentTracker ct = new CommentTracker();
String call = type instanceof PsiPrimitiveType
? "java.lang." + model.type() + ".hashCode(" + ct.text(argument) + ")"
: ct.text(argument, PsiPrecedenceUtil.METHOD_CALL_PRECEDENCE) + ".hashCode()";
if (model.intermediateVariable() != null) {
ct.delete(model.intermediateVariable());
}
ct.replaceAndRestoreComments(element, call);
}
}
}

View File

@@ -1,9 +1,9 @@
<html>
<body>
<p>
Informs you when bitwise operations can be replaced with the <code>Long.hashCode</code> method.
It detects instances of the pattern <code>(int)(x ^ (x >>> 32))</code> where <code>x</code> is a variable of type <code>long</code>.
This improves readability of code.
Informs you when bitwise operations can be replaced with the <code>Long.hashCode()</code> or <code>Double.hashCode()</code> method.
It detects instances of the pattern <code>(int)(x ^ (x >>> 32))</code> where <code>x</code> is a variable of type <code>long</code> or
the result of the previous <code>Double.doubleToLongBits()</code> call. This replacement shortens the code improves its readability.
</p>
<p>
Example:

View File

@@ -3,6 +3,7 @@ public class Test {
Long var = 1234567890123456789L;
public void testMethod() {
int result = var.hashCode();
/*shift amount*/
int result = var.hashCode();
}
}

View File

@@ -4,6 +4,7 @@ public class Test {
Long var1 = 1234567890123456784L;
public void testMethod(boolean f) {
int result = (f ? var : var1).hashCode();
/*shift amount*/
int result = (f ? var : var1).hashCode();
}
}

View File

@@ -0,0 +1,6 @@
// "Replace with 'Double.hashCode()'" "true-preview"
public class Test {
void test(double d) {
System.out.println(Double.hashCode(d));
}
}

View File

@@ -0,0 +1,7 @@
// "Replace with 'Double.hashCode()'" "true-preview"
public class Test {
void test(Double d) {
/*comment*/
System.out.println(d.hashCode());
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with 'Long.hashCode()'" "true-preview"
public class Test {
void test(double d) {
long l = Double.doubleToLongBits(d);
System.out.println(Long.hashCode(l));
System.out.println(l);
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with 'Long.hashCode()'" "true-preview"
public class Test {
void test(double d) {
long l = Double.doubleToLongBits(d);
d++;
System.out.println(Long.hashCode(l));
}
}

View File

@@ -3,6 +3,7 @@ public class Test {
long var = 1234567890123456789L;
public void testMethod() {
int result = Long.hashCode(var);
/*shift amount*/
int result = Long.hashCode(var);
}
}

View File

@@ -0,0 +1,7 @@
// "Replace with 'Double.hashCode()'" "true-preview"
public class Test {
void test(double d) {
long l = Double.doubleToLongBits(d);
System.out.println((int) (l<caret> ^ (l >>> 32)));
}
}

View File

@@ -0,0 +1,7 @@
// "Replace with 'Double.hashCode()'" "true-preview"
public class Test {
void test(Double d) {
long l = Double.doubleToLongBits(/*comment*/d);
System.out.println((int) (l ^ <caret>(l >>> 32)));
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with 'Long.hashCode()'" "true-preview"
public class Test {
void test(double d) {
long l = Double.doubleToLongBits(d);
System.out.println((int) (l ^ (l<caret> >>> 32)));
System.out.println(l);
}
}

View File

@@ -0,0 +1,8 @@
// "Replace with 'Long.hashCode()'" "true-preview"
public class Test {
void test(double d) {
long l = Double.doubleToLongBits(d);
d++;
System.out.println((int) (l<caret> ^ (l >>> 32)));
}
}