Skip to content

Commit 7a5edf1

Browse files
andreaguarinoguillaume-dequenne
authored andcommitted
SONARPY-962 Fix fully qualified name of methods of class symbols inheriting from private typeshed symbols
1 parent d7183cf commit 7a5edf1

File tree

5 files changed

+75
-15
lines changed

5 files changed

+75
-15
lines changed

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.sonar.python.types.TypeShed;
4646
import org.sonar.python.types.protobuf.SymbolsProtos;
4747

48+
import static org.sonar.python.semantic.SymbolUtils.isPrivateName;
4849
import static org.sonar.python.semantic.SymbolUtils.pathOf;
4950
import static org.sonar.python.tree.TreeUtils.locationInFile;
5051
import static org.sonar.python.types.TypeShed.isValidForProjectPythonVersion;
@@ -54,6 +55,7 @@ public class ClassSymbolImpl extends SymbolImpl implements ClassSymbol {
5455

5556
private final List<Symbol> superClasses = new ArrayList<>();
5657
private List<String> superClassesFqns = new ArrayList<>();
58+
private List<String> inlinedSuperClassFqn = new ArrayList<>();
5759
private Set<Symbol> allSuperClasses = null;
5860
private Set<Symbol> allSuperClassesIncludingAmbiguousSymbols = null;
5961
private boolean hasSuperClassWithoutSymbol = false;
@@ -143,15 +145,38 @@ public ClassSymbolImpl(SymbolsProtos.ClassSymbol classSymbolProto, String module
143145
classSymbolProto.getOverloadedMethodsList().stream()
144146
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
145147
.forEach(proto -> descriptorsByFqn.computeIfAbsent(proto.getFullname(), d -> new HashSet<>()).add(proto));
148+
149+
inlineInheritedMethodsFromPrivateClass(classSymbolProto.getSuperClassesList(), descriptorsByFqn);
150+
146151
for (Map.Entry<String, Set<Object>> entry : descriptorsByFqn.entrySet()) {
147-
Set<Symbol> symbols = symbolsFromProtobufDescriptors(entry.getValue(), true, moduleName);
152+
Set<Symbol> symbols = symbolsFromProtobufDescriptors(entry.getValue(), fullyQualifiedName, moduleName);
148153
methods.add(symbols.size() > 1 ? AmbiguousSymbolImpl.create(symbols) : symbols.iterator().next());
149154
}
150155
addMembers(methods);
151-
superClassesFqns = classSymbolProto.getSuperClassesList().stream().map(TypeShed::normalizedFqn).collect(Collectors.toList());
156+
superClassesFqns.addAll(classSymbolProto.getSuperClassesList().stream().map(TypeShed::normalizedFqn).collect(Collectors.toList()));
157+
superClassesFqns.removeAll(inlinedSuperClassFqn);
152158
validForPythonVersions = new HashSet<>(classSymbolProto.getValidForList());
153159
}
154160

161+
private void inlineInheritedMethodsFromPrivateClass(List<String> superClassesFqns, Map<String, Set<Object>> descriptorsByFqn) {
162+
for (String superClassFqn : superClassesFqns) {
163+
if (isPrivateName(superClassFqn)) {
164+
SymbolsProtos.ClassSymbol superClass = TypeShed.classDescriptorWithFQN(superClassFqn);
165+
if (superClass == null) return;
166+
inlinedSuperClassFqn.add(superClassFqn);
167+
for (SymbolsProtos.FunctionSymbol functionSymbol : superClass.getMethodsList()) {
168+
String methodFqn = this.fullyQualifiedName + "." + functionSymbol.getName();
169+
descriptorsByFqn.computeIfAbsent(methodFqn, d -> new HashSet<>()).add(functionSymbol);
170+
}
171+
for (SymbolsProtos.OverloadedFunctionSymbol functionSymbol : superClass.getOverloadedMethodsList()) {
172+
String methodFqn = this.fullyQualifiedName + "." + functionSymbol.getName();
173+
descriptorsByFqn.computeIfAbsent(methodFqn, d -> new HashSet<>()).add(functionSymbol);
174+
}
175+
this.superClassesFqns.addAll(superClass.getSuperClassesList());
176+
}
177+
}
178+
}
179+
155180
@Override
156181
public ClassSymbolImpl copyWithoutUsages() {
157182
ClassSymbolImpl copiedClassSymbol = new ClassSymbolImpl(name(), this);

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,17 @@ public class FunctionSymbolImpl extends SymbolImpl implements FunctionSymbol {
8888
}
8989

9090
public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, String moduleName) {
91-
this(functionSymbolProto, false, functionSymbolProto.getValidForList(), moduleName);
91+
this(functionSymbolProto, null, functionSymbolProto.getValidForList(), moduleName);
9292
}
9393

94-
public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, boolean insideClass, String moduleName) {
95-
this(functionSymbolProto, insideClass, functionSymbolProto.getValidForList(), moduleName);
94+
public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, @Nullable String containerClassFqn, String moduleName) {
95+
this(functionSymbolProto, containerClassFqn, functionSymbolProto.getValidForList(), moduleName);
9696
}
9797

