Skip to content

Commit 60c51e1

Browse files
SONARPY-1807 Populate the SymbolTable out of SymbolTableBuilderV2 and make it accessible from TypeInferenceV2
1 parent 0e845d1 commit 60c51e1

File tree

9 files changed

+202
-94
lines changed

9 files changed

+202
-94
lines changed

python-frontend/src/main/java/org/sonar/plugins/python/api/PythonVisitorContext.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,8 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
4747
this.parsingException = null;
4848
SymbolTableBuilder symbolTableBuilder = packageName != null ? new SymbolTableBuilder(packageName, pythonFile) : new SymbolTableBuilder(pythonFile);
4949
symbolTableBuilder.visitFileInput(rootTree);
50-
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
51-
symbolTableBuilderV2.visitFileInput(rootTree);
52-
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()), pythonFile));
50+
var symbolTable = new SymbolTableBuilderV2(rootTree).build();
51+
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()), pythonFile, symbolTable));
5352
}
5453

5554
public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
@@ -58,9 +57,10 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
5857
this.rootTree = rootTree;
5958
this.parsingException = null;
6059
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);
61-
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
62-
symbolTableBuilderV2.visitFileInput(rootTree);
63-
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile));
60+
61+
var symbolTable = new SymbolTableBuilderV2(rootTree)
62+
.build();
63+
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile, symbolTable));
6464
}
6565

6666
public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
@@ -69,9 +69,9 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
6969
this.rootTree = rootTree;
7070
this.parsingException = null;
7171
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);
72-
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
73-
symbolTableBuilderV2.visitFileInput(rootTree);
74-
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile));
72+
var symbolTable = new SymbolTableBuilderV2(rootTree)
73+
.build();
74+
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile, symbolTable));
7575
}
7676

7777
public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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;
21+
22+
import java.util.HashSet;
23+
import java.util.Map;
24+
import java.util.Optional;
25+
import java.util.Set;
26+
import org.sonar.plugins.python.api.tree.Tree;
27+
28+
public record SymbolTable(Map<Tree, ScopeV2> scopesByRootTree) {
29+
30+
public Set<SymbolV2> getSymbolsByRootTree(Tree tree) {
31+
return Optional.ofNullable(scopesByRootTree.get(tree))
32+
.map(ScopeV2::symbols)
33+
.map(Map::values)
34+
.map(HashSet::new)
35+
.orElseGet(HashSet::new);
36+
}
37+
38+
}

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,22 @@
2626
import org.sonar.plugins.python.api.tree.Tree;
2727

