StreamToLoopInspection: reuse variable of inexact type (IDEA-167414)

This commit is contained in:
Tagir Valeev
2017-08-24 15:25:19 +07:00
parent e5a70eb974
commit 2e611b4917
4 changed files with 186 additions and 7 deletions
@@ -475,9 +475,17 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
}
public String declareResult(String desiredName, PsiType type, String initializer, @NotNull ResultKind kind) {
return declareResult(desiredName, type, null, initializer, kind);
}
public String declareResult(String desiredName,
PsiType type,
String mostAbstractAllowedType,
String initializer,
@NotNull ResultKind kind) {
if (kind != ResultKind.UNKNOWN && myStreamExpression.getParent() instanceof PsiVariable) {
PsiVariable var = (PsiVariable)myStreamExpression.getParent();
if (EquivalenceChecker.getCanonicalPsiEquivalence().typesAreEquivalent(var.getType(), type) &&
if (isCompatibleType(var, type, mostAbstractAllowedType) &&
var.getParent() instanceof PsiDeclarationStatement && (kind == ResultKind.FINAL || canUseAsNonFinal(var))) {
PsiDeclarationStatement declaration = (PsiDeclarationStatement)var.getParent();
if(declaration.getDeclaredElements().length == 1) {
@@ -506,6 +514,14 @@ public class StreamToLoopInspection extends BaseJavaBatchLocalInspectionTool {
return name;
}
private static boolean isCompatibleType(@NotNull PsiVariable var, @NotNull PsiType type, @Nullable String mostAbstractAllowedType) {
if (EquivalenceChecker.getCanonicalPsiEquivalence().typesAreEquivalent(var.getType(), type)) return true;
if (mostAbstractAllowedType == null) return false;
PsiType[] superTypes = type.getSuperTypes();
return Arrays.stream(superTypes).anyMatch(superType -> InheritanceUtil.isInheritor(superType, mostAbstractAllowedType) &&
isCompatibleType(var, superType, mostAbstractAllowedType));
}
@Contract("null -> false")
private static boolean canUseAsNonFinal(PsiVariable var) {
if (!(var instanceof PsiLocalVariable)) return false;
@@ -591,10 +591,14 @@ abstract class TerminalOperation extends Operation {
final PsiType myType;
final Function<StreamToLoopReplacementContext, String> myAccNameSupplier;
final FunctionHelper mySupplier;
final String myMostAbstractAllowedType;
CollectorBasedTerminalOperation(PsiType type, Function<StreamToLoopReplacementContext, String> accNameSupplier,
CollectorBasedTerminalOperation(PsiType type,
String mostAbstractAllowedType,
Function<StreamToLoopReplacementContext, String> accNameSupplier,
FunctionHelper accSupplier) {
myType = type;
myMostAbstractAllowedType = mostAbstractAllowedType;
myAccNameSupplier = accNameSupplier;
mySupplier = accSupplier;
}
@@ -603,7 +607,8 @@ abstract class TerminalOperation extends Operation {
String initAccumulator(StreamVariable inVar, StreamToLoopReplacementContext context) {
transform(context, inVar.getName());
PsiType resultType = correctReturnType(myType);
return context.declareResult(myAccNameSupplier.apply(context), resultType, getSupplier(), ResultKind.FINAL);
return context
.declareResult(myAccNameSupplier.apply(context), resultType, myMostAbstractAllowedType, getSupplier(), ResultKind.FINAL);
}
@Override
@@ -710,7 +715,8 @@ abstract class TerminalOperation extends Operation {
private final boolean myList;
public ToCollectionTerminalOperation(PsiType resultType, FunctionHelper fn, String desiredName) {
super(resultType, context -> fn.suggestFinalOutputNames(context, desiredName, "collection").get(0), fn);
super(resultType, CommonClassNames.JAVA_UTIL_COLLECTION,
context -> fn.suggestFinalOutputNames(context, desiredName, "collection").get(0), fn);
myList = InheritanceUtil.isInheritor(resultType, CommonClassNames.JAVA_UTIL_LIST);
}
@@ -809,7 +815,7 @@ abstract class TerminalOperation extends Operation {
PsiExpression merger,
FunctionHelper supplier,
PsiType resultType) {
super(resultType, context -> "map", supplier);
super(resultType, CommonClassNames.JAVA_UTIL_MAP, context -> "map", supplier);
myKeyExtractor = keyExtractor;
myValueExtractor = valueExtractor;
myMerger = merger;
@@ -878,7 +884,7 @@ abstract class TerminalOperation extends Operation {
private String myKeyVar;
public GroupByTerminalOperation(FunctionHelper keyExtractor, FunctionHelper supplier, PsiType resultType, CollectorOperation collector) {
super(resultType, context -> "map", supplier);
super(resultType, CommonClassNames.JAVA_UTIL_MAP, context -> "map", supplier);
myKeyExtractor = keyExtractor;
myCollector = collector;
}
@@ -947,7 +953,7 @@ abstract class TerminalOperation extends Operation {
PsiType resultType = context.createType(myResultType);
resultType = correctTypeParameters(resultType, CommonClassNames.JAVA_UTIL_MAP,
Collections.singletonMap("V", myCollector::correctReturnType));
String map = context.declareResult("map", resultType, "new java.util.HashMap<>()", ResultKind.FINAL);
String map = context.declareResult("map", resultType, CommonClassNames.JAVA_UTIL_MAP, "new java.util.HashMap<>()", ResultKind.FINAL);
myPredicate.transform(context, inVar.getName());
myCollector.transform(context, inVar.getName());
context.addBeforeStep(map + ".put(false, " + myCollector.getSupplier() + ");");
@@ -0,0 +1,111 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class InExactVariable {
public void testMap() {
HashMap<String, String> map = new HashMap<>();
for (Integer integer2 : Arrays.asList(1, 2, 3, 4)) {
String of = String.valueOf(integer2);
map.putIfAbsent(of.trim(), of);
}
Object map1 = map;
HashMap<String, String> map2 = new HashMap<>();
for (Integer integer1 : Arrays.asList(1, 2, 3, 4)) {
String valueOf = String.valueOf(integer1);
map2.putIfAbsent(valueOf.trim(), valueOf);
}
Map<String, String> map3 = new HashMap<>();
for (Integer integer : Arrays.asList(1, 2, 3, 4)) {
String s = String.valueOf(integer);
map3.putIfAbsent(s.trim(), s);
}
}
public void testList() {
List<String> result1 = new ArrayList<>();
for (Integer integer5 : Arrays.asList(1, 2, 3, 4)) {
String valueOf1 = String.valueOf(integer5);
result1.add(valueOf1);
}
Object list1 = result1;
List<String> result = new ArrayList<>();
for (Integer integer4 : Arrays.asList(1, 2, 3, 4)) {
String s1 = String.valueOf(integer4);
result.add(s1);
}
Iterable<String> list2 = result;
Collection<String> list3 = new ArrayList<>();
for (Integer integer3 : Arrays.asList(1, 2, 3, 4)) {
String value = String.valueOf(integer3);
list3.add(value);
}
List<String> list4 = new ArrayList<>();
for (Integer integer2 : Arrays.asList(1, 2, 3, 4)) {
String of = String.valueOf(integer2);
list4.add(of);
}
Collection<Object> list5 = new ArrayList<>();
for (Integer integer1 : Arrays.asList(1, 2, 3, 4)) {
String valueOf = String.valueOf(integer1);
list5.add(valueOf);
}
List<String> list = new ArrayList<>();
for (Integer integer : Arrays.asList(1, 2, 3, 4)) {
String s = String.valueOf(integer);
list.add(s);
}
Collection<?> list6 = list;
}
public void testPartition() {
Map<Boolean, List<String>> map = new HashMap<>();
map.put(false, new ArrayList<>());
map.put(true, new ArrayList<>());
for (Integer integer1 : Arrays.asList(1, 2, 3, 4)) {
String s = String.valueOf(integer1);
map.get(s.length() > 1).add(s);
}
Object map1 = map;
Map<Boolean, List<String>> map2 = new HashMap<>();
map2.put(false, new ArrayList<>());
map2.put(true, new ArrayList<>());
for (Integer integer : Arrays.asList(1, 2, 3, 4)) {
String x = String.valueOf(integer);
map2.get(x.length() > 1).add(x);
}
}
public void testGroupingBy() {
TreeMap<Integer, Set<String>> result = new TreeMap<>();
for (Integer integer4 : Arrays.asList(1, 2, 3, 4)) {
String s1 = String.valueOf(integer4);
result.computeIfAbsent(s1.length(), k2 -> new HashSet<>()).add(s1);
}
Object map1 = result;
TreeMap<Integer, Set<String>> map2 = new TreeMap<>();
for (Integer integer3 : Arrays.asList(1, 2, 3, 4)) {
String value = String.valueOf(integer3);
map2.computeIfAbsent(value.length(), key1 -> new HashSet<>()).add(value);
}
NavigableMap<Integer, Set<String>> map3 = new TreeMap<>();
for (Integer integer2 : Arrays.asList(1, 2, 3, 4)) {
String of = String.valueOf(integer2);
map3.computeIfAbsent(of.length(), k1 -> new HashSet<>()).add(of);
}
SortedMap<Integer, Set<String>> map4 = new TreeMap<>();
for (Integer integer1 : Arrays.asList(1, 2, 3, 4)) {
String valueOf = String.valueOf(integer1);
map4.computeIfAbsent(valueOf.length(), key -> new HashSet<>()).add(valueOf);
}
TreeMap<Integer, Set<String>> map = new TreeMap<>();
for (Integer integer : Arrays.asList(1, 2, 3, 4)) {
String s = String.valueOf(integer);
map.computeIfAbsent(s.length(), k -> new HashSet<>()).add(s);
}
Cloneable map5 = map;
}
}
@@ -0,0 +1,46 @@
// "Fix all 'Stream API call chain can be replaced with loop' problems in file" "true"
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class InExactVariable {
public void testMap() {
Object map1 = Stream.of(1, 2, 3, 4).map(String::valueOf)
.co<caret>llect(Collectors.toMap(String::trim, Function.identity(), (a, b) -> a, HashMap::new));
HashMap<String, String> map2 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors
.toMap(String::trim, Function.identity(), (a, b) -> a, HashMap::new));
Map<String, String> map3 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors
.toMap(String::trim, Function.identity(), (a, b) -> a, HashMap::new));
}
public void testList() {
Object list1 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList());
Iterable<String> list2 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList());
Collection<String> list3 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList());
List<String> list4 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList());
Collection<Object> list5 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList());
Collection<?> list6 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors.toList());
}
public void testPartition() {
Object map1 = Stream.of(1, 2, 3, 4).map(String::valueOf)
.collect(Collectors.partitioningBy(x -> x.length() > 1));
Map<Boolean, List<String>> map2 = Stream.of(1, 2, 3, 4).map(String::valueOf)
.collect(Collectors.partitioningBy((String x) -> x.length() > 1));
}
public void testGroupingBy() {
Object map1 = Stream.of(1, 2, 3, 4).map(String::valueOf)
.collect(Collectors.groupingBy(String::length, TreeMap::new, Collectors.toSet()));
TreeMap<Integer, Set<String>> map2 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors
.groupingBy(String::length, TreeMap::new, Collectors.toSet()));
NavigableMap<Integer, Set<String>> map3 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors
.groupingBy(String::length, TreeMap::new, Collectors.toSet()));
SortedMap<Integer, Set<String>> map4 = Stream.of(1, 2, 3, 4).map(String::valueOf).collect(Collectors
.groupingBy(String::length, TreeMap::new, Collectors.toSet()));
Cloneable map5 = Stream.of(1, 2, 3, 4).map(String::valueOf)
.collect(Collectors.groupingBy(String::length, TreeMap::new, Collectors.toSet()));
}
}