diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties index ee17bfbf1234..9b9787c0c640 100644 --- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties +++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties @@ -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 diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/UseHashCodeMethodInspection.java b/java/java-impl-inspections/src/com/intellij/codeInspection/UseHashCodeMethodInspection.java index e50dfbca176e..b2e779358046 100644 --- a/java/java-impl-inspections/src/com/intellij/codeInspection/UseHashCodeMethodInspection.java +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/UseHashCodeMethodInspection.java @@ -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); } } } \ No newline at end of file diff --git a/java/java-impl/src/inspectionDescriptions/UseHashCodeMethodInspection.html b/java/java-impl/src/inspectionDescriptions/UseHashCodeMethodInspection.html index ed32623af34c..830d942e8df1 100644 --- a/java/java-impl/src/inspectionDescriptions/UseHashCodeMethodInspection.html +++ b/java/java-impl/src/inspectionDescriptions/UseHashCodeMethodInspection.html @@ -1,9 +1,9 @@
-Informs you when bitwise operations can be replaced with the Long.hashCode method.
- It detects instances of the pattern (int)(x ^ (x >>> 32)) where x is a variable of type long.
- This improves readability of code.
+Informs you when bitwise operations can be replaced with the Long.hashCode() or Double.hashCode() method.
+ It detects instances of the pattern (int)(x ^ (x >>> 32)) where x is a variable of type long or
+ the result of the previous Double.doubleToLongBits() call. This replacement shortens the code improves its readability.
Example:
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterBoxed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterBoxed.java
index a698c23cc4f6..9e802d9bbfa7 100644
--- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterBoxed.java
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterBoxed.java
@@ -3,6 +3,7 @@ public class Test {
Long var = 1234567890123456789L;
public void testMethod() {
- int result = var.hashCode();
+ /*shift amount*/
+ int result = var.hashCode();
}
}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterBoxedExpr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterBoxedExpr.java
index ac3c928f64d9..25ca602d1028 100644
--- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterBoxedExpr.java
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterBoxedExpr.java
@@ -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();
}
}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterDouble.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterDouble.java
new file mode 100644
index 000000000000..baca2f6a4104
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterDouble.java
@@ -0,0 +1,6 @@
+// "Replace with 'Double.hashCode()'" "true-preview"
+public class Test {
+ void test(double d) {
+ System.out.println(Double.hashCode(d));
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterDoubleBoxed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterDoubleBoxed.java
new file mode 100644
index 000000000000..d60b0b805d99
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterDoubleBoxed.java
@@ -0,0 +1,7 @@
+// "Replace with 'Double.hashCode()'" "true-preview"
+public class Test {
+ void test(Double d) {
+ /*comment*/
+ System.out.println(d.hashCode());
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterNoDouble.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterNoDouble.java
new file mode 100644
index 000000000000..9909e1fab33a
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterNoDouble.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterNoDouble2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterNoDouble2.java
new file mode 100644
index 000000000000..c3ef6b005134
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterNoDouble2.java
@@ -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));
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterSimple.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterSimple.java
index 75a1bb8333a2..6b7c0c6ee887 100644
--- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterSimple.java
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/afterSimple.java
@@ -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);
}
}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/beforeDouble.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/beforeDouble.java
new file mode 100644
index 000000000000..fd4d664ddeef
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/useHashCode/beforeDouble.java
@@ -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