Skip to content

Commit 91c3f99

Browse files
joke1196sonartech
authored andcommitted
SONARPY-3247: S5953: fix FP on generic types defined with Python's "new" generic syntax (#462)
GitOrigin-RevId: 97637b177c0aa684b3b84047e7827bd21cbbca6a
1 parent 35bd154 commit 91c3f99

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

python-checks/src/main/java/org/sonar/python/checks/UndefinedSymbolsCheck.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@
2020
import java.util.HashMap;
2121
import java.util.List;
2222
import java.util.Map;
23+
import javax.annotation.Nullable;
2324
import org.sonar.check.Rule;
2425
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2526
import org.sonar.plugins.python.api.SubscriptionContext;
2627
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
2728
import org.sonar.plugins.python.api.tree.CallExpression;
29+
import org.sonar.plugins.python.api.tree.ClassDef;
2830
import org.sonar.plugins.python.api.tree.FileInput;
2931
import org.sonar.plugins.python.api.tree.ImportFrom;
3032
import org.sonar.plugins.python.api.tree.Name;
3133
import org.sonar.plugins.python.api.tree.Tree;
34+
import org.sonar.plugins.python.api.tree.TypeParams;
35+
import org.sonar.python.tree.TreeUtils;
3236

3337
@Rule(key = "S5953")
3438
public class UndefinedSymbolsCheck extends PythonSubscriptionCheck {
@@ -68,11 +72,29 @@ private static class UnresolvedSymbolsVisitor extends BaseTreeVisitor {
6872

6973
@Override
7074
public void visitName(Name name) {
71-
if (name.isVariable() && name.symbol() == null && !name.name().startsWith("_")) {
75+
if (name.isVariable() && name.symbol() == null && !name.name().startsWith("_") && !isTypeVar(name)) {
7276
nameIssues.computeIfAbsent(name.name(), k -> new ArrayList<>()).add(name);
7377
}
7478
}
7579

80+
private static boolean isTypeVar(Name name) {
81+
return TreeUtils.firstAncestor(name, tree -> classWithTypeVar(tree, name)) != null;
82+
}
83+
84+
private static boolean classWithTypeVar(Tree tree, Name name) {
85+
if (tree instanceof ClassDef classDef) {
86+
return hasTypeVar(classDef.typeParams(), name);
87+
}
88+
return false;
89+
}
90+
91+
private static boolean hasTypeVar(@Nullable TypeParams typeParams, Name name) {
92+
if (typeParams != null && !typeParams.typeParamsList().isEmpty()) {
93+
return typeParams.typeParamsList().stream().anyMatch(typeParam -> typeParam.name().name().equals(name.name()));
94+
}
95+
return false;
96+
}
97+
7698
@Override
7799
public void visitImportFrom(ImportFrom importFrom) {
78100
hasWildcardImport |= importFrom.wildcard() != null;

python-checks/src/test/resources/checks/undefinedSymbols/undefinedSymbols.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,9 @@ def undefined_generic_type[T: T](a: T) -> T:
156156
x: T = a
157157
a.foo()
158158

159+
class TypeVarTest[T]:
160+
def from_class_def(self):
161+
a: list[T] = ...
162+
163+
def unknow_generic(self):
164+
a: list[X] = ... # Noncompliant

0 commit comments

Comments
 (0)