Skip to content

Commit 3be15cc

Browse files
SONARPY-2332 Fix FP on S5845 when comparing enum objects with types (#2170)
1 parent 0a4a5b8 commit 3be15cc

File tree

3 files changed

+48
-8
lines changed

3 files changed

+48
-8
lines changed

python-checks/src/test/resources/checks/tests/assertOnDissimilarTypes.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,8 @@ class ComparingTypeAndEnum(unittest.TestCase):
198198
def test_type_of_enum_and_enum_class(self):
199199
EnumType = Enum("EnumType", names=["one", "two"])
200200
enum_member = EnumType(1)
201-
# FP SONARPY-2332: EnumType is a proper "type" object since the Enum constructor with argument creates a new Enum type
202-
self.assertIs(type(enum_member), EnumType) # Noncompliant
201+
# EnumType is a proper "type" object since the Enum constructor with argument creates a new Enum type
202+
self.assertIs(type(enum_member), EnumType)
203203

204204
class DerivedEnumWithMembers(Enum):
205205
ONE = 1
@@ -208,7 +208,7 @@ class DerivedEnumWithMembers(Enum):
208208
class ComparingTypeAndDerivedEnumWithMembers(unittest.TestCase):
209209
def test_type_of_derived_enum_and_derived_enum_class(self):
210210
derived_enum_member = DerivedEnumWithMembers(1)
211-
self.assertIs(type(enum_member), EnumType)
211+
self.assertIs(type(derived_enum_member), EnumType)
212212

213213
class DerivedEnumWithoutMembers(Enum):
214214
...
@@ -218,12 +218,12 @@ class ComparingTypeAndDerivedEnumWithoutMembers(unittest.TestCase):
218218
def test_type_of_derived_enum_and_derived_enum_class(self):
219219
DerivedEnumType = DerivedEnumWithoutMembers("DerivedEnumType", names=["one", "two"])
220220
derived_enum_member = DerivedEnumType(1)
221-
# FP SONARPY-2332: DerivedEnumType is a proper "type" object because `DerivedEnumWithoutMembers(str, list)` will create a new enum,
221+
# DerivedEnumType is a proper "type" object because `DerivedEnumWithoutMembers(str, list)` will create a new enum,
222222
# just like `Enum(str, list)` would
223-
self.assertIs(type(derived_enum_member), DerivedEnumType) # Noncompliant
223+
self.assertIs(type(derived_enum_member), DerivedEnumType)
224224

225225
DerivedEnumTypeFromDict = DerivedEnumWithoutMembers("DerivedEnumType", OrderedDict([("one", 1), ("two", 2)]))
226226
derived_enum_member_from_dict = DerivedEnumTypeFromDict(1)
227-
# FP SONARPY-2332: DerivedEnumTypeFromDict is a proper "type" object because `DerivedEnumWithoutMembers(str, list)` will create a new enum,
227+
# DerivedEnumTypeFromDict is a proper "type" object because `DerivedEnumWithoutMembers(str, list)` will create a new enum,
228228
# just like `Enum(str, list)`
229-
self.assertIs(type(derived_enum_member_from_dict), DerivedEnumTypeFromDict) # Noncompliant
229+
self.assertIs(type(derived_enum_member_from_dict), DerivedEnumTypeFromDict)

python-frontend/src/main/java/org/sonar/python/types/RuntimeType.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,21 @@ public boolean isIdentityComparableWith(InferredType other) {
5252
if (other instanceof UnionType) {
5353
return other.isIdentityComparableWith(this);
5454
}
55-
return this.equals(other);
55+
return isComparingTypeWithMetaclass(other) || this.equals(other);
56+
}
57+
58+
private boolean isComparingTypeWithMetaclass(InferredType other) {
59+
if (other instanceof RuntimeType otherRuntimeType) {
60+
boolean hasOtherMetaClass = hasMetaclassInHierarchy(otherRuntimeType.getTypeClass());
61+
boolean hasThisMetaClass = hasMetaclassInHierarchy(getTypeClass());
62+
return (InferredTypes.TYPE.equals(this) && hasOtherMetaClass)
63+
|| (hasThisMetaClass && InferredTypes.TYPE.equals(otherRuntimeType));
64+
}
65+
return false;
66+
}
67+
68+
private static boolean hasMetaclassInHierarchy(ClassSymbol classSymbol) {
69+
return classSymbol.hasMetaClass() || classSymbol.superClasses().stream().filter(ClassSymbol.class::isInstance).anyMatch(c -> hasMetaclassInHierarchy((ClassSymbol) c));
5670
}
5771

5872
@Override

python-frontend/src/test/java/org/sonar/python/types/RuntimeTypeTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,32 @@ void isIdentityComparableWith() {
6464
assertThat(aType.isIdentityComparableWith(new DeclaredType(b))).isTrue();
6565
}
6666

67+
@Test
68+
void isIdentityComparableWithMetaclass() {
69+
ClassSymbolImpl metaclassSymbol = new ClassSymbolImpl("Meta", "Meta");
70+
metaclassSymbol.setHasMetaClass();
71+
RuntimeType metaClassType = new RuntimeType(metaclassSymbol);
72+
73+
ClassSymbolImpl classSymbolWithSuperMetaClass = new ClassSymbolImpl("SuperMeta", "SuperMeta");
74+
classSymbolWithSuperMetaClass.addSuperClass(metaclassSymbol);
75+
RuntimeType superMetaClassType = new RuntimeType(classSymbolWithSuperMetaClass);
76+
77+
UnknownClassType unknownClassType = new UnknownClassType(metaclassSymbol);
78+
79+
assertThat(InferredTypes.TYPE.isIdentityComparableWith(InferredTypes.TYPE)).isTrue();
80+
assertThat(InferredTypes.TYPE.isIdentityComparableWith(metaClassType)).isTrue();
81+
assertThat(InferredTypes.TYPE.isIdentityComparableWith(superMetaClassType)).isTrue();
82+
assertThat(InferredTypes.TYPE.isIdentityComparableWith(unknownClassType)).isFalse();
83+
84+
assertThat(metaClassType.isIdentityComparableWith(InferredTypes.TYPE)).isTrue();
85+
assertThat(metaClassType.isIdentityComparableWith(metaClassType)).isTrue();
86+
assertThat(metaClassType.isIdentityComparableWith(superMetaClassType)).isFalse();
87+
88+
assertThat(superMetaClassType.isIdentityComparableWith(InferredTypes.TYPE)).isTrue();
89+
assertThat(superMetaClassType.isIdentityComparableWith(metaClassType)).isFalse();
90+
assertThat(superMetaClassType.isIdentityComparableWith(superMetaClassType)).isTrue();
91+
}
92+
6793
@Test
6894
void member() {
6995
ClassSymbolImpl x = new ClassSymbolImpl("x", "x");

0 commit comments

Comments
 (0)