[java-intentions] AddTypeArgumentsConditionalFix: report the branch

After changes in highlighting, it's not possible anymore to attach a fix to the range. In general, it's good, because the user may have no idea that the fix is available at a specific offset. So instead, we allow invoking the fix at the whole error range but indicate which branch will be updated.
Also: parentheses supported; fix all option added, minor touch-ups

GitOrigin-RevId: d756252cd1d3c061f52bdb70d62bed3bedcb69e3
This commit is contained in:
Tagir Valeev
2025-01-15 16:22:17 +01:00
committed by intellij-monorepo-bot
parent 19b4123fe6
commit 07960b2159
16 changed files with 68 additions and 59 deletions

View File

@@ -1,4 +1,6 @@
add.explicit.type.arguments=Add explicit type arguments
add.explicit.type.arguments.then=Add explicit type arguments to then-branch call
add.explicit.type.arguments.else=Add explicit type arguments to else-branch call
change.type.arguments=Change type arguments
change.type.arguments.to.0=Change type arguments to <{0}>

View File

@@ -5,27 +5,24 @@ import com.intellij.codeInsight.intention.CommonIntentionAction;
import com.intellij.codeInsight.intention.PriorityAction;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.modcommand.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.function.Consumer;
public class AddTypeArgumentsConditionalFix extends PsiUpdateModCommandAction<PsiMethodCallExpression> {
private static final Logger LOG = Logger.getInstance(AddTypeArgumentsConditionalFix.class);
private final @NotNull PsiSubstitutor mySubstitutor;
private final PsiSubstitutor mySubstitutor;
private final PsiMethod myMethod;
public AddTypeArgumentsConditionalFix(PsiSubstitutor substitutor, PsiMethodCallExpression expression, PsiMethod method) {
private AddTypeArgumentsConditionalFix(@NotNull PsiSubstitutor substitutor, @NotNull PsiMethodCallExpression expression) {
super(expression);
mySubstitutor = substitutor;
myMethod = method;
}
@Override
@@ -34,17 +31,27 @@ public class AddTypeArgumentsConditionalFix extends PsiUpdateModCommandAction<Ps
}
@Override
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiMethodCallExpression element) {
if (!mySubstitutor.isValid() || !myMethod.isValid()) return null;
return Presentation.of(getFamilyName()).withPriority(PriorityAction.Priority.HIGH);
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiMethodCallExpression call) {
if (!mySubstitutor.isValid()) return null;
String name = getFamilyName();
if (PsiUtil.skipParenthesizedExprUp(call.getParent()) instanceof PsiConditionalExpression conditional) {
if (PsiTreeUtil.isAncestor(conditional.getThenExpression(), call, false)) {
name = JavaAnalysisBundle.message("add.explicit.type.arguments.then");
} else if (PsiTreeUtil.isAncestor(conditional.getElseExpression(), call, false)) {
name = JavaAnalysisBundle.message("add.explicit.type.arguments.else");
}
}
return Presentation.of(name).withPriority(PriorityAction.Priority.HIGH)
.withFixAllOption(this);
}
@Override
protected void invoke(@NotNull ActionContext context, @NotNull PsiMethodCallExpression call, @NotNull ModPsiUpdater updater) {
final PsiTypeParameter[] typeParameters = myMethod.getTypeParameters();
PsiMethod method = call.resolveMethod();
if (method == null) return;
final PsiTypeParameter[] typeParameters = method.getTypeParameters();
final String typeArguments = "<" + StringUtil.join(typeParameters, parameter -> {
final PsiType substituteTypeParam = mySubstitutor.substitute(parameter);
LOG.assertTrue(substituteTypeParam != null);
final PsiType substituteTypeParam = Objects.requireNonNull(mySubstitutor.substitute(parameter));
return GenericsUtil.eliminateWildcards(substituteTypeParam).getCanonicalText();
}, ", ") + ">";
final PsiExpression expression = call.getMethodExpression().getQualifierExpression();
@@ -53,9 +60,9 @@ public class AddTypeArgumentsConditionalFix extends PsiUpdateModCommandAction<Ps
withTypeArgsText = expression.getText();
}
else {
if (isInStaticContext(call, null) || myMethod.hasModifierProperty(PsiModifier.STATIC)) {
final PsiClass aClass = myMethod.getContainingClass();
LOG.assertTrue(aClass != null);
if (isInStaticContext(call, null) || method.hasModifierProperty(PsiModifier.STATIC)) {
final PsiClass aClass = method.getContainingClass();
if (aClass == null) return;
withTypeArgsText = aClass.getQualifiedName();
}
else {
@@ -73,47 +80,47 @@ public class AddTypeArgumentsConditionalFix extends PsiUpdateModCommandAction<Ps
}
public static void register(@NotNull Consumer<? super CommonIntentionAction> highlightInfo, @Nullable PsiExpression expression, @NotNull PsiType lType) {
if (lType != PsiTypes.nullType() && expression instanceof PsiConditionalExpression) {
final PsiExpression thenExpression = ((PsiConditionalExpression)expression).getThenExpression();
final PsiExpression elseExpression = ((PsiConditionalExpression)expression).getElseExpression();
if (lType != PsiTypes.nullType() && expression instanceof PsiConditionalExpression conditional) {
PsiExpression thenExpression = PsiUtil.skipParenthesizedExprDown(conditional.getThenExpression());
PsiExpression elseExpression = PsiUtil.skipParenthesizedExprDown(conditional.getElseExpression());
if (thenExpression != null && elseExpression != null) {
final PsiType thenType = thenExpression.getType();
final PsiType elseType = elseExpression.getType();
PsiType thenType = thenExpression.getType();
PsiType elseType = elseExpression.getType();
if (thenType != null && elseType != null) {
final boolean thenAssignable = TypeConversionUtil.isAssignable(lType, thenType);
final boolean elseAssignable = TypeConversionUtil.isAssignable(lType, elseType);
if (!thenAssignable && thenExpression instanceof PsiMethodCallExpression) {
inferTypeArgs(highlightInfo, lType, thenExpression);
boolean thenAssignable = TypeConversionUtil.isAssignable(lType, thenType);
boolean elseAssignable = TypeConversionUtil.isAssignable(lType, elseType);
if (!thenAssignable && thenExpression instanceof PsiMethodCallExpression call) {
inferTypeArgs(highlightInfo, lType, call);
}
if (!elseAssignable && elseExpression instanceof PsiMethodCallExpression) {
inferTypeArgs(highlightInfo, lType, elseExpression);
if (!elseAssignable && elseExpression instanceof PsiMethodCallExpression call) {
inferTypeArgs(highlightInfo, lType, call);
}
}
}
}
}
private static void inferTypeArgs(Consumer<? super ModCommandAction> fixConsumer, PsiType lType, PsiExpression expression) {
final JavaResolveResult result = ((PsiMethodCallExpression)expression).resolveMethodGenerics();
final PsiMethod method = (PsiMethod)result.getElement();
private static void inferTypeArgs(@NotNull Consumer<? super ModCommandAction> fixConsumer,
@NotNull PsiType lType,
@NotNull PsiMethodCallExpression call) {
PsiMethod method = call.resolveMethod();
if (method != null) {
final PsiType returnType = method.getReturnType();
final PsiClass aClass = method.getContainingClass();
PsiType returnType = method.getReturnType();
PsiClass aClass = method.getContainingClass();
if (returnType != null && aClass != null && aClass.getQualifiedName() != null) {
final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(method.getProject());
final PsiDeclarationStatement variableDeclarationStatement =
javaPsiFacade.getElementFactory().createVariableDeclarationStatement("xxx", lType, expression, expression);
final PsiExpression initializer =
((PsiLocalVariable)variableDeclarationStatement.getDeclaredElements()[0]).getInitializer();
LOG.assertTrue(initializer != null);
JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(method.getProject());
PsiDeclarationStatement variableDeclarationStatement =
javaPsiFacade.getElementFactory().createVariableDeclarationStatement("xxx", lType, call, call);
PsiExpression initializer =
Objects.requireNonNull(((PsiLocalVariable)variableDeclarationStatement.getDeclaredElements()[0]).getInitializer());
final PsiSubstitutor substitutor = javaPsiFacade.getResolveHelper()
PsiSubstitutor substitutor = javaPsiFacade.getResolveHelper()
.inferTypeArguments(method.getTypeParameters(), method.getParameterList().getParameters(),
((PsiMethodCallExpression)expression).getArgumentList().getExpressions(), PsiSubstitutor.EMPTY,
call.getArgumentList().getExpressions(), PsiSubstitutor.EMPTY,
initializer, DefaultParameterTypeInferencePolicy.INSTANCE);
PsiType substitutedType = substitutor.substitute(returnType);
if (substitutedType != null && TypeConversionUtil.isAssignable(lType, substitutedType)) {
fixConsumer.accept(new AddTypeArgumentsConditionalFix(substitutor, (PsiMethodCallExpression)expression, method));
fixConsumer.accept(new AddTypeArgumentsConditionalFix(substitutor, call));
}
}
}

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to else-branch call" "true-preview"
import java.util.*;
class Test {

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {

View File

@@ -1,9 +1,9 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {
<T> List<T> f() { return new ArrayList<T>(); }
void someMethod(Test t, boolean b) {
List<String> s = b ? t.<String>f() : new ArrayList<String>();
List<String> s = b ? (t.<String>f()) : new ArrayList<String>();
}
}

View File

@@ -1,9 +1,9 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to else-branch call" "true-preview"
import java.util.*;
class Test {
static <T> List<T> f() { return new ArrayList<T>(); }
void someMethod(Test t, boolean b) {
List<String> s = b ? t.<String>f() : new ArrayList<String>();
List<String> s = b ? new ArrayList<String>() : t.<String>f();
}
}

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to else-branch call" "true-preview"
import java.util.*;
class Test {

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {

View File

@@ -1,9 +1,9 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {
<T> List<T> f() { return new ArrayList<T>(); }
void someMethod(Test t, boolean b) {
List<String> s = b ? t.f<caret>() : new ArrayList<String>();
List<String> s = b ? (t.f<caret>()) : new ArrayList<String>();
}
}

View File

@@ -1,9 +1,9 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to else-branch call" "true-preview"
import java.util.*;
class Test {
static <T> List<T> f() { return new ArrayList<T>(); }
void someMethod(Test t, boolean b) {
List<String> s = b ? t.f<caret>() : new ArrayList<String>();
List<String> s = b ? new ArrayList<String>() : t.f<caret>();
}
}

View File

@@ -1,4 +1,4 @@
// "Add explicit type arguments" "true-preview"
// "Add explicit type arguments to then-branch call" "true-preview"
import java.util.*;
class Test {