Skip to content

Commit 2cf30df

Browse files
SONARPY-1747 Fix incorrect type equality when classes vary in metaclass status (#1762)
1 parent 9884586 commit 2cf30df

File tree

8 files changed

+72
-6
lines changed

8 files changed

+72
-6
lines changed

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,28 @@ def test_other(self):
157157
self.assertEqual(5, *a)
158158
self.assertEqual("5", b) # Noncompliant
159159
a.assertEqual("string", True)
160+
161+
162+
class AmbiguousSymbolsNoType(unittest.TestCase):
163+
def test_signature_on_class(self):
164+
class ClassWithMultipleDefinitions:
165+
def __init__(self, a):
166+
pass
167+
168+
class CM(type):
169+
def __call__(cls, a):
170+
pass
171+
class ClassWithMultipleDefinitions(metaclass=CM):
172+
def __init__(self, b):
173+
pass
174+
175+
with ...:
176+
class CM(type):
177+
@classmethod
178+
def __call__(cls, a):
179+
return a
180+
class ClassWithMultipleDefinitions(metaclass=CM):
181+
def __init__(self, b):
182+
pass
183+
184+
self.assertEqual(ClassWithMultipleDefinitions(1), 1) # OK, ambiguous type

python-frontend/src/main/java/org/sonar/plugins/python/api/symbols/ClassSymbol.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,7 @@ public interface ClassSymbol extends Symbol {
5353

5454
@Beta
5555
boolean hasDecorators();
56+
57+
@Beta
58+
boolean hasMetaClass();
5659
}

python-frontend/src/main/java/org/sonar/python/semantic/ClassSymbolImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ public Optional<Symbol> resolveMember(String memberName) {
254254
return Optional.empty();
255255
}
256256

257+
@Override
257258
public boolean hasMetaClass() {
258259
return hasMetaClass || membersByName().get("__metaclass__") != null;
259260
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,9 +474,10 @@ public static boolean containsDeclaredType(InferredType type) {
474474
}
475475

476476
public static String getBuiltinCategory(InferredType inferredType) {
477-
return BUILTINS_TYPE_CATEGORY.keySet().stream()
477+
List<String> list = BUILTINS_TYPE_CATEGORY.keySet().stream()
478478
.filter(inferredType::canOnlyBe)
479-
.map(BUILTINS_TYPE_CATEGORY::get).findFirst().orElse(null);
479+
.map(BUILTINS_TYPE_CATEGORY::get).toList();
480+
return list.size() == 1 ? list.get(0) : null;
480481
}
481482

482483
public static Map<String, String> getBuiltinsTypeCategory() {

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ public boolean equals(Object o) {
112112
RuntimeType that = (RuntimeType) o;
113113
return Objects.equals(getTypeClass().name(), that.getTypeClass().name()) &&
114114
Objects.equals(getTypeClass().fullyQualifiedName(), that.getTypeClass().fullyQualifiedName())
115+
&& Objects.equals(getTypeClass().hasUnresolvedTypeHierarchy(), that.getTypeClass().hasUnresolvedTypeHierarchy())
116+
&& Objects.equals(getTypeClass().hasDecorators(), that.getTypeClass().hasDecorators())
117+
&& Objects.equals(getTypeClass().hasMetaClass(), that.getTypeClass().hasMetaClass())
115118
&& Objects.equals(typeClassSuperClassesFQN(), that.typeClassSuperClassesFQN())
116119
&& Objects.equals(typeClassMembersFQN(), that.typeClassMembersFQN());
117120
}
@@ -136,7 +139,14 @@ boolean hasUnresolvedHierarchy() {
136139

137140
@Override
138141
public int hashCode() {
139-
return Objects.hash(getTypeClass().name(), getTypeClass().fullyQualifiedName(), typeClassSuperClassesFQN(), typeClassMembersFQN());
142+
return Objects.hash(
143+
getTypeClass().name(),
144+
getTypeClass().fullyQualifiedName(),
145+
getTypeClass().hasDecorators(),
146+
getTypeClass().hasUnresolvedTypeHierarchy(),
147+
getTypeClass().hasMetaClass(),
148+
typeClassSuperClassesFQN(),
149+
typeClassMembersFQN());
140150
}
141151

142152
@Override

python-frontend/src/test/java/org/sonar/python/semantic/ClassSymbolTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void no_parents() {
5353
ClassSymbol classSymbol = (ClassSymbol) symbol;
5454
assertThat(classSymbol.superClasses()).hasSize(0);
5555
assertThat(classSymbol.hasUnresolvedTypeHierarchy()).isFalse();
56+
assertThat(classSymbol.hasMetaClass()).isFalse();
5657
}
5758

5859
@Test

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.Arrays;
2424
import java.util.HashSet;
2525
import java.util.List;
26-
2726
import org.junit.jupiter.api.Test;
2827
import org.sonar.plugins.python.api.LocationInFile;
2928
import org.sonar.plugins.python.api.symbols.ClassSymbol;
@@ -35,14 +34,12 @@
3534
import org.sonar.python.PythonTestUtils;
3635
import org.sonar.python.semantic.AmbiguousSymbolImpl;
3736
import org.sonar.python.semantic.ClassSymbolImpl;
38-
import org.sonar.python.semantic.FunctionSymbolImpl;
3937
import org.sonar.python.semantic.SymbolImpl;
4038
import org.sonar.python.types.protobuf.SymbolsProtos;
4139

4240
import static org.assertj.core.api.Assertions.assertThat;
4341
import static org.sonar.python.PythonTestUtils.lastExpression;
4442
import static org.sonar.python.PythonTestUtils.lastExpressionInFunction;
45-
import static org.sonar.python.PythonTestUtils.pythonFile;
4643
import static org.sonar.python.types.InferredTypes.COMPLEX;
4744
import static org.sonar.python.types.InferredTypes.DECL_INT;
4845
import static org.sonar.python.types.InferredTypes.DECL_STR;
@@ -501,6 +498,7 @@ void test_builtin_category() {
501498
assertThat(getBuiltinCategory(LIST)).isEqualTo(BuiltinTypes.LIST);
502499
assertThat(getBuiltinCategory(SET)).isEqualTo(BuiltinTypes.SET);
503500
assertThat(getBuiltinCategory(TUPLE)).isEqualTo(BuiltinTypes.TUPLE);
501+
assertThat(getBuiltinCategory(anyType())).isNull();
504502
assertThat(getBuiltinsTypeCategory()).isNotNull();
505503
assertThat(getBuiltinsTypeCategory()).isNotEmpty();
506504
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@
2020
package org.sonar.python.types;
2121

2222
import java.util.Collections;
23+
import java.util.Set;
2324
import org.junit.jupiter.api.Test;
2425
import org.sonar.plugins.python.api.symbols.ClassSymbol;
2526
import org.sonar.plugins.python.api.tree.ClassDef;
2627
import org.sonar.plugins.python.api.tree.FileInput;
2728
import org.sonar.plugins.python.api.tree.Tree;
2829
import org.sonar.plugins.python.api.types.InferredType;
2930
import org.sonar.python.PythonTestUtils;
31+
import org.sonar.python.index.ClassDescriptor;
3032
import org.sonar.python.semantic.ClassSymbolImpl;
3133
import org.sonar.python.semantic.SymbolImpl;
3234
import org.sonar.python.semantic.SymbolTableBuilder;
@@ -204,6 +206,18 @@ void test_equals() {
204206
RuntimeType x = new RuntimeType(new ClassSymbolImpl("X", null));
205207
RuntimeType y = new RuntimeType(new ClassSymbolImpl("Y", null));
206208
assertThat(x).isNotEqualTo(y);
209+
210+
RuntimeType fff1 = new RuntimeType(new ClassSymbolImpl(generateDescriptor(false, false, false), "a"));
211+
RuntimeType fff2 = new RuntimeType(new ClassSymbolImpl(generateDescriptor(false, false, false), "a"));
212+
RuntimeType tff = new RuntimeType(new ClassSymbolImpl(generateDescriptor(true, false, false), "a"));
213+
RuntimeType ftf = new RuntimeType(new ClassSymbolImpl(generateDescriptor(false, true, false), "a"));
214+
RuntimeType fft = new RuntimeType(new ClassSymbolImpl(generateDescriptor(false, false, true), "a"));
215+
216+
assertThat(fff1)
217+
.isEqualTo(fff2)
218+
.isNotEqualTo(tff)
219+
.isNotEqualTo(ftf)
220+
.isNotEqualTo(fft);
207221
}
208222

209223
@Test
@@ -348,6 +362,19 @@ void test_mustBeOrExtend() {
348362
assertThat(typeX2.mustBeOrExtend("x2")).isTrue();
349363
}
350364

365+
ClassDescriptor generateDescriptor(boolean hasDecorators, boolean hasMetaClass, boolean hasUnresolvedHierarchy) {
366+
return new ClassDescriptor("a",
367+
"a",
368+
Set.of(),
369+
Set.of(),
370+
hasDecorators,
371+
null,
372+
hasUnresolvedHierarchy,
373+
hasMetaClass,
374+
null,
375+
false);
376+
}
377+
351378
@Test
352379
void test_resolveDeclaredMember() {
353380
ClassSymbolImpl typeClass = new ClassSymbolImpl("x", "x");

0 commit comments

Comments
 (0)