Skip to content

Commit 8e787b5

Browse files
SONARJAVA-4263 Fix FP on S2325 when method requires type parameter from parent class. (#4954)
Co-authored-by: Dorian Burihabwa <75226315+dorian-burihabwa-sonarsource@users.noreply.github.com>
1 parent 681d80c commit 8e787b5

3 files changed

Lines changed: 100 additions & 6 deletions

File tree

its/ruling/src/test/resources/guava/java-S2325.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
{
22
"com.google.guava:guava:src/com/google/common/cache/LocalCache.java": [
3-
1964,
4-
3515
3+
1964
54
],
65
"com.google.guava:guava:src/com/google/common/collect/HashBiMap.java": [
76
358
87
],
98
"com.google.guava:guava:src/com/google/common/collect/MapMakerInternalMap.java": [
109
1975
1110
],
12-
"com.google.guava:guava:src/com/google/common/collect/TreeRangeMap.java": [
13-
263
14-
],
1511
"com.google.guava:guava:src/com/google/common/io/ByteSource.java": [
1612
213,
1713
236

java-checks-test-sources/default/src/main/java/checks/StaticMethodCheckSample.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.IOException;
44
import java.io.ObjectStreamException;
55
import java.io.Serializable;
6+
import java.util.List;
67
import java.util.Map;
78
import java.util.HashMap;
89
import javax.annotation.Nullable;
@@ -363,3 +364,55 @@ synchronized private String magicWordSynchronized2() { // Noncompliant [[quickfi
363364
return magicWord;
364365
}
365366
}
367+
368+
class ReturnTypeCheck {
369+
static class SingleTypeVar<T> {
370+
private List<T> requiresTypeVar() { // Compliant
371+
return List.of();
372+
}
373+
374+
private List<String> doesNotRequireTypeVar() { // Noncompliant
375+
return List.of();
376+
}
377+
378+
private static List<String> staticDoesNotRequireTypeVar() { // Compliant
379+
return List.of();
380+
}
381+
382+
private <U> List<U> requiresAnotherTypeVar() { // Noncompliant
383+
return List.of();
384+
}
385+
386+
private <T> List<T> requiresAnotherTypeVarShadowed() { // Noncompliant
387+
return List.of();
388+
}
389+
}
390+
391+
static class TwoTypeVar<T1, T2> {
392+
private List<T1> oneUsed1() { // Compliant
393+
return List.of();
394+
}
395+
396+
private <U> Map<T1, U> oneUsed2() { // Compliant
397+
return Map.of();
398+
}
399+
400+
private static <T1, U> Map<T1, U> bothShadowed() { // Compliant
401+
return Map.of();
402+
}
403+
404+
private List<String> noneUsed() { // Noncompliant
405+
return List.of();
406+
}
407+
408+
private static List<String> staticNoneUsed() { // Compliant
409+
return List.of();
410+
}
411+
}
412+
413+
static class Nested<T> {
414+
private List<List<T>> requiresNestedTypeVar() { // Compliant
415+
return List.of();
416+
}
417+
}
418+
}

java-checks/src/main/java/org/sonar/java/checks/StaticMethodCheck.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616
*/
1717
package org.sonar.java.checks;
1818

19+
import java.util.ArrayList;
1920
import java.util.Deque;
2021
import java.util.LinkedList;
22+
import java.util.List;
2123
import java.util.Objects;
24+
import java.util.Set;
25+
import java.util.stream.Collectors;
2226
import javax.annotation.CheckForNull;
2327
import org.sonar.check.Rule;
2428
import org.sonar.java.checks.helpers.QuickFixHelper;
@@ -37,6 +41,7 @@
3741
import org.sonar.plugins.java.api.tree.Modifier;
3842
import org.sonar.plugins.java.api.tree.ModifierKeywordTree;
3943
import org.sonar.plugins.java.api.tree.Tree;
44+
import org.sonar.plugins.java.api.tree.TypeParameterTree;
4045

4146
@Rule(key = "S2325")
4247
public class StaticMethodCheck extends BaseTreeVisitor implements JavaFileScanner {
@@ -85,7 +90,10 @@ public void visitMethod(MethodTree tree) {
8590
// In case it cannot be determined (isOverriding returns null), consider as overriding to avoid FP.
8691
return;
8792
}
88-
if ((symbol.isPrivate() || symbol.isFinal() || classTree.symbol().isFinal()) && !symbol.isStatic() && !reference.hasNonStaticReference()) {
93+
if ((symbol.isPrivate() || symbol.isFinal() || classTree.symbol().isFinal())
94+
&& !symbol.isStatic()
95+
&& !reference.hasNonStaticReference()
96+
&& !returnRequiresParentTypeParameter(symbol)) {
8997
QuickFixHelper.newIssue(context)
9098
.forRule(this)
9199
.onTree(tree.simpleName())
@@ -95,6 +103,43 @@ public void visitMethod(MethodTree tree) {
95103
}
96104
}
97105

106+
/**
107+
* Method's return type requires type parameter and therefore cannot be made static.
108+
*/
109+
private static boolean returnRequiresParentTypeParameter(Symbol.MethodSymbol symbol) {
110+
List<Type> returnTypeVars = new ArrayList<>();
111+
collectTypeVars(returnTypeVars, symbol.returnType().type());
112+
if (returnTypeVars.isEmpty()) {
113+
return false;
114+
}
115+
// called from visitMethod, so we have declaration (NPE not possible)
116+
ClassTree parent = (ClassTree) symbol.declaration().parent();
117+
Set<Symbol> parentTypeParam =
118+
parent
119+
.typeParameters()
120+
.stream()
121+
.map(TypeParameterTree::symbol)
122+
.collect(Collectors.toUnmodifiableSet());
123+
return returnTypeVars
124+
.stream()
125+
.map(Type::symbol)
126+
.anyMatch(parentTypeParam::contains);
127+
}
128+
129+
private static void collectTypeVars(List<Type> accumulator, Type t) {
130+
// Consider a type such as `Map<T,Map<String,U>>`, it has two type
131+
// arguments: `T` and `Map<String,U>`. We collect variables in the `accumulator`,
132+
// and descend recursively otherwise (which is fine for simple types, such as String,
133+
// which do not have variables).
134+
for(Type tt: t.typeArguments()) {
135+
if (tt.isTypeVar()) {
136+
accumulator.add(tt);
137+
} else {
138+
collectTypeVars(accumulator, tt);
139+
}
140+
}
141+
}
142+
98143
private static JavaQuickFix getQuickFix(MethodTree tree) {
99144
Tree insertPosition = QuickFixHelper.nextToken(tree.modifiers());
100145

0 commit comments

Comments
 (0)