[java-intentions] Improve messages for add cast fixes

GitOrigin-RevId: b097e12c61cac86e789945a89316a7fef391c770
This commit is contained in:
Tagir Valeev
2022-08-02 11:54:56 +02:00
committed by intellij-monorepo-bot
parent 105b015e65
commit c7063c97ef
50 changed files with 132 additions and 82 deletions

View File

@@ -1,11 +1,13 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
import com.intellij.codeInsight.daemon.impl.quickfix.ReplaceExpressionAction;
import com.intellij.codeInsight.daemon.impl.quickfix.WrapExpressionFix;
import com.intellij.codeInsight.daemon.impl.quickfix.AddTypeCastFix;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.PsiTreeUtil;
@@ -30,12 +32,13 @@ import static com.intellij.util.ObjectUtils.tryCast;
*/
class AdaptExpressionTypeFixUtil {
private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance();
private AdaptExpressionTypeFixUtil() {}
static void registerPatchParametersFixes(@NotNull HighlightInfo info,
@NotNull PsiMethodCallExpression call,
@NotNull PsiMethod method,
@NotNull PsiType expectedTypeByParent) {
private AdaptExpressionTypeFixUtil() { }
private static void registerPatchParametersFixes(@NotNull HighlightInfo info,
@NotNull PsiMethodCallExpression call,
@NotNull PsiMethod method,
@NotNull PsiType expectedTypeByParent) {
JavaResolveResult result = call.resolveMethodGenerics();
if (!(result instanceof MethodCandidateInfo)) return;
PsiType methodType = method.getReturnType();
@@ -103,31 +106,59 @@ class AdaptExpressionTypeFixUtil {
registerExpectedTypeFixes(info, lambdaBody, expectedFnReturnType);
}
static void registerExpectedTypeFixes(@NotNull HighlightInfo info, @NotNull PsiExpression arg, @Nullable PsiType expectedType) {
/**
* Registers fixes (if any) that update code to match the expression type with the desired type.
*
* @param info error highlighting to attach fixes to
* @param expression expression whose type is incorrect
* @param expectedType desired expression type.
*/
static void registerExpectedTypeFixes(@NotNull HighlightInfo info, @NotNull PsiExpression expression, @Nullable PsiType expectedType) {
if (expectedType == null) return;
expectedType = GenericsUtil.getVariableTypeByExpressionType(expectedType);
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapWithAdapterFix(expectedType, arg));
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapWithOptionalFix(expectedType, arg));
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapExpressionFix(expectedType, arg));
PsiType argType = arg.getType();
if (arg instanceof PsiMethodCallExpression) {
JavaResolveResult result = ((PsiMethodCallExpression)arg).resolveMethodGenerics();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapWithAdapterFix(expectedType, expression));
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapWithOptionalFix(expectedType, expression));
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapExpressionFix(expectedType, expression));
PsiType argType = expression.getType();
if (expression instanceof PsiMethodCallExpression) {
JavaResolveResult result = ((PsiMethodCallExpression)expression).resolveMethodGenerics();
if (result instanceof MethodCandidateInfo) {
argType = ((MethodCandidateInfo)result).getSubstitutor(false).substitute(argType);
}
}
PsiType castToType = suggestCastTo(expectedType, argType);
if (castToType != null) {
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddTypeCastFix(castToType, arg));
TextRange range = expression.getTextRange();
String role = info.startOffset == range.getStartOffset() && info.endOffset == range.getEndOffset() ? null : getRole(expression);
QuickFixAction.registerQuickFixAction(info, new AddTypeCastFix(castToType, expression, role));
}
if (arg instanceof PsiMethodCallExpression) {
PsiMethod argMethod = ((PsiMethodCallExpression)arg).resolveMethod();
if (expression instanceof PsiMethodCallExpression) {
PsiMethod argMethod = ((PsiMethodCallExpression)expression).resolveMethod();
if (argMethod != null) {
registerPatchParametersFixes(info, (PsiMethodCallExpression)arg, argMethod, expectedType);
registerPatchParametersFixes(info, (PsiMethodCallExpression)expression, argMethod, expectedType);
}
}
}
private static String getRole(@NotNull PsiExpression expression) {
PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent());
if (parent instanceof PsiExpressionList) {
int count = ((PsiExpressionList)parent).getExpressionCount();
if (count > 1) {
long index = StreamEx.of(((PsiExpressionList)parent).getExpressions())
.map(PsiUtil::skipParenthesizedExprDown).indexOf(expression).orElse(-1);
if (index != -1) {
return QuickFixBundle.message("fix.expression.role.nth.argument", index + 1);
}
}
return QuickFixBundle.message("fix.expression.role.argument");
}
if (parent instanceof PsiLambdaExpression) {
return QuickFixBundle.message("fix.expression.role.lambda.return");
}
return null;
}
private static @Nullable Map.Entry<PsiTypeParameter, PsiType> findDesiredSubstitution(@Nullable PsiType expected,
@Nullable PsiType actual,
@Nullable PsiType methodType) {

View File

@@ -18,27 +18,32 @@ import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import java.util.List;
import java.util.Objects;
public class AddTypeCastFix extends LocalQuickFixAndIntentionActionOnPsiElement
public class AddTypeCastFix extends LocalQuickFixAndIntentionActionOnPsiElement
implements HighPriorityAction, IntentionActionWithFixAllOption {
@SafeFieldForPreview
private final PsiType myType;
private final @IntentionName String myName;
public AddTypeCastFix(@NotNull PsiType type, @NotNull PsiExpression expression) {
this(type, expression, "add.typecast.text");
this(type, expression, null);
}
public AddTypeCastFix(@NotNull PsiType type, @NotNull PsiExpression expression, @PropertyKey(resourceBundle = QuickFixBundle.BUNDLE) String messageKey) {
public AddTypeCastFix(@NotNull PsiType type, @NotNull PsiExpression expression, @Nls @Nullable String role) {
super(expression);
boolean literalConversion = tryConvertNumericLiteral(expression, type) != null;
if (role == null) {
role = QuickFixBundle.message(literalConversion ? "fix.expression.role.literal" : "fix.expression.role.expression");
}
myType = type;
myName = QuickFixBundle.message(messageKey, type.isValid() ? type.getCanonicalText() : "");
myName = QuickFixBundle.message(literalConversion ? "add.typecast.convert.text" : "add.typecast.cast.text",
type.isValid() ? type.getCanonicalText() : "", role);
}
@Override
@@ -76,7 +81,14 @@ public class AddTypeCastFix extends LocalQuickFixAndIntentionActionOnPsiElement
public static void addTypeCast(Project project, PsiExpression originalExpression, PsiType type) {
PsiExpression typeCast = createCastExpression(originalExpression, project, type);
originalExpression.replace(typeCast);
originalExpression.replace(Objects.requireNonNull(typeCast));
}
private static String tryConvertNumericLiteral(PsiElement expr, @NotNull PsiType type) {
if (expr instanceof PsiLiteralExpression) {
return PsiLiteralUtil.tryConvertNumericLiteral((PsiLiteralExpression)expr, type);
}
return null;
}
static PsiExpression createCastExpression(PsiExpression original, Project project, PsiType type) {
@@ -86,11 +98,9 @@ public class AddTypeCastFix extends LocalQuickFixAndIntentionActionOnPsiElement
if (type.equals(PsiType.NULL)) return null;
PsiElementFactory factory = JavaPsiFacade.getElementFactory(original.getProject());
if (expression instanceof PsiLiteralExpression) {
String newLiteral = PsiLiteralUtil.tryConvertNumericLiteral((PsiLiteralExpression)expression, type);
if (newLiteral != null) {
return factory.createExpressionFromText(newLiteral, null);
}
String newLiteral = tryConvertNumericLiteral(expression, type);
if (newLiteral != null) {
return factory.createExpressionFromText(newLiteral, null);
}
if (type instanceof PsiEllipsisType) type = ((PsiEllipsisType)type).toArrayType();
String text = "(" + type.getCanonicalText(false) + ")value";
@@ -159,7 +169,7 @@ public class AddTypeCastFix extends LocalQuickFixAndIntentionActionOnPsiElement
else if (psiClass.findFieldByName(referenceName, true) == null) {
continue;
}
registrar.register(fixRange, new AddTypeCastFix(conjunct, qualifier, "add.qualifier.typecast.text"), null);
registrar.register(fixRange, new AddTypeCastFix(conjunct, qualifier, QuickFixBundle.message("fix.expression.role.qualifier")), null);
}
}
}

View File

@@ -17,9 +17,10 @@ public final class CastMethodArgumentFix extends MethodArgumentFix implements Hi
private CastMethodArgumentFix(PsiExpressionList list, int i, PsiType toType, final ArgumentFixerActionFactory factory) {
super(list, i, toType, factory);
myText = list.getExpressionCount() == 1
? QuickFixBundle.message("cast.single.parameter.text", myToType.getPresentableText())
: QuickFixBundle.message("cast.parameter.text", myIndex + 1, myToType.getPresentableText());
String role = list.getExpressionCount() == 1
? QuickFixBundle.message("fix.expression.role.argument")
: QuickFixBundle.message("fix.expression.role.nth.argument", myIndex + 1);
myText = QuickFixBundle.message("add.typecast.cast.text", myToType.getPresentableText(), role);
}
@Override

View File

@@ -30,7 +30,18 @@ add.runtime.exception.to.throws.text=Add runtime exception(s) to method signatur
add.runtime.exception.to.throws.family=Add Runtime Exception to Method Signature
add.typecast.family=Add type cast
add.typecast.text=Cast to ''{0}''
add.qualifier.typecast.text=Cast qualifier to ''{0}''
# {1} = one of fix.expression.role.xyz values
add.typecast.cast.text=Cast {1} to ''{0}''
# {1} = one of fix.expression.role.xyz values
add.typecast.convert.text=Convert {1} to ''{0}''
fix.expression.role.qualifier=qualifier
fix.expression.role.literal=literal
fix.expression.role.expression=expression
fix.expression.role.argument=argument
fix.expression.role.nth.argument={0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} argument
fix.expression.role.lambda.return=lambda return
add.docTag.to.custom.tags=Add ''@{0}'' to custom tags
add.docTag.to.custom.tags.preview=Adds this tag to a list of custom tags that will be ignored by this inspection.
fix.javadoc.family=Fix Javadoc
@@ -39,9 +50,6 @@ adjust.package.text=Set package name to ''{0}''
bring.variable.to.scope.family=Bring variable to scope
bring.variable.to.scope.text=Bring ''{0}'' into scope
cast.parameter.text=Cast {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} argument to ''{1}''
cast.single.parameter.text=Cast argument to ''{0}''
add.type.arguments.text=Add explicit type arguments to {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} argument
add.type.arguments.single.argument.text=Add explicit type arguments

View File

@@ -1,4 +1,4 @@
// "Cast to 'char'" "true-preview"
// "Cast expression to 'char'" "true-preview"
class a {
void f(int i) {
<caret>char c = (char) i;

View File

@@ -1,4 +1,4 @@
// "Cast to 'A.Iterator<java.lang.String>'" "true-preview"
// "Cast expression to 'A.Iterator<java.lang.String>'" "true-preview"
class A {
interface Iterator<T> {
}

View File

@@ -1,4 +1,4 @@
// "Cast to 'b'" "true-preview"
// "Cast expression to 'b'" "true-preview"
class a {
void f(a a) {
<caret>b b = (b) a;

View File

@@ -1,4 +1,4 @@
// "Cast to 'int'" "true-preview"
// "Cast expression to 'int'" "true-preview"
class a {
void f() {
int[] ii = {<caret>(int) 1.3};

View File

@@ -1,4 +1,4 @@
// "Cast to 'int'" "true-preview"
// "Cast expression to 'int'" "true-preview"
class a {
void f() {
int i;

View File

@@ -1,4 +1,4 @@
// "Cast to 'float'" "true-preview"
// "Cast expression to 'float'" "true-preview"
class a {
float f() {
double d = 4;

View File

@@ -1,4 +1,4 @@
// "Cast to 'int'" "true-preview"
// "Cast expression to 'int'" "true-preview"
class a {
void f() {
double d = 4;

View File

@@ -1,4 +1,4 @@
// "Cast to 'char'" "true-preview"
// "Cast expression to 'char'" "true-preview"
class a {
void f() {
double d = 4;

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.lang.String'" "true-preview"
// "Cast expression to 'java.lang.String'" "true-preview"
class a {
void f() {
Object y = null;

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.lang.Integer'" "true-preview"
// "Cast expression to 'java.lang.Integer'" "true-preview"
class A {
{
Number n = 0;

View File

@@ -1,4 +1,4 @@
// "Cast to 'int'" "true-preview"
// "Cast expression to 'int'" "true-preview"
import java.util.*;
import java.util.function.*;

View File

@@ -1,4 +1,4 @@
// "Cast to 'B'" "true-preview"
// "Cast expression to 'B'" "true-preview"
import java.util.*;
class A { }

View File

@@ -1,4 +1,4 @@
// "Cast to 'B'" "true-preview"
// "Cast expression to 'B'" "true-preview"
class A {
void f(B b) {
B s = b == null ? <caret>(B) this : b;

View File

@@ -1,4 +1,4 @@
// "Cast to 'B'" "true-preview"
// "Cast expression to 'B'" "true-preview"
class A {
void f(B b) {
B s =b == null ? null : <caret>(B) this;

View File

@@ -1,4 +1,4 @@
// "Cast to 'B'" "true-preview"
// "Cast expression to 'B'" "true-preview"
class A {
void f(B b) {
B s = <caret>(B) (b == null ? this : this);

View File

@@ -1,4 +1,4 @@
// "Cast to 'int[]'" "true-preview"
// "Cast expression to 'int[]'" "true-preview"
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.util.List<java.lang.Integer>'" "true-preview"
// "Cast expression to 'java.util.List<java.lang.Integer>'" "true-preview"
import java.lang.annotation.*;
import java.util.*;
import static java.lang.annotation.ElementType.*;

View File

@@ -1,4 +1,4 @@
// "Cast to 'char'" "true-preview"
// "Cast expression to 'char'" "true-preview"
class a {
void f(int i) {
<caret>char c = i;

View File

@@ -1,4 +1,4 @@
// "Cast to 'A.Iterator<java.lang.String>'" "true-preview"
// "Cast expression to 'A.Iterator<java.lang.String>'" "true-preview"
class A {
interface Iterator<T> {
}

View File

@@ -1,4 +1,4 @@
// "Cast to 'b'" "true-preview"
// "Cast expression to 'b'" "true-preview"
class a {
void f(a a) {
<caret>b b = a;

View File

@@ -1,4 +1,4 @@
// "Cast to 'int'" "true-preview"
// "Cast expression to 'int'" "true-preview"
class a {
void f() {
int[] ii = { <caret>1.3 };

View File

@@ -1,4 +1,4 @@
// "Cast to 'int'" "true-preview"
// "Cast expression to 'int'" "true-preview"
class a {
void f() {
int i;

View File

@@ -1,4 +1,4 @@
// "Cast to 'float'" "true-preview"
// "Cast expression to 'float'" "true-preview"
class a {
float f() {
double d = 4;

View File

@@ -1,4 +1,4 @@
// "Cast to 'int'" "true-preview"
// "Cast expression to 'int'" "true-preview"
class a {
void f() {
double d = 4;

View File

@@ -1,4 +1,4 @@
// "Cast to 'char'" "true-preview"
// "Cast expression to 'char'" "true-preview"
class a {
void f() {
double d = 4;

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.lang.String'" "false"
// "Cast expression to 'java.lang.String'" "false"
class a {
void f() {
String s = <caret>1;

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.lang.String'" "true-preview"
// "Cast expression to 'java.lang.String'" "true-preview"
class a {
void f() {
Object y = null;

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.lang.Noolean'" "false"
// "Cast expression to 'java.lang.Noolean'" "false"
class X {
boolean foo() {return true;}
static void bar() {

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.lang.Integer'" "true-preview"
// "Cast expression to 'java.lang.Integer'" "true-preview"
class A {
{
Number n = 0;

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.util.List<java.lang.String>'" "false"
// "Cast expression to 'java.util.List<java.lang.String>'" "false"
import java.util.*;
class a {

View File

@@ -1,4 +1,4 @@
// "Cast to 'int'" "true-preview"
// "Cast expression to 'int'" "true-preview"
import java.util.*;
import java.util.function.*;

View File

@@ -1,4 +1,4 @@
// "Cast to '?'" "false"
// "Cast expression to '?'" "false"
class Scope<T> {
T val;
void f(Scope<?> s) {

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.lang.Object'" "false"
// "Cast expression to 'java.lang.Object'" "false"
class Scope<T> {
T val;
void f() {

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.lang.Object'" "false"
// "Cast expression to 'java.lang.Object'" "false"
class A {
void f() {
var y = new Object();

View File

@@ -1,4 +1,4 @@
// "Cast to 'B'" "true-preview"
// "Cast expression to 'B'" "true-preview"
import java.util.*;
class A { }

View File

@@ -1,4 +1,4 @@
// "Cast to 'B'" "false"
// "Cast expression to 'B'" "false"
import java.util.*;
class A { }

View File

@@ -1,4 +1,4 @@
// "Cast to 'B'" "true-preview"
// "Cast expression to 'B'" "true-preview"
class A {
void f(B b) {
B s = b == null ? <caret>this : b;

View File

@@ -1,4 +1,4 @@
// "Cast to 'B'" "true-preview"
// "Cast expression to 'B'" "true-preview"
class A {
void f(B b) {
B s =b == null ? null : <caret>this;

View File

@@ -1,4 +1,4 @@
// "Cast to 'B'" "true-preview"
// "Cast expression to 'B'" "true-preview"
class A {
void f(B b) {
B s = <caret>b == null ? this : this;

View File

@@ -1,4 +1,4 @@
// "Cast to 'int[]'" "true-preview"
// "Cast expression to 'int[]'" "true-preview"
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

View File

@@ -1,4 +1,4 @@
// "Cast to 'java.util.List<java.lang.Integer>'" "true-preview"
// "Cast expression to 'java.util.List<java.lang.Integer>'" "true-preview"
import java.lang.annotation.*;
import java.util.*;
import static java.lang.annotation.ElementType.*;

View File

@@ -1,4 +1,4 @@
// "Cast to 'void'" "false"
// "Cast expression to 'void'" "false"
class Test {
void test() {
Runnable r = () -> (System.out.<caret>println());

View File

@@ -1,4 +1,4 @@
// "Cast to 'long'" "true-preview"
// "Cast lambda return to 'long'" "true-preview"
import java.util.function.*;
class Demo {

View File

@@ -1,4 +1,4 @@
// "Cast to 'long'" "true-preview"
// "Convert argument to 'long'" "true-preview"
import java.util.*;
class Demo {

View File

@@ -1,4 +1,4 @@
// "Cast to 'long'" "true-preview"
// "Cast lambda return to 'long'" "true-preview"
import java.util.function.*;
class Demo {

View File

@@ -1,4 +1,4 @@
// "Cast to 'long'" "true-preview"
// "Convert argument to 'long'" "true-preview"
import java.util.*;
class Demo {