Skip to content

Commit 0d065f1

Browse files
joke1196sonartech
authored andcommitted
SONARPY-3223 Serialize and deserialize typing.Self to and from protbuf stubs (#690)
GitOrigin-RevId: 7dbbfca4acb57d88082aac4015cc83644469cae8
1 parent a0d9672 commit 0d065f1

File tree

5 files changed

+134
-36
lines changed

5 files changed

+134
-36
lines changed

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

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@
4343
*/
4444
@Beta
4545
public final class SelfType implements PythonType {
46-
private final ClassType innerType;
46+
private final TypeWrapper typeWrapper;
4747

48-
private SelfType(ClassType innerType) {
49-
this.innerType = innerType;
48+
private SelfType(TypeWrapper typeWrapper) {
49+
this.typeWrapper = typeWrapper;
5050
}
5151

5252
private static String formatWithBrackets(String prefix, String content) {
@@ -76,21 +76,21 @@ public static PythonType of(@Nullable PythonType type) {
7676
if (type == null || type == PythonType.UNKNOWN) {
7777
return PythonType.UNKNOWN;
7878
}
79-
79+
8080
if (type instanceof SelfType) {
8181
return type;
8282
}
83-
83+
8484
if (type instanceof ClassType classType) {
85-
return new SelfType(classType);
85+
return new SelfType(TypeWrapper.of(classType));
8686
}
87-
87+
8888
if (type instanceof UnionType unionType) {
8989
return UnionType.or(unionType.candidates().stream()
9090
.map(SelfType::of)
9191
.toList());
9292
}
93-
93+
9494
if (type instanceof ObjectType objectType) {
9595
PythonType unwrapped = objectType.unwrappedType();
9696
PythonType wrappedType = of(unwrapped);
@@ -102,82 +102,92 @@ public static PythonType of(@Nullable PythonType type) {
102102
}
103103
return PythonType.UNKNOWN;
104104
}
105+
return PythonType.UNKNOWN;
106+
}
105107

108+
public PythonType innerType() {
109+
var type = typeWrapper.type();
110+
if (type instanceof ClassType) {
111+
return type;
112+
}
106113
return PythonType.UNKNOWN;
107114
}
108115

109-
public ClassType innerType() {
110-
return innerType;
116+
public TypeWrapper typeWrapper() {
117+
return typeWrapper;
111118
}
112119

113120
@Override
114121
public String name() {
115-
return formatAsSelf(innerType.name());
122+
return formatAsSelf(typeWrapper.type().name());
116123
}
117124

118125
@Override
119126
public Optional<String> displayName() {
120-
return innerType.displayName()
127+
return typeWrapper.type().displayName()
121128
.map(SelfType::formatAsSelf);
122129
}
123130

124131
@Override
125132
public Optional<String> instanceDisplayName() {
126-
return innerType.instanceDisplayName()
133+
return typeWrapper.type().instanceDisplayName()
127134
.map(SelfType::formatAsSelf);
128135
}
129136

130137
@Override
131138
public boolean isCompatibleWith(PythonType another) {
132-
return innerType.isCompatibleWith(another);
139+
return typeWrapper.type().isCompatibleWith(another);
133140
}
134141

135142
@Override
136143
public String key() {
137-
return formatAsSelf(innerType.key());
144+
return formatAsSelf(typeWrapper.type().key());
138145
}
139146

140147
@Override
141148
public Optional<PythonType> resolveMember(String memberName) {
142-
return innerType.resolveMember(memberName);
149+
return typeWrapper.type().resolveMember(memberName);
143150
}
144151

145152
@Override
146153
public TriBool hasMember(String memberName) {
147-
return innerType.hasMember(memberName);
154+
return typeWrapper.type().hasMember(memberName);
148155
}
149156

150157
@Override
151158
public Optional<LocationInFile> definitionLocation() {
152-
return innerType.definitionLocation();
159+
return typeWrapper.type().definitionLocation();
153160
}
154161

155162
@Override
156163
public PythonType unwrappedType() {
157-
return innerType.unwrappedType();
164+
return typeWrapper.type().unwrappedType();
158165
}
159166

160167
@Override
161168
public TypeSource typeSource() {
162-
return innerType.typeSource();
169+
return typeWrapper.type().typeSource();
163170
}
164171

165172
@Override
166173
public boolean equals(Object o) {
167-
if (this == o) return true;
168-
if (o == null || getClass() != o.getClass()) return false;
174+
if (this == o) {
175+
return true;
176+
}
177+
if (o == null || getClass() != o.getClass()) {
178+
return false;
179+
}
169180
SelfType selfType = (SelfType) o;
170-
return Objects.equals(innerType, selfType.innerType);
181+
return Objects.equals(typeWrapper, selfType.typeWrapper);
171182
}
172183

173184
@Override
174185
public int hashCode() {
175-
return Objects.hash(SelfType.class, innerType);
186+
return Objects.hash(SelfType.class, typeWrapper);
176187
}
177188

178189
@Override
179190
public String toString() {
180-
return formatWithBrackets("SelfType", innerType.toString());
191+
return formatWithBrackets("SelfType", typeWrapper.toString());
181192
}
182193
}
183-

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

Lines changed: 10 additions & 8 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 com.google.common.annotations.VisibleForTesting;
1920
import java.util.ArrayList;
2021
import java.util.List;
2122
import java.util.Objects;
@@ -116,13 +117,16 @@ private static Descriptor convert(String moduleFqn, FunctionType type) {
116117
type.hasDecorators(),
117118
type.definitionLocation().orElse(null),
118119
null,
119-
null
120-
);
120+
null);
121121
}
122122

123-
private static Descriptor convert(String moduleFqn, String parentFqn, String symbolName, SelfType selfType) {
123+
@VisibleForTesting
124+
static Descriptor convert(String moduleFqn, String parentFqn, String symbolName, SelfType selfType) {
124125
var innerType = selfType.innerType();
125-
return convert(moduleFqn, parentFqn, symbolName, innerType, true);
126+
if (!(innerType instanceof ClassType classType)) {
127+
throw new IllegalStateException("SelfType's innerType is not a ClassType " + selfType.name());
128+
}
129+
return convert(moduleFqn, parentFqn, symbolName, classType, true);
126130
}
127131

128132
private static Descriptor convert(String moduleFqn, String parentFqn, String symbolName, ClassType type, boolean isSelf) {
@@ -171,15 +175,13 @@ private static Descriptor convert(String moduleFqn, String parentFqn, String sym
171175
.collect(Collectors.toSet());
172176
return new AmbiguousDescriptor(symbolName,
173177
symbolFqn(moduleFqn, symbolName),
174-
candidates
175-
);
178+
candidates);
176179
}
177180

178181
private static Descriptor convert(String parentFqn, String symbolName, UnknownType.UnresolvedImportType type) {
179182
return new VariableDescriptor(symbolName,
180183
symbolFqn(parentFqn, symbolName),
181-
type.importPath()
182-
);
184+
type.importPath());
183185
}
184186

185187
private static FunctionDescriptor.Parameter convert(String moduleFqn, ParameterV2 parameter) {

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
import java.util.Set;
2121
import java.util.function.Predicate;
2222
import java.util.stream.Collectors;
23+
import org.sonar.plugins.python.api.types.v2.PythonType;
24+
import org.sonar.plugins.python.api.types.v2.SelfType;
2325
import org.sonar.python.index.TypeAnnotationDescriptor;
2426
import org.sonar.python.types.v2.LazyUnionType;
25-
import org.sonar.plugins.python.api.types.v2.PythonType;
2627

2728
public class TypeAnnotationToPythonTypeConverter {
2829

@@ -58,6 +59,13 @@ public PythonType convert(ConversionContext context, TypeAnnotationDescriptor ty
5859
// SONARPY-2179: This case only makes sense for parameter types, which are not supported yet
5960
return context.lazyTypesContext().getOrCreateLazyType("dict");
6061
case TYPE_VAR:
62+
if(Boolean.TRUE.equals(type.isSelf())){
63+
if(type.args().size() != 1){
64+
return PythonType.UNKNOWN;
65+
}
66+
PythonType innerType = convert(context, type.args().get(0));
67+
return SelfType.of(innerType);
68+
}
6169
return Optional.of(type)
6270
.filter(TypeAnnotationToPythonTypeConverter::filterTypeVar)
6371
.map(TypeAnnotationDescriptor::fullyQualifiedName)
@@ -78,7 +86,6 @@ public PythonType convert(ConversionContext context, TypeAnnotationDescriptor ty
7886
public static boolean filterTypeVar(TypeAnnotationDescriptor type) {
7987
return Optional.of(type)
8088
// Filtering self returning methods until the SONARPY-1472 will be solved
81-
.filter(Predicate.not(TypeAnnotationDescriptor::isSelf))
8289
.map(TypeAnnotationDescriptor::fullyQualifiedName)
8390
.filter(Predicate.not(String::isEmpty))
8491
// We ignore TypeVar referencing "builtins.object" or "object" to avoid false positives

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,16 @@ void selfTypeOfObjectTypeWithAttributes() {
189189
assertThat(innerSelfType.innerType()).isEqualTo(classType);
190190
}
191191

192+
@Test
193+
void selfTypetypeWrapper() {
194+
ClassType classType = (ClassType) INT_TYPE;
195+
SelfType selfType = (SelfType) SelfType.of(classType);
196+
197+
TypeWrapper wrappedType = selfType.typeWrapper();
198+
assertThat(wrappedType).isNotNull();
199+
assertThat(wrappedType.type()).isEqualTo(classType.unwrappedType());
200+
}
201+
192202
@Test
193203
void selfTypeOfUnionType() {
194204
ClassType classTypeA = (ClassType) INT_TYPE;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.python.semantic.v2.converter;
18+
19+
import java.util.List;
20+
import org.assertj.core.api.Assertions;
21+
import org.junit.jupiter.api.Test;
22+
import org.mockito.Mockito;
23+
import org.sonar.python.index.TypeAnnotationDescriptor;
24+
import org.sonar.python.semantic.v2.LazyTypesContext;
25+
import org.sonar.plugins.python.api.types.v2.PythonType;
26+
27+
class TypeAnnotationToPythonTypeConverterTest {
28+
29+
@Test
30+
void typeVarWithIncorrectArgsAndIsSelfTrueReturnsUnknown() {
31+
var lazyTypesContext = Mockito.mock(LazyTypesContext.class);
32+
var ctx = Mockito.mock(ConversionContext.class);
33+
var converter = new TypeAnnotationToPythonTypeConverter();
34+
35+
Mockito.when(ctx.lazyTypesContext()).thenReturn(lazyTypesContext);
36+
37+
// Create a TYPE_VAR TypeAnnotationDescriptor with isSelf=true and multiple args
38+
var innerArg1 = new TypeAnnotationDescriptor("int", TypeAnnotationDescriptor.TypeKind.INSTANCE, List.of(), "int", false);
39+
var innerArg2 = new TypeAnnotationDescriptor("str", TypeAnnotationDescriptor.TypeKind.INSTANCE, List.of(), "str", false);
40+
var typeVarDescriptor = new TypeAnnotationDescriptor(
41+
"TypeVar",
42+
TypeAnnotationDescriptor.TypeKind.TYPE_VAR,
43+
List.of(innerArg1, innerArg2),
44+
"typing.TypeVar",
45+
true
46+
);
47+
48+
var result = converter.convert(ctx, typeVarDescriptor);
49+
50+
Assertions.assertThat(result)
51+
.as("TYPE_VAR with isSelf=true and args.size() > 1 should return UNKNOWN")
52+
.isEqualTo(PythonType.UNKNOWN);
53+
54+
55+
// Create a TYPE_VAR TypeAnnotationDescriptor with isSelf=true and zero args
56+
typeVarDescriptor = new TypeAnnotationDescriptor(
57+
"TypeVar",
58+
TypeAnnotationDescriptor.TypeKind.TYPE_VAR,
59+
List.of(),
60+
"typing.TypeVar",
61+
true
62+
);
63+
result = converter.convert(ctx, typeVarDescriptor);
64+
65+
Assertions.assertThat(result)
66+
.as("TYPE_VAR with isSelf=true and args.size() == 0 should return UNKNOWN")
67+
.isEqualTo(PythonType.UNKNOWN);
68+
}
69+
}

0 commit comments

Comments
 (0)