Skip to content

Commit 81be222

Browse files
ghislainpiotguillaume-dequenne
authored andcommitted
SONARPY-2178 Add awareness of the Python version & disambiguation to the DescriptorToPythonTypeConverters (#2024)
1 parent 9f8560a commit 81be222

File tree

8 files changed

+152
-90
lines changed

8 files changed

+152
-90
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020
package org.sonar.python.semantic.v2.typeshed;
2121

22-
import java.util.function.Function;
22+
import java.util.Set;
2323
import java.util.stream.Collectors;
2424
import java.util.stream.Stream;
2525
import org.sonar.python.index.ClassDescriptor;
@@ -31,13 +31,15 @@ public class ClassSymbolToDescriptorConverter {
3131
private final VarSymbolToDescriptorConverter varConverter;
3232
private final FunctionSymbolToDescriptorConverter functionConverter;
3333
private final OverloadedFunctionSymbolToDescriptorConverter overloadedFunctionConverter;
34+
private final Set<String> projectPythonVersions;
3435

3536
public ClassSymbolToDescriptorConverter(VarSymbolToDescriptorConverter varConverter,
3637
FunctionSymbolToDescriptorConverter functionConverter,
37-
OverloadedFunctionSymbolToDescriptorConverter overloadedFunctionConverter) {
38+
OverloadedFunctionSymbolToDescriptorConverter overloadedFunctionConverter, Set<String> projectPythonVersions) {
3839
this.varConverter = varConverter;
3940
this.functionConverter = functionConverter;
4041
this.overloadedFunctionConverter = overloadedFunctionConverter;
42+
this.projectPythonVersions = projectPythonVersions;
4143
}
4244

4345
public ClassDescriptor convert(SymbolsProtos.ClassSymbol classSymbol) {
@@ -47,20 +49,21 @@ public ClassDescriptor convert(SymbolsProtos.ClassSymbol classSymbol) {
4749

4850
var variableDescriptors = classSymbol.getAttributesList()
4951
.stream()
52+
.filter(d -> ProtoUtils.isValidForPythonVersion(d.getValidForList(), projectPythonVersions))
5053
.map(varConverter::convert);
5154

5255
var functionDescriptors = classSymbol.getMethodsList()
5356
.stream()
57+
.filter(d -> ProtoUtils.isValidForPythonVersion(d.getValidForList(), projectPythonVersions))
5458
.map(s -> functionConverter.convert(s, true));
5559

5660
var overloadedFunctionDescriptors = classSymbol.getOverloadedMethodsList()
5761
.stream()
62+
.filter(d -> ProtoUtils.isValidForPythonVersion(d.getValidForList(), projectPythonVersions))
5863
.map(s -> overloadedFunctionConverter.convert(s, true));
5964

60-
var members = Stream.of(variableDescriptors, functionDescriptors, overloadedFunctionDescriptors)
61-
.flatMap(Function.identity())
62-
.map(Descriptor.class::cast)
63-
.collect(Collectors.toSet());
65+
var members = ProtoUtils.disambiguateByName(Stream.of(variableDescriptors, functionDescriptors, overloadedFunctionDescriptors))
66+
.values().stream().map(Descriptor.class::cast).collect(Collectors.toSet());
6467

6568
return new ClassDescriptor.ClassDescriptorBuilder()
6669
.withName(classSymbol.getName())

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

Lines changed: 19 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,13 @@
1919
*/
2020
package org.sonar.python.semantic.v2.typeshed;
2121

22-
import java.util.HashMap;
23-
import java.util.HashSet;
24-
import java.util.List;
2522
import java.util.Map;
2623
import java.util.Set;
24+
import java.util.stream.Collectors;
25+
import java.util.stream.Stream;
2726
import javax.annotation.CheckForNull;
2827
import javax.annotation.Nullable;
29-
import org.sonar.plugins.python.api.ProjectPythonVersion;
3028
import org.sonar.plugins.python.api.PythonVersionUtils;
31-
import org.sonar.python.index.AmbiguousDescriptor;
3229
import org.sonar.python.index.Descriptor;
3330
import org.sonar.python.index.ModuleDescriptor;
3431
import org.sonar.python.types.protobuf.SymbolsProtos;
@@ -38,14 +35,14 @@ public class ModuleSymbolToDescriptorConverter {
3835
private final FunctionSymbolToDescriptorConverter functionConverter;
3936
private final VarSymbolToDescriptorConverter variableConverter;
4037
private final OverloadedFunctionSymbolToDescriptorConverter overloadedFunctionConverter;
41-
private final Set<String> supportedPythonVersions;
38+
private final Set<String> projectPythonVersions;
4239

43-
public ModuleSymbolToDescriptorConverter() {
40+
public ModuleSymbolToDescriptorConverter(Set<PythonVersionUtils.Version> projectPythonVersions) {
41+
this.projectPythonVersions = projectPythonVersions.stream().map(PythonVersionUtils.Version::serializedValue).collect(Collectors.toSet());
4442
functionConverter = new FunctionSymbolToDescriptorConverter();
4543
variableConverter = new VarSymbolToDescriptorConverter();
4644
overloadedFunctionConverter = new OverloadedFunctionSymbolToDescriptorConverter(functionConverter);
47-
classConverter = new ClassSymbolToDescriptorConverter(variableConverter, functionConverter, overloadedFunctionConverter);
48-
supportedPythonVersions = ProjectPythonVersion.currentVersionValues();
45+
classConverter = new ClassSymbolToDescriptorConverter(variableConverter, functionConverter, overloadedFunctionConverter, this.projectPythonVersions);
4946
}
5047

5148
@CheckForNull
@@ -62,56 +59,28 @@ public ModuleDescriptor convert(@Nullable SymbolsProtos.ModuleSymbol moduleSymbo
6259
}
6360

6461
private Map<String, Descriptor> getModuleDescriptors(SymbolsProtos.ModuleSymbol moduleSymbol) {
65-
Map<String, Set<Descriptor>> protoSymbolsByName = new HashMap<>();
66-
moduleSymbol.getClassesList()
62+
var classesStream = moduleSymbol.getClassesList()
6763
.stream()
68-
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
64+
.filter(d -> ProtoUtils.isValidForPythonVersion(d.getValidForList(), projectPythonVersions))
6965
.map(classConverter::convert)
70-
.forEach(descriptor -> protoSymbolsByName.computeIfAbsent(descriptor.name(), d -> new HashSet<>()).add(descriptor));
71-
moduleSymbol.getFunctionsList()
66+
.map(Descriptor.class::cast);
67+
var functionsStream = moduleSymbol.getFunctionsList()
7268
.stream()
73-
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
69+
.filter(d -> ProtoUtils.isValidForPythonVersion(d.getValidForList(), projectPythonVersions))
7470
.map(functionConverter::convert)
75-
.forEach(descriptor -> protoSymbolsByName.computeIfAbsent(descriptor.name(), d -> new HashSet<>()).add(descriptor));
76-
moduleSymbol.getOverloadedFunctionsList()
71+
.map(Descriptor.class::cast);
72+
var overloadedFunctionsStream = moduleSymbol.getOverloadedFunctionsList()
7773
.stream()
78-
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
74+
.filter(d -> ProtoUtils.isValidForPythonVersion(d.getValidForList(), projectPythonVersions))
7975
.map(overloadedFunctionConverter::convert)
80-
.forEach(descriptor -> protoSymbolsByName.computeIfAbsent(descriptor.name(), d -> new HashSet<>()).add(descriptor));
81-
moduleSymbol.getVarsList()
76+
.map(Descriptor.class::cast);
77+
var variablesStream = moduleSymbol.getVarsList()
8278
.stream()
83-
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
79+
.filter(d -> ProtoUtils.isValidForPythonVersion(d.getValidForList(), projectPythonVersions))
8480
.map(variableConverter::convert)
85-
.forEach(descriptor -> protoSymbolsByName.computeIfAbsent(descriptor.name(), d -> new HashSet<>()).add(descriptor));
81+
.map(Descriptor.class::cast);
8682

87-
var descriptorsByName = new HashMap<String, Descriptor>();
88-
89-
protoSymbolsByName.forEach((name, descriptors) -> {
90-
Descriptor disambiguatedDescriptor = disambiguateSymbolsWithSameName(descriptors);
91-
descriptorsByName.put(name, disambiguatedDescriptor);
92-
});
93-
94-
return descriptorsByName;
95-
}
96-
97-
private static Descriptor disambiguateSymbolsWithSameName(Set<Descriptor> descriptors) {
98-
if (descriptors.size() > 1) {
99-
return AmbiguousDescriptor.create(descriptors);
100-
}
101-
return descriptors.iterator().next();
102-
}
103-
104-
private boolean isValidForProjectPythonVersion(List<String> validForPythonVersions) {
105-
if (validForPythonVersions.isEmpty()) {
106-
return true;
107-
}
108-
if (supportedPythonVersions.stream().allMatch(PythonVersionUtils.Version.V_312.serializedValue()::equals)
109-
&& validForPythonVersions.contains(PythonVersionUtils.Version.V_311.serializedValue())) {
110-
return true;
111-
}
112-
HashSet<String> intersection = new HashSet<>(validForPythonVersions);
113-
intersection.retainAll(supportedPythonVersions);
114-
return !intersection.isEmpty();
83+
return ProtoUtils.disambiguateByName(Stream.of(classesStream, functionsStream, overloadedFunctionsStream, variablesStream));
11584
}
11685

11786
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ public class OverloadedFunctionSymbolToDescriptorConverter {
2828

2929
private final FunctionSymbolToDescriptorConverter functionConverter;
3030

31-
public OverloadedFunctionSymbolToDescriptorConverter(FunctionSymbolToDescriptorConverter functionConverter2) {
32-
functionConverter = functionConverter2;
31+
public OverloadedFunctionSymbolToDescriptorConverter(FunctionSymbolToDescriptorConverter functionConverter) {
32+
this.functionConverter = functionConverter;
3333
}
3434

3535
public AmbiguousDescriptor convert(SymbolsProtos.OverloadedFunctionSymbol overloadedFunctionSymbol) {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2024 SonarSource SA
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 GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.semantic.v2.typeshed;
21+
22+
import java.util.HashSet;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Set;
26+
import java.util.stream.Collectors;
27+
import java.util.stream.Stream;
28+
import org.sonar.plugins.python.api.PythonVersionUtils;
29+
import org.sonar.python.index.AmbiguousDescriptor;
30+
import org.sonar.python.index.Descriptor;
31+
32+
public class ProtoUtils {
33+
private ProtoUtils() {
34+
// utility class
35+
}
36+
37+
static Map<String, Descriptor> disambiguateByName(Stream<Stream<? extends Descriptor>> input) {
38+
return input.flatMap(i -> i)
39+
.collect(Collectors.groupingBy(
40+
Descriptor::name, Collectors.collectingAndThen(
41+
Collectors.toSet(),
42+
ProtoUtils::disambiguateSymbolsWithSameName)));
43+
}
44+
45+
private static Descriptor disambiguateSymbolsWithSameName(Set<Descriptor> descriptors) {
46+
if (descriptors.size() > 1) {
47+
return AmbiguousDescriptor.create(descriptors);
48+
}
49+
return descriptors.iterator().next();
50+
}
51+
52+
static boolean isValidForPythonVersion(List<String> validForPythonVersions, Set<String> supportedPythonVersions) {
53+
if (validForPythonVersions.isEmpty()) {
54+
return true;
55+
}
56+
// TODO: SONARPY-1522 - remove this workaround when we will have all the stubs for Python 3.12.
57+
if (supportedPythonVersions.stream().allMatch(PythonVersionUtils.Version.V_312.serializedValue()::equals)
58+
&& validForPythonVersions.contains(PythonVersionUtils.Version.V_311.serializedValue())) {
59+
return true;
60+
}
61+
HashSet<String> intersection = new HashSet<>(validForPythonVersions);
62+
intersection.retainAll(supportedPythonVersions);
63+
return !intersection.isEmpty();
64+
}
65+
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import javax.annotation.CheckForNull;
3232
import org.slf4j.Logger;
3333
import org.slf4j.LoggerFactory;
34+
import org.sonar.plugins.python.api.ProjectPythonVersion;
35+
import org.sonar.plugins.python.api.PythonVersionUtils;
3436
import org.sonar.python.index.ClassDescriptor;
3537
import org.sonar.python.index.Descriptor;
3638
import org.sonar.python.index.ModuleDescriptor;
@@ -60,7 +62,11 @@ public class TypeShedDescriptorsProvider {
6062
private final Map<String, Map<String, Descriptor>> cachedDescriptors;
6163

6264
public TypeShedDescriptorsProvider(Set<String> projectBasePackages) {
63-
moduleConverter = new ModuleSymbolToDescriptorConverter();
65+
this(projectBasePackages, ProjectPythonVersion.currentVersions());
66+
}
67+
68+
public TypeShedDescriptorsProvider(Set<String> projectBasePackages, Set<PythonVersionUtils.Version> projectPythonVersions) {
69+
moduleConverter = new ModuleSymbolToDescriptorConverter(projectPythonVersions);
6470
cachedDescriptors = new HashMap<>();
6571
this.projectBasePackages = projectBasePackages;
6672
}

python-frontend/src/test/java/org/sonar/python/semantic/v2/typeshed/ClassSymbolToDescriptorConverterTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.stream.Collectors;
2424
import org.assertj.core.api.Assertions;
2525
import org.junit.jupiter.api.Test;
26+
import org.sonar.plugins.python.api.ProjectPythonVersion;
2627
import org.sonar.python.index.AmbiguousDescriptor;
2728
import org.sonar.python.index.Descriptor;
2829
import org.sonar.python.index.FunctionDescriptor;
@@ -36,7 +37,7 @@ void test() {
3637
var functionConverter = new FunctionSymbolToDescriptorConverter();
3738
var variableConverter = new VarSymbolToDescriptorConverter();
3839
var overloadedFunctionConverter = new OverloadedFunctionSymbolToDescriptorConverter(functionConverter);
39-
var converter = new ClassSymbolToDescriptorConverter(variableConverter, functionConverter, overloadedFunctionConverter);
40+
var converter = new ClassSymbolToDescriptorConverter(variableConverter, functionConverter, overloadedFunctionConverter, ProjectPythonVersion.currentVersionValues());
4041

4142
var symbol = SymbolsProtos.ClassSymbol.newBuilder()
4243
.setName("MyClass")
@@ -102,7 +103,7 @@ void builtinsTest() {
102103
var functionConverter = new FunctionSymbolToDescriptorConverter();
103104
var variableConverter = new VarSymbolToDescriptorConverter();
104105
var overloadedFunctionConverter = new OverloadedFunctionSymbolToDescriptorConverter(functionConverter);
105-
var converter = new ClassSymbolToDescriptorConverter(variableConverter, functionConverter, overloadedFunctionConverter);
106+
var converter = new ClassSymbolToDescriptorConverter(variableConverter, functionConverter, overloadedFunctionConverter, ProjectPythonVersion.currentVersionValues());
106107

107108
var symbol = SymbolsProtos.ClassSymbol.newBuilder()
108109
.setName("int")

python-frontend/src/test/java/org/sonar/python/semantic/v2/typeshed/ModuleSymbolToDescriptorConverterTest.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.util.Set;
2323
import org.assertj.core.api.Assertions;
2424
import org.junit.jupiter.api.Test;
25-
import org.sonar.plugins.python.api.ProjectPythonVersion;
2625
import org.sonar.plugins.python.api.PythonVersionUtils;
2726
import org.sonar.python.index.AmbiguousDescriptor;
2827
import org.sonar.python.index.ClassDescriptor;
@@ -34,7 +33,7 @@ class ModuleSymbolToDescriptorConverterTest {
3433

3534
@Test
3635
void test() {
37-
var converter = new ModuleSymbolToDescriptorConverter();
36+
var converter = new ModuleSymbolToDescriptorConverter(PythonVersionUtils.allVersions());
3837
var symbol = SymbolsProtos.ModuleSymbol.newBuilder()
3938
.setFullyQualifiedName("module")
4039
.addVars(SymbolsProtos.VarSymbol.newBuilder()
@@ -86,15 +85,14 @@ void test() {
8685

8786
@Test
8887
void nullSymbolTest() {
89-
var converter = new ModuleSymbolToDescriptorConverter();
88+
var converter = new ModuleSymbolToDescriptorConverter(PythonVersionUtils.allVersions());
9089
var descriptor = converter.convert(null);
9190
Assertions.assertThat(descriptor).isNull();
9291
}
9392

9493
@Test
9594
void validForPythonVersionsTest() {
96-
ProjectPythonVersion.setCurrentVersions(Set.of(PythonVersionUtils.Version.V_312));
97-
var converter = new ModuleSymbolToDescriptorConverter();
95+
var converter = new ModuleSymbolToDescriptorConverter(Set.of(PythonVersionUtils.Version.V_312));
9896
var symbol = SymbolsProtos.ModuleSymbol.newBuilder()
9997
.setFullyQualifiedName("module")
10098
.addVars(SymbolsProtos.VarSymbol.newBuilder()
@@ -113,8 +111,7 @@ void validForPythonVersionsTest() {
113111
Assertions.assertThat(descriptor.members().get("v1")).isInstanceOf(VariableDescriptor.class);
114112
Assertions.assertThat(descriptor.members().get("v2")).isNull();
115113

116-
ProjectPythonVersion.setCurrentVersions(Set.of(PythonVersionUtils.Version.V_39));
117-
converter = new ModuleSymbolToDescriptorConverter();
114+
converter = new ModuleSymbolToDescriptorConverter(Set.of(PythonVersionUtils.Version.V_39));
118115
descriptor = converter.convert(symbol);
119116
Assertions.assertThat(descriptor).isNotNull();
120117
Assertions.assertThat(descriptor.members().get("v1")).isNull();

0 commit comments

Comments
 (0)