2828
public class SymbolTableBuilderV2 extends BaseTreeVisitor {
29-
private FileInput fileInput;
29+
private final FileInput fileInput;
3030
private Map<Tree, ScopeV2> scopesByRootTree;
3131

32-
public SymbolTableBuilderV2() {
33-
fileInput = null;
32+
public SymbolTableBuilderV2(FileInput fileInput) {
33+
this.fileInput = fileInput;
3434
}
3535

3636
@Override
3737
public void visitFileInput(FileInput fileInput) {
38-
this.fileInput = fileInput;
3938
scopesByRootTree = new HashMap<>();
4039
fileInput.accept(new WriteUsagesVisitor(scopesByRootTree));
4140
fileInput.accept(new ReadUsagesVisitor(scopesByRootTree));
4241
}
4342

44-
public SymbolTableBuilderV2 withFileInput(FileInput fileInput) {
45-
this.fileInput = fileInput;
46-
return this;
47-
}
48-
49-
public void build() {
43+
public SymbolTable build() {
5044
fileInput.accept(this);
45+
return new SymbolTable(scopesByRootTree);
5146
}
5247
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,14 @@
7373
public class TypeInferenceV2 extends BaseTreeVisitor {
7474

7575
private final ProjectLevelTypeTable projectLevelTypeTable;
76+
private final SymbolTable symbolTable;
7677
private final String fileId;
7778

7879
private final Deque<PythonType> typeStack = new ArrayDeque<>();
7980

80-
public TypeInferenceV2(ProjectLevelTypeTable projectLevelTypeTable, PythonFile pythonFile) {
81+
public TypeInferenceV2(ProjectLevelTypeTable projectLevelTypeTable, PythonFile pythonFile, SymbolTable symbolTable) {
8182
this.projectLevelTypeTable = projectLevelTypeTable;
83+
this.symbolTable = symbolTable;
8284
Path path = pathOf(pythonFile);
8385
this.fileId = path != null ? path.toString() : pythonFile.toString();
8486
}

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

Lines changed: 116 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,106 @@
1919
*/
2020
package org.sonar.python.semantic.v2;
2121

22-
import org.junit.jupiter.api.Assertions;
22+
import java.util.Set;
23+
import org.assertj.core.api.Assertions;
2324
import org.junit.jupiter.api.Test;
2425
import org.sonar.plugins.python.api.tree.AssignmentStatement;
2526
import org.sonar.plugins.python.api.tree.FileInput;
2627
import org.sonar.plugins.python.api.tree.FunctionDef;
2728
import org.sonar.plugins.python.api.tree.ImportName;
2829
import org.sonar.plugins.python.api.tree.Name;
2930
import org.sonar.python.PythonTestUtils;
31+
import org.sonar.python.tree.TreeUtils;
3032

3133
class SymbolTableBuilderV2Test {
3234

3335

3436
@Test
35-
void test() {
36-
var builder = new SymbolTableBuilderV2();
37+
void testSymbolTableModuleSymbols() {
38+
FileInput fileInput = PythonTestUtils.parse(
39+
"""
40+
import lib
41+
42+
v = lib.foo()
43+
a = lib.A()
44+
b = a.do_something()
45+
x = 42
46+
47+
def script_do_something(param):
48+
c = 42
49+
return c
50+
51+
script_do_something()
52+
"""
53+
);
54+
55+
var symbolTable = new SymbolTableBuilderV2(fileInput)
56+
.build();
57+
58+
var moduleSymbols = symbolTable.getSymbolsByRootTree(fileInput);
59+
Assertions.assertThat(moduleSymbols)
60+
.hasSize(6)
61+
.extracting(SymbolV2::name)
62+
.contains("lib", "a", "b", "v", "x", "script_do_something");
63+
}
64+
65+
@Test
66+
void testSymbolTableLocalSymbols() {
67+
FileInput fileInput = PythonTestUtils.parse(
68+
"""
69+
import lib
70+
a = lib.A()
71+
def script_do_something(param):
72+
c = 42
73+
return c
74+
"""
75+
);
76+
77+
var symbolTable = new SymbolTableBuilderV2(fileInput)
78+
.build();
79+
80+
var localSymbols = TreeUtils.firstChild(fileInput, FunctionDef.class::isInstance)
81+
.map(symbolTable::getSymbolsByRootTree)
82+
.orElseGet(Set::of);
83+
84+
Assertions.assertThat(localSymbols)
85+
.hasSize(2)
86+
.extracting(SymbolV2::name)
87+
.contains("param", "c");
88+
}
89+
90+
@Test
91+
void testSymbolTableGlobalSymbols() {
92+
FileInput fileInput = PythonTestUtils.parse(
93+
"""
94+
global a
95+
def script_do_something(param):
96+
global b
97+
98+
"""
99+
);
100+
101+
var symbolTable = new SymbolTableBuilderV2(fileInput)
102+
.build();
103+
104+
var localSymbols = TreeUtils.firstChild(fileInput, FunctionDef.class::isInstance)
105+
.map(symbolTable::getSymbolsByRootTree)
106+
.orElseGet(Set::of);
107+
var moduleSymbols = symbolTable.getSymbolsByRootTree(fileInput);
108+
109+
Assertions.assertThat(localSymbols)
110+
.hasSize(1)
111+
.extracting(SymbolV2::name)
112+
.contains("param");
113+
114+
Assertions.assertThat(moduleSymbols)
115+
.hasSize(3)
116+
.extracting(SymbolV2::name)
117+
.contains("a", "b", "script_do_something");
118+
}
119+
120+
@Test
121+
void testNameSymbols() {
37122
FileInput fileInput = PythonTestUtils.parse(
38123
"""
39124
import lib
@@ -49,89 +134,68 @@ def script_do_something(param):
49134
script_do_something()
50135
"""
51136
);
52-
builder.visitFileInput(fileInput);
53137

54-
Assertions.assertNotNull(fileInput.statements());
138+
new SymbolTableBuilderV2(fileInput)
139+
.build();
140+
141+
var statements = fileInput.statements().statements();
55142

56143
{
57-
var importStatement = (ImportName) fileInput.statements().statements().get(0);
144+
var importStatement = (ImportName) statements.get(0);
58145
var libName = importStatement.modules().get(0).dottedName().names().get(0);
59-
var symbol = libName.symbolV2();
60-
Assertions.assertNotNull(symbol);
61-
Assertions.assertEquals("lib", symbol.name());
62-
Assertions.assertEquals(3, symbol.usages().size());
146+
assertNameSymbol(libName, "lib", 3);
63147
}
64148

65149
{
66-
var assignmentStatement = (AssignmentStatement) fileInput.statements().statements().get(1);
150+
var assignmentStatement = (AssignmentStatement) statements.get(1);
67151
var vName = (Name) assignmentStatement.children().get(0).children().get(0);
68-
var vNameSymbol = vName.symbolV2();
69-
Assertions.assertNotNull(vNameSymbol);
70-
Assertions.assertEquals("v", vNameSymbol.name());
71-
Assertions.assertEquals(1, vNameSymbol.usages().size());
152+
assertNameSymbol(vName, "v", 1);
72153

73154
var libName = (Name) assignmentStatement.children().get(2).children().get(0).children().get(0);
74-
var symbol = libName.symbolV2();
75-
Assertions.assertNotNull(symbol);
76-
Assertions.assertEquals("lib", symbol.name());
77-
Assertions.assertEquals(3, symbol.usages().size());
155+
assertNameSymbol(libName, "lib", 3);
78156
}
79157

80158
{
81-
var assignmentStatement = (AssignmentStatement) fileInput.statements().statements().get(2);
159+
var assignmentStatement = (AssignmentStatement) statements.get(2);
82160
var aName = (Name) assignmentStatement.children().get(0).children().get(0);
83-
var aNameSymbol = aName.symbolV2();
84-
Assertions.assertNotNull(aNameSymbol);
85-
Assertions.assertEquals("a", aNameSymbol.name());
86-
Assertions.assertEquals(2, aNameSymbol.usages().size());
161+
assertNameSymbol(aName, "a", 2);
87162

88163
var libName = (Name) assignmentStatement.children().get(2).children().get(0).children().get(0);
89-
var symbol = libName.symbolV2();
90-
Assertions.assertNotNull(symbol);
91-
Assertions.assertEquals("lib", symbol.name());
92-
Assertions.assertEquals(3, symbol.usages().size());
164+
assertNameSymbol(libName, "lib", 3);
93165
}
94166

95167
{
96-
var assignmentStatement = (AssignmentStatement) fileInput.statements().statements().get(3);
168+
var assignmentStatement = (AssignmentStatement) statements.get(3);
97169
var bName = (Name) assignmentStatement.children().get(0).children().get(0);
98-
var bNameSymbol = bName.symbolV2();
99-
Assertions.assertNotNull(bNameSymbol);
100-
Assertions.assertEquals("b", bNameSymbol.name());
101-
Assertions.assertEquals(1, bNameSymbol.usages().size());
170+
assertNameSymbol(bName, "b", 1);
102171

103172
var aName = (Name) assignmentStatement.children().get(2).children().get(0).children().get(0);
104-
var aNameSymbol = aName.symbolV2();
105-
Assertions.assertNotNull(aNameSymbol);
106-
Assertions.assertEquals("a", aNameSymbol.name());
107-
Assertions.assertEquals(2, aNameSymbol.usages().size());
173+
assertNameSymbol(aName, "a", 2);
108174
}
109175

110176
{
111-
var assignmentStatement = (AssignmentStatement) fileInput.statements().statements().get(4);
177+
var assignmentStatement = (AssignmentStatement) statements.get(4);
112178
var xName = (Name) assignmentStatement.children().get(0).children().get(0);
113-
var xNameSymbol = xName.symbolV2();
114-
Assertions.assertNotNull(xNameSymbol);
115-
Assertions.assertEquals("x", xNameSymbol.name());
116-
Assertions.assertEquals(1, xNameSymbol.usages().size());
179+
assertNameSymbol(xName, "x", 1);
117180
}
118181

119182
{
120-
var functionDef = (FunctionDef) fileInput.statements().statements().get(5);
183+
var functionDef = (FunctionDef) statements.get(5);
121184
var scriptFunctionName = (Name) functionDef.name();
122-
var scriptFunctionNameSymbol = scriptFunctionName.symbolV2();
123-
Assertions.assertNotNull(scriptFunctionNameSymbol);
124-
Assertions.assertEquals("script_do_something", scriptFunctionNameSymbol.name());
125-
Assertions.assertEquals(2, scriptFunctionNameSymbol.usages().size());
185+
assertNameSymbol(scriptFunctionName, "script_do_something", 2);
126186
}
127187

128188
{
129-
var functionCallName = (Name) fileInput.statements().statements().get(6).children().get(0).children().get(0);
130-
var functionCallNameSymbol = functionCallName.symbolV2();
131-
Assertions.assertNotNull(functionCallNameSymbol);
132-
Assertions.assertEquals("script_do_something", functionCallNameSymbol.name());
133-
Assertions.assertEquals(2, functionCallNameSymbol.usages().size());
189+
var functionCallName = (Name) statements.get(6).children().get(0).children().get(0);
190+
assertNameSymbol(functionCallName, "script_do_something", 2);
134191
}
135192

136193
}
194+
195+
private static void assertNameSymbol(Name name, String expectedSymbolName, int expectedUsagesCount) {
196+
var symbol = name.symbolV2();
197+
Assertions.assertThat(symbol).isNotNull();
198+
Assertions.assertThat(symbol.name()).isEqualTo(expectedSymbolName);
199+
Assertions.assertThat(symbol.usages()).hasSize(expectedUsagesCount);
200+
}
137201
}

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,9 @@ static void init() {
6565
@Test
6666
void test() {
6767
var pythonFile = PythonTestUtils.pythonFile("script.py");
68-
var builder = new SymbolTableBuilderV2();
69-
builder.visitFileInput(fileInput);
70-
var typeInferenceV2 = new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()), pythonFile);
71-
fileInput.accept(typeInferenceV2);
68+
var symbolTable = new SymbolTableBuilderV2(fileInput)
69+
.build();
70+
fileInput.accept(new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()), pythonFile, symbolTable));
7271

7372
System.out.println("hello");
7473
}
@@ -273,10 +272,10 @@ private FileInput inferTypes(String lines) {
273272

274273
private FileInput inferTypes(String lines, Map<String, Set<Symbol>> globalSymbols) {
275274
FileInput root = parseWithoutSymbols(lines);
276-
var symbolTableBuilderV2 = new SymbolTableBuilderV2();
277-
root.accept(symbolTableBuilderV2);
278-
var typeInferenceV2 = new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.from(globalSymbols)), pythonFile);
279-
root.accept(typeInferenceV2);
275+
276+
var symbolTable = new SymbolTableBuilderV2(root)
277+
.build();
278+
root.accept(new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.from(globalSymbols)), pythonFile, symbolTable));
280279
return root;
281280
}
282281
}

0 commit comments

Comments
 (0)