Skip to content

Commit 8e09061

Browse files
SONARPY-2022 Resolve type aliases types from TypeShed (#1906)
1 parent 1e5afc0 commit 8e09061

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.sonar.python.semantic.v2;
2121

2222
import java.util.List;
23+
import java.util.Map;
2324
import java.util.Optional;
2425
import java.util.stream.IntStream;
2526
import org.sonar.python.semantic.ProjectLevelSymbolTable;
@@ -31,6 +32,17 @@ public class ProjectLevelTypeTable {
3132
private final SymbolsModuleTypeProvider symbolsModuleTypeProvider;
3233
private final ModuleType rootModule;
3334
private final LazyTypesContext lazyTypesContext;
35+
private final Map<String, Map<String, String>> aliasMembers = Map.ofEntries(
36+
Map.entry("typing", Map.ofEntries(
37+
Map.entry("List", "list"),
38+
Map.entry("Tuple", "tuple"),
39+
Map.entry("Dict", "dict"),
40+
Map.entry("Set", "set"),
41+
Map.entry("FrozenSet", "frozenset"),
42+
Map.entry("Type", "type")
43+
))
44+
);
45+
3446

3547
public ProjectLevelTypeTable(ProjectLevelSymbolTable projectLevelSymbolTable) {
3648
this(projectLevelSymbolTable, new TypeShed(projectLevelSymbolTable));
@@ -62,10 +74,13 @@ public PythonType getType(List<String> typeFqnParts) {
6274
if (resolvedMember.isPresent()) {
6375
parent = resolvedMember.get();
6476
} else if (parent instanceof ModuleType module) {
65-
var moduleFqn = IntStream.rangeClosed(0, i)
77+
var moduleFqnParts = IntStream.rangeClosed(0, i)
6678
.mapToObj(typeFqnParts::get)
6779
.toList();
68-
parent = symbolsModuleTypeProvider.convertModuleType(moduleFqn, module);
80+
parent = symbolsModuleTypeProvider.convertModuleType(moduleFqnParts, module);
81+
if (parent instanceof ModuleType moduleType) {
82+
addAliasMembers(moduleFqnParts, moduleType);
83+
}
6984
} else {
7085
return PythonType.UNKNOWN;
7186
}
@@ -76,4 +91,15 @@ public PythonType getType(List<String> typeFqnParts) {
7691
public LazyTypesContext lazyTypesContext() {
7792
return lazyTypesContext;
7893
}
94+
95+
public void addAliasMembers(List<String> moduleFqnParts, ModuleType moduleType) {
96+
String moduleFqn = String.join(".", moduleFqnParts);
97+
aliasMembers.getOrDefault(moduleFqn, Map.of())
98+
.forEach((memberName, alias) -> {
99+
var pythonType = getType(alias);
100+
if (pythonType != PythonType.UNKNOWN) {
101+
moduleType.members().put(memberName, pythonType);
102+
}
103+
});
104+
}
79105
}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,15 @@
6868
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
6969
import static org.sonar.python.PythonTestUtils.parse;
7070
import static org.sonar.python.types.v2.TypesTestUtils.DICT_TYPE;
71+
import static org.sonar.python.types.v2.TypesTestUtils.FROZENSET_TYPE;
7172
import static org.sonar.python.types.v2.TypesTestUtils.INT_TYPE;
7273
import static org.sonar.python.types.v2.TypesTestUtils.LIST_TYPE;
7374
import static org.sonar.python.types.v2.TypesTestUtils.NONE_TYPE;
7475
import static org.sonar.python.types.v2.TypesTestUtils.PROJECT_LEVEL_TYPE_TABLE;
7576
import static org.sonar.python.types.v2.TypesTestUtils.SET_TYPE;
7677
import static org.sonar.python.types.v2.TypesTestUtils.STR_TYPE;
78+
import static org.sonar.python.types.v2.TypesTestUtils.TUPLE_TYPE;
79+
import static org.sonar.python.types.v2.TypesTestUtils.TYPE_TYPE;
7780

7881
class TypeInferenceV2Test {
7982

@@ -1560,6 +1563,37 @@ def f():
15601563
.isSameAs(PythonType.UNKNOWN);
15611564
}
15621565

1566+
@Test
1567+
void typing_aliases_are_resolved_to_builtin_equivalent() {
1568+
var fileInput = inferTypes("""
1569+
import typing
1570+
a = typing.Tuple
1571+
a
1572+
b = typing.List
1573+
b
1574+
c = typing.Dict
1575+
c
1576+
d = typing.Set
1577+
d
1578+
e = typing.FrozenSet
1579+
e
1580+
f = typing.Type
1581+
f
1582+
""");
1583+
var aExpr = ((ExpressionStatement) fileInput.statements().statements().get(2)).expressions().get(0);
1584+
assertThat(aExpr.typeV2()).isEqualTo(TUPLE_TYPE);
1585+
var bExpr = ((ExpressionStatement) fileInput.statements().statements().get(4)).expressions().get(0);
1586+
assertThat(bExpr.typeV2()).isEqualTo(LIST_TYPE);
1587+
var cExpr = ((ExpressionStatement) fileInput.statements().statements().get(6)).expressions().get(0);
1588+
assertThat(cExpr.typeV2()).isEqualTo(DICT_TYPE);
1589+
var dExpr = ((ExpressionStatement) fileInput.statements().statements().get(8)).expressions().get(0);
1590+
assertThat(dExpr.typeV2()).isEqualTo(SET_TYPE);
1591+
var eExpr = ((ExpressionStatement) fileInput.statements().statements().get(10)).expressions().get(0);
1592+
assertThat(eExpr.typeV2()).isEqualTo(FROZENSET_TYPE);
1593+
var fExpr = ((ExpressionStatement) fileInput.statements().statements().get(12)).expressions().get(0);
1594+
assertThat(fExpr.typeV2()).isEqualTo(TYPE_TYPE);
1595+
}
1596+
15631597
@Test
15641598
void inferLoopOverNonIterableVarTypeInTry() {
15651599
var root = inferTypes("""

python-frontend/src/test/java/org/sonar/python/types/v2/TypesTestUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ public class TypesTestUtils {
3636
public static final PythonType BOOL_TYPE = BUILTINS.resolveMember("bool").get();
3737
public static final PythonType STR_TYPE = BUILTINS.resolveMember("str").get();
3838
public static final PythonType LIST_TYPE = BUILTINS.resolveMember("list").get();
39+
public static final PythonType TUPLE_TYPE = BUILTINS.resolveMember("tuple").get();
3940
public static final PythonType SET_TYPE = BUILTINS.resolveMember("set").get();
41+
public static final PythonType FROZENSET_TYPE = BUILTINS.resolveMember("frozenset").get();
4042
public static final PythonType DICT_TYPE = BUILTINS.resolveMember("dict").get();
4143
public static final PythonType NONE_TYPE = BUILTINS.resolveMember("NoneType").get();
44+
public static final PythonType TYPE_TYPE = BUILTINS.resolveMember("type").get();
4245

4346
public static FileInput parseAndInferTypes(String... code) {
4447
return parseAndInferTypes(PythonTestUtils.pythonFile(""), code);

0 commit comments

Comments
 (0)