Skip to content

Commit 16bdfcb

Browse files
SONARPY-2000 Propagate types of imported names (#1863)
1 parent 7871dde commit 16bdfcb

File tree

3 files changed

+81
-11
lines changed

3 files changed

+81
-11
lines changed

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.sonar.plugins.python.api.tree.Expression;
3030
import org.sonar.plugins.python.api.tree.ForStatement;
3131
import org.sonar.plugins.python.api.tree.FunctionDef;
32+
import org.sonar.plugins.python.api.tree.ImportName;
3233
import org.sonar.plugins.python.api.tree.Name;
3334
import org.sonar.plugins.python.api.tree.Parameter;
3435
import org.sonar.plugins.python.api.tree.Statement;
@@ -41,13 +42,13 @@
4142
public class FlowSensitiveTypeInference extends ForwardAnalysis {
4243
private final Set<SymbolV2> trackedVars;
4344
private final Map<Statement, Assignment> assignmentsByAssignmentStatement;
44-
private final Map<Statement, Definition> definitionsByDefinitionStatement;
45+
private final Map<Statement, Set<Definition>> definitionsByDefinitionStatement;
4546
private final Map<String, PythonType> parameterTypesByName;
4647

4748
public FlowSensitiveTypeInference(
4849
Set<SymbolV2> trackedVars,
4950
Map<Statement, Assignment> assignmentsByAssignmentStatement,
50-
Map<Statement, Definition> definitionsByDefinitionStatement,
51+
Map<Statement, Set<Definition>> definitionsByDefinitionStatement,
5152
Map<String, PythonType> parameterTypesByName
5253
) {
5354
this.trackedVars = trackedVars;
@@ -86,7 +87,9 @@ public void updateProgramState(Tree element, ProgramState programState) {
8687
updateTree(assignment.variable(), state);
8788
}
8889
} else if (element instanceof FunctionDef functionDef) {
89-
handleDefinition(functionDef, state);
90+
handleDefinitions(functionDef, state);
91+
} else if (element instanceof ImportName importName) {
92+
handleDefinitions(importName, state);
9093
} else if (element instanceof Parameter parameter) {
9194
handleParameter(parameter, state);
9295
} else if (isForLoopAssignment(element)) {
@@ -150,13 +153,13 @@ private void handleAssignment(Statement assignmentStatement, TypeInferenceProgra
150153
});
151154
}
152155

153-
private void handleDefinition(Statement definitionStatement, TypeInferenceProgramState programState) {
156+
private void handleDefinitions(Statement definitionStatement, TypeInferenceProgramState programState) {
154157
Optional.ofNullable(definitionsByDefinitionStatement.get(definitionStatement))
155-
.ifPresent(definition -> {
156-
SymbolV2 symbol = definition.lhsSymbol();
158+
.ifPresent(definitions -> definitions.forEach(d -> {
159+
SymbolV2 symbol = d.lhsSymbol();
157160
if (trackedVars.contains(symbol)) {
158-
programState.setTypes(symbol, Set.of(definition.lhsName.typeV2()));
161+
programState.setTypes(symbol, Set.of(d.lhsName.typeV2()));
159162
}
160-
});
163+
}));
161164
}
162165
}

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.sonar.plugins.python.api.tree.Expression;
3434
import org.sonar.plugins.python.api.tree.ForStatement;
3535
import org.sonar.plugins.python.api.tree.FunctionDef;
36+
import org.sonar.plugins.python.api.tree.ImportName;
3637
import org.sonar.plugins.python.api.tree.Name;
3738
import org.sonar.plugins.python.api.tree.Parameter;
3839
import org.sonar.plugins.python.api.tree.Statement;
@@ -42,7 +43,7 @@
4243
public class PropagationVisitor extends BaseTreeVisitor {
4344
private final Map<SymbolV2, Set<Propagation>> propagationsByLhs;
4445
private final Map<Statement, Assignment> assignmentsByAssignmentStatement;
45-
private final Map<Statement, Definition> definitionsByDefinitionStatement;
46+
private final Map<Statement, Set<Definition>> definitionsByDefinitionStatement;
4647

4748
public PropagationVisitor() {
4849
propagationsByLhs = new HashMap<>();
@@ -55,7 +56,7 @@ public Map<Statement, Assignment> assignmentsByAssignmentStatement() {
5556
return assignmentsByAssignmentStatement;
5657
}
5758

58-
public Map<Statement, Definition> definitionsByDefinitionStatement() {
59+
public Map<Statement, Set<Definition>> definitionsByDefinitionStatement() {
5960
return definitionsByDefinitionStatement;
6061
}
6162

@@ -69,7 +70,7 @@ public void visitFunctionDef(FunctionDef functionDef) {
6970
Name name = functionDef.name();
7071
var symbol = name.symbolV2();
7172
Definition definition = new Definition(symbol, name);
72-
definitionsByDefinitionStatement.put(functionDef, definition);
73+
definitionsByDefinitionStatement.computeIfAbsent(functionDef, k -> new HashSet<>()).add(definition);
7374
propagationsByLhs.computeIfAbsent(symbol, s -> new HashSet<>()).add(definition);
7475
}
7576

@@ -113,6 +114,32 @@ public void visitAnnotatedAssignment(AnnotatedAssignment annotatedAssignment){
113114
}
114115
}
115116

117+
@Override
118+
public void visitImportName(ImportName importName) {
119+
super.visitImportName(importName);
120+
importName.modules()
121+
.forEach(aliasedName -> {
122+
var names = aliasedName.dottedName().names();
123+
Name alias = aliasedName.alias();
124+
if (alias != null) {
125+
SymbolV2 aliasSymbol = alias.symbolV2();
126+
Definition definition = new Definition(aliasSymbol, alias);
127+
definitionsByDefinitionStatement.computeIfAbsent(importName, k -> new HashSet<>()).add(definition);
128+
propagationsByLhs.computeIfAbsent(aliasSymbol, s -> new HashSet<>()).add(definition);
129+
} else {
130+
for (int i = names.size() - 1; i >= 0; i--) {
131+
Name name = names.get(i);
132+
SymbolV2 symbolV2 = name.symbolV2();
133+
if (symbolV2 != null) {
134+
Definition definition = new Definition(symbolV2, name);
135+
definitionsByDefinitionStatement.computeIfAbsent(importName, k -> new HashSet<>()).add(definition);
136+
propagationsByLhs.computeIfAbsent(symbolV2, s -> new HashSet<>()).add(definition);
137+
}
138+
}
139+
}
140+
});
141+
}
142+
116143
@Override
117144
public void visitForStatement(ForStatement forStatement) {
118145
scan(forStatement.testExpressions());

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,6 +1633,46 @@ def bar(self): ...
16331633
assertThat(unionType.candidates()).containsExactlyInAnyOrder(classA, classB);
16341634
}
16351635

1636+
@Test
1637+
@Disabled("Duplicate PythonType for NONE_TYPE")
1638+
void imported_symbol_call_return_type() {
1639+
assertThat(lastExpression(
1640+
"""
1641+
import fcntl
1642+
ret = fcntl.flock(..., ...)
1643+
ret
1644+
"""
1645+
).typeV2()).isEqualTo(NONE_TYPE);
1646+
}
1647+
1648+
@Test
1649+
void basic_imported_symbol() {
1650+
assertThat(lastExpression(
1651+
"""
1652+
import fcntl
1653+
fcntl
1654+
"""
1655+
).typeV2()).isInstanceOf(ModuleType.class);
1656+
}
1657+
1658+
@Test
1659+
void basic_imported_symbols() {
1660+
FileInput fileInput = inferTypes(
1661+
"""
1662+
import fcntl, math
1663+
fcntl
1664+
math
1665+
"""
1666+
);
1667+
PythonType fnctlModule = ((ExpressionStatement) fileInput.statements().statements().get(1)).expressions().get(0).typeV2();
1668+
assertThat(fnctlModule).isInstanceOf(ModuleType.class);
1669+
assertThat(fnctlModule.name()).isEqualTo("fcntl");
1670+
PythonType mathModule = ((ExpressionStatement) fileInput.statements().statements().get(2)).expressions().get(0).typeV2();
1671+
assertThat(mathModule).isInstanceOf(ModuleType.class);
1672+
assertThat(mathModule.name()).isEqualTo("math");
1673+
assertThat(((UnionType) mathModule.resolveMember("acos").get()).candidates()).allMatch(FunctionType.class::isInstance);
1674+
}
1675+
16361676
private static FileInput inferTypes(String lines) {
16371677
return inferTypes(lines, PROJECT_LEVEL_TYPE_TABLE);
16381678
}

0 commit comments

Comments
 (0)