[java-intentions] AddVariableInitializerFix: suggest more suitable initial values instead of null for some types

Fixes IDEA-344453 Intellij should not try to initialize an Optional var with null

GitOrigin-RevId: 8097988bf1335a282138e8d09e350c3a5f65204d
This commit is contained in:
Tagir Valeev
2024-02-01 14:10:37 +01:00
committed by intellij-monorepo-bot
parent 48cb7b5154
commit 5bd02b55c3
31 changed files with 183 additions and 70 deletions

View File

@@ -58,7 +58,7 @@ class MethodDuplicatesMatchProvider implements MatchProvider {
}
}
else {
methodCallExpression.getArgumentList().add(factory.createExpressionFromText(PsiTypesUtil.getDefaultValueOfType(parameter.getType()), parameter));
methodCallExpression.getArgumentList().add(factory.createExpressionFromText(PsiTypesUtil.getDefaultValueOfType(parameter.getType(), true), parameter));
}
}
if (needQualifier || needStaticQualifier || nameConflicts) {

View File

@@ -90,7 +90,7 @@ public class AddReturnFix extends PsiUpdateModCommandAction<PsiParameterListOwne
return conversion;
}
}
return PsiTypesUtil.getDefaultValueOfType(type);
return PsiTypesUtil.getDefaultValueOfType(type, true);
}
@NonNls

View File

