Skip to content

Commit 775b43c

Browse files
SONARPY-1887 Resolve the fact that a class has a metaclass across its type hierarchy (#1811)
1 parent 920bf3a commit 775b43c

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ class Factory: ...
8484
class Base(metaclass=Factory): ...
8585
class A(Base): ...
8686
a = A()
87-
# TODO: resolve type hierarchy and metaclasses
88-
a() # Noncompliant
87+
a()
8988

9089

9190
def decorators():

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,12 @@ public TriBool hasMember(String memberName) {
148148
}
149149

150150
public boolean hasMetaClass() {
151-
return !this.metaClasses.isEmpty();
151+
return !this.metaClasses.isEmpty() ||
152+
this.superClasses()
153+
.stream()
154+
.filter(ClassType.class::isInstance)
155+
.map(ClassType.class::cast)
156+
.anyMatch(ClassType::hasMetaClass);
152157
}
153158

154159
public TriBool instancesHaveMember(String memberName) {

python-frontend/src/test/java/org/sonar/python/semantic/v2/TypeInferenceV2Test.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,47 @@ def f():
11851185
.isEqualTo("int");
11861186
}
11871187

1188+
@Test
1189+
void inferClassHierarchyHasMetaClass() {
1190+
var root = inferTypes("""
1191+
class CustomMetaClass:
1192+
...
1193+
1194+
class ParentClass(metaclass=CustomMetaClass):
1195+
...
1196+
1197+
class ChildClass(ParentClass):
1198+
...
1199+
1200+
def f():
1201+
a = ChildClass()
1202+
a
1203+
""");
1204+
1205+
1206+
var childClassType = TreeUtils.firstChild(root.statements().statements().get(2), ClassDef.class::isInstance)
1207+
.map(ClassDef.class::cast)
1208+
.map(ClassDef::name)
1209+
.map(Expression::typeV2)
1210+
.map(ClassType.class::cast)
1211+
.get();
1212+
1213+
Assertions.assertThat(childClassType.hasMetaClass()).isTrue();
1214+
1215+
var aType = TreeUtils.firstChild(root.statements().statements().get(3), ExpressionStatement.class::isInstance)
1216+
.map(ExpressionStatement.class::cast)
1217+
.flatMap(expressionStatement -> TreeUtils.firstChild(expressionStatement, Name.class::isInstance))
1218+
.map(Name.class::cast)
1219+
.map(Expression::typeV2)
1220+
.get();
1221+
1222+
Assertions.assertThat(aType)
1223+
.isNotNull()
1224+
.isNotEqualTo(PythonType.UNKNOWN)
1225+
.extracting(PythonType::unwrappedType)
1226+
.isSameAs(childClassType);
1227+
}
1228+
11881229
private static FileInput inferTypes(String lines) {
11891230
return inferTypes(lines, new HashMap<>());
11901231
}

0 commit comments

Comments
 (0)