98-
public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, boolean insideClass, List<String> validFor, String moduleName) {
99-
super(functionSymbolProto.getName(), TypeShed.normalizedFqn(functionSymbolProto.getFullyQualifiedName(), moduleName, functionSymbolProto.getName()));
98+
public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, @Nullable String containerClassFqn, List<String> validFor, String moduleName) {
99+
super(functionSymbolProto.getName(), TypeShed.normalizedFqn(functionSymbolProto.getFullyQualifiedName(), moduleName, functionSymbolProto.getName(), containerClassFqn));
100100
setKind(Kind.FUNCTION);
101-
isInstanceMethod = insideClass && !functionSymbolProto.getIsStatic() && !functionSymbolProto.getIsClassMethod();
101+
isInstanceMethod = containerClassFqn != null && !functionSymbolProto.getIsStatic() && !functionSymbolProto.getIsClassMethod();
102102
isAsynchronous = functionSymbolProto.getIsAsynchronous();
103103
hasDecorators = functionSymbolProto.getHasDecorators();
104104
decorators = functionSymbolProto.getResolvedDecoratorNamesList();

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,4 +304,8 @@ public static Set<Symbol> flattenAmbiguousSymbols(Set<Symbol> symbols) {
304304
}
305305
return alternatives;
306306
}
307+
308+
public static boolean isPrivateName(String name) {
309+
return name.startsWith("_") && !name.startsWith("__");
310+
}
307311
}

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

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ public static String normalizedFqn(String fqn) {
179179
}
180180

181181
public static String normalizedFqn(String fqn, String moduleName, String localName) {
182+
return normalizedFqn(fqn, moduleName, localName, null);
183+
}
184+
185+
public static String normalizedFqn(String fqn, String moduleName, String localName, @Nullable String containerClassFqn) {
186+
if (containerClassFqn != null) return containerClassFqn + "." + localName;
182187
if (fqn.startsWith(moduleName)) return normalizedFqn(fqn);
183188
return moduleName + "." + localName;
184189
}
@@ -192,20 +197,20 @@ public static boolean isValidForProjectPythonVersion(List<String> validForPython
192197
return !intersection.isEmpty();
193198
}
194199

