Skip to content

Commit 1335394

Browse files
SONARPY-1824 Fix incorrect inference for global variables assigned within functions (#1804)
1 parent d5f5b68 commit 1335394

File tree

4 files changed

+58
-9
lines changed

4 files changed

+58
-9
lines changed

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public class ScopeV2 {
3737
private final List<ScopeV2> childrenScopes;
3838
private final Map<String, SymbolV2> symbolsByName = new HashMap<>();
3939
private final Set<SymbolV2> symbols = new HashSet<>();
40+
private final Set<String> globalNames = new HashSet<>();
41+
private final Set<String> nonlocalNames = new HashSet<>();
4042

4143
public ScopeV2(@Nullable ScopeV2 parent, Tree rootTree) {
4244
this.parent = parent;
@@ -79,8 +81,7 @@ SymbolV2 resolve(String symbolName) {
7981
}
8082

8183
private boolean isExistingSymbol(String symbolName) {
82-
//return symbolsByName.containsKey(symbolName) || globalNames.contains(symbolName) || nonlocalNames.contains(symbolName);
83-
return symbolsByName.containsKey(symbolName);
84+
return symbolsByName.containsKey(symbolName) || globalNames.contains(symbolName) || nonlocalNames.contains(symbolName);
8485
}
8586

8687
void createSelfParameter(Parameter parameter) {
@@ -96,6 +97,14 @@ void createSelfParameter(Parameter parameter) {
9697
symbol.addUsage(nameTree, UsageV2.Kind.PARAMETER);
9798
}
9899

100+
public void addGlobalName(Name name) {
101+
this.globalNames.add(name.name());
102+
}
103+
104+
public void addNonLocalName(Name name) {
105+
this.nonlocalNames.add(name.name());
106+
}
107+
99108
public Map<String, SymbolV2> symbols() {
100109
return symbolsByName;
101110
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.sonar.plugins.python.api.tree.ImportName;
5050
import org.sonar.plugins.python.api.tree.LambdaExpression;
5151
import org.sonar.plugins.python.api.tree.Name;
52+
import org.sonar.plugins.python.api.tree.NonlocalStatement;
5253
import org.sonar.plugins.python.api.tree.Parameter;
5354
import org.sonar.plugins.python.api.tree.ParameterList;
5455
import org.sonar.plugins.python.api.tree.Token;
@@ -281,10 +282,19 @@ public void visitAssignmentExpression(AssignmentExpression assignmentExpression)
281282
@Override
282283
public void visitGlobalStatement(GlobalStatement globalStatement) {
283284
// Global statements are not binding usages, but we consider them as such for symbol creation
284-
globalStatement.variables().forEach(name -> moduleScope.addBindingUsage(name, UsageV2.Kind.GLOBAL_DECLARATION, null));
285+
globalStatement.variables().forEach(name -> {
286+
moduleScope.addBindingUsage(name, UsageV2.Kind.GLOBAL_DECLARATION, null);
287+
currentScope().addGlobalName(name);
288+
});
285289
super.visitGlobalStatement(globalStatement);
286290
}
287291

292+
@Override
293+
public void visitNonlocalStatement(NonlocalStatement pyNonlocalStatementTree) {
294+
pyNonlocalStatementTree.variables().forEach(name -> currentScope().addNonLocalName(name));
295+
super.visitNonlocalStatement(pyNonlocalStatementTree);
296+
}
297+
288298
@Override
289299
public void visitExceptClause(ExceptClause exceptClause) {
290300
boundNamesFromExpression(exceptClause.exceptionInstance()).forEach(name -> addBindingUsage(name, UsageV2.Kind.EXCEPTION_INSTANCE));

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

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

22+
import java.util.List;
2223
import java.util.Set;
2324
import org.assertj.core.api.Assertions;
2425
import org.junit.jupiter.api.Test;
@@ -27,6 +28,7 @@
2728
import org.sonar.plugins.python.api.tree.FunctionDef;
2829
import org.sonar.plugins.python.api.tree.ImportName;
2930
import org.sonar.plugins.python.api.tree.Name;
31+
import org.sonar.plugins.python.api.tree.Tree;
3032
import org.sonar.python.PythonTestUtils;
3133
import org.sonar.python.tree.TreeUtils;
3234

@@ -94,6 +96,7 @@ void testSymbolTableGlobalSymbols() {
9496
global a
9597
def script_do_something(param):
9698
global b
99+
b = 42
97100
98101
"""
99102
);
@@ -117,6 +120,34 @@ def script_do_something(param):
117120
.contains("a", "b", "script_do_something");
118121
}
119122

123+
@Test
124+
void testNonLocalSymbol() {
125+
FileInput fileInput = PythonTestUtils.parse(
126+
"""
127+
def foo():
128+
a = 42
129+
def inner(param):
130+
nonlocal a
131+
a = "hello"
132+
print(a)
133+
134+
"""
135+
);
136+
137+
var symbolTable = new SymbolTableBuilderV2(fileInput)
138+
.build();
139+
140+
var moduleSymbols = symbolTable.getSymbolsByRootTree(fileInput);
141+
List<FunctionDef> functionDefs = PythonTestUtils.getAllDescendant(fileInput, tree -> tree.is(Tree.Kind.FUNCDEF));
142+
143+
Set<SymbolV2> fooSymbols = symbolTable.getSymbolsByRootTree(functionDefs.get(0));
144+
Set<SymbolV2> innerSymbols = symbolTable.getSymbolsByRootTree(functionDefs.get(1));
145+
146+
Assertions.assertThat(moduleSymbols).extracting(SymbolV2::name).containsExactlyInAnyOrder("foo");
147+
Assertions.assertThat(fooSymbols).extracting(SymbolV2::name).containsExactlyInAnyOrder("a", "inner");
148+
Assertions.assertThat(innerSymbols).extracting(SymbolV2::name).containsExactlyInAnyOrder("param");
149+
}
150+
120151
@Test
121152
void testNameSymbols() {
122153
FileInput fileInput = PythonTestUtils.parse(

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -396,16 +396,15 @@ def foo(param: int):
396396
void inferTypeForReassignedBuiltinsInsideFunction() {
397397
FileInput root = inferTypes("""
398398
def foo():
399-
global list
400-
list = 42
401-
list = "hello"
402-
list
399+
global x
400+
x = 42
401+
x = "hello"
402+
x
403403
""");
404404

405405
var functionDef = (FunctionDef) root.statements().statements().get(0);
406406
var expressionStatement = (ExpressionStatement) functionDef.body().statements().get(3);
407-
// TODO: Shouldn't this be UNKNOWN due to glboal?
408-
assertThat(expressionStatement.expressions().get(0).typeV2().unwrappedType()).isEqualTo(STR_TYPE);
407+
assertThat(expressionStatement.expressions().get(0).typeV2().unwrappedType()).isEqualTo(PythonType.UNKNOWN);
409408
}
410409

411410
@Test

0 commit comments

Comments
 (0)