Skip to content

Commit 810eb5e

Browse files
Seppli11sonartech
authored andcommitted
SONARPY-3629 Serialize all ClassType fields to ClassDescriptor (#833)
GitOrigin-RevId: 234f31e8fa2a38e546300f2fcbaf87951b42e3e7
1 parent 45018d2 commit 810eb5e

13 files changed

Lines changed: 475 additions & 9 deletions

File tree

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public TriBool isCompatibleWith(PythonType another) {
102102
return this.isCompatibleWith(functionType.returnType());
103103
}
104104
if (another instanceof ClassType classType) {
105-
if (this.hasDecorators || classType.hasDecorators) {
105+
if ((this.hasDecorators() && this.isUserDefinedType()) || (classType.hasDecorators() && classType.isUserDefinedType())) {
106106
return TriBool.UNKNOWN;
107107
}
108108
var isASubClass = this.isASubClassFrom(classType);
@@ -211,7 +211,7 @@ public boolean hasMetaClass() {
211211
}
212212

213213
public TriBool instancesHaveMember(String memberName) {
214-
if (hasUnresolvedHierarchy() || hasMetaClass() || hasDecorators()) {
214+
if (hasUnresolvedHierarchy() || hasMetaClass() || (hasDecorators() && isUserDefinedType())) {
215215
return TriBool.UNKNOWN;
216216
}
217217
if ("NamedTuple".equals(this.name)) {
@@ -230,6 +230,11 @@ public Optional<LocationInFile> definitionLocation() {
230230
return Optional.ofNullable(this.locationInFile);
231231
}
232232

233+
private boolean isUserDefinedType() {
234+
// assumes if definedLocation is not present that the type is loaded from typeshed
235+
return definitionLocation().isPresent();
236+
}
237+
233238
@Override
234239
public String toString() {
235240
return "ClassType[%s]".formatted(name);

python-frontend/src/main/java/org/sonar/python/index/ClassDescriptor.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
*/
1717
package org.sonar.python.index;
1818

19+
import java.util.ArrayList;
1920
import java.util.Collection;
2021
import java.util.HashSet;
22+
import java.util.List;
2123
import java.util.Set;
2224
import javax.annotation.CheckForNull;
2325
import javax.annotation.Nonnull;
@@ -30,27 +32,31 @@ public class ClassDescriptor implements Descriptor {
3032
private final String fullyQualifiedName;
3133
private final Collection<String> superClasses;
3234
private final Set<Descriptor> members;
35+
private final List<Descriptor> attributes;
3336
private final boolean hasDecorators;
3437
private final LocationInFile definitionLocation;
3538
private final boolean hasSuperClassWithoutDescriptor;
3639
private final boolean hasMetaClass;
3740
private final String metaclassFQN;
41+
private final List<Descriptor> metaClasses;
3842
private final boolean supportsGenerics;
3943
private final boolean isSelf;
4044

4145
private ClassDescriptor(String name, String fullyQualifiedName, Collection<String> superClasses, Set<Descriptor> members,
42-
boolean hasDecorators, @Nullable LocationInFile definitionLocation, boolean hasSuperClassWithoutDescriptor, boolean hasMetaClass,
43-
@Nullable String metaclassFQN, boolean supportsGenerics, boolean isSelf) {
46+
List<Descriptor> attributes, boolean hasDecorators, @Nullable LocationInFile definitionLocation, boolean hasSuperClassWithoutDescriptor,
47+
boolean hasMetaClass, @Nullable String metaclassFQN, List<Descriptor> metaClasses, boolean supportsGenerics, boolean isSelf) {
4448

4549
this.name = name;
4650
this.fullyQualifiedName = fullyQualifiedName;
4751
this.superClasses = superClasses;
4852
this.members = members;
53+
this.attributes = attributes;
4954
this.hasDecorators = hasDecorators;
5055
this.definitionLocation = definitionLocation;
5156
this.hasSuperClassWithoutDescriptor = hasSuperClassWithoutDescriptor;
5257
this.hasMetaClass = hasMetaClass;
5358
this.metaclassFQN = metaclassFQN;
59+
this.metaClasses = metaClasses;
5460
this.supportsGenerics = supportsGenerics;
5561
this.isSelf = isSelf;
5662
}
@@ -79,6 +85,10 @@ public Collection<Descriptor> members() {
7985
return members;
8086
}
8187

88+
public List<Descriptor> attributes() {
89+
return attributes;
90+
}
91+
8292
public boolean hasDecorators() {
8393
return hasDecorators;
8494
}
@@ -87,6 +97,7 @@ public boolean hasSuperClassWithoutDescriptor() {
8797
return hasSuperClassWithoutDescriptor;
8898
}
8999

100+
@CheckForNull
90101
public LocationInFile definitionLocation() {
91102
return definitionLocation;
92103
}
@@ -100,6 +111,10 @@ public String metaclassFQN() {
100111
return metaclassFQN;
101112
}
102113

114+
public List<Descriptor> metaClasses() {
115+
return metaClasses;
116+
}
117+
103118
public boolean supportsGenerics() {
104119
return supportsGenerics;
105120
}
@@ -114,11 +129,13 @@ public static class ClassDescriptorBuilder {
114129
private String fullyQualifiedName;
115130
private Collection<String> superClasses = new HashSet<>();
116131
private Set<Descriptor> members = new HashSet<>();
132+
private List<Descriptor> attributes = new ArrayList<>();
117133
private boolean hasDecorators = false;
118134
private LocationInFile definitionLocation = null;
119135
private boolean hasSuperClassWithoutDescriptor = false;
120136
private boolean hasMetaClass = false;
121137
private String metaclassFQN = null;
138+
private List<Descriptor> metaClasses = new ArrayList<>();
122139
private boolean supportsGenerics = false;
123140
private boolean isSelf = false;
124141

@@ -142,6 +159,11 @@ public ClassDescriptorBuilder withMembers(Set<Descriptor> members) {
142159
return this;
143160
}
144161

162+
public ClassDescriptorBuilder withAttributes(List<Descriptor> attributes) {
163+
this.attributes = attributes;
164+
return this;
165+
}
166+
145167
public ClassDescriptorBuilder withHasDecorators(boolean hasDecorators) {
146168
this.hasDecorators = hasDecorators;
147169
return this;
@@ -167,6 +189,11 @@ public ClassDescriptorBuilder withMetaclassFQN(@Nullable String metaclassFQN) {
167189
return this;
168190
}
169191

192+
public ClassDescriptorBuilder withMetaClasses(List<Descriptor> metaClasses) {
193+
this.metaClasses = metaClasses;
194+
return this;
195+
}
196+
170197
public ClassDescriptorBuilder withSupportsGenerics(boolean supportsGenerics) {
171198
this.supportsGenerics = supportsGenerics;
172199
return this;
@@ -178,8 +205,8 @@ public ClassDescriptorBuilder withIsSelf(boolean isSelf) {
178205
}
179206

180207
public ClassDescriptor build() {
181-
return new ClassDescriptor(name, fullyQualifiedName, superClasses, members, hasDecorators, definitionLocation,
182-
hasSuperClassWithoutDescriptor, hasMetaClass, metaclassFQN, supportsGenerics, isSelf);
208+
return new ClassDescriptor(name, fullyQualifiedName, superClasses, members, attributes, hasDecorators, definitionLocation,
209+
hasSuperClassWithoutDescriptor, hasMetaClass, metaclassFQN, metaClasses, supportsGenerics, isSelf);
183210
}
184211
}
185212
}

python-frontend/src/main/java/org/sonar/python/index/DescriptorsToProtobuf.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,11 @@ private static DescriptorsProtos.ClassDescriptor toProtobuf(ClassDescriptor clas
8181
.setFullyQualifiedName(classDescriptor.fullyQualifiedName())
8282
.addAllSuperClasses(classDescriptor.superClasses())
8383
.setMembers(toProtobufDescriptorList(classDescriptor.members()))
84+
.setAttributes(toProtobufDescriptorList(classDescriptor.attributes()))
8485
.setHasDecorators(classDescriptor.hasDecorators())
8586
.setHasSuperClassWithoutDescriptor(classDescriptor.hasSuperClassWithoutDescriptor())
8687
.setHasMetaClass(classDescriptor.hasMetaClass())
88+
.setMetaClasses(toProtobufDescriptorList(classDescriptor.metaClasses()))
8789
.setSupportsGenerics(classDescriptor.supportsGenerics());
8890
LocationInFile definitionLocation = classDescriptor.definitionLocation();
8991
if (definitionLocation != null) {
@@ -222,16 +224,20 @@ private static ClassDescriptor fromProtobuf(DescriptorsProtos.ClassDescriptor cl
222224
LocationInFile definitionLocation = classDescriptorProto.hasDefinitionLocation() ? fromProtobuf(classDescriptorProto.getDefinitionLocation()) : null;
223225
String fullyQualifiedName = classDescriptorProto.getFullyQualifiedName();
224226
Set<Descriptor> members = fromProtobufDescriptorList(classDescriptorProto.getMembers());
227+
List<Descriptor> attributes = fromProtobufDescriptorListAsList(classDescriptorProto.getAttributes());
228+
List<Descriptor> metaClasses = fromProtobufDescriptorListAsList(classDescriptorProto.getMetaClasses());
225229
return new ClassDescriptor.ClassDescriptorBuilder()
226230
.withName(classDescriptorProto.getName())
227231
.withFullyQualifiedName(fullyQualifiedName)
228232
.withSuperClasses(new ArrayList<>(classDescriptorProto.getSuperClassesList()))
229233
.withMembers(members)
234+
.withAttributes(attributes)
230235
.withHasDecorators(classDescriptorProto.getHasDecorators())
231236
.withDefinitionLocation(definitionLocation)
232237
.withHasSuperClassWithoutDescriptor(classDescriptorProto.getHasSuperClassWithoutDescriptor())
233238
.withHasMetaClass(classDescriptorProto.getHasMetaClass())
234239
.withMetaclassFQN(metaclassFQN)
240+
.withMetaClasses(metaClasses)
235241
.withSupportsGenerics(classDescriptorProto.getSupportsGenerics())
236242
.build();
237243
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,14 @@ public ClassTypeBuilder withSuperClasses(PythonType... types) {
8484
public List<PythonType> metaClasses() {
8585
return metaClasses;
8686
}
87+
88+
public ClassTypeBuilder withAttributes(List<PythonType> attributes) {
89+
this.attributes.addAll(attributes);
90+
return this;
91+
}
92+
93+
public ClassTypeBuilder withMetaClasses(List<PythonType> metaClasses) {
94+
this.metaClasses.addAll(metaClasses);
95+
return this;
96+
}
8797
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.sonar.python.semantic.v2.converter;
1818

19+
import java.util.List;
1920
import org.sonar.plugins.python.api.types.v2.Member;
2021
import org.sonar.plugins.python.api.types.v2.PythonType;
2122
import org.sonar.plugins.python.api.types.v2.SelfType;
@@ -29,6 +30,7 @@ public class ClassDescriptorToPythonTypeConverter implements DescriptorToPythonT
2930
private static PythonType convert(ConversionContext ctx, ClassDescriptor from) {
3031
var typeBuilder = new ClassTypeBuilder(from.name(), from.fullyQualifiedName())
3132
.withIsGeneric(from.supportsGenerics())
33+
.withHasDecorators(from.hasDecorators())
3234
.withDefinitionLocation(from.definitionLocation());
3335

3436
from.superClasses().stream()
@@ -41,6 +43,16 @@ private static PythonType convert(ConversionContext ctx, ClassDescriptor from) {
4143
.map(TypeWrapper::of)
4244
.forEach(typeBuilder::addSuperClass);
4345

46+
List<PythonType> attributes = from.attributes().stream()
47+
.map(ctx::convert)
48+
.toList();
49+
typeBuilder.withAttributes(attributes);
50+
51+
List<PythonType> metaClasses = from.metaClasses().stream()
52+
.map(ctx::convert)
53+
.toList();
54+
typeBuilder.withMetaClasses(metaClasses);
55+
4456
var classType = typeBuilder.build();
4557
ctx.pushParent(classType);
4658
from.members()

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,14 @@ private static Descriptor convert(String moduleFqn, String parentFqn, String sym
189189
}
190190
}
191191

192+
List<Descriptor> attributeDescriptors = type.attributes().stream()
193+
.map(attr -> convertTypeParameter(moduleFqn, attr))
194+
.toList();
195+
196+
List<Descriptor> metaClassDescriptors = type.metaClasses().stream()
197+
.map(meta -> convertTypeParameter(moduleFqn, meta))
198+
.toList();
199+
192200
var metaclassFQN = type.metaClasses()
193201
.stream()
194202
.map(metaClass -> FullyQualifiedNameHelper.getFullyQualifiedName(metaClass).orElse(null))
@@ -201,11 +209,13 @@ private static Descriptor convert(String moduleFqn, String parentFqn, String sym
201209
.withFullyQualifiedName(symbolFqn)
202210
.withSuperClasses(superClasses)
203211
.withMembers(memberDescriptors)
212+
.withAttributes(attributeDescriptors)
204213
.withHasDecorators(type.hasDecorators())
205214
.withDefinitionLocation(type.definitionLocation().orElse(null))
206215
.withHasSuperClassWithoutDescriptor(hasSuperClassWithoutDescriptor)
207216
.withHasMetaClass(type.hasMetaClass())
208217
.withMetaclassFQN(metaclassFQN)
218+
.withMetaClasses(metaClassDescriptors)
209219
.withSupportsGenerics(type.isGeneric())
210220
.withIsSelf(isSelf)
211221
.build();

python-frontend/src/main/protobuf/descriptors.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ message ClassDescriptor {
4444
optional string metaClassFQN = 12;
4545
bool supportsGenerics = 13;
4646
DescriptorList members = 14;
47+
DescriptorList attributes = 15;
48+
DescriptorList metaClasses = 16;
4749
}
4850

4951
message ParameterDescriptor {

python-frontend/src/test/java/org/sonar/plugins/python/api/types/v2/ClassTypeTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@
4242
import org.sonar.python.semantic.SymbolUtils;
4343
import org.sonar.python.semantic.v2.ClassTypeBuilder;
4444
import org.sonar.python.semantic.v2.SymbolTableBuilderV2;
45+
import org.sonar.python.semantic.v2.TestProject;
4546
import org.sonar.python.semantic.v2.TypeInferenceV2;
4647
import org.sonar.python.semantic.v2.typetable.ProjectLevelTypeTable;
4748

4849
import static org.assertj.core.api.Assertions.assertThat;
50+
import static org.assertj.core.api.InstanceOfAssertFactories.type;
4951
import static org.sonar.python.PythonTestUtils.parse;
5052
import static org.sonar.python.PythonTestUtils.parseWithoutSymbols;
5153
import static org.sonar.python.PythonTestUtils.pythonFile;
@@ -807,6 +809,36 @@ void is_compatible_with_unknown_type() {
807809
assertThat(classA.isCompatibleWith(PythonType.UNKNOWN)).isEqualTo(TriBool.UNKNOWN);
808810
}
809811

812+
@Test
813+
void is_compatible_with_ignore_has_decorators_for_stdlib_types() {
814+
var collectionType = PROJECT_LEVEL_TYPE_TABLE.getType("typing.Collection");
815+
var objectType = PROJECT_LEVEL_TYPE_TABLE.getType("object");
816+
817+
var collectionClassType = assertThat(collectionType).asInstanceOf(type(ClassType.class)).actual();
818+
assertThat(objectType).isNotEqualTo(PythonType.UNKNOWN);
819+
820+
assertThat(collectionClassType.hasDecorators()).isTrue();
821+
assertThat(collectionClassType.isCompatibleWith(objectType)).isEqualTo(TriBool.TRUE);
822+
}
823+
824+
@Test
825+
void is_compatible_with_ignore_has_decorators_for_user_defined_types() {
826+
var project = new TestProject();
827+
project.addModule("my_module.py", """
828+
@decorator
829+
class Test: ...
830+
""");
831+
832+
var myClassType = project.projectLevelTypeTable().getType("my_module.Test");
833+
var myClassClassType = assertThat(myClassType).asInstanceOf(type(ClassType.class)).actual();
834+
835+
var objectType = PROJECT_LEVEL_TYPE_TABLE.getType("object");
836+
assertThat(objectType).isNotEqualTo(PythonType.UNKNOWN);
837+
838+
assertThat(myClassClassType.hasDecorators()).isTrue();
839+
assertThat(myClassClassType.isCompatibleWith(objectType)).isEqualTo(TriBool.UNKNOWN);
840+
}
841+
810842
@Test
811843
void type_annotations_scope() {
812844
FileInput fileInput = PythonTestUtils.parse(

python-frontend/src/test/java/org/sonar/plugins/python/api/types/v2/UnionTypeTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
import org.sonar.plugins.python.api.TriBool;
2727
import org.sonar.plugins.python.api.tree.ExpressionStatement;
2828
import org.sonar.plugins.python.api.tree.FileInput;
29+
import org.sonar.plugins.python.api.types.v2.UnknownType.UnresolvedImportType;
2930
import org.sonar.python.semantic.v2.FunctionTypeBuilder;
3031
import org.sonar.python.semantic.v2.LazyTypesContext;
31-
import org.sonar.plugins.python.api.types.v2.UnknownType.UnresolvedImportType;
3232
import org.sonar.python.types.v2.LazyType;
3333

3434
import static org.assertj.core.api.Assertions.assertThat;
@@ -204,4 +204,10 @@ void is_compatible_with_object() {
204204
var objectType = BUILTINS.resolveMember("object").get();
205205
assertThat(unionType.isCompatibleWith(objectType)).isEqualTo(TriBool.TRUE);
206206
}
207+
208+
@Test
209+
void is_str_compatible_with_object() {
210+
var objectType = BUILTINS.resolveMember("object").get();
211+
assertThat(STR_TYPE.isCompatibleWith(objectType)).isEqualTo(TriBool.TRUE);
212+
}
207213
}

0 commit comments

Comments
 (0)