195-
public static Set<Symbol> symbolsFromProtobufDescriptors(Set<Object> protobufDescriptors, boolean isInsideClass, String moduleName) {
200+
public static Set<Symbol> symbolsFromProtobufDescriptors(Set<Object> protobufDescriptors, @Nullable String containerClassFqn, String moduleName) {
196201
Set<Symbol> symbols = new HashSet<>();
197202
for (Object descriptor : protobufDescriptors) {
198203
if (descriptor instanceof SymbolsProtos.ClassSymbol) {
199204
symbols.add(new ClassSymbolImpl(((SymbolsProtos.ClassSymbol) descriptor), moduleName));
200205
}
201206
if (descriptor instanceof SymbolsProtos.FunctionSymbol) {
202-
symbols.add(new FunctionSymbolImpl(((SymbolsProtos.FunctionSymbol) descriptor), isInsideClass, moduleName));
207+
symbols.add(new FunctionSymbolImpl(((SymbolsProtos.FunctionSymbol) descriptor), containerClassFqn, moduleName));
203208
}
204209
if (descriptor instanceof OverloadedFunctionSymbol) {
205210
if (((OverloadedFunctionSymbol) descriptor).getDefinitionsList().size() < 2) {
206211
throw new IllegalStateException("Overloaded function symbols should have at least two definitions.");
207212
}
208-
symbols.add(fromOverloadedFunction(((OverloadedFunctionSymbol) descriptor), isInsideClass, moduleName));
213+
symbols.add(fromOverloadedFunction(((OverloadedFunctionSymbol) descriptor), containerClassFqn, moduleName));
209214
}
210215
if (descriptor instanceof SymbolsProtos.VarSymbol) {
211216
SymbolsProtos.VarSymbol varSymbol = (SymbolsProtos.VarSymbol) descriptor;
@@ -220,6 +225,24 @@ public static Set<Symbol> symbolsFromProtobufDescriptors(Set<Object> protobufDes
220225
return symbols;
221226
}
222227

228+
229+
@CheckForNull
230+
public static SymbolsProtos.ClassSymbol classDescriptorWithFQN(String fullyQualifiedName) {
231+
String[] fqnSplitByDot = fullyQualifiedName.split("\\.");
232+
String symbolLocalNameFromFqn = fqnSplitByDot[fqnSplitByDot.length - 1];
233+
String moduleName = Arrays.stream(fqnSplitByDot, 0, fqnSplitByDot.length - 1).collect(Collectors.joining("."));
234+
InputStream resource = TypeShed.class.getResourceAsStream(PROTOBUF + moduleName + ".protobuf");
235+
if (resource == null) return null;
236+
ModuleSymbol moduleSymbol = deserializedModule(moduleName, resource);
237+
if (moduleSymbol == null) return null;
238+
for (SymbolsProtos.ClassSymbol classSymbol : moduleSymbol.getClassesList()) {
239+
if (classSymbol.getName().equals(symbolLocalNameFromFqn)) {
240+
return classSymbol;
241+
}
242+
}
243+
return null;
244+
}
245+
223246
//================================================================================
224247
// Private methods
225248
//================================================================================
@@ -260,7 +283,7 @@ private static Map<String, Symbol> searchTypeShedForModule(String moduleName) {
260283
* This method sort ambiguous symbol by python version and returns the one which is valid for
261284
* the most recent Python version.
262285
*/
263-
private static Symbol disambiguateWithLatestPythonSymbol(Set<Symbol> alternatives) {
286+
static Symbol disambiguateWithLatestPythonSymbol(Set<Symbol> alternatives) {
264287
int max = Integer.MIN_VALUE;
265288
Symbol latestPythonSymbol = null;
266289
for (Symbol alternative : alternatives) {
@@ -322,7 +345,7 @@ static Map<String, Symbol> getSymbolsFromProtobufModule(@Nullable ModuleSymbol m
322345

323346
for (Map.Entry<String, Set<Object>> entry : descriptorsByName.entrySet()) {
324347
String name = entry.getKey();
325-
Set<Symbol> symbols = symbolsFromProtobufDescriptors(entry.getValue(), false, moduleSymbol.getFullyQualifiedName());
348+
Set<Symbol> symbols = symbolsFromProtobufDescriptors(entry.getValue(), null, moduleSymbol.getFullyQualifiedName());
326349
Symbol disambiguatedSymbol = disambiguateSymbolsWithSameName(name, symbols, moduleSymbol.getFullyQualifiedName());
327350
deserializedSymbols.put(name, disambiguatedSymbol);
328351
}
@@ -356,9 +379,9 @@ private static boolean haveAllTheSameFqn(Set<Symbol> symbols) {
356379
return firstFqn != null && symbols.stream().map(Symbol::fullyQualifiedName).allMatch(firstFqn::equals);
357380
}
358381

359-
private static AmbiguousSymbol fromOverloadedFunction(OverloadedFunctionSymbol overloadedFunctionSymbol, boolean isInsideClass, String moduleName) {
382+
private static AmbiguousSymbol fromOverloadedFunction(OverloadedFunctionSymbol overloadedFunctionSymbol, @Nullable String containerClassFqn, String moduleName) {
360383
Set<Symbol> overloadedSymbols = overloadedFunctionSymbol.getDefinitionsList().stream()
361-
.map(def -> new FunctionSymbolImpl(def, isInsideClass, overloadedFunctionSymbol.getValidForList(), moduleName))
384+
.map(def -> new FunctionSymbolImpl(def, containerClassFqn, overloadedFunctionSymbol.getValidForList(), moduleName))
362385
.collect(Collectors.toSet());
363386
return AmbiguousSymbolImpl.create(overloadedSymbols);
364387
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,4 +520,12 @@ public void symbol_from_submodule_access() {
520520
Symbol samefileFromSubModule = osPath.get("samefile");
521521
assertThat(samefileFromSubModule).isSameAs(samefile);
522522
}
523+
524+
@Test
525+
public void typeshed_private_modules_should_not_affect_fqn() {
526+
Map<String, Symbol> socketModule = symbolsForModule("socket");
527+
ClassSymbol socket = (ClassSymbol) TypeShed.disambiguateWithLatestPythonSymbol(((AmbiguousSymbol) socketModule.get("socket")).alternatives());
528+
assertThat(socket.declaredMembers()).extracting(Symbol::name, Symbol::fullyQualifiedName).contains(tuple("connect", "socket.socket.connect"));
529+
assertThat(socket.superClasses()).extracting(Symbol::fullyQualifiedName).containsExactly("object");
530+
}
523531
}

0 commit comments

Comments
 (0)