@@ -5,10 +5,12 @@ import com.intellij.codeInsight.lookup.ExpressionLookupItem;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.template.PsiElementResult;
import com.intellij.codeInsight.template.impl.ConstantNode;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.java.JavaBundle;
import com.intellij.modcommand.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
@@ -54,18 +56,19 @@ public class AddVariableInitializerFix extends PsiUpdateModCommandAction<PsiVari
static LookupElement @NotNull [] suggestInitializer(final PsiVariable variable) {
PsiType type = variable.getType();
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(variable.getProject());
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(variable.getProject());
final List<LookupElement> result = new SmartList<>();
final String defaultValue = PsiTypesUtil.getDefaultValueOfType(type);
final ExpressionLookupItem defaultExpression = new ExpressionLookupItem(elementFactory.createExpressionFromText(defaultValue, variable));
result.add(defaultExpression);
if (type instanceof PsiClassType) {
if (type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
result.add(new ExpressionLookupItem(elementFactory.createExpressionFromText("\"\"", variable)));
}
final PsiClass aClass = PsiTypesUtil.getPsiClass(type);
if (aClass != null && !aClass.hasModifierProperty(PsiModifier.ABSTRACT) && PsiUtil.hasDefaultConstructor(aClass)) {
List<LookupElement> result = new SmartList<>();
String defaultValue = PsiTypesUtil.getDefaultValueOfType(type);
String customDefaultValue = PsiTypesUtil.getDefaultValueOfType(type, true);
if (!customDefaultValue.equals(defaultValue)) {
PsiExpression customDef = elementFactory.createExpressionFromText(customDefaultValue, variable);
result.add(new ExpressionLookupItem((PsiExpression)JavaCodeStyleManager.getInstance(variable.getProject()).shortenClassReferences(customDef)));
}
result.add(new ExpressionLookupItem(elementFactory.createExpressionFromText(defaultValue, variable)));
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (aClass != null) {
if (!aClass.hasModifierProperty(PsiModifier.ABSTRACT) && PsiUtil.hasDefaultConstructor(aClass)) {
String typeText = type.getCanonicalText(false);
if (aClass.getTypeParameters().length > 0 && PsiUtil.isLanguageLevel7OrHigher(variable)) {
if (!PsiDiamondTypeImpl.haveConstructorsGenericsParameters(aClass)) {

View File

@@ -75,42 +75,84 @@ public final class PsiTypesUtil {
}
}
/**
* @param type type to get default value for (null, 0, or false)
* @return a string representing an expression for default value of a given type
*/
@NotNull
public static String getDefaultValueOfType(PsiType type) {
public static String getDefaultValueOfType(@Nullable PsiType type) {
return getDefaultValueOfType(type, false);
}
/**
* @param type type to return default value for
* @param customDefaultValues if true, non-null values for object types could be returned that represent an absent value
* for a specific type (e.g., empty string, empty list, etc.)
* @return a string representing an expression for default value of a given type
*/
@NotNull
public static String getDefaultValueOfType(PsiType type, boolean customDefaultValues) {
if (type instanceof PsiArrayType) {
int count = type.getArrayDimensions() - 1;
PsiType componentType = type.getDeepComponentType();
if (componentType instanceof PsiClassType) {
final PsiClassType classType = (PsiClassType)componentType;
if (classType.resolve() instanceof PsiTypeParameter) {
return PsiKeyword.NULL;
}
}
PsiType erasedComponentType = TypeConversionUtil.erasure(componentType);
StringBuilder buffer = new StringBuilder();
buffer.append(PsiKeyword.NEW);
buffer.append(" ");
buffer.append(erasedComponentType.getCanonicalText());
buffer.append("[0]");
for (int i = 0; i < count; i++) {
buffer.append("[]");
}
return buffer.toString();
}
public static String getDefaultValueOfType(@Nullable PsiType type, boolean customDefaultValues) {
if (type instanceof PsiPrimitiveType) {
return PsiTypes.booleanType().equals(type) ? PsiKeyword.FALSE : "0";
}
if (customDefaultValues) {
PsiType rawType = type instanceof PsiClassType ? ((PsiClassType)type).rawType() : null;
if (rawType != null && rawType.equalsToText(CommonClassNames.JAVA_UTIL_OPTIONAL)) {
return CommonClassNames.JAVA_UTIL_OPTIONAL + ".empty()";
if (type instanceof PsiArrayType) {
int count = type.getArrayDimensions() - 1;
PsiType componentType = type.getDeepComponentType();
if (componentType instanceof PsiClassType) {
final PsiClassType classType = (PsiClassType)componentType;
if (classType.resolve() instanceof PsiTypeParameter) {
return PsiKeyword.NULL;
}
}
PsiType erasedComponentType = TypeConversionUtil.erasure(componentType);
StringBuilder buffer = new StringBuilder();
buffer.append(PsiKeyword.NEW);
buffer.append(" ");
buffer.append(erasedComponentType.getCanonicalText());
buffer.append("[0]");
for (int i = 0; i < count; i++) {
buffer.append("[]");
}
return buffer.toString();
}
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (psiClass != null) {
String typeText = psiClass.getQualifiedName();
if (typeText != null) {
switch (typeText) {
case CommonClassNames.JAVA_UTIL_OPTIONAL:
case "java.util.OptionalInt":
case "java.util.OptionalLong":
case "java.util.OptionalDouble":
case CommonClassNames.JAVA_UTIL_STREAM_STREAM:
case CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM:
case CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM:
case CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM:
return typeText + ".empty()";
case CommonClassNames.JAVA_LANG_STRING:
return "\"\"";
case CommonClassNames.JAVA_LANG_INTEGER:
case CommonClassNames.JAVA_LANG_LONG:
case CommonClassNames.JAVA_LANG_SHORT:
case CommonClassNames.JAVA_LANG_BYTE:
return "0";
case CommonClassNames.JAVA_LANG_FLOAT:
return "0f";
case CommonClassNames.JAVA_LANG_DOUBLE:
return "0.0";
case CommonClassNames.JAVA_UTIL_SET:
return PsiUtil.isLanguageLevel9OrHigher(psiClass) ? "java.util.Set.of()" : "java.util.Collections.emptySet()";
case CommonClassNames.JAVA_UTIL_COLLECTION:
case CommonClassNames.JAVA_UTIL_LIST:
return PsiUtil.isLanguageLevel9OrHigher(psiClass) ? "java.util.List.of()" : "java.util.Collections.emptyList()";
case CommonClassNames.JAVA_UTIL_MAP:
return PsiUtil.isLanguageLevel9OrHigher(psiClass) ? "java.util.Map.of()" : "java.util.Collections.emptyMap()";
}
}
}
}
return PsiKeyword.NULL;

View File

@@ -1,7 +1,7 @@
// "Add 'return' statement" "true-preview"
class a {
String f() {
return <caret><selection>null</selection>;
return <caret><selection>""</selection>;
}
}

View File

@@ -0,0 +1,10 @@
// "Initialize variable 'list'" "true-preview"
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> list = Collections.emptyList();
System.out.println(list);
}
}

View File

@@ -0,0 +1,13 @@
// "Initialize variable 'msg'" "true-preview"
import java.util.Optional;
public class OptionalTest {
public static void main(String[] args) {
Optional<String> msg = Optional.empty();
if (args.length > 5) {
msg = Optional.of("hello");
}
System.out.println("msg = "+msg);
}
}

View File

@@ -0,0 +1,10 @@
// "Initialize variable 'string'" "true-preview"
import java.util.*;
public class Test {
public static void main(String[] args) {
String string = "";
System.out.println(string);
}
}

View File

@@ -0,0 +1,10 @@
// "Initialize variable 'list'" "true-preview"
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> list;
System.out.println(<caret>list);
}
}

View File

@@ -0,0 +1,13 @@
// "Initialize variable 'msg'" "true-preview"
import java.util.Optional;
public class OptionalTest {
public static void main(String[] args) {
Optional<String> msg;
if (args.length > 5) {
msg = Optional.of("hello");
}
System.out.println("msg = "+<caret>msg);
}
}

View File

@@ -0,0 +1,10 @@
// "Initialize variable 'string'" "true-preview"
import java.util.*;
public class Test {
public static void main(String[] args) {
String string;
System.out.println(<caret>string);
}
}

View File

@@ -15,6 +15,6 @@ class B extends A<String> {
class ABC extends A<Integer> {
@Override
String foo(Integer integer) {
return null;
return "";
}
}

View File

@@ -11,7 +11,7 @@ class FooImpl extends Foo<String> {
@Override
public @Nullable String getSmth() {
return null;
return "";
}
}

View File

@@ -5,7 +5,7 @@ public class ExTest {
}
{
String[] a = new String[0];
String[] a = null;
try {
a = new String[]{maybeThrow("")};
} catch (Ex e) {

View File

@@ -3,7 +3,7 @@
}
class d implements ff {
public String f() {
<caret><selection>return null;</selection>
<caret><selection>return "";</selection>
}
public Class<? extends Annotation> annotationType() {

View File

@@ -3,6 +3,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -46,16 +47,16 @@ class M implements Map {
@NotNull
public Set keySet() {
return null;
return Collections.emptySet();
}
@NotNull
public Collection values() {
return null;
return Collections.emptyList();
}
@NotNull
public Set<Entry> entrySet() {
return null;
return Collections.emptySet();
}
}

View File

@@ -1,12 +1,12 @@
record R() implements Nameable, Sizable {
@Override
public String name() {
return null;
return "";
}
@Override
public String lastName() {
return null;
return "";
}
@Override

View File

@@ -11,6 +11,6 @@ record R() {
@Override
public String toString() {
return null;
return "";
}
}

View File

@@ -11,6 +11,6 @@ interface B
class C implements A, B
{
public String foo() {
<selection>return null;</selection>
return "";
}
}

View File

@@ -6,6 +6,6 @@ interface I<T> {
class C implements I<String> {
@Override
public String foo(String x) {
<selection>return null;</selection>
<selection>return "";</selection>
}
}

View File

@@ -11,7 +11,7 @@ record R() {
@Override
public String toString() {
return null;
return "";
}
@Override

View File

@@ -1,5 +1,6 @@
package p;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -43,17 +44,17 @@ class M implements Map {
@NN
public Set keySet() {
return null;
return Collections.emptySet();
}
@NN
public Collection values() {
return null;
return Collections.emptyList();
}
@NN
public Set<Entry> entrySet() {
return null;
return Collections.emptySet();
}
}

View File

@@ -2,5 +2,5 @@
import org.jetbrains.annotations.NotNull;
class X {
@NotNull String x = null;
@NotNull String x = "";
}

View File

@@ -5,6 +5,6 @@ class X {
@NotNull String x;
X() {
x = null;
x = "";
}
}

View File

@@ -5,7 +5,7 @@ class A {
class B extends A {
public B(String a) {
this(a, null);
this(a, "");
}
public B(String a, final String anObject) {
super(a);

View File

@@ -4,6 +4,6 @@ class Test {
}
void bar() {
foo(null);
foo("");
}
}

View File

@@ -291,7 +291,7 @@ public class OverrideImplementTest extends LightJavaCodeInsightFixtureTestCase {
class C implements I {
@Override
public @TA List<@TA String> i(@TA String p1, @TA(1) int @TA(3) [] @TA(2) [] p2) throws @TA IllegalArgumentException {
return null;
return Collections.emptyList();
}
}""".stripIndent());
}
@@ -338,7 +338,7 @@ public class OverrideImplementTest extends LightJavaCodeInsightFixtureTestCase {
@TA
@Override
public List<@TA String> i(@TA String p1, @TA(1) int @TA(3) [] @TA(2) [] p2) throws @TA IllegalArgumentException {
return null;
return Collections.emptyList();
}
}""".stripIndent());
}
@@ -455,7 +455,7 @@ public class OverrideImplementTest extends LightJavaCodeInsightFixtureTestCase {
class C implements I {
@Override
public List<String> i(String p) {
return null;
return Collections.emptyList();
}
}""".stripIndent());
}
@@ -503,7 +503,7 @@ public class OverrideImplementTest extends LightJavaCodeInsightFixtureTestCase {
@A("")
@Override
public List<String> i(@A("a") String p) {
return null;
return Collections.emptyList();
}
}""".stripIndent());
}

View File

@@ -555,7 +555,7 @@ public class JavaLiveTemplateTest extends LiveTemplateTestCase {
class A {
{
String s = "null";
String s = "";
s.toString();
}
}""");

View File

@@ -12,7 +12,7 @@ class Y implements T<String> {
@Override
public String foo(String s) {
return null;
return "";
}
}

View File

@@ -12,7 +12,7 @@ class Y implements T<String> {
@Override
public String getFoo() {
return null;
return "";
}
}

View File

@@ -20,7 +20,7 @@ class Y implements T<String> {
@Override
public String getFoo() {
return null;
return "";
}
}
@@ -40,7 +40,7 @@ class W implements T<Integer> {
@Override
public Integer getFoo() {
return null;
return 0;
}
@Override