Skip to content

Commit f8c6c2a

Browse files
SONARPY-1789 Resolve inherited class members (#1770)
1 parent 27fe31b commit f8c6c2a

File tree

13 files changed

+120
-90
lines changed

13 files changed

+120
-90
lines changed

its/ruling/src/test/resources/expected/python-S5756.json

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,4 @@
11
{
2-
"project:buildbot-0.8.6p1/buildbot/test/unit/test_revlinks.py": [
3-
81
4-
],
5-
"project:django-2.2.3/django/contrib/gis/geos/prototypes/io.py": [
6-
151,
7-
153,
8-
238,
9-
249,
10-
258,
11-
263,
12-
270,
13-
276,
14-
281,
15-
285
16-
],
17-
"project:django-2.2.3/django/db/transaction.py": [
18-
297
19-
],
20-
"project:django-2.2.3/django/test/utils.py": [
21-
789
22-
],
232
"project:pecos/examples/qp2q/preprocessing/session_data_processing.py": [
243
34
254
],

its/ruling/src/test/resources/expected_extended/calibre/python-S5756.json

Lines changed: 0 additions & 25 deletions
This file was deleted.

its/ruling/src/test/resources/expected_extended/salt/python-S5756.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,27 @@ def typing_named_tuple_no_fp():
181181
from typing import NamedTuple
182182
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
183183
employee = Employee("Sam", 42)
184+
185+
186+
class Parent:
187+
def __call__(self):
188+
...
189+
190+
class Child(Parent):
191+
...
192+
193+
194+
def inherited_call_method():
195+
child = Child()
196+
child() # OK
197+
198+
199+
some_global_func = None
200+
201+
def assigning_global(my_func):
202+
global some_global_func
203+
some_global_func = my_func
204+
205+
def calling_global_func():
206+
# FP
207+
some_global_func() # Noncompliant

python-frontend/src/main/java/org/sonar/python/semantic/v2/ProjectLevelTypeTable.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.stream.IntStream;
2424
import org.sonar.python.semantic.ProjectLevelSymbolTable;
2525
import org.sonar.python.types.v2.ModuleType;
26+
import org.sonar.python.types.v2.PythonType;
2627

2728
public class ProjectLevelTypeTable {
2829

@@ -42,7 +43,7 @@ public ModuleType getModule(String... moduleName) {
4243
public ModuleType getModule(List<String> moduleNameParts) {
4344
var parent = rootModule;
4445
for (int i = 0; i < moduleNameParts.size(); i++) {
45-
var existing = parent.resolveMember(moduleNameParts.get(i));
46+
var existing = parent.resolveMember(moduleNameParts.get(i)).orElse(PythonType.UNKNOWN);
4647

4748
if (existing instanceof ModuleType existingModule) {
4849
parent = existingModule;

python-frontend/src/main/java/org/sonar/python/semantic/v2/TypeInferenceV2.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ public void visitFileInput(FileInput fileInput) {
8080
public void visitStringLiteral(StringLiteral stringLiteral) {
8181
ModuleType builtins = this.projectLevelTypeTable.getModule(BUILTINS);
8282
// TODO: multiple object types to represent str instance?
83-
((StringLiteralImpl) stringLiteral).typeV2(new ObjectType(builtins.resolveMember("str"), List.of(), List.of()));
83+
PythonType strType = builtins.resolveMember("str").orElse(PythonType.UNKNOWN);
84+
((StringLiteralImpl) stringLiteral).typeV2(new ObjectType(strType, List.of(), List.of()));
8485
}
8586

8687
@Override
@@ -89,15 +90,17 @@ public void visitNumericLiteral(NumericLiteral numericLiteral) {
8990
InferredType type = numericLiteral.type();
9091
String memberName = ((RuntimeType) type).getTypeClass().fullyQualifiedName();
9192
if (memberName != null) {
92-
((NumericLiteralImpl) numericLiteral).typeV2(new ObjectType(builtins.resolveMember(memberName), List.of(), List.of()));
93+
PythonType pythonType = builtins.resolveMember(memberName).orElse(PythonType.UNKNOWN);
94+
((NumericLiteralImpl) numericLiteral).typeV2(new ObjectType(pythonType, List.of(), List.of()));
9395
}
9496
}
9597

9698
@Override
9799
public void visitNone(NoneExpression noneExpression) {
98100
ModuleType builtins = this.projectLevelTypeTable.getModule(BUILTINS);
99101
// TODO: multiple object types to represent str instance?
100-
((NoneExpressionImpl) noneExpression).typeV2(new ObjectType(builtins.resolveMember("NoneType"), List.of(), List.of()));
102+
PythonType noneType = builtins.resolveMember("NoneType").orElse(PythonType.UNKNOWN);
103+
((NoneExpressionImpl) noneExpression).typeV2(new ObjectType(noneType, List.of(), List.of()));
101104
}
102105

103106
@Override
@@ -106,7 +109,8 @@ public void visitListLiteral(ListLiteral listLiteral) {
106109
scan(listLiteral.elements());
107110
List<PythonType> pythonTypes = listLiteral.elements().expressions().stream().map(Expression::typeV2).distinct().toList();
108111
// TODO: cleanly reduce attributes
109-
((ListLiteralImpl) listLiteral).typeV2(new ObjectType(builtins.resolveMember("list"), pythonTypes, List.of()));
112+
PythonType listType = builtins.resolveMember("list").orElse(PythonType.UNKNOWN);
113+
((ListLiteralImpl) listLiteral).typeV2(new ObjectType(listType, pythonTypes, List.of()));
110114
}
111115

112116
@Override
@@ -233,7 +237,7 @@ public void visitImportFrom(ImportFrom importFrom) {
233237
.stream()
234238
.findFirst()
235239
.ifPresent(name -> {
236-
var type = module.resolveMember(name.name());
240+
var type = module.resolveMember(name.name()).orElse(PythonType.UNKNOWN);
237241

238242
var boundName = Optional.ofNullable(aliasedName.alias())
239243
.orElse(name);
@@ -265,7 +269,9 @@ public void visitAssignmentStatement(AssignmentStatement assignmentStatement) {
265269
public void visitQualifiedExpression(QualifiedExpression qualifiedExpression) {
266270
scan(qualifiedExpression.qualifier());
267271
if (qualifiedExpression.name() instanceof NameImpl name) {
268-
var nameType = qualifiedExpression.qualifier().typeV2().resolveMember(qualifiedExpression.name().name());
272+
var nameType = qualifiedExpression.qualifier().typeV2()
273+
.resolveMember(qualifiedExpression.name().name())
274+
.orElse(PythonType.UNKNOWN);
269275
name.typeV2(nameType);
270276
}
271277
}

python-frontend/src/main/java/org/sonar/python/types/v2/ClassType.java

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ public boolean isCompatibleWith(PythonType another) {
7171
var isASubClass = this.isASubClassFrom(other);
7272
var areAttributeCompatible = this.areAttributesCompatible(other);
7373
var isDuckTypeCompatible = !this.members.isEmpty() && other.members.stream().allMatch(member -> this.members.contains(member));
74-
return Objects.equals(this, another) || "builtins.object".equals(other.name()) ||
74+
return Objects.equals(this, another) || "builtins.object".equals(other.name()) ||
7575
isDuckTypeCompatible ||
76-
( isASubClass && areAttributeCompatible) ;
76+
(isASubClass && areAttributeCompatible);
7777
}
7878
return true;
7979
}
@@ -96,20 +96,33 @@ public String key() {
9696
}
9797

9898
@Override
99-
public PythonType resolveMember(String memberName) {
99+
public Optional<PythonType> resolveMember(String memberName) {
100+
return localMember(memberName)
101+
.or(() -> inheritedMember(memberName));
102+
}
103+
104+
private Optional<PythonType> localMember(String memberName) {
100105
return members.stream()
101106
.filter(m -> m.name().equals(memberName))
102107
.map(Member::type)
103-
.findFirst().orElse(PythonType.UNKNOWN);
108+
.findFirst();
109+
}
110+
111+
private Optional<PythonType> inheritedMember(String memberName) {
112+
return superClasses.stream()
113+
.map(s -> s.resolveMember(memberName))
114+
.filter(Optional::isPresent)
115+
.map(Optional::get)
116+
.findFirst();
104117
}
105118

106119
public boolean hasUnresolvedHierarchy() {
107120
return superClasses.stream().anyMatch(s -> {
108-
if (s instanceof ClassType parentClassType) {
109-
return parentClassType.hasUnresolvedHierarchy();
121+
if (s instanceof ClassType parentClassType) {
122+
return parentClassType.hasUnresolvedHierarchy();
123+
}
124+
return true;
110125
}
111-
return true;
112-
}
113126
);
114127
}
115128

@@ -138,8 +151,7 @@ public TriBool instancesHaveMember(String memberName) {
138151
// TODO: instances of NamedTuple are type
139152
return TriBool.TRUE;
140153
}
141-
// TODO: look at parents
142-
return resolveMember(memberName) != PythonType.UNKNOWN ? TriBool.TRUE : TriBool.FALSE;
154+
return resolveMember(memberName).isPresent() ? TriBool.TRUE : TriBool.FALSE;
143155
}
144156

145157
@Override

python-frontend/src/main/java/org/sonar/python/types/v2/ModuleType.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.HashMap;
2323
import java.util.Map;
2424
import java.util.Objects;
25+
import java.util.Optional;
2526
import javax.annotation.Nullable;
2627

2728
public record ModuleType(@Nullable String name, @Nullable ModuleType parent, Map<String, PythonType> members) implements PythonType {
@@ -33,9 +34,9 @@ public ModuleType(@Nullable String name, @Nullable ModuleType parent) {
3334
this(name, parent, new HashMap<>());
3435
}
3536

36-
public PythonType resolveMember(String memberName) {
37-
// FIXME: handle case where type is missing
38-
return members.getOrDefault(memberName, PythonType.UNKNOWN);
37+
@Override
38+
public Optional<PythonType> resolveMember(String memberName) {
39+
return Optional.ofNullable(members.get(memberName));
3940
}
4041

4142
@Override

python-frontend/src/main/java/org/sonar/python/types/v2/ObjectType.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.ArrayList;
2323
import java.util.List;
2424
import java.util.Objects;
25+
import java.util.Optional;
2526

2627
public record ObjectType(PythonType type, List<PythonType> attributes, List<Member> members) implements PythonType {
2728

@@ -40,17 +41,17 @@ public boolean isCompatibleWith(PythonType another) {
4041
}
4142

4243
@Override
43-
public PythonType resolveMember(String memberName) {
44+
public Optional<PythonType> resolveMember(String memberName) {
4445
return members().stream()
4546
.filter(member -> Objects.equals(member.name(), memberName))
4647
.map(Member::type)
4748
.findFirst()
48-
.orElseGet(() -> type.resolveMember(memberName));
49+
.or(() -> type.resolveMember(memberName));
4950
}
5051

5152
@Override
5253
public TriBool hasMember(String memberName) {
53-
if (resolveMember(memberName) != PythonType.UNKNOWN) {
54+
if (resolveMember(memberName).isPresent()) {
5455
return TriBool.TRUE;
5556
}
5657
if (type instanceof ClassType classType) {

python-frontend/src/main/java/org/sonar/python/types/v2/PythonType.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
*/
2020
package org.sonar.python.types.v2;
2121

22+
import java.util.Optional;
23+
2224
/**
2325
* PythonType
2426
*/
@@ -41,8 +43,8 @@ default String key() {
4143
return name();
4244
}
4345

44-
default PythonType resolveMember(String memberName) {
45-
return PythonType.UNKNOWN;
46+
default Optional<PythonType> resolveMember(String memberName) {
47+
return Optional.empty();
4648
}
4749

4850
default TriBool hasMember(String memberName) {

0 commit comments

Comments
 (0)